diff --git a/CHANGELOG.md b/CHANGELOG.md index 2895e4ccb8..f20ee01177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,17 @@ Changelog ### Features +- Removes depreciated types, requests, and API methods associated with the V0 API. Within the /api directory, organizes requests, types, and errors into subdirectories based on the categories Accounts, Addresses, Common, Nodes, Transactions, and Wallets. ([PR 1088](https://github.com/input-output-hk/daedalus/pull/1088)) +- Refactors and improves Network Status Store to use V1 API. ([PR 1081](https://github.com/input-output-hk/daedalus/pull/1081)) +- Implemented the V1 API endpoints for redeeming ADA with all types of certificates. Updates ADA redemption API methods, flow types, and variable names to match the V1 nomenclature. ([PR 1080](https://github.com/input-output-hk/daedalus/pull/1080)) +- Implements the missing V1 API endpoints for checking if a node update is available and responding to the update by either applying it or postponing it. Adds the V1 endpoint for adaTestReset.js. ([PR 1079](https://github.com/input-output-hk/daedalus/pull/1079)) +- Implemented the V1 API specifications for 4 pieces of functionality and their associated API requests within Daedalus. These include: Setting a passphrase or changing an existing passphrase. Deleting an existing wallet and all its accounts. Checking for an available node update and displaying a notification about the update and versioning. Updating the name and/or assurance level of an existing wallet. ([PR 1042](https://github.com/input-output-hk/daedalus/pull/1042)) - Implemented a switch instead of a link for “hide used” addresses on the Receive screen ([PR 935](https://github.com/input-output-hk/daedalus/pull/935)) - Implemented forms submission on "Enter" key press ([PR 981](https://github.com/input-output-hk/daedalus/pull/981)) - Improved the loading UX ([PR 723](https://github.com/input-output-hk/daedalus/pull/723)) +- Integrated the V1 API endpoint for Ada payments ([PR 1031](https://github.com/input-output-hk/daedalus/pull/1031)) +- Integrated the V1 API endpoint for creating and restoring Wallets ([PR 1018](https://github.com/input-output-hk/daedalus/pull/1018)) +- Integrated the V1 API endpoints for fetching all accounts associated with a wallet, fetching a single address, and creating a new address ([PR 1037](https://github.com/input-output-hk/daedalus/pull/1037)) - Send cert and key with api requests to support tls-auth ([PR 1072](https://github.com/input-output-hk/daedalus/pull/1072)) ### Fixes diff --git a/cardano-sl-src.json b/cardano-sl-src.json index 81a1473f68..2267f46ffb 100644 --- a/cardano-sl-src.json +++ b/cardano-sl-src.json @@ -1,6 +1,6 @@ { "url": "https://github.com/input-output-hk/cardano-sl", - "rev": "6fa649355d93a1ebfaace3edbbfc54a7304ce5ff", - "sha256": "0xp6fl0pfcdvi54j3nvp5jp8q4q8vwr68zf4h6m1g755i9malbz1", + "rev": "515afc3fb6e8b9804a6c325defa95a778df8c8e8", + "sha256": "0f7djx171jvcp1wxnpy063xnqh28qhl701n9ldsy4bj89bj2iza2", "fetchSubmodules": false } diff --git a/features/import-wallet-via-sidebar.feature b/features/import-wallet-via-sidebar.feature index 7ff807fac4..0d22666704 100644 --- a/features/import-wallet-via-sidebar.feature +++ b/features/import-wallet-via-sidebar.feature @@ -15,13 +15,13 @@ Feature: Import Wallet via Sidebar And I select a valid wallet import key file And I click on the import wallet button in import wallet dialog Then I should not see the import wallet dialog anymore - And I should have newly created "Genesis wallet" wallet loaded - And I should be on the "Genesis wallet" wallet "summary" screen + And I should have newly created "Imported Wallet" wallet loaded + And I should be on the "Imported Wallet" wallet "summary" screen And I should see the restore status notification while import is running And I should not see the restore status notification once import is finished Scenario: Wallet Already Imported Error - Given I have a "Genesis wallet" with funds + Given I have a "Imported Wallet" with funds When I try to import the wallet with funds again Then I see the import wallet dialog with an error that the wallet already exists @@ -40,7 +40,7 @@ Feature: Import Wallet via Sidebar | Secret123 | Secret123 | And I click on the import wallet button in import wallet dialog Then I should not see the import wallet dialog anymore - And I should have newly created "Genesis wallet" wallet loaded - And I should be on the "Genesis wallet" wallet "summary" screen + And I should have newly created "Imported Wallet" wallet loaded + And I should be on the "Imported Wallet" wallet "summary" screen And I should see the restore status notification while import is running And I should not see the restore status notification once import is finished diff --git a/features/paper-wallets-certificate.feature b/features/paper-wallets-certificate.feature index 60648fbcda..ad7a3c5f05 100644 --- a/features/paper-wallets-certificate.feature +++ b/features/paper-wallets-certificate.feature @@ -2,7 +2,7 @@ Feature: Paper Wallets Certificate generation Background: Given I have completed the basic setup - And I have a "Genesis wallet" with funds + And I have a "Imported Wallet" with funds Scenario: Paper wallets certificate success generation Given The sidebar shows the "wallets" category @@ -24,8 +24,8 @@ Feature: Paper Wallets Certificate generation And Cardano explorer link and wallet address should be valid And I click on the finish button And I should not see the create paper wallet certificate dialog anymore - When I click on the "Genesis wallet" wallet in the sidebar - And I am on the "Genesis wallet" wallet "send" screen + When I click on the "Imported Wallet" wallet in the sidebar + And I am on the "Imported Wallet" wallet "send" screen And I fill out the send form: | amount | | 0.000010 | @@ -33,7 +33,7 @@ Feature: Paper Wallets Certificate generation And I click on the next button in the wallet send form And I see send money confirmation dialog And I submit the wallet send form - Then I should be on the "Genesis wallet" wallet "summary" screen + Then I should be on the "Imported Wallet" wallet "summary" screen And the latest transaction should show: | title | amountWithoutFees | | wallet.transaction.sent | -0.000010 | diff --git a/features/receive-money.feature b/features/receive-money.feature index 85ca1afee1..e798faa623 100644 --- a/features/receive-money.feature +++ b/features/receive-money.feature @@ -2,7 +2,7 @@ Feature: Receive money Background: Given I have completed the basic setup - And I have a "Genesis wallet" with funds + And I have a "Imported Wallet" with funds And I have the following wallets: | name | | TargetWallet | @@ -11,8 +11,8 @@ Feature: Receive money Given I am on the "TargetWallet" wallet "receive" screen And I generate 1 addresses And I have made the following transactions: - | sender | receiver | amount | - | Genesis wallet | TargetWallet | 1 | + | source | destination | amount | + | Imported Wallet | TargetWallet | 1 | Then I should see 2 addresses When I click the ShowUsed switch Then I should see 1 addresses diff --git a/features/send-money-to-receiver.feature b/features/send-money-to-receiver.feature index 622ee2a60c..283cd57321 100644 --- a/features/send-money-to-receiver.feature +++ b/features/send-money-to-receiver.feature @@ -7,8 +7,8 @@ Feature: Send Money to Receiver | first | Scenario: User Sends Money to Receiver - Given I have a "Genesis wallet" with funds - And I am on the "Genesis wallet" wallet "send" screen + Given I have a "Imported Wallet" with funds + And I am on the "Imported Wallet" wallet "send" screen And I can see the send form When I fill out the send form with a transaction to "first" wallet: | amount | @@ -17,7 +17,7 @@ Feature: Send Money to Receiver And I click on the next button in the wallet send form And I see send money confirmation dialog And I submit the wallet send form - Then I should be on the "Genesis wallet" wallet "summary" screen + Then I should be on the "Imported Wallet" wallet "summary" screen And the latest transaction should show: | title | amountWithoutFees | | wallet.transaction.sent | -0.000010 | @@ -26,8 +26,8 @@ Feature: Send Money to Receiver | 0.000010 | Scenario: User Sends Money from wallet with spending password to Receiver - Given I have a "Genesis wallet" with funds and password - And I am on the "Genesis wallet" wallet "send" screen + Given I have a "Imported Wallet" with funds and password + And I am on the "Imported Wallet" wallet "send" screen And I can see the send form When I fill out the send form with a transaction to "first" wallet: | amount | @@ -37,7 +37,7 @@ Feature: Send Money to Receiver And I see send money confirmation dialog And I enter wallet spending password in confirmation dialog "Secret123" And I submit the wallet send form - Then I should be on the "Genesis wallet" wallet "summary" screen + Then I should be on the "Imported Wallet" wallet "summary" screen And the latest transaction should show: | title | amountWithoutFees | | wallet.transaction.sent | -0.000010 | diff --git a/features/step_definitions/local-time-difference-steps.js b/features/step_definitions/local-time-difference-steps.js index 64030ce56a..cbe864526d 100644 --- a/features/step_definitions/local-time-difference-steps.js +++ b/features/step_definitions/local-time-difference-steps.js @@ -3,7 +3,7 @@ import { Given, Then } from 'cucumber'; Given(/^I set wrong local time difference$/, async function () { await this.client.executeAsync((timeDifference, done) => { daedalus.api.ada.setLocalTimeDifference(timeDifference) - .then(() => daedalus.stores.networkStatus._updateLocalTimeDifference()) + .then(() => daedalus.stores.networkStatus._updateNetworkStatus()) .then(done) .catch((error) => done(error)); }, 1511823600000); diff --git a/features/step_definitions/settings-steps.js b/features/step_definitions/settings-steps.js index db479cac97..cfe6d0b9cc 100644 --- a/features/step_definitions/settings-steps.js +++ b/features/step_definitions/settings-steps.js @@ -80,7 +80,7 @@ Then(/^I should have wallet with "Strict" assurance level set$/, async function .catch((error) => done(error)); }); const activeWallet = wallets.value.find((w) => w.name === activeWalletName); - expect(activeWallet.assurance).to.equal('CWAStrict'); + expect(activeWallet.assurance).to.equal('strict'); }); Then(/^I should see new wallet name "([^"]*)"$/, async function (walletName) { diff --git a/features/step_definitions/transactions-steps.js b/features/step_definitions/transactions-steps.js index 8a29a664d8..db7fe3f39c 100644 --- a/features/step_definitions/transactions-steps.js +++ b/features/step_definitions/transactions-steps.js @@ -9,10 +9,10 @@ import { getWalletByName } from '../support/helpers/wallets-helpers'; // use only when the order is important because it's slower! Given(/^I have made the following transactions:$/, { timeout: 40000 }, async function (table) { const txData = table.hashes().map((t) => ({ - sender: getWalletByName.call(this, t.sender).id, - receiver: getWalletByName.call(this, t.receiver).id, - amount: new BigNumber(t.amount).times(LOVELACES_PER_ADA), - password: t.password || null, + walletId: getWalletByName.call(this, t.source).id, + destinationWalletId: getWalletByName.call(this, t.destination).id, + amount: parseInt(new BigNumber(t.amount).times(LOVELACES_PER_ADA), 10), + spendingPassword: t.password || null, })); this.transactions = []; // Sequentially (and async) create transactions with for loop @@ -21,12 +21,12 @@ Given(/^I have made the following transactions:$/, { timeout: 40000 }, async fun new window.Promise((resolve) => ( // Need to fetch the wallets data async and wait for all results window.Promise.all([ - daedalus.stores.ada.addresses.getAccountIdByWalletId(transaction.sender), - daedalus.stores.ada.addresses.getAddressesByWalletId(transaction.receiver) + daedalus.stores.ada.addresses.getAccountIndexByWalletId(transaction.walletId), + daedalus.stores.ada.addresses.getAddressesByWalletId(transaction.destinationWalletId) ]).then(results => ( daedalus.api.ada.createTransaction(window.Object.assign(transaction, { - sender: results[0], // Account id of sender wallet - receiver: results[1][0].id // First address of receiving wallet + accountIndex: results[0], // Account index of sender wallet + address: results[1][0].id // First address of receiving wallet })).then(resolve) )) )).then(done) diff --git a/features/step_definitions/wallets-steps.js b/features/step_definitions/wallets-steps.js index 7ec1b42daf..f1788ec09a 100644 --- a/features/step_definitions/wallets-steps.js +++ b/features/step_definitions/wallets-steps.js @@ -25,23 +25,24 @@ import { } from '../support/helpers/notifications-helpers'; const defaultWalletKeyFilePath = path.resolve(__dirname, '../support/default-wallet.key'); -const defaultWalletJSONFilePath = path.resolve(__dirname, '../support/default-wallet.json'); +// const defaultWalletJSONFilePath = path.resolve(__dirname, '../support/default-wallet.json'); +// ^^ JSON wallet file import is currently not working due to missing JSON import V1 API endpoint -Given(/^I have a "Genesis wallet" with funds$/, async function () { +Given(/^I have a "Imported Wallet" with funds$/, async function () { await importWalletWithFunds(this.client, { keyFilePath: defaultWalletKeyFilePath, password: null, }); - const wallet = await waitUntilWalletIsLoaded.call(this, 'Genesis wallet'); + const wallet = await waitUntilWalletIsLoaded.call(this, 'Imported Wallet'); addOrSetWalletsForScenario.call(this, wallet); }); -Given(/^I have a "Genesis wallet" with funds and password$/, async function () { +Given(/^I have a "Imported Wallet" with funds and password$/, async function () { await importWalletWithFunds(this.client, { keyFilePath: defaultWalletKeyFilePath, password: 'Secret123', }); - const wallet = await waitUntilWalletIsLoaded.call(this, 'Genesis wallet'); + const wallet = await waitUntilWalletIsLoaded.call(this, 'Imported Wallet'); addOrSetWalletsForScenario.call(this, wallet); }); @@ -99,7 +100,10 @@ When(/^I see the import wallet dialog$/, function () { }); When(/^I select a valid wallet import key file$/, function () { - return importWalletDialog.selectFile(this.client, { filePath: defaultWalletJSONFilePath }); + // return importWalletDialog.selectFile(this.client, { filePath: defaultWalletJSONFilePath }); + // ^^ JSON wallet file import is currently not working due to missing JSON import V1 API endpoint + // so we have to use the KEY wallet file instead: + return importWalletDialog.selectFile(this.client, { filePath: defaultWalletKeyFilePath }); }); When(/^I toggle "Activate to create password" switch on the import wallet key dialog$/, function () { @@ -310,7 +314,10 @@ When(/^I try to import the wallet with funds again$/, async function () { await addWalletPage.waitForVisible(this.client); await addWalletPage.clickImportButton(this.client); await importWalletDialog.waitForDialog(this.client); - await importWalletDialog.selectFile(this.client, { filePath: defaultWalletJSONFilePath }); + // await importWalletDialog.selectFile(this.client, { filePath: defaultWalletJSONFilePath }); + // ^^ JSON wallet file import is currently not working due to missing JSON import V1 API endpoint + // so we have to use the KEY wallet file instead: + await importWalletDialog.selectFile(this.client, { filePath: defaultWalletKeyFilePath }); return importWalletDialog.clickImport(this.client); }); diff --git a/features/support/default-wallet.json b/features/support/default-wallet.json index 777a1778ff..23cfc7af1b 100644 --- a/features/support/default-wallet.json +++ b/features/support/default-wallet.json @@ -1 +1 @@ -{"wallet":{"accounts":[{"name":"Genesis account","index":2147483648}],"walletSecretKey":"WIAwbsQgbz9X0WhvOnVeH+yRs7Ri93ESTdMspBHzeLnPUR6hLZL/NazfB40z2x8FZhLwNIt83DCuMR1nGG+ZqvsD/ouyzg3ec729fnrqEMO4A+qPTJmpiRgQZfYO2KDJDRxLtMyofXl90VVZOEke/QddnZ8CGHoR/lCemJgZuvzBpw==","walletMeta":{"name":"Genesis wallet","assurance":"normal","unit":"ADA"},"passwordHash":"WGQxNHw4fDF8V0NERGRHY0JGcThzelVyeFdza00wM1VjYnloeVBBQXBvdWtwdWFsUTExNGVFdz09fFJXMk5kUmVJYmg2REtsa2lsWG8rQ1lvTStRZmJkMzRmRVd0MG4rSy82YUU9"},"fileType":"WALLETS_EXPORT","fileVersion":"1.0.0"} \ No newline at end of file +{"wallet":{"accounts":[{"name":"Genesis account","index":2147483648}],"walletSecretKey":"WIAwbsQgbz9X0WhvOnVeH+yRs7Ri93ESTdMspBHzeLnPUR6hLZL/NazfB40z2x8FZhLwNIt83DCuMR1nGG+ZqvsD/ouyzg3ec729fnrqEMO4A+qPTJmpiRgQZfYO2KDJDRxLtMyofXl90VVZOEke/QddnZ8CGHoR/lCemJgZuvzBpw==","walletMeta":{"name":"Imported Wallet","assurance":"normal","unit":"ADA"},"passwordHash":"WGQxNHw4fDF8V0NERGRHY0JGcThzelVyeFdza00wM1VjYnloeVBBQXBvdWtwdWFsUTExNGVFdz09fFJXMk5kUmVJYmg2REtsa2lsWG8rQ1lvTStRZmJkMzRmRVd0MG4rSy82YUU9"},"fileType":"WALLETS_EXPORT","fileVersion":"1.0.0"} \ No newline at end of file diff --git a/features/support/helpers/notifications-helpers.js b/features/support/helpers/notifications-helpers.js index 3034d82e1a..d13b3d7f9e 100644 --- a/features/support/helpers/notifications-helpers.js +++ b/features/support/helpers/notifications-helpers.js @@ -1,9 +1,9 @@ -import { syncStateTags } from '../../../source/renderer/app/domains/Wallet'; +import { WalletSyncStateTags } from '../../../source/renderer/app/domains/Wallet'; export const isActiveWalletBeingRestored = async (client) => { const result = await client.execute((expectedSyncTag) => ( daedalus.stores.ada.wallets.active.syncState.tag === expectedSyncTag - ), syncStateTags.RESTORING); + ), WalletSyncStateTags.RESTORING); return result.value; }; diff --git a/features/support/helpers/wallets-helpers.js b/features/support/helpers/wallets-helpers.js index 09fa60e5fe..f1eb79d0a3 100644 --- a/features/support/helpers/wallets-helpers.js +++ b/features/support/helpers/wallets-helpers.js @@ -14,8 +14,8 @@ export const fillOutWalletSendForm = async function (values) { const formSelector = '.WalletSendForm_component'; await this.client.setValue(`${formSelector} .receiver .SimpleInput_input`, values.address); await this.client.setValue(`${formSelector} .amount .SimpleInput_input`, values.amount); - if (values.walletPassword) { - await this.client.setValue(`${formSelector} .walletPassword .SimpleInput_input`, values.walletPassword); + if (values.spendingPassword) { + await this.client.setValue(`${formSelector} .spendingPassword .SimpleInput_input`, values.spendingPassword); } this.walletSendFormValues = values; }; @@ -58,8 +58,8 @@ export const addOrSetWalletsForScenario = function (wallet) { }; export const importWalletWithFunds = async (client, { keyFilePath, password }) => ( - await client.executeAsync((filePath, walletPassword, done) => { - daedalus.api.ada.importWalletFromKey({ filePath, walletPassword }) + await client.executeAsync((filePath, spendingPassword, done) => { + daedalus.api.ada.importWalletFromKey({ filePath, spendingPassword }) .then(() => ( daedalus.stores.ada.wallets.refreshWalletsData() .then(done) @@ -75,7 +75,7 @@ const createWalletsAsync = async (table, context) => { daedalus.api.ada.createWallet({ name: wallet.name, mnemonic: daedalus.utils.crypto.generateMnemonic(), - password: wallet.password || null, + spendingPassword: wallet.password || null, }) ))) .then(() => ( @@ -104,7 +104,7 @@ const createWalletsSequentially = async (wallets, context) => { daedalus.api.ada.createWallet({ name: wallet.name, mnemonic: daedalus.utils.crypto.generateMnemonic(), - password: wallet.password || null, + spendingPassword: wallet.password || null, }).then(() => ( daedalus.stores.ada.wallets.walletsRequest.execute() .then((storeWallets) => ( diff --git a/features/transactions-display.feature b/features/transactions-display.feature index cf599fa529..3b7fab4703 100644 --- a/features/transactions-display.feature +++ b/features/transactions-display.feature @@ -7,7 +7,7 @@ Feature: Display wallet transactions Background: Given I have completed the basic setup - And I have a "Genesis wallet" with funds + And I have a "Imported Wallet" with funds And I have the following wallets: | name | | TargetWallet | @@ -22,13 +22,13 @@ Feature: Display wallet transactions Scenario: More than five transactions Given I have made the following transactions: - | sender | receiver | amount | - | Genesis wallet | TargetWallet | 1 | - | Genesis wallet | TargetWallet | 2 | - | Genesis wallet | TargetWallet | 3 | - | Genesis wallet | TargetWallet | 4 | - | Genesis wallet | TargetWallet | 5 | - | Genesis wallet | TargetWallet | 6 | + | source | destination | amount | + | Imported Wallet | TargetWallet | 1 | + | Imported Wallet | TargetWallet | 2 | + | Imported Wallet | TargetWallet | 3 | + | Imported Wallet | TargetWallet | 4 | + | Imported Wallet | TargetWallet | 5 | + | Imported Wallet | TargetWallet | 6 | When I am on the "TargetWallet" wallet "summary" screen Then I should see the following transactions: | type | amount | diff --git a/installers/dhall/linux64.dhall b/installers/dhall/linux64.dhall index 912de70a16..be78c731ee 100644 --- a/installers/dhall/linux64.dhall +++ b/installers/dhall/linux64.dhall @@ -11,7 +11,7 @@ in , logsPrefix = "${dataDir}/Logs" , topology = "\${DAEDALUS_CONFIG}/wallet-topology.yaml" , updateLatestPath = "${dataDir}/installer.sh" - , walletDBPath = "${dataDir}/Wallet/" + , walletDBPath = "${dataDir}/Wallet" , tlsPath = "${dataDir}/tls" } , pass = diff --git a/source/common/ipc-api/go-to-network-status-screen.js b/source/common/ipc-api/go-to-network-status-screen.js new file mode 100644 index 0000000000..98a672170a --- /dev/null +++ b/source/common/ipc-api/go-to-network-status-screen.js @@ -0,0 +1 @@ +export const GO_TO_NETWORK_STATUS_SCREEN_CHANNEL = 'GO_TO_NETWORK_STATUS_SCREEN'; diff --git a/source/main/index.js b/source/main/index.js index 9253eb3097..69ccc10973 100644 --- a/source/main/index.js +++ b/source/main/index.js @@ -13,6 +13,7 @@ import { installChromeExtensions } from './utils/installChromeExtensions'; import environment from '../common/environment'; import { OPEN_ABOUT_DIALOG_CHANNEL } from '../common/ipc-api/open-about-dialog'; import { GO_TO_ADA_REDEMPTION_SCREEN_CHANNEL } from '../common/ipc-api/go-to-ada-redemption-screen'; +import { GO_TO_NETWORK_STATUS_SCREEN_CHANNEL } from '../common/ipc-api/go-to-network-status-screen'; import mainErrorHandler from './utils/mainErrorHandler'; setupLogging(); @@ -35,6 +36,10 @@ const goToAdaRedemption = () => { if (mainWindow) mainWindow.webContents.send(GO_TO_ADA_REDEMPTION_SCREEN_CHANNEL); }; +const goToNetworkStatus = () => { + if (mainWindow) mainWindow.webContents.send(GO_TO_NETWORK_STATUS_SCREEN_CHANNEL); +}; + const restartInSafeMode = () => { app.exit(21); }; @@ -46,6 +51,7 @@ const restartWithoutSafeMode = () => { const menuActions = { openAbout, goToAdaRedemption, + goToNetworkStatus, restartInSafeMode, restartWithoutSafeMode, }; diff --git a/source/main/menus/osx.js b/source/main/menus/osx.js index 6d21f10c37..d5a18552ec 100644 --- a/source/main/menus/osx.js +++ b/source/main/menus/osx.js @@ -2,7 +2,7 @@ import { compact } from 'lodash'; import environment from '../../common/environment'; export const osxMenu = (app, window, { - openAbout, goToAdaRedemption, restartInSafeMode, restartWithoutSafeMode + openAbout, goToAdaRedemption, goToNetworkStatus, restartInSafeMode, restartWithoutSafeMode }, isInSafeMode) => ( [{ label: 'Daedalus', @@ -25,6 +25,12 @@ export const osxMenu = (app, window, { restartWithoutSafeMode() : restartInSafeMode(); }, + }, { + label: 'Network status', + accelerator: 'Command+S', + click() { + goToNetworkStatus(); + }, }, { label: 'Quit', accelerator: 'Command+Q', diff --git a/source/main/menus/win-linux.js b/source/main/menus/win-linux.js index 3c68f66233..eeb1048feb 100644 --- a/source/main/menus/win-linux.js +++ b/source/main/menus/win-linux.js @@ -2,7 +2,7 @@ import { compact } from 'lodash'; import environment from '../../common/environment'; export const winLinuxMenu = (app, window, { - openAbout, goToAdaRedemption, restartInSafeMode, restartWithoutSafeMode + openAbout, goToAdaRedemption, goToNetworkStatus, restartInSafeMode, restartWithoutSafeMode }, isInSafeMode) => ( [{ label: 'Daedalus', @@ -25,6 +25,12 @@ export const winLinuxMenu = (app, window, { restartWithoutSafeMode() : restartInSafeMode(); }, + }, { + label: 'Network status', + accelerator: 'Ctrl+S', + click() { + goToNetworkStatus(); + }, }, { label: 'Close', accelerator: 'Ctrl+W', diff --git a/source/renderer/app/Routes.js b/source/renderer/app/Routes.js index ca31ee07ff..927aa62423 100644 --- a/source/renderer/app/Routes.js +++ b/source/renderer/app/Routes.js @@ -6,6 +6,7 @@ import { ROUTES } from './routes-config'; // PAGES import Root from './containers/Root'; import AdaRedemptionPage from './containers/wallet/AdaRedemptionPage'; +import NetworkStatusPage from './containers/status/NetworkStatusPage'; import WalletAddPage from './containers/wallet/WalletAddPage'; import LanguageSelectionPage from './containers/profile/LanguageSelectionPage'; import Settings from './containers/settings/Settings'; @@ -30,6 +31,7 @@ export const Routes = ( {/* */} + diff --git a/source/renderer/app/actions/ada/ada-redemption-actions.js b/source/renderer/app/actions/ada/ada-redemption-actions.js index 34ea796d5c..bad18232be 100644 --- a/source/renderer/app/actions/ada/ada-redemption-actions.js +++ b/source/renderer/app/actions/ada/ada-redemption-actions.js @@ -14,10 +14,10 @@ export default class AdaRedemptionActions { setAdaPasscode: Action<{ adaPasscode: string }> = new Action(); setAdaAmount: Action<{ adaAmount: string }> = new Action(); setDecryptionKey: Action<{ decryptionKey: string }> = new Action(); - redeemAda: Action<{ walletId: string, walletPassword: ?string }> = new Action(); + redeemAda: Action<{ walletId: string, spendingPassword: ?string }> = new Action(); // eslint-disable-next-line max-len - redeemPaperVendedAda: Action<{ walletId: string, shieldedRedemptionKey: string, walletPassword: ?string }> = new Action(); - adaSuccessfullyRedeemed: Action = new Action(); + redeemPaperVendedAda: Action<{ walletId: string, shieldedRedemptionKey: string, spendingPassword: ?string }> = new Action(); + adaSuccessfullyRedeemed: Action<{ walletId: string, amount: number }> = new Action(); acceptRedemptionDisclaimer: Action = new Action(); // TODO: refactor dialog toggles to use dialog-actions instead closeAdaRedemptionSuccessOverlay: Action = new Action(); diff --git a/source/renderer/app/actions/ada/addresses-actions.js b/source/renderer/app/actions/ada/addresses-actions.js index 834297777d..80fcebc5f9 100644 --- a/source/renderer/app/actions/ada/addresses-actions.js +++ b/source/renderer/app/actions/ada/addresses-actions.js @@ -4,6 +4,11 @@ import Action from '../lib/Action'; // ======= ADDRESSES ACTIONS ======= export default class AddressesActions { - createAddress: Action<{ walletId: string, password: ?string }> = new Action(); + createAddress: Action<{ + spendingPassword?: string, + accountIndex?: number, + walletId: string + }> = new Action(); + resetErrors: Action = new Action(); } diff --git a/source/renderer/app/actions/ada/wallets-actions.js b/source/renderer/app/actions/ada/wallets-actions.js index 04daf72403..46ff78e06e 100644 --- a/source/renderer/app/actions/ada/wallets-actions.js +++ b/source/renderer/app/actions/ada/wallets-actions.js @@ -5,13 +5,13 @@ import type { walletExportTypeChoices } from '../../types/walletExportTypes'; export type WalletImportFromFileParams = { filePath: string, walletName: ?string, - walletPassword: ?string, + spendingPassword: ?string, }; // ======= WALLET ACTIONS ======= export default class WalletsActions { - createWallet: Action<{ name: string, password: ?string }> = new Action(); + createWallet: Action<{ name: string, spendingPassword: ?string }> = new Action(); // eslint-disable-next-line max-len restoreWallet: Action<{recoveryPhrase: string, walletName: string, walletPassword: ?string, type?: string }> = new Action(); importWalletFromFile: Action = new Action(); diff --git a/source/renderer/app/api/accounts/errors.js b/source/renderer/app/api/accounts/errors.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/source/renderer/app/api/accounts/requests/getAccounts.js b/source/renderer/app/api/accounts/requests/getAccounts.js new file mode 100644 index 0000000000..e6f7180f9e --- /dev/null +++ b/source/renderer/app/api/accounts/requests/getAccounts.js @@ -0,0 +1,20 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { Accounts } from '../types'; +import { request } from '../../utils/request'; + +export type GetAccountsParams = { + walletId: string, +}; + +export const getAccounts = ( + config: RequestConfig, + { walletId }: GetAccountsParams +): Promise => ( + request({ + hostname: 'localhost', + method: 'GET', + path: `/api/v1/wallets/${walletId}/accounts`, + ...config, + }) +); diff --git a/source/renderer/app/api/accounts/types.js b/source/renderer/app/api/accounts/types.js new file mode 100644 index 0000000000..d2036c5a3a --- /dev/null +++ b/source/renderer/app/api/accounts/types.js @@ -0,0 +1,12 @@ +// @flow +import type { Addresses } from '../addresses/types'; + +export type Account = { + amount: number, + addresses: Addresses, + name: string, + walletId: string, + index: number +}; + +export type Accounts = Array; diff --git a/source/renderer/app/api/ada/adaTestReset.js b/source/renderer/app/api/ada/adaTestReset.js deleted file mode 100644 index 22347bedb3..0000000000 --- a/source/renderer/app/api/ada/adaTestReset.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import { request } from './lib/request'; -import type { RequestConfig } from './types'; - -export const adaTestReset = ( - config: RequestConfig -): Promise => ( - request({ - hostname: 'localhost', - method: 'POST', - path: '/api/test/reset', - ...config, - }) -); diff --git a/source/renderer/app/api/ada/adaTxFee.js b/source/renderer/app/api/ada/adaTxFee.js deleted file mode 100644 index 549a394062..0000000000 --- a/source/renderer/app/api/ada/adaTxFee.js +++ /dev/null @@ -1,24 +0,0 @@ -// @flow -import type { AdaTransactionFee, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type AdaTxFeeParams = { - sender: string, - receiver: string, - amount: string, - // "groupingPolicy" - Spend everything from the address - // "OptimizeForSize" for no grouping - groupingPolicy: ?'OptimizeForSecurity' | 'OptimizeForSize', -}; - -export const adaTxFee = ( - config: RequestConfig, - { sender, receiver, amount, groupingPolicy }: AdaTxFeeParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'POST', - path: `/api/txs/fee/${sender}/${receiver}/${amount}`, - ...config, - }, {}, { groupingPolicy }) -); diff --git a/source/renderer/app/api/ada/applyAdaUpdate.js b/source/renderer/app/api/ada/applyAdaUpdate.js deleted file mode 100644 index 35a02d1e33..0000000000 --- a/source/renderer/app/api/ada/applyAdaUpdate.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import { request } from './lib/request'; -import type { RequestConfig } from './types'; - -export const applyAdaUpdate = ( - config: RequestConfig, -): Promise => ( - request({ - hostname: 'localhost', - method: 'POST', - path: '/api/update/apply', - ...config, - }) -); diff --git a/source/renderer/app/api/ada/changeAdaWalletPassphrase.js b/source/renderer/app/api/ada/changeAdaWalletPassphrase.js deleted file mode 100644 index ac9245b7f5..0000000000 --- a/source/renderer/app/api/ada/changeAdaWalletPassphrase.js +++ /dev/null @@ -1,24 +0,0 @@ -// @flow -import type { AdaWallet, RequestConfig } from './types'; -import { request } from './lib/request'; -import { encryptPassphrase } from './lib/encryptPassphrase'; - -export type ChangeAdaWalletPassphraseParams = { - walletId: string, - oldPassword: ?string, - newPassword: ?string, -}; - -export const changeAdaWalletPassphrase = ( - config: RequestConfig, - { walletId, oldPassword, newPassword }: ChangeAdaWalletPassphraseParams -): Promise => { - const encryptedOldPassphrase = oldPassword ? encryptPassphrase(oldPassword) : null; - const encryptedNewPassphrase = newPassword ? encryptPassphrase(newPassword) : null; - return request({ - hostname: 'localhost', - method: 'POST', - path: `/api/wallets/password/${walletId}`, - ...config, - }, { old: encryptedOldPassphrase, new: encryptedNewPassphrase }); -}; diff --git a/source/renderer/app/api/ada/deleteAdaWallet.js b/source/renderer/app/api/ada/deleteAdaWallet.js deleted file mode 100644 index ea16828c20..0000000000 --- a/source/renderer/app/api/ada/deleteAdaWallet.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow -import { request } from './lib/request'; -import type { RequestConfig } from './types'; - -export type DeleteAdaWalletParams = { - walletId: string, -}; - -export const deleteAdaWallet = ( - config: RequestConfig, - { walletId }: DeleteAdaWalletParams -): Promise<[]> => ( - request({ - hostname: 'localhost', - method: 'DELETE', - path: `/api/wallets/${walletId}`, - ...config, - }) -); diff --git a/source/renderer/app/api/ada/getAdaAccountRecoveryPhrase.js b/source/renderer/app/api/ada/getAdaAccountRecoveryPhrase.js deleted file mode 100644 index 00be1e1d40..0000000000 --- a/source/renderer/app/api/ada/getAdaAccountRecoveryPhrase.js +++ /dev/null @@ -1,7 +0,0 @@ -// @flow -import type { AdaWalletRecoveryPhraseResponse } from './types'; -import { generateMnemonic } from '../../utils/crypto'; - -export const getAdaAccountRecoveryPhrase = (): AdaWalletRecoveryPhraseResponse => ( - generateMnemonic().split(' ') -); diff --git a/source/renderer/app/api/ada/getAdaAccounts.js b/source/renderer/app/api/ada/getAdaAccounts.js deleted file mode 100644 index 296ca4efdb..0000000000 --- a/source/renderer/app/api/ada/getAdaAccounts.js +++ /dev/null @@ -1,18 +0,0 @@ -// @flow -import type { AdaAccounts, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type GetAdaAccountsParams = { - ca: Uint8Array, -}; - -export const getAdaAccounts = ( - config: RequestConfig -): Promise => ( - request({ - hostname: 'localhost', - method: 'GET', - path: '/api/accounts', - ...config, - }) -); diff --git a/source/renderer/app/api/ada/getAdaAddressHistory.js b/source/renderer/app/api/ada/getAdaAddressHistory.js deleted file mode 100644 index 49a5531eae..0000000000 --- a/source/renderer/app/api/ada/getAdaAddressHistory.js +++ /dev/null @@ -1,22 +0,0 @@ -// @flow -import type { AdaTransactions, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type GetAdaAddressHistoryParams = { - accountId: string, - address: string, - skip: number, - limit: number, -}; - -export const getAdaAddressHistory = ( - config: RequestConfig, - { accountId, address, skip, limit }: GetAdaAddressHistoryParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'GET', - path: '/api/txs/histories', - ...config, - }, { accountId, address, skip, limit }) -); diff --git a/source/renderer/app/api/ada/getAdaHistory.js b/source/renderer/app/api/ada/getAdaHistory.js deleted file mode 100644 index 7c4c07c6c6..0000000000 --- a/source/renderer/app/api/ada/getAdaHistory.js +++ /dev/null @@ -1,23 +0,0 @@ -// @flow -import type { AdaTransactions, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type GetAdaHistoryParams = { - walletId: ?string, - accountId: ?string, - address: ?string, - skip: number, - limit: number, -}; - -export const getAdaHistory = ( - config: RequestConfig, - { walletId, accountId, address, skip, limit }: GetAdaHistoryParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'GET', - path: '/api/txs/histories', - ...config, - }, { walletId, accountId, address, skip, limit }) -); diff --git a/source/renderer/app/api/ada/getAdaHistoryByAccount.js b/source/renderer/app/api/ada/getAdaHistoryByAccount.js deleted file mode 100644 index e7d50e1ae6..0000000000 --- a/source/renderer/app/api/ada/getAdaHistoryByAccount.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import type { AdaTransactions, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type GetAdaHistoryByAccountParams = { - accountId: string, - skip: number, - limit: number, -}; - -export const getAdaHistoryByAccount = ( - config: RequestConfig, - { accountId, skip, limit }: GetAdaHistoryByAccountParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'GET', - path: '/api/txs/histories', - ...config - }, { accountId, skip, limit }) -); diff --git a/source/renderer/app/api/ada/getAdaHistoryByWallet.js b/source/renderer/app/api/ada/getAdaHistoryByWallet.js deleted file mode 100644 index bcfed5b63b..0000000000 --- a/source/renderer/app/api/ada/getAdaHistoryByWallet.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import type { AdaTransactions, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type GetAdaHistoryByWalletParams = { - walletId: string, - skip: number, - limit: number, -}; - -export const getAdaHistoryByWallet = ( - config: RequestConfig, - { walletId, skip, limit }: GetAdaHistoryByWalletParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'GET', - path: '/api/txs/histories', - ...config - }, { walletId, skip, limit }) -); diff --git a/source/renderer/app/api/ada/getAdaLocalTimeDifference.js b/source/renderer/app/api/ada/getAdaLocalTimeDifference.js deleted file mode 100644 index b1d499a9c5..0000000000 --- a/source/renderer/app/api/ada/getAdaLocalTimeDifference.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import type { AdaLocalTimeDifference, RequestConfig } from './types'; -import { request } from './lib/request'; - -export const getAdaLocalTimeDifference = ( - config: RequestConfig -): Promise => ( - request({ - hostname: 'localhost', - method: 'GET', - path: '/api/settings/time/difference', - ...config - }) -); diff --git a/source/renderer/app/api/ada/getAdaSyncProgress.js b/source/renderer/app/api/ada/getAdaSyncProgress.js deleted file mode 100644 index ca2f7c9d21..0000000000 --- a/source/renderer/app/api/ada/getAdaSyncProgress.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import type { AdaSyncProgressResponse, RequestConfig } from './types'; -import { request } from './lib/request'; - -export const getAdaSyncProgress = ( - config: RequestConfig, -): Promise => ( - request({ - hostname: 'localhost', - method: 'GET', - path: '/api/settings/sync/progress', - ...config - }) -); diff --git a/source/renderer/app/api/ada/getAdaWalletAccounts.js b/source/renderer/app/api/ada/getAdaWalletAccounts.js deleted file mode 100644 index 230733ab0d..0000000000 --- a/source/renderer/app/api/ada/getAdaWalletAccounts.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow -import type { AdaAccounts, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type GetAdaWalletAccountsParams = { - walletId: string, -}; - -export const getAdaWalletAccounts = ( - config: RequestConfig, - { walletId }: GetAdaWalletAccountsParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'GET', - path: '/api/accounts', - ...config - }, { accountId: walletId }) -); diff --git a/source/renderer/app/api/ada/getAdaWalletCertificateAdditionalMnemonics.js b/source/renderer/app/api/ada/getAdaWalletCertificateAdditionalMnemonics.js deleted file mode 100644 index 365fe98fcf..0000000000 --- a/source/renderer/app/api/ada/getAdaWalletCertificateAdditionalMnemonics.js +++ /dev/null @@ -1,9 +0,0 @@ -// @flow -import type { AdaWalletCertificateAdditionalMnemonicsResponse } from './types'; -import { generateMnemonic } from '../../utils/crypto'; -import { PAPER_WALLET_WRITTEN_WORDS_COUNT } from '../../config/cryptoConfig'; - -// eslint-disable-next-line -export const getAdaWalletCertificateAdditionalMnemonics = (): AdaWalletCertificateAdditionalMnemonicsResponse => ( - generateMnemonic(PAPER_WALLET_WRITTEN_WORDS_COUNT).split(' ') -); diff --git a/source/renderer/app/api/ada/getAdaWalletCertificateRecoveryPhrase.js b/source/renderer/app/api/ada/getAdaWalletCertificateRecoveryPhrase.js deleted file mode 100644 index 460c1dae8d..0000000000 --- a/source/renderer/app/api/ada/getAdaWalletCertificateRecoveryPhrase.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import type { AdaWalletCertificateRecoveryPhraseResponse } from './types'; -import { scramblePaperWalletMnemonic } from '../../utils/crypto'; - -export type GetAdaWalletCertificateRecoveryPhraseParams = { - passphrase: string, - scrambledInput: string, -}; - -export const getAdaWalletCertificateRecoveryPhrase = ( - { passphrase, scrambledInput }: GetAdaWalletCertificateRecoveryPhraseParams -): AdaWalletCertificateRecoveryPhraseResponse => ( - scramblePaperWalletMnemonic(passphrase, scrambledInput) -); diff --git a/source/renderer/app/api/ada/getAdaWalletRecoveryPhraseFromCertificate.js b/source/renderer/app/api/ada/getAdaWalletRecoveryPhraseFromCertificate.js deleted file mode 100644 index afde8cf1c9..0000000000 --- a/source/renderer/app/api/ada/getAdaWalletRecoveryPhraseFromCertificate.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import type { AdaWalletRecoveryPhraseFromCertificateResponse } from './types'; -import { unscramblePaperWalletMnemonic } from '../../utils/crypto'; - -export type GetAdaWalletRecoveryPhraseFromCertificateParams = { - passphrase: string, // 9-word mnemonic - scrambledInput: string, // 18-word scrambled mnemonic -}; - -export const getAdaWalletRecoveryPhraseFromCertificate = ( - { passphrase, scrambledInput }: GetAdaWalletRecoveryPhraseFromCertificateParams -): AdaWalletRecoveryPhraseFromCertificateResponse => ( - unscramblePaperWalletMnemonic(passphrase, scrambledInput) -); diff --git a/source/renderer/app/api/ada/getAdaWallets.js b/source/renderer/app/api/ada/getAdaWallets.js deleted file mode 100644 index 93a9eee532..0000000000 --- a/source/renderer/app/api/ada/getAdaWallets.js +++ /dev/null @@ -1,18 +0,0 @@ -// @flow -import type { AdaV1Wallets, RequestConfig } from './types'; -import { request } from './lib/v1/request'; -import { MAX_ADA_WALLETS_COUNT } from '../../config/numbersConfig'; - -export const getAdaWallets = ( - config: RequestConfig -): Promise => ( - request({ - hostname: 'localhost', - method: 'GET', - path: '/api/v1/wallets', - ...config - }, { - per_page: MAX_ADA_WALLETS_COUNT, // 50 is the max per_page value - sort_by: 'ASC[created_at]', - }) -); diff --git a/source/renderer/app/api/ada/importAdaBackupJSON.js b/source/renderer/app/api/ada/importAdaBackupJSON.js deleted file mode 100644 index cc69201dbd..0000000000 --- a/source/renderer/app/api/ada/importAdaBackupJSON.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow -import type { AdaWallet, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type ImportAdaBackupJSONParams = { - filePath: string, -}; - -export const importAdaBackupJSON = ( - config: RequestConfig, - { filePath }: ImportAdaBackupJSONParams, -): Promise => ( - request({ - hostname: 'localhost', - method: 'POST', - path: '/api/backup/import', - ...config - }, {}, filePath) -); diff --git a/source/renderer/app/api/ada/importAdaWallet.js b/source/renderer/app/api/ada/importAdaWallet.js deleted file mode 100644 index 626234763a..0000000000 --- a/source/renderer/app/api/ada/importAdaWallet.js +++ /dev/null @@ -1,20 +0,0 @@ -// @flow -import type { AdaWallet, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type ImportAdaWalletParams = { - filePath: string, - walletPassword: ?string, -}; - -export const importAdaWallet = ( - config: RequestConfig, - { walletPassword, filePath }: ImportAdaWalletParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'POST', - path: '/api/wallets/keys', - ...config - }, { passphrase: walletPassword }, filePath) -); diff --git a/source/renderer/app/api/ada/index.js b/source/renderer/app/api/ada/index.js deleted file mode 100644 index 3b230c51a2..0000000000 --- a/source/renderer/app/api/ada/index.js +++ /dev/null @@ -1,890 +0,0 @@ -// @flow -import { split, get } from 'lodash'; -import { action } from 'mobx'; -import { ipcRenderer } from 'electron'; -import BigNumber from 'bignumber.js'; -import { Logger, stringifyData, stringifyError } from '../../../../common/logging'; -import { unixTimestampToDate } from './lib/utils'; -import Wallet from '../../domains/Wallet'; -import WalletTransaction, { transactionTypes } from '../../domains/WalletTransaction'; -import WalletAddress from '../../domains/WalletAddress'; -import { isValidMnemonic } from '../../../../common/decrypt'; -import { isValidRedemptionKey, isValidPaperVendRedemptionKey } from '../../../../common/redemption-key-validation'; -import { LOVELACES_PER_ADA } from '../../config/numbersConfig'; -import { getAdaSyncProgress } from './getAdaSyncProgress'; -import patchAdaApi from './mocks/patchAdaApi'; - -import { getAdaWallets } from './getAdaWallets'; -import { changeAdaWalletPassphrase } from './changeAdaWalletPassphrase'; -import { deleteAdaWallet } from './deleteAdaWallet'; -import { newAdaWallet } from './newAdaWallet'; -import { newAdaWalletAddress } from './newAdaWalletAddress'; -import { restoreAdaWallet } from './restoreAdaWallet'; -import { updateAdaWallet } from './updateAdaWallet'; -import { exportAdaBackupJSON } from './exportAdaBackupJSON'; -import { importAdaBackupJSON } from './importAdaBackupJSON'; -import { importAdaWallet } from './importAdaWallet'; -import { getAdaWalletAccounts } from './getAdaWalletAccounts'; -import { isValidAdaAddress } from './isValidAdaAddress'; -import { adaTxFee } from './adaTxFee'; -import { newAdaPayment } from './newAdaPayment'; -import { redeemAda } from './redeemAda'; -import { redeemAdaPaperVend } from './redeemAdaPaperVend'; -import { nextAdaUpdate } from './nextAdaUpdate'; -import { postponeAdaUpdate } from './postponeAdaUpdate'; -import { applyAdaUpdate } from './applyAdaUpdate'; -import { adaTestReset } from './adaTestReset'; -import { getAdaHistoryByWallet } from './getAdaHistoryByWallet'; -import { getAdaAccountRecoveryPhrase } from './getAdaAccountRecoveryPhrase'; -import { getAdaWalletCertificateAdditionalMnemonics } from './getAdaWalletCertificateAdditionalMnemonics'; -import { getAdaWalletCertificateRecoveryPhrase } from './getAdaWalletCertificateRecoveryPhrase'; -import { getAdaWalletRecoveryPhraseFromCertificate } from './getAdaWalletRecoveryPhraseFromCertificate'; -import { getAdaLocalTimeDifference } from './getAdaLocalTimeDifference'; -import { sendAdaBugReport } from './sendAdaBugReport'; - -import type { - AdaLocalTimeDifference, - AdaSyncProgressResponse, - AdaAddress, - AdaAccounts, - AdaTransaction, - AdaTransactionFee, - AdaTransactions, - AdaWallet, - AdaV1Wallet, - AdaV1Wallets, - AdaWalletRecoveryPhraseResponse, - AdaWalletCertificateAdditionalMnemonicsResponse, - AdaWalletCertificateRecoveryPhraseResponse, - GetWalletCertificateAdditionalMnemonicsResponse, - GetWalletCertificateRecoveryPhraseResponse, - GetWalletRecoveryPhraseFromCertificateResponse, RequestConfig, -} from './types'; - -import type { - CreateWalletRequest, - CreateWalletResponse, - CreateTransactionResponse, - DeleteWalletRequest, - DeleteWalletResponse, - GetLocalTimeDifferenceResponse, - GetSyncProgressResponse, - GetTransactionsRequest, - GetTransactionsResponse, - GetWalletRecoveryPhraseResponse, - GetWalletsResponse, - RestoreWalletRequest, - RestoreWalletResponse, - SendBugReportRequest, - SendBugReportResponse, - UpdateWalletResponse, - UpdateWalletPasswordRequest, - UpdateWalletPasswordResponse, -} from '../common'; - -import { - GenericApiError, - IncorrectWalletPasswordError, - WalletAlreadyRestoredError, - ReportRequestError, InvalidMnemonicError, -} from '../common'; - -import { - AllFundsAlreadyAtReceiverAddressError, - NotAllowedToSendMoneyToRedeemAddressError, - NotAllowedToSendMoneyToSameAddressError, - NotEnoughFundsForTransactionFeesError, - NotEnoughMoneyToSendError, - RedeemAdaError, - WalletAlreadyImportedError, - WalletFileImportError, -} from './errors'; - -import { - ADA_CERTIFICATE_MNEMONIC_LENGHT, - ADA_REDEMPTION_PASSPHRASE_LENGHT, - WALLET_RECOVERY_PHRASE_WORD_COUNT -} from '../../config/cryptoConfig'; - -import { AdaV1AssuranceOptions } from './types'; -import { assuranceModeOptions } from '../../types/transactionAssuranceTypes'; - -/** - * The api layer that is used for all requests to the - * cardano backend when working with the ADA coin. - */ - -// ADA specific Request / Response params -export type GetAddressesResponse = { - accountId: ?string, - addresses: Array, -}; -export type GetAddressesRequest = { - walletId: string, -}; -export type CreateAddressResponse = WalletAddress; -export type CreateAddressRequest = { - accountId: string, - password: ?string, -}; - -export type CreateTransactionRequest = { - sender: string, - receiver: string, - amount: string, - password?: ?string, -}; -export type UpdateWalletRequest = { - walletId: string, - name: string, - assurance: string, -}; -export type RedeemAdaRequest = { - redemptionCode: string, - accountId: string, - walletPassword: ?string, -}; -export type RedeemAdaResponse = Wallet; -export type RedeemPaperVendedAdaRequest = { - shieldedRedemptionKey: string, - mnemonics: string, - accountId: string, - walletPassword: ?string, -}; -export type RedeemPaperVendedAdaResponse = RedeemPaperVendedAdaRequest; -export type ImportWalletFromKeyRequest = { - filePath: string, - walletPassword: ?string, -}; -export type ImportWalletFromKeyResponse = Wallet; -export type ImportWalletFromFileRequest = { - filePath: string, - walletPassword: ?string, - walletName: ?string, -}; -export type ImportWalletFromFileResponse = Wallet; -export type NextUpdateResponse = ?{ - version: ?string, -}; -export type PostponeUpdateResponse = Promise; -export type ApplyUpdateResponse = Promise; - -export type TransactionFeeRequest = { - sender: string, - receiver: string, - amount: string, -}; -export type TransactionFeeResponse = BigNumber; -export type ExportWalletToFileRequest = { - walletId: string, - filePath: string, - password: ?string -}; -export type ExportWalletToFileResponse = []; -export type GetWalletCertificateRecoveryPhraseRequest = { - passphrase: string, - input: string, -}; -export type GetWalletRecoveryPhraseFromCertificateRequest = { - passphrase: string, - scrambledInput: string, -}; - -// const notYetImplemented = () => new Promise((_, reject) => { -// reject(new ApiMethodNotYetImplementedError()); -// }); - -// Commented out helper code for testing async APIs -// (async () => { -// const result = await ClientApi.nextUpdate(); -// console.log('nextUpdate', result); -// })(); - -// Commented out helper code for testing sync APIs -// (() => { -// const result = ClientApi.isValidRedeemCode('HSoXEnt9X541uHvtzBpy8vKfTo1C9TkAX3wat2c6ikg='); -// console.log('isValidRedeemCode', result); -// })(); - - -export default class AdaApi { - - config: RequestConfig; - - constructor(isTest: boolean, config: RequestConfig) { - this.setRequestConfig(config); - if (isTest) patchAdaApi(this); - } - - setRequestConfig(config: RequestConfig) { - this.config = config; - } - - getWallets = async (): Promise => { - Logger.debug('AdaApi::getWallets called'); - try { - const response: AdaV1Wallets = await getAdaWallets(this.config); - Logger.debug('AdaApi::getWallets success: ' + stringifyData(response)); - return response.map(data => _createWalletFromServerV1Data(data)); - } catch (error) { - Logger.error('AdaApi::getWallets error: ' + stringifyError(error)); - throw new GenericApiError(); - } - }; - - getAddresses = async (request: GetAddressesRequest): Promise => { - Logger.debug('AdaApi::getAddresses called: ' + stringifyData(request)); - const { walletId } = request; - try { - const response: AdaAccounts = await getAdaWalletAccounts(this.config, { walletId }); - Logger.debug('AdaApi::getAddresses success: ' + stringifyData(response)); - if (!response.length) { - return new Promise((resolve) => resolve({ accountId: null, addresses: [] })); - } - // For now only the first wallet account is used - const firstAccount = response[0]; - const firstAccountId = firstAccount.caId; - const firstAccountAddresses = firstAccount.caAddresses; - - return new Promise((resolve) => resolve({ - accountId: firstAccountId, - addresses: firstAccountAddresses.map(data => _createAddressFromServerData(data)), - })); - } catch (error) { - Logger.error('AdaApi::getAddresses error: ' + stringifyError(error)); - throw new GenericApiError(); - } - }; - - getTransactions = async (request: GetTransactionsRequest): Promise => { - Logger.debug('AdaApi::searchHistory called: ' + stringifyData(request)); - const { walletId, skip, limit } = request; - try { - const history: AdaTransactions = await getAdaHistoryByWallet(this.config, { - walletId, - skip, - limit - }); - Logger.debug('AdaApi::searchHistory success: ' + stringifyData(history)); - return new Promise((resolve) => resolve({ - transactions: history[0].map(data => _createTransactionFromServerData(data)), - total: history[1] - })); - } catch (error) { - Logger.error('AdaApi::searchHistory error: ' + stringifyError(error)); - throw new GenericApiError(); - } - }; - - createWallet = async (request: CreateWalletRequest): Promise => { - Logger.debug('AdaApi::createWallet called'); - const { name, mnemonic, password } = request; - const assurance = 'CWANormal'; - const unit = 0; - try { - const walletInitData = { - cwInitMeta: { - cwName: name, - cwAssurance: assurance, - cwUnit: unit, - }, - cwBackupPhrase: { - bpToList: split(mnemonic), // array of mnemonic words - } - }; - const wallet: AdaWallet = await newAdaWallet(this.config, { - password, - walletInitData - }); - Logger.debug('AdaApi::createWallet success'); - return _createWalletFromServerData(wallet); - } catch (error) { - Logger.error('AdaApi::createWallet error: ' + stringifyError(error)); - throw new GenericApiError(); - } - }; - - deleteWallet = async (request: DeleteWalletRequest): Promise => { - Logger.debug('AdaApi::deleteWallet called: ' + stringifyData(request)); - try { - const { walletId } = request; - await deleteAdaWallet(this.config, { walletId }); - Logger.debug('AdaApi::deleteWallet success: ' + stringifyData(request)); - return true; - } catch (error) { - Logger.error('AdaApi::deleteWallet error: ' + stringifyError(error)); - throw new GenericApiError(); - } - }; - - createTransaction = async ( - request: CreateTransactionRequest - ): Promise => { - Logger.debug('AdaApi::createTransaction called'); - const { sender, receiver, amount, password } = request; - // sender must be set as accountId (account.caId) and not walletId - try { - // default value. Select (OptimizeForSecurity | OptimizeForSize) will be implemented - const groupingPolicy = 'OptimizeForSecurity'; - const response: AdaTransaction = await newAdaPayment(this.config, - { sender, receiver, amount, groupingPolicy, password } - ); - Logger.debug('AdaApi::createTransaction success: ' + stringifyData(response)); - return _createTransactionFromServerData(response); - } catch (error) { - Logger.debug('AdaApi::createTransaction error: ' + stringifyError(error)); - // eslint-disable-next-line max-len - if (error.message.includes('It\'s not allowed to send money to the same address you are sending from')) { - throw new NotAllowedToSendMoneyToSameAddressError(); - } - if (error.message.includes('Destination address can\'t be redeem address')) { - throw new NotAllowedToSendMoneyToRedeemAddressError(); - } - if (error.message.includes('Not enough money')) { - throw new NotEnoughMoneyToSendError(); - } - if (error.message.includes('Passphrase doesn\'t match')) { - throw new IncorrectWalletPasswordError(); - } - throw new GenericApiError(); - } - }; - - calculateTransactionFee = async ( - request: TransactionFeeRequest - ): Promise => { - Logger.debug('AdaApi::calculateTransactionFee called'); - const { sender, receiver, amount } = request; - try { - // default value. Select (OptimizeForSecurity | OptimizeForSize) will be implemented - const groupingPolicy = 'OptimizeForSecurity'; - const response: adaTxFee = await adaTxFee(this.config, - { sender, receiver, amount, groupingPolicy } - ); - Logger.debug('AdaApi::calculateTransactionFee success: ' + stringifyData(response)); - return _createTransactionFeeFromServerData(response); - } catch (error) { - Logger.debug('AdaApi::calculateTransactionFee error: ' + stringifyError(error)); - // eslint-disable-next-line max-len - if (error.message.includes('not enough money on addresses which are not included in output addresses set')) { - throw new AllFundsAlreadyAtReceiverAddressError(); - } - if (error.message.includes('not enough money')) { - throw new NotEnoughFundsForTransactionFeesError(); - } - throw new GenericApiError(); - } - }; - - createAddress = async (request: CreateAddressRequest): Promise => { - Logger.debug('AdaApi::createAddress called'); - const { accountId, password } = request; - try { - const response: AdaAddress = await newAdaWalletAddress(this.config, - { password, accountId } - ); - Logger.debug('AdaApi::createAddress success: ' + stringifyData(response)); - return _createAddressFromServerData(response); - } catch (error) { - Logger.debug('AdaApi::createAddress error: ' + stringifyError(error)); - if (error.message.includes('Passphrase doesn\'t match')) { - throw new IncorrectWalletPasswordError(); - } - throw new GenericApiError(); - } - }; - - isValidAddress = (address: string): Promise => ( - isValidAdaAddress(this.config, { address }) - ); - - isValidMnemonic(mnemonic: string): Promise { - return isValidMnemonic(mnemonic, WALLET_RECOVERY_PHRASE_WORD_COUNT); - } - - isValidRedemptionKey(mnemonic: string): Promise { - return isValidRedemptionKey(mnemonic); - } - - isValidPaperVendRedemptionKey(mnemonic: string): Promise { - return isValidPaperVendRedemptionKey(mnemonic); - } - - isValidRedemptionMnemonic(mnemonic: string): Promise { - return isValidMnemonic(mnemonic, ADA_REDEMPTION_PASSPHRASE_LENGHT); - } - - isValidCertificateMnemonic(mnemonic: string): boolean { - return mnemonic.split(' ').length === ADA_CERTIFICATE_MNEMONIC_LENGHT; - } - - getWalletRecoveryPhrase(): Promise { - Logger.debug('AdaApi::getWalletRecoveryPhrase called'); - try { - const response: Promise = new Promise( - (resolve) => resolve(getAdaAccountRecoveryPhrase()) - ); - Logger.debug('AdaApi::getWalletRecoveryPhrase success'); - return response; - } catch (error) { - Logger.error('AdaApi::getWalletRecoveryPhrase error: ' + stringifyError(error)); - throw new GenericApiError(); - } - } - - // eslint-disable-next-line max-len - getWalletCertificateAdditionalMnemonics(): Promise { - Logger.debug('AdaApi::getWalletCertificateAdditionalMnemonics called'); - try { - const response: Promise = new Promise( - (resolve) => resolve(getAdaWalletCertificateAdditionalMnemonics()) - ); - Logger.debug('AdaApi::getWalletCertificateAdditionalMnemonics success'); - return response; - } catch (error) { - Logger.error('AdaApi::getWalletCertificateAdditionalMnemonics error: ' + stringifyError(error)); - throw new GenericApiError(); - } - } - - getWalletCertificateRecoveryPhrase( - request: GetWalletCertificateRecoveryPhraseRequest - ): Promise { - Logger.debug('AdaApi::getWalletCertificateRecoveryPhrase called'); - const { passphrase, input } = request; - try { - const response: Promise = new Promise( - (resolve) => resolve(getAdaWalletCertificateRecoveryPhrase({ - passphrase, - scrambledInput: input, - })) - ); - Logger.debug('AdaApi::getWalletCertificateRecoveryPhrase success'); - return response; - } catch (error) { - Logger.error('AdaApi::getWalletCertificateRecoveryPhrase error: ' + stringifyError(error)); - throw new GenericApiError(); - } - } - - getWalletRecoveryPhraseFromCertificate( - request: GetWalletRecoveryPhraseFromCertificateRequest - ): Promise { - Logger.debug('AdaApi::getWalletRecoveryPhraseFromCertificate called'); - const { passphrase, scrambledInput } = request; - try { - const response = getAdaWalletRecoveryPhraseFromCertificate({ passphrase, scrambledInput }); - Logger.debug('AdaApi::getWalletRecoveryPhraseFromCertificate success'); - return Promise.resolve(response); - } catch (error) { - Logger.debug('AdaApi::getWalletRecoveryPhraseFromCertificate error: ' + stringifyError(error)); - return Promise.reject(new InvalidMnemonicError()); - } - } - - restoreWallet = async (request: RestoreWalletRequest): Promise => { - Logger.debug('AdaApi::restoreWallet called'); - const { recoveryPhrase, walletName, walletPassword } = request; - const assurance = 'CWANormal'; - const unit = 0; - - const walletInitData = { - cwInitMeta: { - cwName: walletName, - cwAssurance: assurance, - cwUnit: unit, - }, - cwBackupPhrase: { - bpToList: split(recoveryPhrase), // array of mnemonic words - } - }; - - try { - const wallet: AdaWallet = await restoreAdaWallet(this.config, - { walletPassword, walletInitData } - ); - Logger.debug('AdaApi::restoreWallet success'); - return _createWalletFromServerData(wallet); - } catch (error) { - Logger.debug('AdaApi::restoreWallet error: ' + stringifyError(error)); - // TODO: backend will return something different here, if multiple wallets - // are restored from the key and if there are duplicate wallets we will get - // some kind of error and present the user with message that some wallets - // where not imported/restored if some where. if no wallets are imported - // we will error out completely with throw block below - if (error.message.includes('Wallet with that mnemonics already exists')) { - throw new WalletAlreadyRestoredError(); - } - // We don't know what the problem was -> throw generic error - throw new GenericApiError(); - } - }; - - importWalletFromKey = async ( - request: ImportWalletFromKeyRequest - ): Promise => { - Logger.debug('AdaApi::importWalletFromKey called'); - const { filePath, walletPassword } = request; - try { - const importedWallet: AdaWallet = await importAdaWallet(this.config, - { walletPassword, filePath } - ); - Logger.debug('AdaApi::importWalletFromKey success'); - return _createWalletFromServerData(importedWallet); - } catch (error) { - Logger.debug('AdaApi::importWalletFromKey error: ' + stringifyError(error)); - if (error.message.includes('already exists')) { - throw new WalletAlreadyImportedError(); - } - throw new WalletFileImportError(); - } - }; - - importWalletFromFile = async ( - request: ImportWalletFromFileRequest - ): Promise => { - Logger.debug('AdaApi::importWalletFromFile called'); - const { filePath, walletPassword } = request; - const isKeyFile = filePath.split('.').pop().toLowerCase() === 'key'; - try { - const importedWallet: AdaWallet = isKeyFile ? ( - await importAdaWallet(this.config, { walletPassword, filePath }) - ) : ( - await importAdaBackupJSON(this.config, { filePath }) - ); - Logger.debug('AdaApi::importWalletFromFile success'); - return _createWalletFromServerData(importedWallet); - } catch (error) { - Logger.debug('AdaApi::importWalletFromFile error: ' + stringifyError(error)); - if (error.message.includes('already exists')) { - throw new WalletAlreadyImportedError(); - } - throw new WalletFileImportError(); - } - }; - - redeemAda = async (request: RedeemAdaRequest): Promise => { - Logger.debug('AdaApi::redeemAda called'); - const { redemptionCode, accountId, walletPassword } = request; - try { - const walletRedeemData = { - crWalletId: accountId, - crSeed: redemptionCode, - }; - - const response: AdaTransaction = await redeemAda(this.config, - { walletPassword, walletRedeemData } - ); - - Logger.debug('AdaApi::redeemAda success'); - return _createTransactionFromServerData(response); - } catch (error) { - Logger.debug('AdaApi::redeemAda error: ' + stringifyError(error)); - if (error.message.includes('Passphrase doesn\'t match')) { - throw new IncorrectWalletPasswordError(); - } - throw new RedeemAdaError(); - } - }; - - redeemPaperVendedAda = async ( - request: RedeemPaperVendedAdaRequest - ): Promise => { - Logger.debug('AdaApi::redeemAdaPaperVend called'); - const { shieldedRedemptionKey, mnemonics, accountId, walletPassword } = request; - try { - const redeemPaperVendedData = { - pvWalletId: accountId, - pvSeed: shieldedRedemptionKey, - pvBackupPhrase: { - bpToList: split(mnemonics), - } - }; - - const response: AdaTransaction = await redeemAdaPaperVend(this.config, - { walletPassword, redeemPaperVendedData } - ); - - Logger.debug('AdaApi::redeemAdaPaperVend success'); - return _createTransactionFromServerData(response); - } catch (error) { - Logger.debug('AdaApi::redeemAdaPaperVend error: ' + stringifyError(error)); - if (error.message.includes('Passphrase doesn\'t match')) { - throw new IncorrectWalletPasswordError(); - } - throw new RedeemAdaError(); - } - }; - - async sendBugReport(requestFormData: SendBugReportRequest): Promise { - Logger.debug('AdaApi::sendBugReport called: ' + stringifyData(requestFormData)); - try { - await sendAdaBugReport({ requestFormData }); - Logger.debug('AdaApi::sendBugReport success'); - return true; - } catch (error) { - Logger.error('AdaApi::sendBugReport error: ' + stringifyError(error)); - throw new ReportRequestError(); - } - } - - nextUpdate = async (): Promise => { - Logger.debug('AdaApi::nextUpdate called'); - let nextUpdate = null; - try { - // TODO: add flow type definitions for nextUpdate response - const response: Promise = await nextAdaUpdate(this.config); - Logger.debug('AdaApi::nextUpdate success: ' + stringifyData(response)); - if (response && response.cuiSoftwareVersion) { - nextUpdate = { - version: get(response, ['cuiSoftwareVersion', 'svNumber'], null) - }; - } - } catch (error) { - if (error.message.includes('No updates available')) { - Logger.debug('AdaApi::nextUpdate success: No updates available'); - } else { - Logger.error('AdaApi::nextUpdate error: ' + stringifyError(error)); - } - // throw new GenericApiError(); - } - return nextUpdate; - // TODO: remove hardcoded response after node update is tested - // nextUpdate = { - // cuiSoftwareVersion: { - // svAppName: { - // getApplicationName: 'cardano' - // }, - // svNumber: 1 - // }, - // cuiBlockVesion: { - // bvMajor: 0, - // bvMinor: 1, - // bvAlt: 0 - // }, - // cuiScriptVersion: 1, - // cuiImplicit: false, - // cuiVotesFor: 2, - // cuiVotesAgainst: 0, - // cuiPositiveStake: { - // getCoin: 66666 - // }, - // cuiNegativeStake: { - // getCoin: 0 - // } - // }; - // if (nextUpdate && nextUpdate.cuiSoftwareVersion && nextUpdate.cuiSoftwareVersion.svNumber) { - // return { version: nextUpdate.cuiSoftwareVersion.svNumber }; - // } else if (nextUpdate) { - // return { version: null }; - // } - // return null; - }; - - postponeUpdate = async (): PostponeUpdateResponse => { - Logger.debug('AdaApi::postponeUpdate called'); - try { - const response: Promise = await postponeAdaUpdate(this.config); - Logger.debug('AdaApi::postponeUpdate success: ' + stringifyData(response)); - } catch (error) { - Logger.error('AdaApi::postponeUpdate error: ' + stringifyError(error)); - throw new GenericApiError(); - } - }; - - applyUpdate = async (): ApplyUpdateResponse => { - Logger.debug('AdaApi::applyUpdate called'); - try { - const response: Promise = await applyAdaUpdate(this.config); - Logger.debug('AdaApi::applyUpdate success: ' + stringifyData(response)); - ipcRenderer.send('kill-process'); - } catch (error) { - Logger.error('AdaApi::applyUpdate error: ' + stringifyError(error)); - throw new GenericApiError(); - } - }; - - getSyncProgress = async (): Promise => { - Logger.debug('AdaApi::syncProgress called'); - try { - const response: AdaSyncProgressResponse = await getAdaSyncProgress(this.config); - Logger.debug('AdaApi::syncProgress success: ' + stringifyData(response)); - const localDifficulty = response._spLocalCD.getChainDifficulty.getBlockCount; - // In some cases we dont get network difficulty & we need to wait for it from the notify API - let networkDifficulty = null; - if (response._spNetworkCD) { - networkDifficulty = response._spNetworkCD.getChainDifficulty.getBlockCount; - } - return { localDifficulty, networkDifficulty }; - } catch (error) { - Logger.debug('AdaApi::syncProgress error: ' + stringifyError(error)); - throw new GenericApiError(); - } - }; - - updateWallet = async (request: UpdateWalletRequest): Promise => { - Logger.debug('AdaApi::updateWallet called: ' + stringifyData(request)); - const { walletId, name, assurance } = request; - const unit = 0; - - const walletMeta = { - cwName: name, - cwAssurance: assurance, - cwUnit: unit, - }; - - try { - const wallet: AdaWallet = await updateAdaWallet(this.config, { walletId, walletMeta }); - Logger.debug('AdaApi::updateWallet success: ' + stringifyData(wallet)); - return _createWalletFromServerData(wallet); - } catch (error) { - Logger.error('AdaApi::updateWallet error: ' + stringifyError(error)); - throw new GenericApiError(); - } - }; - - updateWalletPassword = async ( - request: UpdateWalletPasswordRequest - ): Promise => { - Logger.debug('AdaApi::updateWalletPassword called'); - const { walletId, oldPassword, newPassword } = request; - try { - await changeAdaWalletPassphrase(this.config, { walletId, oldPassword, newPassword }); - Logger.debug('AdaApi::updateWalletPassword success'); - return true; - } catch (error) { - Logger.debug('AdaApi::updateWalletPassword error: ' + stringifyError(error)); - if (error.message.includes('Invalid old passphrase given')) { - throw new IncorrectWalletPasswordError(); - } - throw new GenericApiError(); - } - }; - - exportWalletToFile = async ( - request: ExportWalletToFileRequest - ): Promise => { - const { walletId, filePath } = request; - Logger.debug('AdaApi::exportWalletToFile called'); - try { - const response: Promise<[]> = await exportAdaBackupJSON(this.config, { - walletId, - filePath - }); - Logger.debug('AdaApi::exportWalletToFile success: ' + stringifyData(response)); - return response; - } catch (error) { - Logger.error('AdaApi::exportWalletToFile error: ' + stringifyError(error)); - throw new GenericApiError(); - } - }; - - testReset = async (): Promise => { - Logger.debug('AdaApi::testReset called'); - try { - const response: Promise = await adaTestReset(this.config); - Logger.debug('AdaApi::testReset success: ' + stringifyData(response)); - return response; - } catch (error) { - Logger.error('AdaApi::testReset error: ' + stringifyError(error)); - throw new GenericApiError(); - } - }; - - getLocalTimeDifference = async (): Promise => { - Logger.debug('AdaApi::getLocalTimeDifference called'); - try { - const response: AdaLocalTimeDifference = await getAdaLocalTimeDifference(this.config); - Logger.debug('AdaApi::getLocalTimeDifference success: ' + stringifyData(response)); - return Math.abs(response); // time offset direction is irrelevant to the UI - } catch (error) { - Logger.error('AdaApi::getLocalTimeDifference error: ' + stringifyError(error)); - throw new GenericApiError(); - } - }; -} - -// ========== TRANSFORM SERVER DATA INTO FRONTEND MODELS ========= - -const _createWalletFromServerData = action( - 'AdaApi::_createWalletFromServerData', (data: AdaWallet) => ( - new Wallet({ - id: data.cwId, - amount: new BigNumber(data.cwAmount.getCCoin).dividedBy(LOVELACES_PER_ADA), - name: data.cwMeta.cwName, - assurance: data.cwMeta.cwAssurance, - hasPassword: data.cwHasPassphrase, - passwordUpdateDate: unixTimestampToDate(data.cwPassphraseLU), - }) - ) -); - -const _createAddressFromServerData = action( - 'AdaApi::_createAddressFromServerData', (data: AdaAddress) => ( - new WalletAddress({ - id: data.cadId, - amount: new BigNumber(data.cadAmount.getCCoin).dividedBy(LOVELACES_PER_ADA), - isUsed: data.cadIsUsed, - }) - ) -); - -const _conditionToTxState = (condition: string) => { - switch (condition) { - case 'CPtxApplying': return 'pending'; - case 'CPtxWontApply': return 'failed'; - default: return 'ok'; // CPtxInBlocks && CPtxNotTracked - } -}; - -const _createTransactionFromServerData = action( - 'AdaApi::_createTransactionFromServerData', (data: AdaTransaction) => { - const coins = data.ctAmount.getCCoin; - const { ctmTitle, ctmDescription, ctmDate } = data.ctMeta; - return new WalletTransaction({ - id: data.ctId, - title: ctmTitle || data.ctIsOutgoing ? 'Ada sent' : 'Ada received', - type: data.ctIsOutgoing ? transactionTypes.EXPEND : transactionTypes.INCOME, - amount: new BigNumber(data.ctIsOutgoing ? -1 * coins : coins).dividedBy(LOVELACES_PER_ADA), - date: unixTimestampToDate(ctmDate), - description: ctmDescription || '', - numberOfConfirmations: data.ctConfirmations, - addresses: { - from: data.ctInputs.map(address => address[0]), - to: data.ctOutputs.map(address => address[0]), - }, - state: _conditionToTxState(data.ctCondition), - }); - } -); - -const _createTransactionFeeFromServerData = action( - 'AdaApi::_createTransactionFeeFromServerData', (data: AdaTransactionFee) => { - const coins = data.getCCoin; - return new BigNumber(coins).dividedBy(LOVELACES_PER_ADA); - } -); - - -// ========== V1 API ========= - -const _createWalletFromServerV1Data = action( - 'AdaApi::_createWalletFromServerV1Data', (data: AdaV1Wallet) => { - const { - id, balance, name, assuranceLevel, - hasSpendingPassword, spendingPasswordLastUpdate, - syncState, - } = data; - return new Wallet({ - id, - amount: new BigNumber(balance).dividedBy(LOVELACES_PER_ADA), - name, - assurance: (assuranceLevel === AdaV1AssuranceOptions.NORMAL ? - assuranceModeOptions.NORMAL : assuranceModeOptions.STRICT - ), - hasPassword: hasSpendingPassword, - passwordUpdateDate: new Date(`${spendingPasswordLastUpdate}Z`), - syncState, - }); - } -); diff --git a/source/renderer/app/api/ada/isValidAdaAddress.js b/source/renderer/app/api/ada/isValidAdaAddress.js deleted file mode 100644 index 418cc6b77c..0000000000 --- a/source/renderer/app/api/ada/isValidAdaAddress.js +++ /dev/null @@ -1,20 +0,0 @@ -// @flow -import { request } from './lib/request'; -import type { RequestConfig } from './types'; - -export type IsValidAdaAddressParams = { - address: string, -}; - -export const isValidAdaAddress = ( - config: RequestConfig, - { address }: IsValidAdaAddressParams -): Promise => { - const encodedAddress = encodeURIComponent(address); - return request({ - hostname: 'localhost', - method: 'GET', - path: `/api/addresses/${encodedAddress}`, - ...config - }); -}; diff --git a/source/renderer/app/api/ada/lib/encryptPassphrase.js b/source/renderer/app/api/ada/lib/encryptPassphrase.js deleted file mode 100644 index 178d31ce08..0000000000 --- a/source/renderer/app/api/ada/lib/encryptPassphrase.js +++ /dev/null @@ -1,9 +0,0 @@ -// @flow -import blakejs from 'blakejs'; - -const bytesToB16 = (bytes) => Buffer.from(bytes).toString('hex'); -const blake2b = (data) => blakejs.blake2b(data, null, 32); - -export const encryptPassphrase = (passphrase: string) => ( - bytesToB16(blake2b(passphrase)) -); diff --git a/source/renderer/app/api/ada/lib/utils.js b/source/renderer/app/api/ada/lib/utils.js deleted file mode 100644 index d2478daddb..0000000000 --- a/source/renderer/app/api/ada/lib/utils.js +++ /dev/null @@ -1,2 +0,0 @@ -// @flow -export const unixTimestampToDate = (timestamp: number) => new Date(timestamp * 1000); diff --git a/source/renderer/app/api/ada/mocks/patchAdaApi.js b/source/renderer/app/api/ada/mocks/patchAdaApi.js deleted file mode 100644 index 6260a593d3..0000000000 --- a/source/renderer/app/api/ada/mocks/patchAdaApi.js +++ /dev/null @@ -1,58 +0,0 @@ -import BigNumber from 'bignumber.js'; -import { Logger } from '../../../../../common/logging'; -import { RedeemAdaError } from '../errors'; -import AdaApi from '../index'; -import type { - RedeemPaperVendedAdaRequest, - RedeemAdaRequest -} from '../index'; - -// ========== LOGGING ========= - -let LOCAL_TIME_DIFFERENCE = 0; -let NEXT_ADA_UPDATE = null; - -const stringifyData = (data) => JSON.stringify(data, null, 2); - -export default (api: AdaApi) => { - // Since we cannot test ada redemption in dev mode, just resolve the requests - api.redeemAda = async (request: RedeemAdaRequest) => { - Logger.debug('AdaApi::redeemAda (PATCHED) called: ' + stringifyData(request)); - const { redemptionCode } = request; - const isValidRedemptionCode = await api.isValidRedemptionKey(redemptionCode); - if (!isValidRedemptionCode) { - Logger.debug('AdaApi::redeemAda failed: not a valid redemption key!'); - throw new RedeemAdaError(); - } - return { amount: new BigNumber(1000) }; - }; - - api.redeemPaperVendedAda = async (request: RedeemPaperVendedAdaRequest) => { - Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) called: ' + stringifyData(request)); - const { shieldedRedemptionKey, mnemonics } = request; - const isValidKey = await api.isValidPaperVendRedemptionKey(shieldedRedemptionKey); - const isValidMnemonic = await api.isValidRedemptionMnemonic(mnemonics); - if (!isValidKey) Logger.debug('AdaApi::redeemPaperVendedAda failed: not a valid redemption key!'); - if (!isValidMnemonic) Logger.debug('AdaApi::redeemPaperVendedAda failed: not a valid mnemonic!'); - if (!isValidKey || !isValidMnemonic) { - throw new RedeemAdaError(); - } - return { amount: new BigNumber(1000) }; - }; - - api.getLocalTimeDifference = async () => ( - Promise.resolve(LOCAL_TIME_DIFFERENCE) - ); - - api.setLocalTimeDifference = async (timeDifference) => { - LOCAL_TIME_DIFFERENCE = timeDifference; - }; - - api.nextUpdate = async () => ( - Promise.resolve(NEXT_ADA_UPDATE) - ); - - api.setNextUpdate = async (nextUpdate) => { - NEXT_ADA_UPDATE = nextUpdate; - }; -}; diff --git a/source/renderer/app/api/ada/newAdaAccount.js b/source/renderer/app/api/ada/newAdaAccount.js deleted file mode 100644 index e1312aae18..0000000000 --- a/source/renderer/app/api/ada/newAdaAccount.js +++ /dev/null @@ -1,31 +0,0 @@ -// @flow -import type { AdaAccount, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type NewAdaAccountQueryParams = { - passphrase: ?string, -}; - -export type NewAdaAccountRawBodyParams = { - accountInitData: { - caInitMeta: { - caName: string, - }, - caInitWId: string, - } -}; - -export const newAdaAccount = ( - config: RequestConfig, - pathParams: {}, - queryParams: NewAdaAccountQueryParams, - rawBodyParams: NewAdaAccountRawBodyParams, -): Promise => { - const { accountInitData } = rawBodyParams; - return request({ - hostname: 'localhost', - method: 'POST', - path: '/api/accounts', - ...config - }, queryParams, accountInitData); -}; diff --git a/source/renderer/app/api/ada/newAdaPayment.js b/source/renderer/app/api/ada/newAdaPayment.js deleted file mode 100644 index 2c0d8c3f4b..0000000000 --- a/source/renderer/app/api/ada/newAdaPayment.js +++ /dev/null @@ -1,26 +0,0 @@ -// @flow -import type { AdaTransaction, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type NewAdaPaymentParams = { - sender: string, - receiver: string, - amount: string, - password: ?string, - // "groupingPolicy" - Spend everything from the address - // "OptimizeForSize" for no grouping - groupingPolicy: ?'OptimizeForSecurity' | 'OptimizeForSize', -}; - - -export const newAdaPayment = ( - config: RequestConfig, - { sender, receiver, amount, groupingPolicy, password }: NewAdaPaymentParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'POST', - path: `/api/txs/payments/${sender}/${receiver}/${amount}`, - ...config - }, { passphrase: password }, { groupingPolicy }) -); diff --git a/source/renderer/app/api/ada/newAdaWallet.js b/source/renderer/app/api/ada/newAdaWallet.js deleted file mode 100644 index 3ca4dce833..0000000000 --- a/source/renderer/app/api/ada/newAdaWallet.js +++ /dev/null @@ -1,20 +0,0 @@ -// @flow -import type { AdaWallet, AdaWalletInitData, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type NewAdaWalletParams = { - password: ?string, - walletInitData: AdaWalletInitData -}; - -export const newAdaWallet = ( - config: RequestConfig, - { password, walletInitData }: NewAdaWalletParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'POST', - path: '/api/wallets/new', - ...config - }, { passphrase: password }, walletInitData) -); diff --git a/source/renderer/app/api/ada/newAdaWalletAddress.js b/source/renderer/app/api/ada/newAdaWalletAddress.js deleted file mode 100644 index 994543ecc6..0000000000 --- a/source/renderer/app/api/ada/newAdaWalletAddress.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import type { AdaAddress, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type NewAdaWalletAddressParams = { - password: ?string, - accountId: string, -}; - -export const newAdaWalletAddress = ( - config: RequestConfig, - { password, accountId }: NewAdaWalletAddressParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'POST', - path: '/api/addresses', - ...config - }, { passphrase: password }, accountId) -); - diff --git a/source/renderer/app/api/ada/nextAdaUpdate.js b/source/renderer/app/api/ada/nextAdaUpdate.js deleted file mode 100644 index 35f7604e50..0000000000 --- a/source/renderer/app/api/ada/nextAdaUpdate.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import { request } from './lib/request'; -import type { RequestConfig } from './types'; - -export const nextAdaUpdate = ( - config: RequestConfig, -): Promise => ( - request({ - hostname: 'localhost', - method: 'GET', - path: '/api/update', - ...config - }) -); diff --git a/source/renderer/app/api/ada/postponeAdaUpdate.js b/source/renderer/app/api/ada/postponeAdaUpdate.js deleted file mode 100644 index 59bb7184f6..0000000000 --- a/source/renderer/app/api/ada/postponeAdaUpdate.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow -import { request } from './lib/request'; -import type { RequestConfig } from './types'; - -export const postponeAdaUpdate = ( - config: RequestConfig -): Promise => ( - request({ - hostname: 'localhost', - method: 'POST', - path: '/api/update/postpone', - ...config - }) -); diff --git a/source/renderer/app/api/ada/redeemAda.js b/source/renderer/app/api/ada/redeemAda.js deleted file mode 100644 index f9060a5f40..0000000000 --- a/source/renderer/app/api/ada/redeemAda.js +++ /dev/null @@ -1,23 +0,0 @@ -// @flow -import type { AdaTransaction, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type RedeemAdaParams = { - walletPassword: ?string, - walletRedeemData: { - crWalletId: string, - crSeed: string, - } -}; - -export const redeemAda = ( - config: RequestConfig, - { walletPassword, walletRedeemData }: RedeemAdaParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'POST', - path: '/api/redemptions/ada', - ...config - }, { passphrase: walletPassword }, walletRedeemData) -); diff --git a/source/renderer/app/api/ada/redeemAdaPaperVend.js b/source/renderer/app/api/ada/redeemAdaPaperVend.js deleted file mode 100644 index 44eb6e1e1b..0000000000 --- a/source/renderer/app/api/ada/redeemAdaPaperVend.js +++ /dev/null @@ -1,26 +0,0 @@ -// @flow -import type { AdaTransaction, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type RedeemAdaPaperVendParams = { - walletPassword: ?string, - redeemPaperVendedData: { - pvWalletId: string, - pvSeed: string, - pvBackupPhrase: { - bpToList: [], - } - } -}; - -export const redeemAdaPaperVend = ( - config: RequestConfig, - { walletPassword, redeemPaperVendedData }: RedeemAdaPaperVendParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'POST', - path: '/api/papervend/redemptions/ada', - ...config - }, { passphrase: walletPassword }, redeemPaperVendedData) -); diff --git a/source/renderer/app/api/ada/restoreAdaWallet.js b/source/renderer/app/api/ada/restoreAdaWallet.js deleted file mode 100644 index dd813fa856..0000000000 --- a/source/renderer/app/api/ada/restoreAdaWallet.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow -import type { AdaWallet, AdaWalletInitData, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type RestoreAdaWalletParams = { - walletPassword: ?string, - walletInitData: AdaWalletInitData -}; - -export const restoreAdaWallet = ( - config: RequestConfig, - { walletPassword, walletInitData }: RestoreAdaWalletParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'POST', - path: '/api/wallets/restore', - ...config - }, { passphrase: walletPassword }, walletInitData) -); - diff --git a/source/renderer/app/api/ada/types.js b/source/renderer/app/api/ada/types.js deleted file mode 100644 index 4d55dc5e58..0000000000 --- a/source/renderer/app/api/ada/types.js +++ /dev/null @@ -1,155 +0,0 @@ -// @flow - -// ========= General Types ========== -export type RequestConfig = { - port: number, - ca: Uint8Array, - cert: Uint8Array, - key: Uint8Array, -}; - -// ========= Response Types ========= -export type AdaAssurance = 'CWANormal' | 'CWAStrict'; -export type AdaTransactionCondition = 'CPtxApplying' | 'CPtxInBlocks' | 'CPtxWontApply' | 'CPtxNotTracked'; -export type AdaWalletRecoveryPhraseResponse = Array; -export type AdaWalletCertificateAdditionalMnemonicsResponse = Array; -export type AdaWalletCertificateRecoveryPhraseResponse = Array; -export type AdaWalletRecoveryPhraseFromCertificateResponse = Array; -export type GetWalletCertificateAdditionalMnemonicsResponse = Array; -export type GetWalletCertificateRecoveryPhraseResponse = Array; -export type GetWalletRecoveryPhraseFromCertificateResponse = Array; - -export type AdaSyncProgressResponse = { - _spLocalCD: { - getChainDifficulty: { - getBlockCount: number, - } - }, - _spNetworkCD: { - getChainDifficulty: { - getBlockCount: number, - } - }, - _spPeers: number, -}; - -export type AdaWalletInitData = { - cwInitMeta: { - cwName: string, - cwAssurance: AdaAssurance, - cwUnit: number, - }, - cwBackupPhrase: { - bpToList: [], - } -}; - -export type AdaAmount = { - getCCoin: number, -}; -export type AdaTransactionTag = 'CTIn' | 'CTOut'; - -export type AdaAddress = { - cadAmount: AdaAmount, - cadId: string, - cadIsUsed: boolean, -}; - -export type AdaAddresses = Array; - -export type AdaAccount = { - caAddresses: AdaAddresses, - caAmount: AdaAmount, - caId: string, - caMeta: { - caName: string, - }, -}; - -export type AdaAccounts = Array; - -export type AdaTransaction = { - ctAmount: AdaAmount, - ctConfirmations: number, - ctId: string, - ctInputs: AdaTransactionInputOutput, - ctIsOutgoing: boolean, - ctMeta: { - ctmDate: Date, - ctmDescription: ?string, - ctmTitle: ?string, - }, - ctOutputs: AdaTransactionInputOutput, - ctCondition: AdaTransactionCondition, -}; - -export type AdaTransactions = [ - Array, - number, -]; - -export type AdaTransactionInputOutput = [ - [string, AdaAmount], -]; - -export type AdaTransactionFee = AdaAmount; - -export type AdaWallet = { - cwAccountsNumber: number, - cwAmount: AdaAmount, - cwHasPassphrase: boolean, - cwId: string, - cwMeta: { - cwAssurance: AdaAssurance, - cwName: string, - csUnit: number, - }, - cwPassphraseLU: Date, -}; - -export type AdaWallets = Array; - -export type AdaLocalTimeDifference = number; - - -// ========== V1 API ========= - -export type AdaV1Assurance = 'normal' | 'strict'; -export type AdaV1WalletSyncStateTag = 'restoring' | 'synced'; - -export type AdaV1WalletSyncState = { - data: ?{ - estimatedCompletionTime: { - quantity: number, - unit: 'milliseconds', - }, - percentage: { - quantity: number, - unit: 'percenage', - }, - throughput: { - quantity: number, - unit: 'blocksPerSecond', - }, - }, - tag: AdaV1WalletSyncStateTag, -}; - -export type AdaV1Wallet = { - assuranceLevel: AdaV1Assurance, - balance: number, - createdAt: string, - hasSpendingPassword: boolean, - id: string, - name: string, - spendingPasswordLastUpdate: string, - syncState: AdaV1WalletSyncState, -}; - -export type AdaV1Wallets = Array; - -export const AdaV1AssuranceOptions: { - NORMAL: AdaV1Assurance, STRICT: AdaV1Assurance, -} = { - NORMAL: 'normal', STRICT: 'strict', -}; diff --git a/source/renderer/app/api/ada/updateAdaWallet.js b/source/renderer/app/api/ada/updateAdaWallet.js deleted file mode 100644 index 3436aed00e..0000000000 --- a/source/renderer/app/api/ada/updateAdaWallet.js +++ /dev/null @@ -1,24 +0,0 @@ -// @flow -import type { AdaWallet, RequestConfig } from './types'; -import { request } from './lib/request'; - -export type UpdateAdaWalletParams = { - walletId: string, - walletMeta: { - cwName: string, - cwAssurance: string, - cwUnit: number, - } -}; - -export const updateAdaWallet = ( - config: RequestConfig, - { walletId, walletMeta }: UpdateAdaWalletParams -): Promise => ( - request({ - hostname: 'localhost', - method: 'PUT', - path: `/api/wallets/${walletId}`, - ...config - }, {}, walletMeta) -); diff --git a/source/renderer/app/api/addresses/errors.js b/source/renderer/app/api/addresses/errors.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/source/renderer/app/api/addresses/requests/createAddress.js b/source/renderer/app/api/addresses/requests/createAddress.js new file mode 100644 index 0000000000..ef9e1ec83c --- /dev/null +++ b/source/renderer/app/api/addresses/requests/createAddress.js @@ -0,0 +1,22 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { Address } from '../types'; +import { request } from '../../utils/request'; + +export type CreateAddressParams = { + spendingPassword?: string, + accountIndex: number, + walletId: string, +}; + +export const createAddress = ( + config: RequestConfig, + { spendingPassword, accountIndex, walletId }: CreateAddressParams +): Promise
=> ( + request({ + hostname: 'localhost', + method: 'POST', + path: '/api/v1/addresses', + ...config, + }, {}, { spendingPassword, accountIndex, walletId }) +); diff --git a/source/renderer/app/api/addresses/requests/getAddress.js b/source/renderer/app/api/addresses/requests/getAddress.js new file mode 100644 index 0000000000..5831c55a0f --- /dev/null +++ b/source/renderer/app/api/addresses/requests/getAddress.js @@ -0,0 +1,21 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { Address } from '../types'; +import { request } from '../../utils/request'; + +export type GetAddressParams = { + address: string, +}; + +export const getAddress = ( + config: RequestConfig, + { address }: GetAddressParams +): Promise
=> { + const encodedAddress = encodeURIComponent(address); + return request({ + hostname: 'localhost', + method: 'GET', + path: `/api/v1/addresses/${encodedAddress}`, + ...config, + }); +}; diff --git a/source/renderer/app/api/addresses/types.js b/source/renderer/app/api/addresses/types.js new file mode 100644 index 0000000000..ecbdb20ca2 --- /dev/null +++ b/source/renderer/app/api/addresses/types.js @@ -0,0 +1,24 @@ +// @flow +export type Address = { + id: string, + used: boolean, + changeAddress: boolean +}; + +export type Addresses = Array
; + +// req/res Address types +export type GetAddressesResponse = { + accountIndex: ?number, + addresses: Addresses, +}; + +export type GetAddressesRequest = { + walletId: string, +}; + +export type CreateAddressRequest = { + spendingPassword?: string, + accountIndex: number, + walletId: string, +}; diff --git a/source/renderer/app/api/api.js b/source/renderer/app/api/api.js new file mode 100644 index 0000000000..7869ed5e69 --- /dev/null +++ b/source/renderer/app/api/api.js @@ -0,0 +1,784 @@ +// @flow +import { split, get } from 'lodash'; +import { action } from 'mobx'; +import { ipcRenderer } from 'electron'; +import BigNumber from 'bignumber.js'; + +// domains +import Wallet from '../domains/Wallet'; +import WalletTransaction, { transactionTypes } from '../domains/WalletTransaction'; +import WalletAddress from '../domains/WalletAddress'; + +// Accounts requests +import { getAccounts } from './accounts/requests/getAccounts'; + +// Addresses requests +import { getAddress } from './addresses/requests/getAddress'; +import { createAddress } from './addresses/requests/createAddress'; + +// Common requests +import { sendBugReport } from './common/requests/sendBugReport'; + +// Nodes requests +import { applyNodeUpdate } from './nodes/requests/applyNodeUpdate'; +import { getNodeInfo } from './nodes/requests/getNodeInfo'; +import { getNextNodeUpdate } from './nodes/requests/getNextNodeUpdate'; +import { postponeNodeUpdate } from './nodes/requests/postponeNodeUpdate'; + +// Transactions requests +import { getTransactionFee } from './transactions/requests/getTransactionFee'; +import { getTransactionHistory } from './transactions/requests/getTransactionHistory'; +import { createTransaction } from './transactions/requests/createTransaction'; +import { redeemAda } from './transactions/requests/redeemAda'; +import { redeemPaperVendedAda } from './transactions/requests/redeemPaperVendedAda'; + +// Wallets requests +import { resetWalletState } from './wallets/requests/resetWalletState'; +import { changeSpendingPassword } from './wallets/requests/changeSpendingPassword'; +import { deleteWallet } from './wallets/requests/deleteWallet'; +import { exportWalletAsJSON } from './wallets/requests/exportWalletAsJSON'; +import { importWalletAsJSON } from './wallets/requests/importWalletAsJSON'; +import { getWallets } from './wallets/requests/getWallets'; +import { importWalletAsKey } from './wallets/requests/importWalletAsKey'; +import { createWallet } from './wallets/requests/createWallet'; +import { restoreWallet } from './wallets/requests/restoreWallet'; +import { updateWallet } from './wallets/requests/updateWallet'; + +// utility functions +import patchAdaApi from './utils/patchAdaApi'; +import { isValidMnemonic } from '../../../common/decrypt'; +import { utcStringToDate, encryptPassphrase } from './utils'; +import { + Logger, + stringifyData, + stringifyError +} from '../../../common/logging'; +import { + isValidRedemptionKey, + isValidPaperVendRedemptionKey +} from '../utils/redemption-key-validation'; +import { + unscrambleMnemonics, + scrambleMnemonics, + generateAccountMnemonics, + generateAdditionalMnemonics +} from './utils/mnemonics'; + +// config constants +import { + LOVELACES_PER_ADA, + MAX_TRANSACTIONS_PER_PAGE +} from '../config/numbersConfig'; +import { + ADA_CERTIFICATE_MNEMONIC_LENGTH, + ADA_REDEMPTION_PASSPHRASE_LENGTH, + WALLET_RECOVERY_PHRASE_WORD_COUNT +} from '../config/cryptoConfig'; + +// Accounts types +import type { Accounts } from './accounts/types'; + +// Addresses Types +import type { + Address, + GetAddressesRequest, + CreateAddressRequest, + GetAddressesResponse +} from './addresses/types'; + +// Common Types +import type { + RequestConfig, + SendBugReportRequest +} from './common/types'; + +// Nodes Types +import type { + NodeInfo, + NodeSoftware, + GetNetworkStatusResponse +} from './nodes/types'; +import type { NodeQueryParams } from './nodes/requests/getNodeInfo'; + +// Transactions Types +import type { RedeemAdaParams } from './transactions/requests/redeemAda'; +import type { RedeemPaperVendedAdaParams } from './transactions/requests/redeemPaperVendedAda'; +import type { + Transaction, + Transactions, + TransactionFee, + TransactionRequest, + GetTransactionsRequest, + GetTransactionsResponse +} from './transactions/types'; + +// Wallets Types +import type { + AdaWallet, + AdaWallets, + CreateWalletRequest, + DeleteWalletRequest, + RestoreWalletRequest, + UpdateWalletPasswordRequest, + ExportWalletToFileRequest, + GetWalletCertificateRecoveryPhraseRequest, + GetWalletRecoveryPhraseFromCertificateRequest, + ImportWalletFromKeyRequest, + ImportWalletFromFileRequest, + UpdateWalletRequest +} from './wallets/types'; + +// Common errors +import { + GenericApiError, + IncorrectWalletPasswordError, + ReportRequestError, + InvalidMnemonicError, + ForbiddenMnemonicError +} from './common/errors'; + +// Wallets errors +import { + WalletAlreadyRestoredError, + WalletAlreadyImportedError, + WalletFileImportError +} from './wallets/errors'; + +// Transactions errors +import { + NotAllowedToSendMoneyToRedeemAddressError, + NotEnoughFundsForTransactionFeesError, + NotEnoughMoneyToSendError, + RedeemAdaError +} from './transactions/errors'; + + +export default class AdaApi { + + config: RequestConfig; + + constructor(isTest: boolean, config: RequestConfig) { + this.setRequestConfig(config); + if (isTest) patchAdaApi(this); + } + + setRequestConfig(config: RequestConfig) { + this.config = config; + } + + getWallets = async (): Promise> => { + Logger.debug('AdaApi::getWallets called'); + try { + const response: AdaWallets = await getWallets(this.config); + Logger.debug('AdaApi::getWallets success: ' + stringifyData(response)); + return response.map(data => _createWalletFromServerData(data)); + } catch (error) { + Logger.error('AdaApi::getWallets error: ' + stringifyError(error)); + throw new GenericApiError(); + } + }; + + getAddresses = async (request: GetAddressesRequest): Promise => { + Logger.debug('AdaApi::getAddresses called: ' + stringifyData(request)); + const { walletId } = request; + try { + const accounts: Accounts = await getAccounts(this.config, { walletId }); + Logger.debug('AdaApi::getAddresses success: ' + stringifyData(accounts)); + + if (!accounts || !accounts.length) { + return new Promise(resolve => resolve({ accountIndex: null, addresses: [] })); + } + + // For now only the first wallet account is used + const firstAccount = accounts[0]; + const { index: accountIndex, addresses } = firstAccount; + + return new Promise(resolve => resolve({ accountIndex, addresses })); + } catch (error) { + Logger.error('AdaApi::getAddresses error: ' + stringifyError(error)); + throw new GenericApiError(); + } + }; + + getTransactions = async (request: GetTransactionsRequest): Promise => { + Logger.debug('AdaApi::searchHistory called: ' + stringifyData(request)); + const { walletId, skip, limit } = request; + const accounts: Accounts = await getAccounts(this.config, { walletId }); + + if (!accounts.length || !accounts[0].index) { + return new Promise(resolve => resolve({ transactions: [], total: 0 })); + } + + let perPage = limit; + if (limit === null || limit > MAX_TRANSACTIONS_PER_PAGE) { + perPage = MAX_TRANSACTIONS_PER_PAGE; + } + + const params = { + accountIndex: accounts[0].index, + page: skip === 0 ? 1 : (skip / limit) + 1, + per_page: perPage, + wallet_id: walletId, + sort_by: 'DES[created_at]', + }; + const pagesToBeLoaded = Math.ceil(limit / params.per_page); + + try { + const response: Transactions = await getTransactionHistory(this.config, params); + const { meta, data: txnHistory } = response; + const { totalPages } = meta.pagination; + const hasMultiplePages = (totalPages > 1 && limit > MAX_TRANSACTIONS_PER_PAGE); + + if (hasMultiplePages) { + let page = 2; + const hasNextPage = () => page < totalPages + 1; + const shouldLoadNextPage = () => limit === null || page <= pagesToBeLoaded; + + for (page; (hasNextPage() && shouldLoadNextPage()); page++) { + const { data: pageHistory } = + await getTransactionHistory(this.config, Object.assign(params, { page })); + txnHistory.push(...pageHistory); + } + if (limit !== null) txnHistory.splice(limit); + } + + const transactions = txnHistory.map(txn => _createTransactionFromServerData(txn)); + const total = transactions.length; + Logger.debug('AdaApi::searchHistory success: ' + stringifyData(txnHistory)); + return new Promise(resolve => resolve({ transactions, total })); + } catch (error) { + Logger.error('AdaApi::searchHistory error: ' + stringifyError(error)); + throw new GenericApiError(); + } + }; + + createWallet = async (request: CreateWalletRequest): Promise => { + Logger.debug('AdaApi::createWallet called'); + const { name, mnemonic, spendingPassword } = request; + const assuranceLevel = 'normal'; + try { + const walletInitData = { + operation: 'create', + backupPhrase: split(mnemonic, ' '), + assuranceLevel, + name, + spendingPassword: spendingPassword ? encryptPassphrase(spendingPassword) : null, + }; + const wallet: AdaWallet = await createWallet(this.config, { walletInitData }); + Logger.debug('AdaApi::createWallet success'); + return _createWalletFromServerData(wallet); + } catch (error) { + Logger.error('AdaApi::createWallet error: ' + stringifyError(error)); + throw new GenericApiError(); + } + }; + + deleteWallet = async (request: DeleteWalletRequest): Promise => { + Logger.debug('AdaApi::deleteWallet called: ' + stringifyData(request)); + try { + const { walletId } = request; + const response = await deleteWallet(this.config, { walletId }); + Logger.debug('AdaApi::deleteWallet success: ' + stringifyData(response)); + return true; + } catch (error) { + Logger.error('AdaApi::deleteWallet error: ' + stringifyError(error)); + throw new GenericApiError(); + } + }; + + createTransaction = async ( + request: TransactionRequest + ): Promise => { + Logger.debug('AdaApi::createTransaction called'); + const { accountIndex, walletId, address, amount, spendingPassword: passwordString } = request; + const spendingPassword = passwordString ? encryptPassphrase(passwordString) : ''; + try { + const data = { + source: { + accountIndex, + walletId, + }, + destinations: [ + { + address, + amount, + }, + ], + groupingPolicy: 'OptimizeForSecurity', + spendingPassword, + }; + const response: Transaction = await createTransaction(this.config, { data }); + Logger.debug('AdaApi::createTransaction success: ' + stringifyData(response)); + return _createTransactionFromServerData(response); + } catch (error) { + Logger.debug('AdaApi::createTransaction error: ' + stringifyError(error)); + if (error.message === 'OutputIsRedeem') { + throw new NotAllowedToSendMoneyToRedeemAddressError(); + } + if (error.message === 'NotEnoughMoney') { + throw new NotEnoughMoneyToSendError(); + } + if (error.message === 'CannotCreateAddress') { + throw new IncorrectWalletPasswordError(); + } + throw new GenericApiError(); + } + }; + + calculateTransactionFee = async ( + request: TransactionRequest + ): Promise => { + Logger.debug('AdaApi::calculateTransactionFee called'); + const { accountIndex, walletId, address, amount, spendingPassword: passwordString } = request; + const spendingPassword = passwordString ? encryptPassphrase(passwordString) : ''; + try { + const data = { + source: { + accountIndex, + walletId, + }, + destinations: [ + { + address, + amount, + }, + ], + groupingPolicy: 'OptimizeForSecurity', + spendingPassword, + }; + const response: TransactionFee = await getTransactionFee(this.config, { data }); + Logger.debug('AdaApi::calculateTransactionFee success: ' + stringifyData(response)); + return _createTransactionFeeFromServerData(response); + } catch (error) { + Logger.debug('AdaApi::calculateTransactionFee error: ' + stringifyError(error)); + if (error.message === 'NotEnoughMoney') { + throw new NotEnoughFundsForTransactionFeesError(); + } + throw new GenericApiError(); + } + }; + + createAddress = async (request: CreateAddressRequest): Promise
=> { + Logger.debug('AdaApi::createAddress called'); + const { spendingPassword: passwordString, accountIndex, walletId } = request; + const spendingPassword = passwordString ? encryptPassphrase(passwordString) : ''; + try { + const address: Address = await createAddress( + this.config, { spendingPassword, accountIndex, walletId } + ); + Logger.debug('AdaApi::createAddress success: ' + stringifyData(address)); + return _createAddressFromServerData(address); + } catch (error) { + Logger.debug('AdaApi::createAddress error: ' + stringifyError(error)); + if (error.message === 'CannotCreateAddress') { + throw new IncorrectWalletPasswordError(); + } + throw new GenericApiError(); + } + }; + + async isValidAddress(address: string): Promise { + Logger.debug('AdaApi::isValidAdaAddress called'); + try { + const response: Address = await getAddress(this.config, { address }); + Logger.debug(`AdaApi::isValidAdaAddress success: ${stringifyData(response)}`); + return true; + } catch (error) { + Logger.debug(`AdaApi::isValidAdaAddress error: ${stringifyError(error)}`); + return false; + } + } + + isValidMnemonic = (mnemonic: string): boolean => ( + isValidMnemonic(mnemonic, WALLET_RECOVERY_PHRASE_WORD_COUNT) + ); + + isValidRedemptionKey = (mnemonic: string): boolean => (isValidRedemptionKey(mnemonic)); + + isValidPaperVendRedemptionKey = (mnemonic: string): boolean => ( + isValidPaperVendRedemptionKey(mnemonic) + ); + + isValidRedemptionMnemonic = (mnemonic: string): boolean => ( + isValidMnemonic(mnemonic, ADA_REDEMPTION_PASSPHRASE_LENGTH) + ); + + isValidCertificateMnemonic = (mnemonic: string): boolean => ( + mnemonic.split(' ').length === ADA_CERTIFICATE_MNEMONIC_LENGTH + ); + + getWalletRecoveryPhrase(): Promise> { + Logger.debug('AdaApi::getWalletRecoveryPhrase called'); + try { + const response: Promise> = new Promise( + (resolve) => resolve(generateAccountMnemonics()) + ); + Logger.debug('AdaApi::getWalletRecoveryPhrase success'); + return response; + } catch (error) { + Logger.error('AdaApi::getWalletRecoveryPhrase error: ' + stringifyError(error)); + throw new GenericApiError(); + } + } + + // eslint-disable-next-line max-len + getWalletCertificateAdditionalMnemonics(): Promise> { + Logger.debug('AdaApi::getWalletCertificateAdditionalMnemonics called'); + try { + const response: Promise> = new Promise( + (resolve) => resolve(generateAdditionalMnemonics()) + ); + Logger.debug('AdaApi::getWalletCertificateAdditionalMnemonics success'); + return response; + } catch (error) { + Logger.error('AdaApi::getWalletCertificateAdditionalMnemonics error: ' + stringifyError(error)); + throw new GenericApiError(); + } + } + + getWalletCertificateRecoveryPhrase( + request: GetWalletCertificateRecoveryPhraseRequest + ): Promise> { + Logger.debug('AdaApi::getWalletCertificateRecoveryPhrase called'); + const { passphrase, input: scrambledInput } = request; + try { + const response: Promise> = new Promise( + (resolve) => resolve(scrambleMnemonics({ passphrase, scrambledInput })) + ); + Logger.debug('AdaApi::getWalletCertificateRecoveryPhrase success'); + return response; + } catch (error) { + Logger.error('AdaApi::getWalletCertificateRecoveryPhrase error: ' + stringifyError(error)); + throw new GenericApiError(); + } + } + + getWalletRecoveryPhraseFromCertificate( + request: GetWalletRecoveryPhraseFromCertificateRequest + ): Promise> { + Logger.debug('AdaApi::getWalletRecoveryPhraseFromCertificate called'); + const { passphrase, scrambledInput } = request; + try { + const response = unscrambleMnemonics({ passphrase, scrambledInput }); + Logger.debug('AdaApi::getWalletRecoveryPhraseFromCertificate success'); + return Promise.resolve(response); + } catch (error) { + Logger.debug('AdaApi::getWalletRecoveryPhraseFromCertificate error: ' + stringifyError(error)); + return Promise.reject(new InvalidMnemonicError()); + } + } + + restoreWallet = async (request: RestoreWalletRequest): Promise => { + Logger.debug('AdaApi::restoreWallet called'); + const { recoveryPhrase, walletName, walletPassword: passwordString } = request; + const assuranceLevel = 'normal'; + const spendingPassword = passwordString ? encryptPassphrase(passwordString) : null; + const walletInitData = { + operation: 'restore', + backupPhrase: split(recoveryPhrase, ' '), + assuranceLevel, + name: walletName, + spendingPassword + }; + + try { + const wallet: AdaWallet = await restoreWallet(this.config, { walletInitData }); + Logger.debug('AdaApi::restoreWallet success'); + return _createWalletFromServerData(wallet); + } catch (error) { + Logger.debug('AdaApi::restoreWallet error: ' + stringifyError(error)); + if (error.message === 'WalletAlreadyExists') { + throw new WalletAlreadyRestoredError(); + } + if (error.message === 'JSONValidationFailed') { + const validationError = get(error, 'diagnostic.validationError', ''); + if (validationError.includes('Forbidden Mnemonic: an example Mnemonic has been submitted')) { + throw new ForbiddenMnemonicError(); + } + } + throw new GenericApiError(); + } + }; + + importWalletFromKey = async ( + request: ImportWalletFromKeyRequest + ): Promise => { + Logger.debug('AdaApi::importWalletFromKey called'); + try { + const importedWallet: AdaWallet = await importWalletAsKey(this.config, request); + Logger.debug('AdaApi::importWalletFromKey success'); + return _createWalletFromServerData(importedWallet); + } catch (error) { + Logger.debug('AdaApi::importWalletFromKey error: ' + stringifyError(error)); + if (error.message === 'WalletAlreadyExists') { + throw new WalletAlreadyImportedError(); + } + throw new WalletFileImportError(); + } + }; + + importWalletFromFile = async ( + request: ImportWalletFromFileRequest + ): Promise => { + Logger.debug('AdaApi::importWalletFromFile called'); + const { filePath } = request; + const isKeyFile = filePath.split('.').pop().toLowerCase() === 'key'; + try { + const importedWallet: AdaWallet = isKeyFile ? ( + await importWalletAsKey(this.config, request) + ) : ( + await importWalletAsJSON(this.config, filePath) + ); + Logger.debug('AdaApi::importWalletFromFile success'); + return _createWalletFromServerData(importedWallet); + } catch (error) { + Logger.debug('AdaApi::importWalletFromFile error: ' + stringifyError(error)); + if (error.message === 'WalletAlreadyExists') { + throw new WalletAlreadyImportedError(); + } + throw new WalletFileImportError(); + } + }; + + async redeemAda(request: RedeemAdaParams): Promise { + Logger.debug('AdaApi::redeemAda called'); + try { + const transaction: Transaction = await redeemAda(this.config, request); + Logger.debug('AdaApi::redeemAda success'); + return _createTransactionFromServerData(transaction); + } catch (error) { + Logger.debug('AdaApi::redeemAda error: ' + stringifyError(error)); + if (error.message === 'CannotCreateAddress') { + throw new IncorrectWalletPasswordError(); + } + throw new RedeemAdaError(); + } + } + + async redeemPaperVendedAda( + request: RedeemPaperVendedAdaParams + ): Promise { + Logger.debug('AdaApi::redeemAdaPaperVend called'); + try { + const transaction: Transaction = await redeemPaperVendedAda(this.config, request); + Logger.debug('AdaApi::redeemAdaPaperVend success'); + return _createTransactionFromServerData(transaction); + } catch (error) { + Logger.debug('AdaApi::redeemAdaPaperVend error: ' + stringifyError(error)); + if (error.message === 'CannotCreateAddress') { + throw new IncorrectWalletPasswordError(); + } + throw new RedeemAdaError(); + } + } + + async sendBugReport(requestFormData: SendBugReportRequest): Promise { + Logger.debug('AdaApi::sendBugReport called: ' + stringifyData(requestFormData)); + try { + await sendBugReport({ requestFormData }); + Logger.debug('AdaApi::sendBugReport success'); + return true; + } catch (error) { + Logger.error('AdaApi::sendBugReport error: ' + stringifyError(error)); + throw new ReportRequestError(); + } + } + + nextUpdate = async (): Promise => { + Logger.debug('AdaApi::nextUpdate called'); + try { + const nodeUpdate = await getNextNodeUpdate(this.config); + if (nodeUpdate && nodeUpdate.version) { + Logger.debug('AdaApi::nextUpdate success: ' + stringifyData(nodeUpdate)); + return nodeUpdate; + } + Logger.debug('AdaApi::nextUpdate success: No Update Available'); + } catch (error) { + Logger.error('AdaApi::nextUpdate error: ' + stringifyError(error)); + throw new GenericApiError(); + } + return null; + }; + + postponeUpdate = async (): Promise => { + Logger.debug('AdaApi::postponeUpdate called'); + try { + const response: Promise = await postponeNodeUpdate(this.config); + Logger.debug('AdaApi::postponeUpdate success: ' + stringifyData(response)); + } catch (error) { + Logger.error('AdaApi::postponeUpdate error: ' + stringifyError(error)); + throw new GenericApiError(); + } + }; + + applyUpdate = async (): Promise => { + Logger.debug('AdaApi::applyUpdate called'); + try { + const response: Promise = await applyNodeUpdate(this.config); + Logger.debug('AdaApi::applyUpdate success: ' + stringifyData(response)); + ipcRenderer.send('kill-process'); + } catch (error) { + Logger.error('AdaApi::applyUpdate error: ' + stringifyError(error)); + throw new GenericApiError(); + } + }; + + updateWallet = async (request: UpdateWalletRequest): Promise => { + Logger.debug('AdaApi::updateWallet called: ' + stringifyData(request)); + const { walletId, assuranceLevel, name } = request; + try { + const wallet: AdaWallet = await updateWallet( + this.config, { walletId, assuranceLevel, name } + ); + Logger.debug('AdaApi::updateWallet success: ' + stringifyData(wallet)); + return _createWalletFromServerData(wallet); + } catch (error) { + Logger.error('AdaApi::updateWallet error: ' + stringifyError(error)); + throw new GenericApiError(); + } + }; + + updateWalletPassword = async ( + request: UpdateWalletPasswordRequest + ): Promise => { + Logger.debug('AdaApi::updateWalletPassword called'); + const { walletId, oldPassword, newPassword } = request; + try { + await changeSpendingPassword(this.config, { walletId, oldPassword, newPassword }); + Logger.debug('AdaApi::updateWalletPassword success'); + return true; + } catch (error) { + Logger.debug('AdaApi::updateWalletPassword error: ' + stringifyError(error)); + const errorMessage = get(error, 'diagnostic.msg', ''); + if (errorMessage.includes('UpdateWalletPasswordOldPasswordMismatch')) { + throw new IncorrectWalletPasswordError(); + } + throw new GenericApiError(); + } + }; + + exportWalletToFile = async ( + request: ExportWalletToFileRequest + ): Promise<[]> => { + const { walletId, filePath } = request; + Logger.debug('AdaApi::exportWalletToFile called'); + try { + const response: Promise<[]> = await exportWalletAsJSON(this.config, { + walletId, + filePath + }); + Logger.debug('AdaApi::exportWalletToFile success: ' + stringifyData(response)); + return response; + } catch (error) { + Logger.error('AdaApi::exportWalletToFile error: ' + stringifyError(error)); + throw new GenericApiError(); + } + }; + + testReset = async (): Promise => { + Logger.debug('AdaApi::testReset called'); + try { + const response: Promise = await resetWalletState(this.config); + Logger.debug('AdaApi::testReset success: ' + stringifyData(response)); + return response; + } catch (error) { + Logger.error('AdaApi::testReset error: ' + stringifyError(error)); + throw new GenericApiError(); + } + }; + + getNetworkStatus = async ( + queryParams?: NodeQueryParams + ): Promise => { + const isForceNTPCheck = !!queryParams; + const loggerText = `AdaApi::getNetworkStatus${isForceNTPCheck ? ' (FORCE-NTP-CHECK)' : ''}`; + Logger.debug(`${loggerText} called`); + try { + const status: NodeInfo = await getNodeInfo(this.config, queryParams); + Logger.debug(`${loggerText} success: ${stringifyData(status)}`); + + const { + blockchainHeight, + subscriptionStatus, + syncProgress, + localBlockchainHeight, + localTimeInformation, + } = status; + + // extract relevant data before sending to NetworkStatusStore + return { + subscriptionStatus, + syncProgress: syncProgress.quantity, + blockchainHeight: get(blockchainHeight, 'quantity', 0), + localBlockchainHeight: localBlockchainHeight.quantity, + localTimeDifference: get(localTimeInformation, 'differenceFromNtpServer.quantity', null), + }; + } catch (error) { + Logger.error(`${loggerText} error: ${stringifyError(error)}`); + throw new GenericApiError(error); + } + }; +} + +// ========== TRANSFORM SERVER DATA INTO FRONTEND MODELS ========= + +const _createWalletFromServerData = action( + 'AdaApi::_createWalletFromServerData', (data: AdaWallet) => { + const { + id, balance, name, assuranceLevel, + hasSpendingPassword, spendingPasswordLastUpdate, + syncState, + } = data; + + return new Wallet({ + id, + amount: new BigNumber(balance).dividedBy(LOVELACES_PER_ADA), + name, + assurance: assuranceLevel, + hasPassword: hasSpendingPassword, + passwordUpdateDate: new Date(`${spendingPasswordLastUpdate}Z`), + syncState, + }); + } +); + +const _createAddressFromServerData = action( + 'AdaApi::_createAddressFromServerData', + (address: Address) => new WalletAddress(address) +); + +const _conditionToTxState = (condition: string) => { + switch (condition) { + case 'applying': + case 'creating': return 'pending'; + case 'wontApply': return 'failed'; + default: return 'ok'; + // Others V0: CPtxInBlocks && CPtxNotTracked + // Others V1: "inNewestBlocks" "persisted" "creating" + } +}; + +const _createTransactionFromServerData = action( + 'AdaApi::_createTransactionFromServerData', (data: Transaction) => { + const { id, direction, amount, confirmations, creationTime, inputs, outputs, status } = data; + return new WalletTransaction({ + id, + title: direction === 'outgoing' ? 'Ada sent' : 'Ada received', + type: direction === 'outgoing' ? transactionTypes.EXPEND : transactionTypes.INCOME, + amount: new BigNumber(direction === 'outgoing' ? (amount * -1) : amount).dividedBy(LOVELACES_PER_ADA), + date: utcStringToDate(creationTime), + description: '', + numberOfConfirmations: confirmations, + addresses: { + from: inputs.map(({ address }) => address), + to: outputs.map(({ address }) => address), + }, + state: _conditionToTxState(status.tag), + }); + } +); + +const _createTransactionFeeFromServerData = action( + 'AdaApi::_createTransactionFeeFromServerData', (data: TransactionFee) => + new BigNumber(data.estimatedAmount).dividedBy(LOVELACES_PER_ADA) +); diff --git a/source/renderer/app/api/common.js b/source/renderer/app/api/common.js deleted file mode 100644 index c3337c9fda..0000000000 --- a/source/renderer/app/api/common.js +++ /dev/null @@ -1,129 +0,0 @@ -import { defineMessages } from 'react-intl'; -import LocalizableError from '../i18n/LocalizableError'; -import { WalletTransaction, Wallet } from '../domains/WalletTransaction'; -import globalMessages from '../i18n/global-messages'; - -const messages = defineMessages({ - genericApiError: { - id: 'api.errors.GenericApiError', - defaultMessage: '!!!An error occurred, please try again later.', - description: 'Generic error message.' - }, - incorrectWalletPasswordError: { - id: 'api.errors.IncorrectPasswordError', - defaultMessage: '!!!Incorrect wallet password.', - description: '"Incorrect wallet password." error message.' - }, - walletAlreadyRestoredError: { - id: 'api.errors.WalletAlreadyRestoredError', - defaultMessage: '!!!Wallet you are trying to restore already exists.', - description: '"Wallet you are trying to restore already exists." error message.' - }, - reportRequestError: { - id: 'api.errors.ReportRequestError', - defaultMessage: '!!!There was a problem sending the support request.', - description: '"There was a problem sending the support request." error message' - }, -}); - -export class GenericApiError extends LocalizableError { - constructor() { - super({ - id: messages.genericApiError.id, - defaultMessage: messages.genericApiError.defaultMessage, - }); - } -} - -export class IncorrectWalletPasswordError extends LocalizableError { - constructor() { - super({ - id: messages.incorrectWalletPasswordError.id, - defaultMessage: messages.incorrectWalletPasswordError.defaultMessage, - }); - } -} - -export class WalletAlreadyRestoredError extends LocalizableError { - constructor() { - super({ - id: messages.walletAlreadyRestoredError.id, - defaultMessage: messages.walletAlreadyRestoredError.defaultMessage, - }); - } -} - -export class ReportRequestError extends LocalizableError { - constructor() { - super({ - id: messages.reportRequestError.id, - defaultMessage: messages.reportRequestError.defaultMessage, - }); - } -} - -export class InvalidMnemonicError extends LocalizableError { - constructor() { - super({ - id: globalMessages.invalidMnemonic.id, - defaultMessage: globalMessages.invalidMnemonic.defaultMessage, - }); - } -} - -export type CreateTransactionResponse = WalletTransaction; -export type CreateWalletResponse = Wallet; -export type DeleteWalletResponse = boolean; -export type GetLocalTimeDifferenceResponse = number; -export type GetWalletsResponse = Array; -export type GetWalletRecoveryPhraseResponse = Array; -export type RestoreWalletResponse = Wallet; -export type UpdateWalletResponse = Wallet; -export type UpdateWalletPasswordResponse = boolean; - -export type CreateWalletRequest = { - name: string, - mnemonic: string, - password: ?string, -}; - -export type UpdateWalletPasswordRequest = { - walletId: string, - oldPassword: ?string, - newPassword: ?string, -}; - -export type DeleteWalletRequest = { - walletId: string, -}; - -export type RestoreWalletRequest = { - recoveryPhrase: string, - walletName: string, - walletPassword: ?string, -}; - -export type GetSyncProgressResponse = { - localDifficulty: ?number, - networkDifficulty: ?number, -}; - -export type GetTransactionsRequest = { - walletId: string, - searchTerm: string, - skip: number, - limit: number, -}; - -export type GetTransactionsResponse = { - transactions: Array, - total: number, -}; - -export type SendBugReportRequest = { - email: string, - subject: string, - problem: string, - logs: Array, -}; -export type SendBugReportResponse = any; diff --git a/source/renderer/app/api/common/errors.js b/source/renderer/app/api/common/errors.js new file mode 100644 index 0000000000..8c86ca42bd --- /dev/null +++ b/source/renderer/app/api/common/errors.js @@ -0,0 +1,86 @@ +import { defineMessages } from 'react-intl'; +import LocalizableError from '../../i18n/LocalizableError'; +import globalMessages from '../../i18n/global-messages'; + +const messages = defineMessages({ + genericApiError: { + id: 'api.errors.GenericApiError', + defaultMessage: '!!!An error occurred, please try again later.', + description: 'Generic error message.' + }, + incorrectWalletPasswordError: { + id: 'api.errors.IncorrectPasswordError', + defaultMessage: '!!!Incorrect wallet password.', + description: '"Incorrect wallet password." error message.' + }, + reportRequestError: { + id: 'api.errors.ReportRequestError', + defaultMessage: '!!!There was a problem sending the support request.', + description: '"There was a problem sending the support request." error message' + }, + apiMethodNotYetImplementedError: { + id: 'api.errors.ApiMethodNotYetImplementedError', + defaultMessage: '!!!This API method is not yet implemented.', + description: '"This API method is not yet implemented." error message.' + }, + forbiddenMnemonicError: { + id: 'api.errors.ForbiddenMnemonicError', + defaultMessage: '!!!Forbidden Mnemonic: an example Mnemonic has been submitted. Please generate a fresh and private Mnemonic from a trusted source.', + description: '"Forbidden Mnemonic: an example Mnemonic has been submitted." error message', + }, +}); + +export class GenericApiError extends LocalizableError { + constructor(values?: Object = {}) { + super({ + id: messages.genericApiError.id, + defaultMessage: messages.genericApiError.defaultMessage, + values, + }); + } +} + +export class IncorrectWalletPasswordError extends LocalizableError { + constructor() { + super({ + id: messages.incorrectWalletPasswordError.id, + defaultMessage: messages.incorrectWalletPasswordError.defaultMessage, + }); + } +} + +export class ReportRequestError extends LocalizableError { + constructor() { + super({ + id: messages.reportRequestError.id, + defaultMessage: messages.reportRequestError.defaultMessage, + }); + } +} + +export class ForbiddenMnemonicError extends LocalizableError { + constructor() { + super({ + id: messages.forbiddenMnemonicError.id, + defaultMessage: messages.forbiddenMnemonicError.defaultMessage, + }); + } +} + +export class InvalidMnemonicError extends LocalizableError { + constructor() { + super({ + id: globalMessages.invalidMnemonic.id, + defaultMessage: globalMessages.invalidMnemonic.defaultMessage, + }); + } +} + +export class ApiMethodNotYetImplementedError extends LocalizableError { + constructor() { + super({ + id: messages.apiMethodNotYetImplementedError.id, + defaultMessage: messages.apiMethodNotYetImplementedError.defaultMessage, + }); + } +} diff --git a/source/renderer/app/api/ada/sendAdaBugReport.js b/source/renderer/app/api/common/requests/sendBugReport.js similarity index 81% rename from source/renderer/app/api/ada/sendAdaBugReport.js rename to source/renderer/app/api/common/requests/sendBugReport.js index dd987504a3..58a385e03c 100644 --- a/source/renderer/app/api/ada/sendAdaBugReport.js +++ b/source/renderer/app/api/common/requests/sendBugReport.js @@ -1,10 +1,10 @@ // @flow import moment from 'moment'; import url from 'url'; -import { request } from '../lib/reportRequest'; -import environment from '../../../../common/environment'; +import { request } from '../../utils/reportRequest'; +import environment from '../../../../../common/environment'; -export type SendAdaBugReportRequestParams = { +export type SendBugReportParams = { requestFormData: { email: string, subject: string, @@ -13,8 +13,8 @@ export type SendAdaBugReportRequestParams = { }, }; -export const sendAdaBugReport = ( - { requestFormData }: SendAdaBugReportRequestParams +export const sendBugReport = ( + { requestFormData }: SendBugReportParams ) => { const { email, subject, problem, compressedLogsFile } = requestFormData; const { version, os, API_VERSION, NETWORK, build, getInstallerVersion, REPORT_URL } = environment; diff --git a/source/renderer/app/api/common/types.js b/source/renderer/app/api/common/types.js new file mode 100644 index 0000000000..220bae40dc --- /dev/null +++ b/source/renderer/app/api/common/types.js @@ -0,0 +1,29 @@ +export type RequestConfig = { + port: number, + ca: Uint8Array, + cert: Uint8Array, + key: Uint8Array, +}; + +export type ResponseBase = { + status: ResponseStatus, + meta: Pagination +}; + +export type ResponseStatus = 'success' | 'fail' | 'error'; + +export type Pagination = { + pagination: { + totalPages: number, + page: number, + perPage: number, + totalEntries: number + } +}; + +export type SendBugReportRequest = { + email: string, + subject: string, + problem: string, + logs: Array, +}; diff --git a/source/renderer/app/api/index.js b/source/renderer/app/api/index.js index 94ccc108e3..ef4e401c90 100644 --- a/source/renderer/app/api/index.js +++ b/source/renderer/app/api/index.js @@ -1,7 +1,7 @@ // @flow import { remote } from 'electron'; -import AdaApi from './ada/index'; -import LocalStorageApi from './localStorage/index'; +import AdaApi from './api'; +import LocalStorageApi from './utils/localStorage'; import environment from '../../../common/environment'; export type Api = { diff --git a/source/renderer/app/api/lib/utils.js b/source/renderer/app/api/lib/utils.js deleted file mode 100644 index 8a4d5ab73c..0000000000 --- a/source/renderer/app/api/lib/utils.js +++ /dev/null @@ -1,6 +0,0 @@ -// @flow - -// 'TextEncoder' is used to measure correct length of UTF-8 strings -export const getContentLength = (content: string) => ( - (new TextEncoder()).encode(content).length -); diff --git a/source/renderer/app/api/nodes/errors.js b/source/renderer/app/api/nodes/errors.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/source/renderer/app/api/nodes/requests/applyNodeUpdate.js b/source/renderer/app/api/nodes/requests/applyNodeUpdate.js new file mode 100644 index 0000000000..bdc1f1154f --- /dev/null +++ b/source/renderer/app/api/nodes/requests/applyNodeUpdate.js @@ -0,0 +1,14 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import { request } from '../../utils/request'; + +export const applyNodeUpdate = ( + config: RequestConfig, +): Promise => ( + request({ + hostname: 'localhost', + method: 'POST', + path: '/api/internal/apply-update', + ...config, + }) +); diff --git a/source/renderer/app/api/nodes/requests/getNextNodeUpdate.js b/source/renderer/app/api/nodes/requests/getNextNodeUpdate.js new file mode 100644 index 0000000000..04e45e97ca --- /dev/null +++ b/source/renderer/app/api/nodes/requests/getNextNodeUpdate.js @@ -0,0 +1,14 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import { request } from '../../utils/request'; + +export const getNextNodeUpdate = ( + config: RequestConfig +): Promise => ( + request({ + hostname: 'localhost', + method: 'GET', + path: '/api/internal/next-update', + ...config, + }) +); diff --git a/source/renderer/app/api/nodes/requests/getNodeInfo.js b/source/renderer/app/api/nodes/requests/getNodeInfo.js new file mode 100644 index 0000000000..8c5f932523 --- /dev/null +++ b/source/renderer/app/api/nodes/requests/getNodeInfo.js @@ -0,0 +1,20 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { NodeInfo } from '../types'; +import { request } from '../../utils/request'; + +export type NodeQueryParams = { + force_ntp_check: boolean, +}; + +export const getNodeInfo = ( + config: RequestConfig, + queryParams?: NodeQueryParams, +): Promise => ( + request({ + hostname: 'localhost', + method: 'GET', + path: '/api/v1/node-info', + ...config, + }, queryParams) +); diff --git a/source/renderer/app/api/nodes/requests/postponeNodeUpdate.js b/source/renderer/app/api/nodes/requests/postponeNodeUpdate.js new file mode 100644 index 0000000000..3254082265 --- /dev/null +++ b/source/renderer/app/api/nodes/requests/postponeNodeUpdate.js @@ -0,0 +1,14 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import { request } from '../../utils/request'; + +export const postponeNodeUpdate = ( + config: RequestConfig +): Promise => ( + request({ + hostname: 'localhost', + method: 'POST', + path: '/api/internal/postpone-update', + ...config, + }) +); diff --git a/source/renderer/app/api/nodes/types.js b/source/renderer/app/api/nodes/types.js new file mode 100644 index 0000000000..afed2d758b --- /dev/null +++ b/source/renderer/app/api/nodes/types.js @@ -0,0 +1,45 @@ +// @flow +export type NodeInfo = { + syncProgress: { + quantity: number, + unit: 'percent' + }, + blockchainHeight: ?{ + quantity: number, + unit: ?'blocks' + }, + localBlockchainHeight: { + quantity: number, + unit: ?'blocks' + }, + localTimeInformation: { + differenceFromNtpServer: ?{ + quantity: number, + unit: ?'microseconds' + } + }, + subscriptionStatus: Object +}; + +export type NodeSettings = { + slotDuration: { + quantity: number, + unit: ?'milliseconds' + }, + softwareInfo: NodeSoftware, + projectVersion: string, + gitRevision: string +}; + +export type NodeSoftware = { + applicationName: string, + version: number +}; + +// req/res Node Types +export type GetNetworkStatusResponse = { + subscriptionStatus: Object, + syncProgress: number, + blockchainHeight: number, + localBlockchainHeight: number +}; diff --git a/source/renderer/app/api/ada/errors.js b/source/renderer/app/api/transactions/errors.js similarity index 69% rename from source/renderer/app/api/ada/errors.js rename to source/renderer/app/api/transactions/errors.js index e1a29dba36..5041ac9921 100644 --- a/source/renderer/app/api/ada/errors.js +++ b/source/renderer/app/api/transactions/errors.js @@ -2,31 +2,6 @@ import { defineMessages } from 'react-intl'; import LocalizableError from '../../i18n/LocalizableError'; const messages = defineMessages({ - apiMethodNotYetImplementedError: { - id: 'api.errors.ApiMethodNotYetImplementedError', - defaultMessage: '!!!This API method is not yet implemented.', - description: '"This API method is not yet implemented." error message.' - }, - walletAlreadyImportedError: { - id: 'api.errors.WalletAlreadyImportedError', - defaultMessage: '!!!Wallet you are trying to import already exists.', - description: '"Wallet you are trying to import already exists." error message.' - }, - redeemAdaError: { - id: 'api.errors.RedeemAdaError', - defaultMessage: '!!!Your ADA could not be redeemed correctly.', - description: '"Your ADA could not be redeemed correctly." error message.' - }, - walletFileImportError: { - id: 'api.errors.WalletFileImportError', - defaultMessage: '!!!Wallet could not be imported, please make sure you are providing a correct file.', - description: '"Wallet could not be imported, please make sure you are providing a correct file." error message.' - }, - notEnoughMoneyToSendError: { - id: 'api.errors.NotEnoughMoneyToSendError', - defaultMessage: '!!!Not enough money to make this transaction.', - description: '"Not enough money to make this transaction." error message.' - }, notAllowedToSendMoneyToSameAddressError: { id: 'api.errors.NotAllowedToSendMoneyToSameAddressError', defaultMessage: '!!!It\'s not allowed to send money to the same address you are sending from. Make sure you have enough addresses with money in this account or send to a different address.', @@ -37,6 +12,16 @@ const messages = defineMessages({ defaultMessage: '!!!It is not allowed to send money to Ada redemption address.', description: '"It is not allowed to send money to Ada redemption address." error message.' }, + notEnoughMoneyToSendError: { + id: 'api.errors.NotEnoughMoneyToSendError', + defaultMessage: '!!!Not enough money to make this transaction.', + description: '"Not enough money to make this transaction." error message.' + }, + redeemAdaError: { + id: 'api.errors.RedeemAdaError', + defaultMessage: '!!!Your ADA could not be redeemed correctly.', + description: '"Your ADA could not be redeemed correctly." error message.' + }, allFundsAlreadyAtReceiverAddressError: { id: 'api.errors.AllFundsAlreadyAtReceiverAddressError', defaultMessage: '!!!All your funds are already at the address you are trying send money to.', @@ -49,38 +34,20 @@ const messages = defineMessages({ }, }); -export class ApiMethodNotYetImplementedError extends LocalizableError { - constructor() { - super({ - id: messages.apiMethodNotYetImplementedError.id, - defaultMessage: messages.apiMethodNotYetImplementedError.defaultMessage, - }); - } -} - -export class WalletAlreadyImportedError extends LocalizableError { - constructor() { - super({ - id: messages.walletAlreadyImportedError.id, - defaultMessage: messages.walletAlreadyImportedError.defaultMessage, - }); - } -} - -export class RedeemAdaError extends LocalizableError { +export class NotAllowedToSendMoneyToSameAddressError extends LocalizableError { constructor() { super({ - id: messages.redeemAdaError.id, - defaultMessage: messages.redeemAdaError.defaultMessage, + id: messages.notAllowedToSendMoneyToSameAddressError.id, + defaultMessage: messages.notAllowedToSendMoneyToSameAddressError.defaultMessage, }); } } -export class WalletFileImportError extends LocalizableError { +export class NotAllowedToSendMoneyToRedeemAddressError extends LocalizableError { constructor() { super({ - id: messages.walletFileImportError.id, - defaultMessage: messages.walletFileImportError.defaultMessage, + id: messages.notAllowedToSendMoneyToRedeemAddressError.id, + defaultMessage: messages.notAllowedToSendMoneyToRedeemAddressError.defaultMessage, }); } } @@ -94,20 +61,11 @@ export class NotEnoughMoneyToSendError extends LocalizableError { } } -export class NotAllowedToSendMoneyToSameAddressError extends LocalizableError { - constructor() { - super({ - id: messages.notAllowedToSendMoneyToSameAddressError.id, - defaultMessage: messages.notAllowedToSendMoneyToSameAddressError.defaultMessage, - }); - } -} - -export class NotAllowedToSendMoneyToRedeemAddressError extends LocalizableError { +export class RedeemAdaError extends LocalizableError { constructor() { super({ - id: messages.notAllowedToSendMoneyToRedeemAddressError.id, - defaultMessage: messages.notAllowedToSendMoneyToRedeemAddressError.defaultMessage, + id: messages.redeemAdaError.id, + defaultMessage: messages.redeemAdaError.defaultMessage, }); } } diff --git a/source/renderer/app/api/transactions/requests/createTransaction.js b/source/renderer/app/api/transactions/requests/createTransaction.js new file mode 100644 index 0000000000..85eb1b86a8 --- /dev/null +++ b/source/renderer/app/api/transactions/requests/createTransaction.js @@ -0,0 +1,28 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { Transaction, PaymentDistribution } from '../types'; +import { request } from '../../utils/request'; + +export type TransactionParams = { + data: { + source: { + accountIndex: number, + walletId: string, + }, + destinations: Array, + groupingPolicy: ?'OptimizeForSecurity' | 'OptimizeForSize', + spendingPassword: ?string + }, +}; + +export const createTransaction = ( + config: RequestConfig, + { data }: TransactionParams +): Promise => ( + request({ + hostname: 'localhost', + method: 'POST', + path: '/api/v1/transactions', + ...config, + }, {}, data) +); diff --git a/source/renderer/app/api/transactions/requests/getTransactionFee.js b/source/renderer/app/api/transactions/requests/getTransactionFee.js new file mode 100644 index 0000000000..c43c2ee764 --- /dev/null +++ b/source/renderer/app/api/transactions/requests/getTransactionFee.js @@ -0,0 +1,17 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { TransactionParams } from './createTransaction'; +import type { TransactionFee } from '../types'; +import { request } from '../../utils/request'; + +export const getTransactionFee = ( + config: RequestConfig, + { data }: TransactionParams +): Promise => ( + request({ + hostname: 'localhost', + method: 'POST', + path: '/api/v1/transactions/fees', + ...config, + }, {}, data) +); diff --git a/source/renderer/app/api/transactions/requests/getTransactionHistory.js b/source/renderer/app/api/transactions/requests/getTransactionHistory.js new file mode 100644 index 0000000000..0aa093b4a9 --- /dev/null +++ b/source/renderer/app/api/transactions/requests/getTransactionHistory.js @@ -0,0 +1,28 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { Transactions } from '../types'; +import { request } from '../../utils/request'; + +export type GetTxnHistoryParams = { + wallet_id: string, + page: number, + per_page: number, + accountIndex: number, + sort_by: string, +}; + +const requestOptions = { + returnMeta: true, +}; + +export const getTransactionHistory = ( + config: RequestConfig, + { ...requestParams }: GetTxnHistoryParams +): Promise => ( + request({ + hostname: 'localhost', + method: 'GET', + path: '/api/v1/transactions', + ...config, + }, requestParams, null, requestOptions) +); diff --git a/source/renderer/app/api/transactions/requests/redeemAda.js b/source/renderer/app/api/transactions/requests/redeemAda.js new file mode 100644 index 0000000000..8fa25fa442 --- /dev/null +++ b/source/renderer/app/api/transactions/requests/redeemAda.js @@ -0,0 +1,24 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { Transaction } from '../types'; +import { request } from '../../utils/request'; + +export type RedeemAdaParams = { + redemptionCode: string, + mnemonic: ?Array, + spendingPassword: string, + walletId: string, + accountIndex: number +}; + +export const redeemAda = ( + config: RequestConfig, + redemptionParams: RedeemAdaParams +): Promise => ( + request({ + hostname: 'localhost', + method: 'POST', + path: '/api/v1/transactions/certificates', + ...config + }, {}, redemptionParams) +); diff --git a/source/renderer/app/api/transactions/requests/redeemPaperVendedAda.js b/source/renderer/app/api/transactions/requests/redeemPaperVendedAda.js new file mode 100644 index 0000000000..1612f14410 --- /dev/null +++ b/source/renderer/app/api/transactions/requests/redeemPaperVendedAda.js @@ -0,0 +1,22 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { Transaction } from '../types'; +import type { RedeemAdaParams } from './redeemAda'; +import { request } from '../../utils/request'; + +export type RedeemPaperVendedAdaParams = { + mnemonic: Array, + ...RedeemAdaParams +}; + +export const redeemPaperVendedAda = ( + config: RequestConfig, + redemptionParams: RedeemPaperVendedAdaParams +): Promise => ( + request({ + hostname: 'localhost', + method: 'POST', + path: '/api/v1/transactions/certificates', + ...config + }, {}, redemptionParams) +); diff --git a/source/renderer/app/api/transactions/types.js b/source/renderer/app/api/transactions/types.js new file mode 100644 index 0000000000..257f05f058 --- /dev/null +++ b/source/renderer/app/api/transactions/types.js @@ -0,0 +1,61 @@ +// @flow +import WalletTransaction from '../../domains/WalletTransaction'; +import type { ResponseBase } from '../common/types'; + +export type Transactions = { + data: Array, + ...ResponseBase +}; + +export type Transaction = { + amount: number, + confirmations: number, + creationTime: string, + direction: 'outgoing' | 'incoming', + id: string, + type: 'local' | 'foreign', + inputs: Array, + outputs: Array, + status: { + tag: 'applying' | 'inNewestBlocks' | 'persisted' | 'wontApply' | 'creating', + data: {}, + }, +}; + +export type PaymentDistribution = { + address: string, + amount: number +}; + +export type TxnAssuranceLevel = 'low' | 'medium' | 'high'; + +export type TransactionState = 'pending' | 'failed' | 'ok'; + +export type TransactionFee = { + estimatedAmount: number, + ...ResponseBase +}; + +export type TrasactionAddresses = { from: Array, to: Array }; +export type TransactionType = 'card' | 'expend' | 'income' | 'exchange'; + +// req/res Transaction Types +export type GetTransactionsRequest = { + walletId: string, + searchTerm: string, + skip: number, + limit: number, +}; + +export type TransactionRequest = { + accountIndex: number, + walletId: string, + address: string, + amount: number, + spendingPassword?: ?string, +}; + +export type GetTransactionsResponse = { + transactions: Array, + total: number, +}; diff --git a/source/renderer/app/api/utils/apiHelpers.js b/source/renderer/app/api/utils/apiHelpers.js new file mode 100644 index 0000000000..23025016ff --- /dev/null +++ b/source/renderer/app/api/utils/apiHelpers.js @@ -0,0 +1,22 @@ +// @flow +import { ApiMethodNotYetImplementedError } from '../common/errors'; + +export const notYetImplemented = async () => ( + new Promise((resolve, reject) => { + reject(new ApiMethodNotYetImplementedError()); + }) +); + +// helper code for testing async APIs +export const testAsync = async (apiMethod: Function) => { + const result = await apiMethod(); + console.log(`testAsync result: ${result}`); + return result; +}; + +// helper code for testing sync APIs +export const testSync = (apiMethod: Function) => { + const result = apiMethod(); + console.log(`testSync result: ${result}`); + return result; +}; diff --git a/source/renderer/app/api/utils/index.js b/source/renderer/app/api/utils/index.js new file mode 100644 index 0000000000..ed65451640 --- /dev/null +++ b/source/renderer/app/api/utils/index.js @@ -0,0 +1,22 @@ +// @flow +import moment from 'moment'; +import blakejs from 'blakejs'; + +// time utils +export const unixTimestampToDate = (timestamp: number) => new Date(timestamp * 1000); +export const utcStringToDate = (createDate: string) => moment.utc(createDate).toDate(); + + +// passphrase utils +const bytesToB16 = (bytes) => Buffer.from(bytes).toString('hex'); +const blake2b = (data) => blakejs.blake2b(data, null, 32); + +export const encryptPassphrase = (passphrase: ?string) => ( + bytesToB16(blake2b(passphrase)) +); + +// string utils +export const getContentLength = (content: string) => ( + // 'TextEncoder' is used to measure correct length of UTF-8 strings + (new TextEncoder()).encode(content).length +); diff --git a/source/renderer/app/api/localStorage/index.js b/source/renderer/app/api/utils/localStorage.js similarity index 100% rename from source/renderer/app/api/localStorage/index.js rename to source/renderer/app/api/utils/localStorage.js diff --git a/source/renderer/app/api/utils/mnemonics.js b/source/renderer/app/api/utils/mnemonics.js new file mode 100644 index 0000000000..a3bf0dd5c8 --- /dev/null +++ b/source/renderer/app/api/utils/mnemonics.js @@ -0,0 +1,32 @@ +import { + unscramblePaperWalletMnemonic, + scramblePaperWalletMnemonic, + generateMnemonic +} from '../../utils/crypto'; +import { PAPER_WALLET_WRITTEN_WORDS_COUNT } from '../../config/cryptoConfig'; + +type MnemonicsParams = { + passphrase: string, // 9-word mnemonic + scrambledInput: string, // 18-word scrambled mnemonic +}; + +export const unscrambleMnemonics = ( + { passphrase, scrambledInput }: MnemonicsParams +): Array => ( + unscramblePaperWalletMnemonic(passphrase, scrambledInput) +); + +export const scrambleMnemonics = ( + { passphrase, scrambledInput }: MnemonicsParams +): Array => ( + scramblePaperWalletMnemonic(passphrase, scrambledInput) +); + +export const generateAccountMnemonics = (): Array => ( + generateMnemonic().split(' ') +); + +// eslint-disable-next-line +export const generateAdditionalMnemonics = (): Array => ( + generateMnemonic(PAPER_WALLET_WRITTEN_WORDS_COUNT).split(' ') +); diff --git a/source/renderer/app/api/utils/patchAdaApi.js b/source/renderer/app/api/utils/patchAdaApi.js new file mode 100644 index 0000000000..ef5aa4ada6 --- /dev/null +++ b/source/renderer/app/api/utils/patchAdaApi.js @@ -0,0 +1,100 @@ +import BigNumber from 'bignumber.js'; +import { get } from 'lodash'; +import AdaApi from '../api'; +import { getNodeInfo } from '../nodes/requests/getNodeInfo'; +import { GenericApiError } from '../common/errors'; +import { Logger, stringifyData, stringifyError } from '../../../../common/logging'; +import { RedeemAdaError } from '../transactions/errors'; +import type { RedeemAdaParams } from '../transactions/requests/redeemAda'; +import type { RedeemPaperVendedAdaParams } from '../transactions/requests/redeemPaperVendedAda'; +import type { NodeQueryParams } from '../nodes/requests/getNodeInfo'; +import type { NodeInfo, GetNetworkStatusResponse } from '../nodes/types'; + +// ========== LOGGING ========= + +let LOCAL_TIME_DIFFERENCE = 0; +let NEXT_ADA_UPDATE = null; + +export default (api: AdaApi) => { + // Since we cannot test ada redemption in dev mode, just resolve the requests + api.redeemAda = (request: RedeemAdaParams) => new Promise((resolve) => { + try { + Logger.debug('AdaApi::redeemAda (PATCHED) called: ' + stringifyData(request)); + const { redemptionCode } = request; + const isValidRedemptionCode = api.isValidRedemptionKey(redemptionCode); + if (!isValidRedemptionCode) { + Logger.debug('AdaApi::redeemAda (PATCHED) failed: not a valid redemption key!'); + throw new RedeemAdaError(); + } + Logger.debug('AdaApi::redeemAda (PATCHED) success'); + resolve({ amount: new BigNumber(1000) }); + } catch (error) { + Logger.debug('AdaApi::redeemAda (PATCHED) error: ' + stringifyError(error)); + throw new RedeemAdaError(); + } + }); + + api.redeemPaperVendedAda = (request: RedeemPaperVendedAdaParams) => new Promise((resolve) => { + try { + Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) called: ' + stringifyData(request)); + const { redemptionCode, mnemonics } = request; + const isValidKey = api.isValidPaperVendRedemptionKey(redemptionCode); + const isValidMnemonic = api.isValidRedemptionMnemonic(mnemonics.join(' ')); + if (!isValidKey) Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) failed: not a valid redemption key!'); + if (!isValidMnemonic) Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) failed: not a valid mnemonic!'); + if (!isValidKey || !isValidMnemonic) { + throw new RedeemAdaError(); + } + Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) success'); + resolve({ amount: new BigNumber(1000) }); + } catch (error) { + Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) error: ' + stringifyError(error)); + throw new RedeemAdaError(); + } + }); + + api.getLocalTimeDifference = async () => ( + Promise.resolve(LOCAL_TIME_DIFFERENCE) + ); + + api.getNetworkStatus = async ( + queryParams?: NodeQueryParams + ): Promise => { + Logger.debug('AdaApi::getNetworkStatus (PATCHED) called'); + try { + const status: NodeInfo = await getNodeInfo(api.config, queryParams); + Logger.debug('AdaApi::getNetworkStatus (PATCHED) success: ' + stringifyData(status)); + + const { + blockchainHeight, + subscriptionStatus, + syncProgress, + localBlockchainHeight, + } = status; + + // extract relevant data before sending to NetworkStatusStore + return { + subscriptionStatus, + syncProgress: syncProgress.quantity, + blockchainHeight: get(blockchainHeight, 'quantity', 0), + localBlockchainHeight: localBlockchainHeight.quantity, + localTimeDifference: LOCAL_TIME_DIFFERENCE, + }; + } catch (error) { + Logger.error('AdaApi::getNetworkStatus (PATCHED) error: ' + stringifyError(error)); + throw new GenericApiError(); + } + }; + + api.setLocalTimeDifference = async (timeDifference) => { + LOCAL_TIME_DIFFERENCE = timeDifference; + }; + + api.nextUpdate = async () => ( + Promise.resolve(NEXT_ADA_UPDATE) + ); + + api.setNextUpdate = async (nextUpdate) => { + NEXT_ADA_UPDATE = nextUpdate; + }; +}; diff --git a/source/renderer/app/api/lib/reportRequest.js b/source/renderer/app/api/utils/reportRequest.js similarity index 100% rename from source/renderer/app/api/lib/reportRequest.js rename to source/renderer/app/api/utils/reportRequest.js diff --git a/source/renderer/app/api/ada/lib/v1/request.js b/source/renderer/app/api/utils/request.js similarity index 70% rename from source/renderer/app/api/ada/lib/v1/request.js rename to source/renderer/app/api/utils/request.js index 86e86724aa..8d7bdb8216 100644 --- a/source/renderer/app/api/ada/lib/v1/request.js +++ b/source/renderer/app/api/utils/request.js @@ -2,8 +2,7 @@ import https from 'https'; import { size, has, get, omit } from 'lodash'; import querystring from 'querystring'; -import { encryptPassphrase } from '../encryptPassphrase'; -import { getContentLength } from '../../../lib/utils'; +import { encryptPassphrase, getContentLength } from './'; export type RequestOptions = { hostname: string, @@ -20,10 +19,14 @@ export type RequestOptions = { }; function typedRequest( - httpOptions: RequestOptions, queryParams?: {}, rawBodyParams?: any + httpOptions: RequestOptions, + queryParams?: {}, + rawBodyParams?: any, + requestOptions?: { returnMeta: boolean }, ): Promise { return new Promise((resolve, reject) => { const options: RequestOptions = Object.assign({}, httpOptions); + const { returnMeta } = Object.assign({}, requestOptions); let hasRequestBody = false; let requestBody = ''; @@ -58,7 +61,8 @@ function typedRequest( requestBody = JSON.stringify(rawBodyParams); options.headers = { 'Content-Length': getContentLength(requestBody), - 'Content-Type': 'application/json', + 'Content-Type': 'application/json; charset=utf-8', + Accept: 'application/json; charset=utf-8', }; } @@ -75,13 +79,35 @@ function typedRequest( // Resolve JSON results and handle backend errors response.on('end', () => { try { + // When deleting a wallet, the API does not return any data in body + // even if it was successful + const { statusCode, statusMessage } = response; + + if (!body && statusCode >= 200 && statusCode <= 206) { + // adds status and data properties so JSON.parse doesn't throw an error + body = `{ + "status": "success", + "data": "statusCode: ${statusCode} -- statusMessage: ${statusMessage}" + }`; + } else if ( + options.path === '/api/internal/next-update' && + statusCode === 404 + ) { + // when nextAdaUpdate receives a 404, it isn't an error + // it means no updates are available + body = `{ + "status": "success", + "data": null + }`; + } + const parsedBody = JSON.parse(body); const status = get(parsedBody, 'status', false); if (status) { if (status === 'success') { - resolve(parsedBody.data); + resolve(returnMeta ? parsedBody : parsedBody.data); } else if (status === 'error' || status === 'fail') { - reject(new Error(parsedBody.message)); + reject(parsedBody); } else { // TODO: find a way to record this case and report to the backend team reject(new Error('Unknown response from backend.')); diff --git a/source/renderer/app/api/ada/lib/request.js b/source/renderer/app/api/utils/requestV0.js similarity index 96% rename from source/renderer/app/api/ada/lib/request.js rename to source/renderer/app/api/utils/requestV0.js index 21c6e1010c..928b7524fc 100644 --- a/source/renderer/app/api/ada/lib/request.js +++ b/source/renderer/app/api/utils/requestV0.js @@ -2,8 +2,7 @@ import https from 'https'; import { size, has, get, omit } from 'lodash'; import querystring from 'querystring'; -import { encryptPassphrase } from './encryptPassphrase'; -import { getContentLength } from '../../lib/utils'; +import { encryptPassphrase, getContentLength } from './'; export type RequestOptions = { hostname: string, diff --git a/source/renderer/app/api/wallets/errors.js b/source/renderer/app/api/wallets/errors.js new file mode 100644 index 0000000000..b22f5501fc --- /dev/null +++ b/source/renderer/app/api/wallets/errors.js @@ -0,0 +1,47 @@ +import { defineMessages } from 'react-intl'; +import LocalizableError from '../../i18n/LocalizableError'; + +const messages = defineMessages({ + walletAlreadyRestoredError: { + id: 'api.errors.WalletAlreadyRestoredError', + defaultMessage: '!!!Wallet you are trying to restore already exists.', + description: '"Wallet you are trying to restore already exists." error message.' + }, + walletAlreadyImportedError: { + id: 'api.errors.WalletAlreadyImportedError', + defaultMessage: '!!!Wallet you are trying to import already exists.', + description: '"Wallet you are trying to import already exists." error message.' + }, + walletFileImportError: { + id: 'api.errors.WalletFileImportError', + defaultMessage: '!!!Wallet could not be imported, please make sure you are providing a correct file.', + description: '"Wallet could not be imported, please make sure you are providing a correct file." error message.' + }, +}); + +export class WalletAlreadyRestoredError extends LocalizableError { + constructor() { + super({ + id: messages.walletAlreadyRestoredError.id, + defaultMessage: messages.walletAlreadyRestoredError.defaultMessage, + }); + } +} + +export class WalletAlreadyImportedError extends LocalizableError { + constructor() { + super({ + id: messages.walletAlreadyImportedError.id, + defaultMessage: messages.walletAlreadyImportedError.defaultMessage, + }); + } +} + +export class WalletFileImportError extends LocalizableError { + constructor() { + super({ + id: messages.walletFileImportError.id, + defaultMessage: messages.walletFileImportError.defaultMessage, + }); + } +} diff --git a/source/renderer/app/api/wallets/requests/changeSpendingPassword.js b/source/renderer/app/api/wallets/requests/changeSpendingPassword.js new file mode 100644 index 0000000000..507b6d0422 --- /dev/null +++ b/source/renderer/app/api/wallets/requests/changeSpendingPassword.js @@ -0,0 +1,25 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { AdaWallet } from '../types'; +import { encryptPassphrase } from '../../utils'; +import { request } from '../../utils/request'; + +export type ChangeSpendingPasswordParams = { + walletId: string, + oldPassword: ?string, + newPassword: ?string, +}; + +export const changeSpendingPassword = ( + config: RequestConfig, + { walletId, oldPassword, newPassword }: ChangeSpendingPasswordParams +): Promise => { + const encryptedOldPassphrase = oldPassword ? encryptPassphrase(oldPassword) : ''; + const encryptedNewPassphrase = newPassword ? encryptPassphrase(newPassword) : ''; + return request({ + hostname: 'localhost', + method: 'PUT', + path: `/api/v1/wallets/${walletId}/password`, + ...config, + }, {}, { old: encryptedOldPassphrase, new: encryptedNewPassphrase }); +}; diff --git a/source/renderer/app/api/wallets/requests/createWallet.js b/source/renderer/app/api/wallets/requests/createWallet.js new file mode 100644 index 0000000000..91f47dc07e --- /dev/null +++ b/source/renderer/app/api/wallets/requests/createWallet.js @@ -0,0 +1,24 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { AdaWallet, WalletAssuranceLevel } from '../types'; +import { request } from '../../utils/request'; + +export type WalletInitData = { + operation: 'create' | 'restore', + backupPhrase: [string], + assuranceLevel: WalletAssuranceLevel, + name: string, + spendingPassword: ?string, +}; + +export const createWallet = ( + config: RequestConfig, + { walletInitData }: { walletInitData: WalletInitData } +): Promise => ( + request({ + hostname: 'localhost', + method: 'POST', + path: '/api/v1/wallets', + ...config, + }, {}, walletInitData) +); diff --git a/source/renderer/app/api/wallets/requests/deleteWallet.js b/source/renderer/app/api/wallets/requests/deleteWallet.js new file mode 100644 index 0000000000..3c9a53f7fd --- /dev/null +++ b/source/renderer/app/api/wallets/requests/deleteWallet.js @@ -0,0 +1,19 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import { request } from '../../utils/request'; + +export type DeleteWalletParams = { + walletId: string, +}; + +export const deleteWallet = ( + config: RequestConfig, + { walletId }: DeleteWalletParams +): Promise<*> => ( + request({ + hostname: 'localhost', + method: 'DELETE', + path: `/api/v1/wallets/${walletId}`, + ...config, + }) +); diff --git a/source/renderer/app/api/ada/exportAdaBackupJSON.js b/source/renderer/app/api/wallets/requests/exportWalletAsJSON.js similarity index 50% rename from source/renderer/app/api/ada/exportAdaBackupJSON.js rename to source/renderer/app/api/wallets/requests/exportWalletAsJSON.js index 79f866cfc0..d5f6ec5bbe 100644 --- a/source/renderer/app/api/ada/exportAdaBackupJSON.js +++ b/source/renderer/app/api/wallets/requests/exportWalletAsJSON.js @@ -1,15 +1,15 @@ // @flow -import { request } from './lib/request'; -import type { RequestConfig } from './types'; +import type { RequestConfig } from '../../common/types'; +import { request } from '../../utils/requestV0'; -export type ExportAdaBackupJSONParams = { +export type ExportWalletAsJSONParams = { walletId: string, filePath: string, }; -export const exportAdaBackupJSON = ( +export const exportWalletAsJSON = ( config: RequestConfig, - { walletId, filePath }: ExportAdaBackupJSONParams, + { walletId, filePath }: ExportWalletAsJSONParams, ): Promise<[]> => ( request({ hostname: 'localhost', diff --git a/source/renderer/app/api/wallets/requests/getWallets.js b/source/renderer/app/api/wallets/requests/getWallets.js new file mode 100644 index 0000000000..4031584a80 --- /dev/null +++ b/source/renderer/app/api/wallets/requests/getWallets.js @@ -0,0 +1,19 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { AdaWallets } from '../types'; +import { request } from '../../utils/request'; +import { MAX_ADA_WALLETS_COUNT } from '../../../config/numbersConfig'; + +export const getWallets = ( + config: RequestConfig +): Promise => ( + request({ + hostname: 'localhost', + method: 'GET', + path: '/api/v1/wallets', + ...config, + }, { + per_page: MAX_ADA_WALLETS_COUNT, // 50 is the max per_page value + sort_by: 'ASC[created_at]', + }) +); diff --git a/source/renderer/app/api/wallets/requests/importWalletAsJSON.js b/source/renderer/app/api/wallets/requests/importWalletAsJSON.js new file mode 100644 index 0000000000..66434b765a --- /dev/null +++ b/source/renderer/app/api/wallets/requests/importWalletAsJSON.js @@ -0,0 +1,16 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { AdaWallet } from '../types'; +import { request } from '../../utils/requestV0'; + +export const importWalletAsJSON = ( + config: RequestConfig, + filePath: string, +): Promise => ( + request({ + hostname: 'localhost', + method: 'POST', + path: '/api/backup/import', + ...config, + }, {}, filePath) +); diff --git a/source/renderer/app/api/wallets/requests/importWalletAsKey.js b/source/renderer/app/api/wallets/requests/importWalletAsKey.js new file mode 100644 index 0000000000..2d02961dff --- /dev/null +++ b/source/renderer/app/api/wallets/requests/importWalletAsKey.js @@ -0,0 +1,21 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { AdaWallet } from '../types'; +import { request } from '../../utils/request'; + +export type ImportWalletAsKey = { + filePath: string, + spendingPassword: ?string, +}; + +export const importWalletAsKey = ( + config: RequestConfig, + walletImportData: ImportWalletAsKey +): Promise => ( + request({ + hostname: 'localhost', + method: 'POST', + path: '/api/internal/import-wallet', + ...config, + }, {}, walletImportData) +); diff --git a/source/renderer/app/api/wallets/requests/resetWalletState.js b/source/renderer/app/api/wallets/requests/resetWalletState.js new file mode 100644 index 0000000000..49627115a8 --- /dev/null +++ b/source/renderer/app/api/wallets/requests/resetWalletState.js @@ -0,0 +1,14 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import { request } from '../../utils/request'; + +export const resetWalletState = ( + config: RequestConfig +): Promise => ( + request({ + hostname: 'localhost', + method: 'DELETE', + path: '/api/internal/reset-wallet-state', + ...config, + }) +); diff --git a/source/renderer/app/api/wallets/requests/restoreWallet.js b/source/renderer/app/api/wallets/requests/restoreWallet.js new file mode 100644 index 0000000000..462e553f02 --- /dev/null +++ b/source/renderer/app/api/wallets/requests/restoreWallet.js @@ -0,0 +1,17 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { WalletInitData } from './createWallet'; +import type { AdaWallet } from '../types'; +import { request } from '../../utils/request'; + +export const restoreWallet = ( + config: RequestConfig, + { walletInitData }: { walletInitData: WalletInitData } +): Promise => ( + request({ + hostname: 'localhost', + method: 'POST', + path: '/api/v1/wallets', + ...config, + }, {}, walletInitData) +); diff --git a/source/renderer/app/api/wallets/requests/updateWallet.js b/source/renderer/app/api/wallets/requests/updateWallet.js new file mode 100644 index 0000000000..34fbd67039 --- /dev/null +++ b/source/renderer/app/api/wallets/requests/updateWallet.js @@ -0,0 +1,22 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { AdaWallet, WalletAssuranceLevel } from '../types'; +import { request } from '../../utils/request'; + +export type UpdateWalletParams = { + walletId: string, + assuranceLevel: WalletAssuranceLevel, + name: string +}; + +export const updateWallet = ( + config: RequestConfig, + { walletId, assuranceLevel, name }: UpdateWalletParams +): Promise => ( + request({ + hostname: 'localhost', + method: 'PUT', + path: `/api/v1/wallets/${walletId}`, + ...config, + }, {}, { assuranceLevel, name }) +); diff --git a/source/renderer/app/api/wallets/types.js b/source/renderer/app/api/wallets/types.js new file mode 100644 index 0000000000..784d1f54f8 --- /dev/null +++ b/source/renderer/app/api/wallets/types.js @@ -0,0 +1,92 @@ +// @flow +export type AdaWallet = { + createdAt: string, + syncState: WalletSyncState, + balance: number, + hasSpendingPassword: boolean, + assuranceLevel: WalletAssuranceLevel, + name: string, + id: string, + spendingPasswordLastUpdate: string, +}; + +export type AdaWallets = Array; + +export type WalletAssuranceLevel = 'normal' | 'strict'; + +export type WalletAssuranceMode = { low: number, medium: number }; + +export type SyncStateTag = 'restoring' | 'synced'; + +export type WalletSyncState = { + data: ?{ + estimatedCompletionTime: { + quantity: number, + unit: 'milliseconds', + }, + percentage: { + quantity: number, + unit: 'percent', + }, + throughput: { + quantity: number, + unit: 'blocksPerSecond', + }, + }, + tag: SyncStateTag, +}; + +// req/res Wallet types +export type CreateWalletRequest = { + name: string, + mnemonic: string, + spendingPassword: ?string, +}; + +export type UpdateWalletPasswordRequest = { + walletId: string, + oldPassword?: string, + newPassword: ?string, +}; + +export type DeleteWalletRequest = { + walletId: string, +}; + +export type RestoreWalletRequest = { + recoveryPhrase: string, + walletName: string, + walletPassword: ?string, +}; + +export type UpdateWalletRequest = { + walletId: string, + assuranceLevel: WalletAssuranceLevel, + name: string +}; +export type ImportWalletFromKeyRequest = { + filePath: string, + spendingPassword: ?string, +}; + +export type ImportWalletFromFileRequest = { + filePath: string, + spendingPassword: ?string, + walletName: ?string, +}; + +export type ExportWalletToFileRequest = { + walletId: string, + filePath: string, + password: ?string +}; + +export type GetWalletCertificateRecoveryPhraseRequest = { + passphrase: string, + input: string, +}; + +export type GetWalletRecoveryPhraseFromCertificateRequest = { + passphrase: string, + scrambledInput: string, +}; diff --git a/source/renderer/app/components/loading/Loading.js b/source/renderer/app/components/loading/Loading.js index 44fb086587..bfcbfe63bf 100644 --- a/source/renderer/app/components/loading/Loading.js +++ b/source/renderer/app/components/loading/Loading.js @@ -23,11 +23,6 @@ const messages = defineMessages({ defaultMessage: '!!!Connecting to network', description: 'Message "Connecting to network" on the loading screen.' }, - waitingForSyncToStart: { - id: 'loading.screen.waitingForSyncToStart', - defaultMessage: '!!!Connected - waiting for block syncing to start', - description: 'Message "Connected - waiting for block syncing to start" on the loading screen.' - }, reconnecting: { id: 'loading.screen.reconnectingToNetworkMessage', defaultMessage: '!!!Network connection lost - reconnecting', @@ -64,20 +59,20 @@ type State = { type Props = { currencyIcon: string, apiIcon: string, - isConnecting: boolean, hasBeenConnected: boolean, - hasBlockSyncingStarted: boolean, - isSyncing: boolean, + isConnected: boolean, isSynced: boolean, syncPercentage: number, loadingDataForNextScreenMessage: ReactIntlMessage, hasLoadedCurrentLocale: boolean, hasLoadedCurrentTheme: boolean, - localTimeDifference: number, + localTimeDifference: ?number, isSystemTimeCorrect: boolean, + isCheckingSystemTime: boolean, currentLocale: string, handleReportIssue: Function, onProblemSolutionClick: Function, + onCheckTheTimeAgain: Function, }; @observer @@ -93,11 +88,11 @@ export default class Loading extends Component { } componentWillReceiveProps(nextProps: Props) { - const startConnectingTimer = nextProps.isConnecting && (connectingInterval === null); + const startConnectingTimer = !nextProps.isConnected && connectingInterval === null; const stopConnectingTimer = ( - this.props.isConnecting && - !nextProps.isConnecting && - (connectingInterval !== null) + !this.props.isConnected && + nextProps.isConnected && + connectingInterval !== null ); if (startConnectingTimer) { @@ -106,11 +101,15 @@ export default class Loading extends Component { this.resetConnectingTimer(); } - const startSyncingTimer = nextProps.isSyncing && (syncingInterval === null); + const startSyncingTimer = ( + nextProps.isConnected && + !nextProps.isSynced && + syncingInterval === null + ); const stopSyncingTimer = ( - this.props.isSyncing && - !nextProps.isSyncing && - (syncingInterval !== null) + !this.props.isSynced && + nextProps.isSynced && + syncingInterval !== null ); if (startSyncingTimer) { @@ -134,20 +133,20 @@ export default class Loading extends Component { const { currencyIcon, apiIcon, - isConnecting, - isSyncing, + isConnected, isSynced, syncPercentage, loadingDataForNextScreenMessage, hasBeenConnected, - hasBlockSyncingStarted, hasLoadedCurrentLocale, hasLoadedCurrentTheme, localTimeDifference, isSystemTimeCorrect, + isCheckingSystemTime, currentLocale, handleReportIssue, onProblemSolutionClick, + onCheckTheTimeAgain, } = this.props; const { connectingTime, syncingTime } = this.state; @@ -155,20 +154,20 @@ export default class Loading extends Component { const componentStyles = classNames([ styles.component, hasLoadedCurrentTheme ? null : styles['is-loading-theme'], - isConnecting ? styles['is-connecting'] : null, - isSyncing ? styles['is-syncing'] : null, + !isConnected ? styles['is-connecting'] : null, + isConnected && !isSynced ? styles['is-syncing'] : null, ]); const daedalusLogoStyles = classNames([ styles.daedalusLogo, - isConnecting ? styles.connectingLogo : styles.syncingLogo, + !isConnected ? styles.connectingLogo : styles.syncingLogo, ]); const currencyLogoStyles = classNames([ styles[`${environment.API}-logo`], - isConnecting ? styles.connectingLogo : styles.syncingLogo, + !isConnected ? styles.connectingLogo : styles.syncingLogo, ]); const apiLogoStyles = classNames([ styles[`${environment.API}-apiLogo`], - isConnecting ? styles.connectingLogo : styles.syncingLogo, + !isConnected ? styles.connectingLogo : styles.syncingLogo, ]); const daedalusLoadingLogo = daedalusLogo; @@ -179,13 +178,15 @@ export default class Loading extends Component { if (hasBeenConnected) { connectingMessage = messages.reconnecting; } else { - connectingMessage = ( - hasBlockSyncingStarted ? messages.waitingForSyncToStart : messages.connecting - ); + connectingMessage = messages.connecting; } - const canReportConnectingIssue = isConnecting && connectingTime >= REPORT_ISSUE_TIME_TRIGGER; - const canReportSyncingIssue = isSyncing && syncingTime >= REPORT_ISSUE_TIME_TRIGGER; + const canReportConnectingIssue = ( + !isConnected && connectingTime >= REPORT_ISSUE_TIME_TRIGGER + ); + const canReportSyncingIssue = ( + isConnected && !isSynced && syncingTime >= REPORT_ISSUE_TIME_TRIGGER + ); const showReportIssue = canReportConnectingIssue || canReportSyncingIssue; const buttonClasses = classNames([ @@ -195,7 +196,7 @@ export default class Loading extends Component { let loadingScreen = null; - if (isConnecting) { + if (!isConnected) { loadingScreen = (

@@ -203,7 +204,17 @@ export default class Loading extends Component {

); - } else if (isSystemTimeCorrect && !isSynced) { + } else if (!isSystemTimeCorrect) { + loadingScreen = ( + + ); + } else if (!isSynced) { loadingScreen = (

@@ -211,7 +222,7 @@ export default class Loading extends Component {

); - } else if (isSystemTimeCorrect) { + } else { loadingScreen = (
@@ -222,14 +233,6 @@ export default class Loading extends Component {
); - } else { - loadingScreen = ( - - ); } return ( @@ -237,7 +240,7 @@ export default class Loading extends Component { {showReportIssue && (

- {isConnecting ? + {!isConnected ? intl.formatMessage(messages.reportConnectingIssueText) : intl.formatMessage(messages.reportSyncingIssueText) } diff --git a/source/renderer/app/components/loading/SystemTimeErrorOverlay.js b/source/renderer/app/components/loading/SystemTimeErrorOverlay.js index b486ea4951..38ca975890 100644 --- a/source/renderer/app/components/loading/SystemTimeErrorOverlay.js +++ b/source/renderer/app/components/loading/SystemTimeErrorOverlay.js @@ -7,6 +7,7 @@ import { defineMessages, intlShape, FormattedHTMLMessage } from 'react-intl'; import { Button } from 'react-polymorph/lib/components/Button'; import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; import attentionIcon from '../../assets/images/attention-big-light.inline.svg'; +import { ALLOWED_TIME_DIFFERENCE } from '../../config/timingConfig'; import styles from './SystemTimeErrorOverlay.scss'; const messages = defineMessages({ @@ -20,17 +21,29 @@ const messages = defineMessages({ defaultMessage: '!!!Attention, Daedalus is unable to sync with the blockchain because the time on your machine is different from the global time. Your time is off by 2 hours 12 minutes 54 seconds.
To synchronize the time and fix this issue, please visit the FAQ section of Daedalus website:', description: 'Text of Sync error overlay' }, + timeOffsetIndeterminable: { + id: 'systemTime.error.timeOffsetIndeterminable', + defaultMessage: '!!!more than 15 seconds', + description: 'Time offset text shown in case NTP service is unreachable' + }, problemSolutionLink: { id: 'systemTime.error.problemSolutionLink', defaultMessage: '!!!daedaluswallet.io/faq', description: 'Link to Daedalus website FAQ page' - } + }, + onCheckTheTimeAgainLink: { + id: 'systemTime.error.onCheckTheTimeAgainLink', + defaultMessage: '!!!Check the time again', + description: 'Text of Check the time again button' + }, }); type Props = { - localTimeDifference: number, + localTimeDifference: ?number, currentLocale: string, onProblemSolutionClick: Function, + onCheckTheTimeAgain: Function, + isCheckingSystemTime: boolean, }; @observer @@ -42,7 +55,7 @@ export default class SystemTimeErrorOverlay extends Component { render() { const { intl } = this.context; - const { localTimeDifference, currentLocale } = this.props; + const { localTimeDifference, currentLocale, isCheckingSystemTime } = this.props; const problemSolutionLink = intl.formatMessage(messages.problemSolutionLink); let humanizedDurationLanguage; @@ -63,10 +76,18 @@ export default class SystemTimeErrorOverlay extends Component { humanizedDurationLanguage = 'en'; } - const timeOffset = humanizeDuration(localTimeDifference / 1000, { - round: true, // round seconds to prevent e.g. 1 day 3 hours *11,56 seconds* - language: humanizedDurationLanguage, - }).replace(/,/g, ''); // replace 1 day, 3 hours, 12 seconds* to clean period without comma + const isNTPServiceReachable = !!localTimeDifference; + const allowedTimeDifferenceInSeconds = ALLOWED_TIME_DIFFERENCE / 1000000; + + const timeOffset = isNTPServiceReachable ? ( + humanizeDuration((localTimeDifference || 0) / 1000, { + round: true, // round seconds to prevent e.g. 1 day 3 hours *11,56 seconds* + language: humanizedDurationLanguage, + }).replace(/,/g, '') // replace 1 day, 3 hours, 12 seconds* to clean period without comma + ) : ( + // NTP service is unreachable so we need to show a generic time offset message + intl.formatMessage(messages.timeOffsetIndeterminable, { allowedTimeDifferenceInSeconds }) + ); return (
@@ -82,11 +103,19 @@ export default class SystemTimeErrorOverlay extends Component { onClick={this.onProblemSolutionClick.bind(this, problemSolutionLink)} /> + +
); } onProblemSolutionClick = (link: string) => { this.props.onProblemSolutionClick(link); - } + }; } diff --git a/source/renderer/app/components/loading/SystemTimeErrorOverlay.scss b/source/renderer/app/components/loading/SystemTimeErrorOverlay.scss index fd789aeef6..eabfac4f16 100644 --- a/source/renderer/app/components/loading/SystemTimeErrorOverlay.scss +++ b/source/renderer/app/components/loading/SystemTimeErrorOverlay.scss @@ -1,3 +1,5 @@ +@import "../../themes/mixins/animations"; + .component { align-items: center; background-color: var(--theme-system-error-overlay-background-color); @@ -49,4 +51,20 @@ opacity: 0.8; } + .checkLink { + color: var(--theme-system-error-overlay-text-color); + cursor: pointer; + font-size: 14px; + line-height: 1.36; + margin-top: 20px; + opacity: 0.8; + border-bottom: 1px solid var(--theme-system-error-overlay-text-color); + + &:disabled { + text-decoration: none; + cursor: default; + @include animated-ellipsis($width: 16px); + } + } + } diff --git a/source/renderer/app/components/sidebar/SidebarCategory.js b/source/renderer/app/components/sidebar/SidebarCategory.js index d9acd83e05..04c45f8dcb 100644 --- a/source/renderer/app/components/sidebar/SidebarCategory.js +++ b/source/renderer/app/components/sidebar/SidebarCategory.js @@ -17,9 +17,10 @@ export default class SidebarCategory extends Component { render() { const { icon, active, className, onClick } = this.props; const componentStyles = classNames([ + className, styles.component, active ? styles.active : null, - styles[className] ? styles[className] : classNames + styles[className] ? styles[className] : null, ]); const iconStyles = classNames([ diff --git a/source/renderer/app/components/status/NetworkStatus.js b/source/renderer/app/components/status/NetworkStatus.js new file mode 100644 index 0000000000..49a366a756 --- /dev/null +++ b/source/renderer/app/components/status/NetworkStatus.js @@ -0,0 +1,312 @@ +// @flow +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import { get } from 'lodash'; +import moment from 'moment'; +import classNames from 'classnames'; +import SVGInline from 'react-svg-inline'; +import { + LineChart, YAxis, XAxis, Line, + CartesianGrid, Tooltip, Legend, + ResponsiveContainer, +} from 'recharts'; +import { + ALLOWED_TIME_DIFFERENCE, + MAX_ALLOWED_STALL_DURATION, +} from '../../config/timingConfig'; +import { UNSYNCED_BLOCKS_ALLOWED } from '../../config/numbersConfig'; +import closeCross from '../../assets/images/close-cross.inline.svg'; +import LocalizableError from '../../i18n/LocalizableError'; +import styles from './NetworkStatus.scss'; + +let syncingInterval = null; + +type Props = { + isNodeResponding: boolean, + isNodeSubscribed: boolean, + isNodeSyncing: boolean, + isNodeInSync: boolean, + isNodeTimeCorrect: boolean, + nodeConnectionError: ?LocalizableError, + isConnected: boolean, + isSynced: boolean, + syncPercentage: number, + hasBeenConnected: boolean, + localTimeDifference: ?number, + isSystemTimeCorrect: boolean, + isForceCheckingNodeTime: boolean, + isSystemTimeChanged: boolean, + mostRecentBlockTimestamp: number, + localBlockHeight: number, + networkBlockHeight: number, + onForceCheckLocalTimeDifference: Function, + onClose: Function, +}; + +type State = { + data: Array<{ + localBlockHeight: ?number, + networkBlockHeight: ?number, + time: number, + }>, +}; + +@observer +export default class NetworkStatus extends Component { + + constructor(props: Props) { + super(props); + let { localBlockHeight, networkBlockHeight } = props; + localBlockHeight = localBlockHeight || null; + networkBlockHeight = networkBlockHeight || null; + this.state = { + data: [ + { localBlockHeight, networkBlockHeight, time: moment(Date.now() - 20000).format('HH:mm:ss') }, + { localBlockHeight, networkBlockHeight, time: moment(Date.now() - 18000).format('HH:mm:ss') }, + { localBlockHeight, networkBlockHeight, time: moment(Date.now() - 16000).format('HH:mm:ss') }, + { localBlockHeight, networkBlockHeight, time: moment(Date.now() - 14000).format('HH:mm:ss') }, + { localBlockHeight, networkBlockHeight, time: moment(Date.now() - 12000).format('HH:mm:ss') }, + { localBlockHeight, networkBlockHeight, time: moment(Date.now() - 10000).format('HH:mm:ss') }, + { localBlockHeight, networkBlockHeight, time: moment(Date.now() - 8000).format('HH:mm:ss') }, + { localBlockHeight, networkBlockHeight, time: moment(Date.now() - 6000).format('HH:mm:ss') }, + { localBlockHeight, networkBlockHeight, time: moment(Date.now() - 4000).format('HH:mm:ss') }, + { localBlockHeight, networkBlockHeight, time: moment(Date.now() - 2000).format('HH:mm:ss') }, + ], + }; + } + + componentWillMount() { + syncingInterval = setInterval(this.syncingTimer, 2000); + } + + componentWillUnmount() { + this.resetSyncingTimer(); + } + + render() { + const { + isNodeResponding, isNodeSubscribed, isNodeSyncing, isNodeInSync, isNodeTimeCorrect, + isConnected, isSynced, syncPercentage, hasBeenConnected, + localTimeDifference, isSystemTimeCorrect, isForceCheckingNodeTime, + isSystemTimeChanged, mostRecentBlockTimestamp, localBlockHeight, networkBlockHeight, + onForceCheckLocalTimeDifference, onClose, nodeConnectionError, + } = this.props; + const { data } = this.state; + const isNTPServiceReachable = !!localTimeDifference; + const connectionError = get(nodeConnectionError, 'values', '{}'); + const { message, code } = connectionError; + + const localTimeDifferenceClasses = classNames([ + ( + !isNTPServiceReachable || + (localTimeDifference && (localTimeDifference > ALLOWED_TIME_DIFFERENCE)) + ) ? styles.red : styles.green, + ]); + + const remainingUnsyncedBlocks = networkBlockHeight - localBlockHeight; + const remainingUnsyncedBlocksClasses = classNames([ + remainingUnsyncedBlocks > UNSYNCED_BLOCKS_ALLOWED ? styles.red : styles.green, + ]); + + const timeSinceLastBlock = moment(Date.now()).diff(moment(mostRecentBlockTimestamp)); + const isBlockchainHeightStalling = timeSinceLastBlock > MAX_ALLOWED_STALL_DURATION; + const timeSinceLastBlockClasses = classNames([ + mostRecentBlockTimestamp > 0 && !isBlockchainHeightStalling ? styles.green : styles.red, + ]); + + return ( +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ DAEDALUS STATUS
+
isConnected: + {isConnected ? 'YES' : 'NO'} +
hasBeenConnected: + {hasBeenConnected ? 'YES' : 'NO'} +
isSynced: + {isSynced ? 'YES' : 'NO'} +
syncPercentage:{syncPercentage.toFixed(2)}%
localBlockHeight:{localBlockHeight}
networkBlockHeight:{networkBlockHeight}
remainingUnsyncedBlocks: + {remainingUnsyncedBlocks} +
timeSinceLastNetworkBlockChange: + {mostRecentBlockTimestamp > 0 ? `${timeSinceLastBlock} ms` : '-'} +
localTimeDifference: + + {isNTPServiceReachable ? ( + `${localTimeDifference || 0} μs` + ) : ( + 'NTP service unreachable' + )} + |  + +
isSystemTimeCorrect: + {isSystemTimeCorrect ? 'YES' : 'NO'} +
isSystemTimeChanged: + {isSystemTimeChanged ? 'YES' : 'NO'} +
isForceCheckingNodeTime: + {isForceCheckingNodeTime ? 'YES' : 'NO'} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + {!isConnected && nodeConnectionError ? ( + + + + ) : null} + +
+ CARDANO NODE STATUS
+
isNodeResponding: + {isNodeResponding ? 'YES' : 'NO'} +
isNodeSubscribed: + {isNodeSubscribed ? 'YES' : 'NO'} +
isNodeTimeCorrect: + {isNodeTimeCorrect ? 'YES' : 'NO'} +
isNodeSyncing: + {isNodeSyncing ? 'YES' : 'NO'} +
isNodeInSync: + {isNodeInSync ? 'YES' : 'NO'} +
+ Connection error:
+
+ message: {message || '-'}
+ code: {code || '-'} +
+
+
+ + + + + (Math.max(0, dataMin - 20)), dataMax => (dataMax + 20)]} + orientation="right" + type="number" + width={100} + /> + + + + + + + + + +
+ ); + } + + getClass = (isTrue: boolean) => ( + classNames([ + isTrue ? styles.green : styles.red, + ]) + ); + + syncingTimer = () => { + const { localBlockHeight, networkBlockHeight } = this.props; + const { data } = this.state; + data.push({ + localBlockHeight, + networkBlockHeight, + time: moment().format('HH:mm:ss'), + }); + this.setState({ data: data.slice(-10) }); + }; + + resetSyncingTimer = () => { + if (syncingInterval !== null) { + clearInterval(syncingInterval); + syncingInterval = null; + } + }; + +} diff --git a/source/renderer/app/components/status/NetworkStatus.scss b/source/renderer/app/components/status/NetworkStatus.scss new file mode 100644 index 0000000000..5ad6bf2d83 --- /dev/null +++ b/source/renderer/app/components/status/NetworkStatus.scss @@ -0,0 +1,99 @@ +.component { + align-items: center; + background: #202225; + height: 100%; + display: flex; + flex-direction: column; + font-family: var(--font-medium); + font-size: 14px; + line-height: 1.5; + justify-content: space-between; + padding: 30px; + width: 100%; +} + +.tables { + color: #fff; + display: flex; + justify-content: space-between; + width: 100%; +} + +.table { + width: calc(50% - 15px); + + th { + text-align: left; + } + + td { + &.topPadding { + padding-top: 10px; + } + + & + td { + text-align: right; + } + } + + .red { + color: #cd3100; + } + + .green { + color: #1cac63; + } + + button { + color: #fff; + cursor: pointer; + font-size: 14px; + line-height: 1.5; + position: relative; + + &::after { + border-top: 1px solid #fff; + bottom: 1px; + content: ''; + left: 0; + position: absolute; + right: 1px; + } + + &:disabled { + cursor: default; + + &::after { + display: none; + } + } + } + + hr { + border: 0; + border-top: 1px solid rgba(255, 255, 255, 0.25); + } + + .error { + color: #cd3100; + font-size: 12px; + font-style: italic; + line-height: 1.36; + padding: 2px 0; + } +} + +.closeButton { + cursor: pointer; + position: fixed; + right: 20px; + top: 20px; + + svg { + width: 16px; + height: 16px; + polygon { + fill: #fff; + } + } +} diff --git a/source/renderer/app/components/wallet/WalletCreateDialog.js b/source/renderer/app/components/wallet/WalletCreateDialog.js index c9a77508af..164b6adea2 100644 --- a/source/renderer/app/components/wallet/WalletCreateDialog.js +++ b/source/renderer/app/components/wallet/WalletCreateDialog.js @@ -154,7 +154,7 @@ export default class WalletCreateDialog extends Component { const { walletName, walletPassword } = form.values(); const walletData = { name: walletName, - password: createPassword ? walletPassword : null, + spendingPassword: createPassword ? walletPassword : null, }; this.props.onSubmit(walletData); }, diff --git a/source/renderer/app/components/wallet/WalletReceive.js b/source/renderer/app/components/wallet/WalletReceive.js index 6d8acfcc5b..b4eb6b5830 100644 --- a/source/renderer/app/components/wallet/WalletReceive.js +++ b/source/renderer/app/components/wallet/WalletReceive.js @@ -15,7 +15,7 @@ import { submitOnEnter } from '../../utils/form'; import BorderedBox from '../widgets/BorderedBox'; import TinySwitch from '../widgets/forms/TinySwitch'; import iconCopy from '../../assets/images/clipboard-ic.inline.svg'; -import WalletAddress from '../../domains/WalletAddress'; +import type { Addresses } from '../../api/addresses/types'; import globalMessages from '../../i18n/global-messages'; import LocalizableError from '../../i18n/LocalizableError'; import styles from './WalletReceive.scss'; @@ -63,7 +63,7 @@ messages.fieldIsRequired = globalMessages.fieldIsRequired; type Props = { walletAddress: string, isWalletAddressUsed: boolean, - walletAddresses: Array, + walletAddresses: Addresses, onGenerateAddress: Function, onCopyAddress: Function, isSidebarExpanded: boolean, @@ -243,13 +243,13 @@ export default class WalletReceive extends Component {

{walletAddresses.map((address, index) => { - const isAddressVisible = !address.isUsed || showUsed; + const isAddressVisible = !address.used || showUsed; if (!isAddressVisible) return null; const addressClasses = classnames([ 'generatedAddress-' + (index + 1), styles.walletAddress, - address.isUsed ? styles.usedWalletAddress : null, + address.used ? styles.usedWalletAddress : null, ]); return (
diff --git a/source/renderer/app/components/wallet/WalletSendForm.js b/source/renderer/app/components/wallet/WalletSendForm.js index 3fa846f600..5fa4d23bb1 100755 --- a/source/renderer/app/components/wallet/WalletSendForm.js +++ b/source/renderer/app/components/wallet/WalletSendForm.js @@ -18,7 +18,7 @@ import styles from './WalletSendForm.scss'; import globalMessages from '../../i18n/global-messages'; import WalletSendConfirmationDialog from './WalletSendConfirmationDialog'; import WalletSendConfirmationDialogContainer from '../../containers/wallet/dialogs/WalletSendConfirmationDialogContainer'; -import { formattedAmountToBigNumber, formattedAmountToNaturalUnits } from '../../utils/formatters'; +import { formattedAmountToBigNumber, formattedAmountToNaturalUnits, formattedAmountToLovelace } from '../../utils/formatters'; import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../config/timingConfig'; export const messages = defineMessages({ @@ -101,7 +101,7 @@ type Props = { currencyMaxIntegerDigits?: number, currencyMaxFractionalDigits: number, validateAmount: (amountInNaturalUnits: string) => Promise, - calculateTransactionFee: (receiver: string, amount: string) => Promise, + calculateTransactionFee: (address: string, amount: number) => Promise, addressValidator: Function, openDialogAction: Function, isDialogOpen: Function, @@ -171,16 +171,16 @@ export default class WalletSendForm extends Component { this._resetTransactionFee(); return [false, this.context.intl.formatMessage(messages.fieldIsRequired)]; } - const isValid = await this.props.addressValidator(value); + const isValidAddress = await this.props.addressValidator(value); const amountField = form.$('amount'); const amountValue = amountField.value; const isAmountValid = amountField.isValid; - if (isValid && isAmountValid) { + if (isValidAddress && isAmountValid) { await this._calculateTransactionFee(value, amountValue); } else { this._resetTransactionFee(); } - return [isValid, this.context.intl.formatMessage(messages.invalidAddress)]; + return [isValidAddress, this.context.intl.formatMessage(messages.invalidAddress)]; }], }, amount: { @@ -327,10 +327,10 @@ export default class WalletSendForm extends Component { } } - async _calculateTransactionFee(receiver: string, amountValue: string) { - const amount = formattedAmountToNaturalUnits(amountValue); + async _calculateTransactionFee(address: string, amountValue: string) { + const amount = formattedAmountToLovelace(amountValue); try { - const fee = await this.props.calculateTransactionFee(receiver, amount); + const fee = await this.props.calculateTransactionFee(address, amount); if (this._isMounted) { this._isCalculatingFee = false; this.setState({ diff --git a/source/renderer/app/components/wallet/ada-redemption/AdaRedemptionForm.js b/source/renderer/app/components/wallet/ada-redemption/AdaRedemptionForm.js index d1a935316b..6008174c4e 100644 --- a/source/renderer/app/components/wallet/ada-redemption/AdaRedemptionForm.js +++ b/source/renderer/app/components/wallet/ada-redemption/AdaRedemptionForm.js @@ -23,7 +23,7 @@ import { InvalidMnemonicError, InvalidEmailError, FieldRequiredError } from '../ import globalMessages from '../../../i18n/global-messages'; import styles from './AdaRedemptionForm.scss'; import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../../config/timingConfig'; -import { ADA_REDEMPTION_PASSPHRASE_LENGHT } from '../../../config/cryptoConfig'; +import { ADA_REDEMPTION_PASSPHRASE_LENGTH } from '../../../config/cryptoConfig'; import { ADA_REDEMPTION_TYPES } from '../../../types/redemptionTypes'; import type { RedemptionTypeChoices } from '../../../types/redemptionTypes'; import { submitOnEnter } from '../../../utils/form'; @@ -182,12 +182,12 @@ where Ada should be redeemed and enter {adaRedemptionPassphraseLength} word mnem defaultMessage: '!!!Enter your Ada amount', description: 'Hint for the Ada amount input field.' }, - walletPasswordPlaceholder: { + spendingPasswordPlaceholder: { id: 'wallet.redeem.dialog.walletPasswordPlaceholder', defaultMessage: '!!!Password', description: 'Placeholder for "spending password"', }, - walletPasswordLabel: { + spendingPasswordLabel: { id: 'wallet.redeem.dialog.walletPasswordLabel', defaultMessage: '!!!Password', description: 'Label for "spending password"', @@ -244,7 +244,7 @@ export default class AdaRedemptionForm extends Component { passPhrase: { label: this.context.intl.formatMessage(messages.passphraseLabel), placeholder: this.context.intl.formatMessage(messages.passphraseHint, { - length: ADA_REDEMPTION_PASSPHRASE_LENGHT + length: ADA_REDEMPTION_PASSPHRASE_LENGTH }), value: [], validators: [({ field }) => { @@ -332,10 +332,10 @@ export default class AdaRedemptionForm extends Component { ]; }], }, - walletPassword: { + spendingPassword: { type: 'password', - label: this.context.intl.formatMessage(messages.walletPasswordLabel), - placeholder: this.context.intl.formatMessage(messages.walletPasswordPlaceholder), + label: this.context.intl.formatMessage(messages.spendingPasswordLabel), + placeholder: this.context.intl.formatMessage(messages.spendingPasswordPlaceholder), value: '', validators: [({ field, form }) => { const password = field.value; @@ -372,11 +372,11 @@ export default class AdaRedemptionForm extends Component { submit = () => { this.form.submit({ onSuccess: (form) => { - const { walletId, shieldedRedemptionKey, walletPassword } = form.values(); + const { walletId, shieldedRedemptionKey, spendingPassword } = form.values(); this.props.onSubmit({ walletId, shieldedRedemptionKey, - walletPassword: walletPassword || null, + spendingPassword: spendingPassword || null, }); }, onError: () => {}, @@ -391,7 +391,7 @@ export default class AdaRedemptionForm extends Component { // We can not user form.reset() call here as it would reset selected walletId // which is a bad UX since we are calling resetForm on certificate add/remove - form.$('walletPassword').reset(); + form.$('spendingPassword').reset(); form.$('adaAmount').reset(); form.$('adaPasscode').reset(); form.$('certificate').reset(); @@ -407,7 +407,7 @@ export default class AdaRedemptionForm extends Component { onWalletChange = (walletId: string) => { const { form } = this; form.$('walletId').value = walletId; - form.$('walletPassword').value = ''; + form.$('spendingPassword').value = ''; } render() { @@ -429,7 +429,7 @@ export default class AdaRedemptionForm extends Component { const emailField = form.$('email'); const adaPasscodeField = form.$('adaPasscode'); const adaAmountField = form.$('adaAmount'); - const walletPasswordField = form.$('walletPassword'); + const spendingPasswordField = form.$('spendingPassword'); const decryptionKeyField = form.$('decryptionKey'); const componentClasses = classnames([ styles.component, @@ -445,7 +445,7 @@ export default class AdaRedemptionForm extends Component { redemptionType === ADA_REDEMPTION_TYPES.RECOVERY_FORCE_VENDED ); - const passwordSubmittable = !walletHasPassword || walletPasswordField.value !== ''; + const passwordSubmittable = !walletHasPassword || spendingPasswordField.value !== ''; let canSubmit = false; if (( @@ -472,18 +472,18 @@ export default class AdaRedemptionForm extends Component { switch (redemptionType) { case ADA_REDEMPTION_TYPES.REGULAR: instructionMessage = messages.instructionsRegular; - instructionValues = { adaRedemptionPassphraseLength: ADA_REDEMPTION_PASSPHRASE_LENGHT }; + instructionValues = { adaRedemptionPassphraseLength: ADA_REDEMPTION_PASSPHRASE_LENGTH }; break; case ADA_REDEMPTION_TYPES.FORCE_VENDED: instructionMessage = messages.instructionsForceVended; break; case ADA_REDEMPTION_TYPES.PAPER_VENDED: instructionMessage = messages.instructionsPaperVended; - instructionValues = { adaRedemptionPassphraseLength: ADA_REDEMPTION_PASSPHRASE_LENGHT }; + instructionValues = { adaRedemptionPassphraseLength: ADA_REDEMPTION_PASSPHRASE_LENGTH }; break; case ADA_REDEMPTION_TYPES.RECOVERY_REGULAR: instructionMessage = messages.instructionsRecoveryRegular; - instructionValues = { adaRedemptionPassphraseLength: ADA_REDEMPTION_PASSPHRASE_LENGHT }; + instructionValues = { adaRedemptionPassphraseLength: ADA_REDEMPTION_PASSPHRASE_LENGTH }; break; case ADA_REDEMPTION_TYPES.RECOVERY_FORCE_VENDED: instructionMessage = messages.instructionsRecoveryForceVended; @@ -588,8 +588,8 @@ export default class AdaRedemptionForm extends Component {
@@ -600,7 +600,7 @@ export default class AdaRedemptionForm extends Component { { ]; }], }, - walletPassword: { + spendingPassword: { type: 'password', - label: this.context.intl.formatMessage(messages.walletPasswordLabel), + label: this.context.intl.formatMessage(messages.spendingPasswordLabel), placeholder: this.context.intl.formatMessage(messages.passwordFieldPlaceholder), value: '', validators: [({ field, form }) => { @@ -146,10 +146,10 @@ export default class WalletFileImportDialog extends Component { value: '', validators: [({ field, form }) => { if (!this.state.createPassword) return [true]; - const walletPassword = form.$('walletPassword').value; - if (walletPassword.length === 0) return [true]; + const spendingPassword = form.$('spendingPassword').value; + if (spendingPassword.length === 0) return [true]; return [ - isValidRepeatPassword(walletPassword, field.value), + isValidRepeatPassword(spendingPassword, field.value), this.context.intl.formatMessage(globalMessages.invalidRepeatPassword) ]; }], @@ -166,10 +166,10 @@ export default class WalletFileImportDialog extends Component { this.form.submit({ onSuccess: (form) => { const { createPassword } = this.state; - const { walletFile, walletPassword, walletName } = form.values(); + const { walletFile, spendingPassword, walletName } = form.values(); const walletData = { filePath: walletFile.path, - walletPassword: createPassword ? walletPassword : null, + spendingPassword: createPassword ? spendingPassword : null, walletName: (walletName.length > 0) ? walletName : null, }; this.props.onSubmit(walletData); @@ -190,8 +190,8 @@ export default class WalletFileImportDialog extends Component { 'WalletFileImportDialog', ]); - // const walletPasswordFieldsClasses = classnames([ - // styles.walletPasswordFields, + // const spendingPasswordFieldsClasses = classnames([ + // styles.spendingPasswordFields, // createPassword ? styles.show : null, // ]); @@ -206,7 +206,7 @@ export default class WalletFileImportDialog extends Component { ]; // const walletNameField = form.$('walletName'); - // const walletPasswordField = form.$('walletPassword'); + // const spendingPasswordField = form.$('spendingPassword'); // const repeatedPasswordField = form.$('repeatPassword'); return ( @@ -254,11 +254,11 @@ export default class WalletFileImportDialog extends Component { />
-
+
{ render() { const { stores } = this.props; const { - isConnecting, isSyncing, isSynced, syncPercentage, hasBeenConnected, - hasBlockSyncingStarted, localTimeDifference, isSystemTimeCorrect, + isConnected, isSynced, syncPercentage, hasBeenConnected, + localTimeDifference, isSystemTimeCorrect, forceCheckTimeDifferenceRequest, + forceCheckLocalTimeDifference, } = stores.networkStatus; const { hasLoadedCurrentLocale, hasLoadedCurrentTheme, currentLocale } = stores.profile; return ( @@ -34,20 +35,20 @@ export default class LoadingPage extends Component { @@ -62,5 +63,5 @@ export default class LoadingPage extends Component { handleProblemSolutionClick = (link: string) => { shell.openExternal(`https://${link}`); - } + }; } diff --git a/source/renderer/app/containers/Root.js b/source/renderer/app/containers/Root.js index d600efeca3..c2f9549e24 100644 --- a/source/renderer/app/containers/Root.js +++ b/source/renderer/app/containers/Root.js @@ -13,20 +13,25 @@ export default class Root extends Component { render() { const { stores, actions, children } = this.props; - const { networkStatus, profile, ada } = stores; + const { networkStatus, profile, ada, app } = stores; + const { isNetworkStatusPage } = app; + const { isConnected, isSynced, isSystemTimeCorrect } = networkStatus; const wallets = stores[environment.API].wallets; const isAdaRedemptionPage = environment.isAdaApi() && ada.adaRedemption.isAdaRedemptionPage; const isPageThatDoesntNeedWallets = ( profile.isSettingsPage || isAdaRedemptionPage ); // Just render any page that doesn't require wallets to be loaded - if (networkStatus.isConnected && isPageThatDoesntNeedWallets) { + if ( + (isConnected && isPageThatDoesntNeedWallets) || + isNetworkStatusPage // Network Status page should be loaded regardless of the network status + ) { return React.Children.only(children); } if ( - !networkStatus.isSynced || + !isSynced || !wallets.hasLoadedWallets || - !networkStatus.isSystemTimeCorrect + !isSystemTimeCorrect ) { return ; } else if (!wallets.hasAnyWallets) { diff --git a/source/renderer/app/containers/status/NetworkStatusPage.js b/source/renderer/app/containers/status/NetworkStatusPage.js new file mode 100644 index 0000000000..927288e6a1 --- /dev/null +++ b/source/renderer/app/containers/status/NetworkStatusPage.js @@ -0,0 +1,57 @@ +// @flow +import React, { Component } from 'react'; +import { inject, observer } from 'mobx-react'; +import { ROUTES } from '../../routes-config'; +import CenteredLayout from '../../components/layout/CenteredLayout'; +import NetworkStatus from '../../components/status/NetworkStatus'; +import type { InjectedProps } from '../../types/injectedPropsType'; + +@inject('stores', 'actions') @observer +export default class NetworkStatusPage extends Component { + + handleClose = () => { + const { actions } = this.props; + actions.router.goToRoute.trigger({ route: ROUTES.ROOT }); + }; + + render() { + const { stores } = this.props; + const { + // Node state + isNodeResponding, isNodeSubscribed, isNodeSyncing, isNodeInSync, isNodeTimeCorrect, + // Application state + isConnected, isSynced, syncPercentage, hasBeenConnected, + localTimeDifference, isSystemTimeCorrect, forceCheckTimeDifferenceRequest, + forceCheckLocalTimeDifference, isSystemTimeChanged, getNetworkStatusRequest, + localBlockHeight, networkBlockHeight, mostRecentBlockTimestamp, + } = stores.networkStatus; + return ( + + + + ); + } + +} diff --git a/source/renderer/app/containers/wallet/AdaRedemptionPage.js b/source/renderer/app/containers/wallet/AdaRedemptionPage.js index 7552f61d48..541eda34eb 100644 --- a/source/renderer/app/containers/wallet/AdaRedemptionPage.js +++ b/source/renderer/app/containers/wallet/AdaRedemptionPage.js @@ -19,14 +19,14 @@ export default class AdaRedemptionPage extends Component { static defaultProps = { actions: null, stores: null }; - onSubmit = (values: { walletId: string, walletPassword: ?string }) => { + onSubmit = (values: { walletId: string, spendingPassword: ?string }) => { this.props.actions.ada.adaRedemption.redeemAda.trigger(values); }; onSubmitPaperVended = (values: { walletId: string, shieldedRedemptionKey: string, - walletPassword: ?string, + spendingPassword: ?string, }) => { this.props.actions.ada.adaRedemption.redeemPaperVendedAda.trigger(values); }; diff --git a/source/renderer/app/containers/wallet/Wallet.js b/source/renderer/app/containers/wallet/Wallet.js index e5bea72e87..8cbe79e923 100644 --- a/source/renderer/app/containers/wallet/Wallet.js +++ b/source/renderer/app/containers/wallet/Wallet.js @@ -11,7 +11,7 @@ import RestoreNotification from '../../components/notifications/RestoreNotificat import { buildRoute } from '../../utils/routing'; import { ROUTES } from '../../routes-config'; import type { InjectedContainerProps } from '../../types/injectedPropsType'; -import { syncStateTags } from '../../domains/Wallet'; +import { WalletSyncStateTags } from '../../domains/Wallet'; import environment from '../../../../common/environment'; import AntivirusRestaurationSlowdownNotification from '../../components/notifications/AntivirusRestaurationSlowdownNotification'; @@ -58,7 +58,7 @@ export default class Wallet extends Component { if (!wallets.active) return ; - const isRestoreActive = get(wallets.active, 'syncState.tag') === syncStateTags.RESTORING; + const isRestoreActive = get(wallets.active, 'syncState.tag') === WalletSyncStateTags.RESTORING; const restoreProgress = get(wallets.active, 'syncState.data.percentage.quantity', 0); const restoreETA = get(wallets.active, 'syncState.data.estimatedCompletionTime.quantity', 0); diff --git a/source/renderer/app/containers/wallet/WalletReceivePage.js b/source/renderer/app/containers/wallet/WalletReceivePage.js index 573ae2ea5c..dfce7a2af4 100755 --- a/source/renderer/app/containers/wallet/WalletReceivePage.js +++ b/source/renderer/app/containers/wallet/WalletReceivePage.js @@ -39,13 +39,14 @@ export default class WalletReceivePage extends Component { this.resetErrors(); } - handleGenerateAddress = (password: string) => { + handleGenerateAddress = (spendingPassword: string) => { const { wallets } = this.props.stores.ada; const wallet = wallets.active; + if (wallet) { this.props.actions.ada.addresses.createAddress.trigger({ walletId: wallet.id, - password, + spendingPassword, }); } }; @@ -74,7 +75,7 @@ export default class WalletReceivePage extends Component { if (!wallet) throw new Error('Active wallet required for WalletReceivePage.'); const walletAddress = addresses.active ? addresses.active.id : ''; - const isWalletAddressUsed = addresses.active ? addresses.active.isUsed : false; + const isWalletAddressUsed = addresses.active ? addresses.active.used : false; const walletAddresses = addresses.all.reverse(); const notification = { diff --git a/source/renderer/app/containers/wallet/WalletSendPage.js b/source/renderer/app/containers/wallet/WalletSendPage.js index 82c035635c..7205fa3bb9 100755 --- a/source/renderer/app/containers/wallet/WalletSendPage.js +++ b/source/renderer/app/containers/wallet/WalletSendPage.js @@ -7,7 +7,7 @@ import WalletSendForm from '../../components/wallet/WalletSendForm'; import type { InjectedProps } from '../../types/injectedPropsType'; import globalMessages from '../../i18n/global-messages'; import { DECIMAL_PLACES_IN_ADA, MAX_INTEGER_PLACES_IN_ADA } from '../../config/numbersConfig'; -import { syncStateTags } from '../../domains/Wallet'; +import { WalletSyncStateTags } from '../../domains/Wallet'; type Props = InjectedProps; @@ -32,7 +32,7 @@ export default class WalletSendPage extends Component { // Guard against potential null values if (!activeWallet) throw new Error('Active wallet required for WalletSendPage.'); - const isRestoreActive = get(activeWallet, 'syncState.tag') === syncStateTags.RESTORING; + const isRestoreActive = get(activeWallet, 'syncState.tag') === WalletSyncStateTags.RESTORING; return ( { currencyMaxIntegerDigits={MAX_INTEGER_PLACES_IN_ADA} currencyMaxFractionalDigits={DECIMAL_PLACES_IN_ADA} validateAmount={validateAmount} - calculateTransactionFee={(receiver, amount) => ( - calculateTransactionFee(activeWallet.id, receiver, amount) + calculateTransactionFee={(address: string, amount: number) => ( + calculateTransactionFee({ walletId: activeWallet.id, address, amount }) )} addressValidator={isValidAddress} isDialogOpen={uiDialogs.isOpen} diff --git a/source/renderer/app/containers/wallet/WalletSummaryPage.js b/source/renderer/app/containers/wallet/WalletSummaryPage.js index 9db8ec58ea..ce76416e72 100755 --- a/source/renderer/app/containers/wallet/WalletSummaryPage.js +++ b/source/renderer/app/containers/wallet/WalletSummaryPage.js @@ -11,7 +11,7 @@ import { DECIMAL_PLACES_IN_ADA } from '../../config/numbersConfig'; import { ROUTES } from '../../routes-config'; import type { InjectedProps } from '../../types/injectedPropsType'; import { formattedWalletAmount } from '../../utils/formatters'; -import { syncStateTags } from '../../domains/Wallet'; +import { WalletSyncStateTags } from '../../domains/Wallet'; export const messages = defineMessages({ noTransactions: { @@ -51,7 +51,7 @@ export default class WalletSummaryPage extends Component { let walletTransactions = null; const noTransactionsLabel = intl.formatMessage(messages.noTransactions); - const isRestoreActive = get(wallet, 'syncState.tag') === syncStateTags.RESTORING; + const isRestoreActive = get(wallet, 'syncState.tag') === WalletSyncStateTags.RESTORING; if (recentTransactionsRequest.isExecutingFirstTime || hasAny || isRestoreActive) { walletTransactions = ( diff --git a/source/renderer/app/containers/wallet/WalletTransactionsPage.js b/source/renderer/app/containers/wallet/WalletTransactionsPage.js index b0468a2975..59e7a2c0ef 100755 --- a/source/renderer/app/containers/wallet/WalletTransactionsPage.js +++ b/source/renderer/app/containers/wallet/WalletTransactionsPage.js @@ -9,7 +9,7 @@ import WalletNoTransactions from '../../components/wallet/transactions/WalletNoT import VerticalFlexContainer from '../../components/layout/VerticalFlexContainer'; import type { InjectedProps } from '../../types/injectedPropsType'; import { formattedWalletAmount } from '../../utils/formatters'; -import { syncStateTags } from '../../domains/Wallet'; +import { WalletSyncStateTags } from '../../domains/Wallet'; export const messages = defineMessages({ noTransactions: { @@ -63,8 +63,9 @@ export default class WalletTransactionsPage extends Component { // let transactionSearch = null; const noTransactionsLabel = intl.formatMessage(messages.noTransactions); const noTransactionsFoundLabel = intl.formatMessage(messages.noTransactionsFound); + const hasMoreToLoad = () => searchLimit !== null && totalAvailable > searchLimit; - const isRestoreActive = get(activeWallet, 'syncState.tag') === syncStateTags.RESTORING; + const isRestoreActive = get(activeWallet, 'syncState.tag') === WalletSyncStateTags.RESTORING; // if (wasSearched || hasAny) { // transactionSearch = ( @@ -83,7 +84,7 @@ export default class WalletTransactionsPage extends Component { transactions={filtered} isLoadingTransactions={searchRequest.isExecutingFirstTime} isRestoreActive={isRestoreActive} - hasMoreToLoad={totalAvailable > searchLimit} + hasMoreToLoad={hasMoreToLoad()} onLoadMore={actions.ada.transactions.loadMoreTransactions.trigger} assuranceMode={activeWallet.assuranceMode} walletId={activeWallet.id} diff --git a/source/renderer/app/containers/wallet/dialogs/WalletCreateDialogContainer.js b/source/renderer/app/containers/wallet/dialogs/WalletCreateDialogContainer.js index 353f70ef7a..efe3ca55aa 100644 --- a/source/renderer/app/containers/wallet/dialogs/WalletCreateDialogContainer.js +++ b/source/renderer/app/containers/wallet/dialogs/WalletCreateDialogContainer.js @@ -12,7 +12,7 @@ export default class WalletCreateDialogContainer extends Component { static defaultProps = { actions: null, stores: null, children: null, onClose: () => {} }; - onSubmit = (values: { name: string, password: ?string }) => { + onSubmit = (values: { name: string, spendingPassword: ?string }) => { this.props.actions[environment.API].wallets.createWallet.trigger(values); }; diff --git a/source/renderer/app/containers/wallet/dialogs/WalletFileImportDialogContainer.js b/source/renderer/app/containers/wallet/dialogs/WalletFileImportDialogContainer.js index 8186b73e8b..8e8e3356ee 100644 --- a/source/renderer/app/containers/wallet/dialogs/WalletFileImportDialogContainer.js +++ b/source/renderer/app/containers/wallet/dialogs/WalletFileImportDialogContainer.js @@ -11,7 +11,7 @@ export default class WalletFileImportDialogContainer extends Component { static defaultProps = { actions: null, stores: null, children: null, onClose: () => {} }; - onSubmit = (values: { filePath: string, walletPassword: ?string, walletName: ?string }) => { + onSubmit = (values: { filePath: string, spendingPassword: ?string, walletName: ?string }) => { this.props.actions.ada.wallets.importWalletFromFile.trigger(values); }; diff --git a/source/renderer/app/domains/Wallet.js b/source/renderer/app/domains/Wallet.js index 53339d468e..ba2b95fdad 100644 --- a/source/renderer/app/domains/Wallet.js +++ b/source/renderer/app/domains/Wallet.js @@ -1,34 +1,54 @@ // @flow import { observable, computed } from 'mobx'; import BigNumber from 'bignumber.js'; -import type { AssuranceMode, AssuranceModeOption } from '../types/transactionAssuranceTypes'; -import type { AdaV1WalletSyncState, AdaV1WalletSyncStateTag } from '../api/ada/types'; -import { assuranceModes, assuranceModeOptions } from '../types/transactionAssuranceTypes'; +import type { + WalletAssuranceLevel, + WalletAssuranceMode, + WalletSyncState, + SyncStateTag +} from '../api/wallets/types'; -export const syncStateTags: { - RESTORING: AdaV1WalletSyncStateTag, SYNCED: AdaV1WalletSyncStateTag, +export const WalletAssuranceModeOptions: { + NORMAL: WalletAssuranceLevel, STRICT: WalletAssuranceLevel, +} = { + NORMAL: 'normal', STRICT: 'strict', +}; + +export const WalletSyncStateTags: { + RESTORING: SyncStateTag, SYNCED: SyncStateTag, } = { RESTORING: 'restoring', SYNCED: 'synced', }; +const WalletAssuranceModes: { NORMAL: WalletAssuranceMode, STRICT: WalletAssuranceMode } = { + NORMAL: { + low: 3, + medium: 9, + }, + STRICT: { + low: 5, + medium: 15, + } +}; + export default class Wallet { id: string = ''; @observable name: string = ''; @observable amount: BigNumber; - @observable assurance: AssuranceModeOption; + @observable assurance: WalletAssuranceLevel; @observable hasPassword: boolean; @observable passwordUpdateDate: ?Date; - @observable syncState: ?AdaV1WalletSyncState; + @observable syncState: ?WalletSyncState; constructor(data: { id: string, name: string, amount: BigNumber, - assurance: AssuranceModeOption, + assurance: WalletAssuranceLevel, hasPassword: boolean, passwordUpdateDate: ?Date, - syncState?: AdaV1WalletSyncState, + syncState?: WalletSyncState, }) { Object.assign(this, data); } @@ -37,11 +57,11 @@ export default class Wallet { return this.amount > 0; } - @computed get assuranceMode(): AssuranceMode { + @computed get assuranceMode(): WalletAssuranceMode { switch (this.assurance) { - case assuranceModeOptions.NORMAL: return assuranceModes.NORMAL; - case assuranceModeOptions.STRICT: return assuranceModes.STRICT; - default: return assuranceModes.NORMAL; + case WalletAssuranceModeOptions.NORMAL: return WalletAssuranceModes.NORMAL; + case WalletAssuranceModeOptions.STRICT: return WalletAssuranceModes.STRICT; + default: return WalletAssuranceModes.NORMAL; } } diff --git a/source/renderer/app/domains/WalletAddress.js b/source/renderer/app/domains/WalletAddress.js index 85e6e796ed..f8fe675ad8 100644 --- a/source/renderer/app/domains/WalletAddress.js +++ b/source/renderer/app/domains/WalletAddress.js @@ -1,17 +1,16 @@ // @flow import { observable } from 'mobx'; -import BigNumber from 'bignumber.js'; export default class WalletAddress { @observable id: string = ''; - @observable amount: BigNumber; - @observable isUsed: boolean = false; + @observable used: boolean = false; + @observable changeAddress: boolean = false; constructor(data: { id: string, - amount: BigNumber, - isUsed: boolean, + used: boolean, + changeAddress: boolean, }) { Object.assign(this, data); } diff --git a/source/renderer/app/domains/WalletTransaction.js b/source/renderer/app/domains/WalletTransaction.js index ae711063a2..7a6a6e1209 100644 --- a/source/renderer/app/domains/WalletTransaction.js +++ b/source/renderer/app/domains/WalletTransaction.js @@ -1,12 +1,13 @@ // @flow import { observable } from 'mobx'; import BigNumber from 'bignumber.js'; -import type { AssuranceMode, AssuranceLevel } from '../types/transactionAssuranceTypes'; -import { assuranceLevels } from '../types/transactionAssuranceTypes'; - -export type TransactionState = 'pending' | 'failed' | 'ok'; -export type TrasactionAddresses = { from: Array, to: Array }; -export type TransactionType = 'card' | 'expend' | 'income' | 'exchange'; +import type { WalletAssuranceMode } from '../api/wallets/types'; +import type { + TxnAssuranceLevel, + TransactionState, + TrasactionAddresses, + TransactionType +} from '../api/transactions/types'; export const transactionStates: { PENDING: TransactionState, FAILED: TransactionState, OK: TransactionState, @@ -14,6 +15,12 @@ export const transactionStates: { PENDING: 'pending', FAILED: 'failed', OK: 'ok', }; +export const TxnAssuranceLevelOptions: { + LOW: TxnAssuranceLevel, MEDIUM: TxnAssuranceLevel, HIGH: TxnAssuranceLevel, +} = { + LOW: 'low', MEDIUM: 'medium', HIGH: 'high', +}; + export const transactionTypes: { CARD: TransactionType, EXPEND: TransactionType, @@ -52,13 +59,13 @@ export default class WalletTransaction { Object.assign(this, data); } - getAssuranceLevelForMode(mode: AssuranceMode): AssuranceLevel { + getAssuranceLevelForMode(mode: WalletAssuranceMode): TxnAssuranceLevel { if (this.numberOfConfirmations < mode.low) { - return assuranceLevels.LOW; + return TxnAssuranceLevelOptions.LOW; } else if (this.numberOfConfirmations < mode.medium) { - return assuranceLevels.MEDIUM; + return TxnAssuranceLevelOptions.MEDIUM; } - return assuranceLevels.HIGH; + return TxnAssuranceLevelOptions.HIGH; } } diff --git a/source/renderer/app/i18n/locales/de-DE.json b/source/renderer/app/i18n/locales/de-DE.json index 0e06fc2d7f..608759d1bc 100644 --- a/source/renderer/app/i18n/locales/de-DE.json +++ b/source/renderer/app/i18n/locales/de-DE.json @@ -3,6 +3,7 @@ "ImageUploadWidget.dropFileHint": "!!!Drop file here", "api.errors.AllFundsAlreadyAtReceiverAddressError": "!!!All your funds are already at the address you are trying send money to.", "api.errors.ApiMethodNotYetImplementedError": "!!!This API method is not yet implemented.", + "api.errors.ForbiddenMnemonicError": "!!!Forbidden Mnemonic: an example Mnemonic has been submitted. Please generate a fresh and private Mnemonic from a trusted source.", "api.errors.GenericApiError": "!!!An error occurred, please try again later.", "api.errors.IncorrectPasswordError": "!!!Incorrect wallet password.", "api.errors.NotAllowedToSendMoneyToRedeemAddressError": "!!!It is not allowed to send money to Ada redemption address.", @@ -78,7 +79,6 @@ "loading.screen.reportIssue.connecting.text": "!!!Having trouble connecting to network?", "loading.screen.reportIssue.syncing.text": "!!!Having trouble syncing?", "loading.screen.syncingBlocksMessage": "!!!Syncing blocks", - "loading.screen.waitingForSyncToStart": "!!!Connected - waiting for block syncing to start", "paper.wallet.create.certificate.completion.dialog.addressCopiedLabel": "!!!copied", "paper.wallet.create.certificate.completion.dialog.addressInstructions": "!!!To receive funds to your paper wallet simply share your wallet address with others.", "paper.wallet.create.certificate.completion.dialog.addressLabel": "!!!Wallet address", @@ -162,9 +162,11 @@ "static.about.copyright": "!!!Input Output HK Limited. Licensed under", "static.about.license": "!!!MIT licence", "static.about.title": "!!!Daedalus", + "systemTime.error.onCheckTheTimeAgainLink": "!!!Check the time again", "systemTime.error.overlayText": "!!!Attention, Daedalus is unable to sync with the blockchain because the time on your machine is different from the global time. You are 2 hours 12 minutes 54 seconds behind.
To synchronize the time and fix this issue, please visit the FAQ section of Daedalus website:", "systemTime.error.overlayTitle": "!!!Unable to sync - incorrect time", "systemTime.error.problemSolutionLink": "!!!daedaluswallet.io/faq", + "systemTime.error.timeOffsetIndeterminable": "!!!more than 15 seconds", "test.environment.developmentLabel": "!!!Development vx", "test.environment.stagingLabel": "!!!Staging vx", "test.environment.testnetLabel": "!!!Testnet vx", @@ -204,12 +206,12 @@ "wallet.file.import.dialog.passwordSwitchLabel": "!!!Password", "wallet.file.import.dialog.passwordSwitchPlaceholder": "!!!Activate to create password", "wallet.file.import.dialog.repeatPasswordLabel": "!!!Repeat password", + "wallet.file.import.dialog.spendingPasswordLabel": "!!!Wallet password", "wallet.file.import.dialog.submitLabel": "!!!Import wallet", "wallet.file.import.dialog.wallet.name.input.hint": "!!!e.g: Shopping Wallet", "wallet.file.import.dialog.wallet.name.input.label": "!!!Wallet name", "wallet.file.import.dialog.walletFileHint": "!!!Drop file here or click to choose", "wallet.file.import.dialog.walletFileLabel": "!!!Import file", - "wallet.file.import.dialog.walletPasswordLabel": "!!!Wallet password", "wallet.navigation.receive": "anfordern", "wallet.navigation.send": "senden", "wallet.navigation.settings": "!!!Settings", diff --git a/source/renderer/app/i18n/locales/defaultMessages.json b/source/renderer/app/i18n/locales/defaultMessages.json index cb3a97a8fd..016cbe9c83 100644 --- a/source/renderer/app/i18n/locales/defaultMessages.json +++ b/source/renderer/app/i18n/locales/defaultMessages.json @@ -2,87 +2,92 @@ { "descriptors": [ { - "defaultMessage": "!!!This API method is not yet implemented.", - "description": "\"This API method is not yet implemented.\" error message.", + "defaultMessage": "!!!An error occurred, please try again later.", + "description": "Generic error message.", "end": { "column": 3, - "line": 9 + "line": 10 }, - "file": "source/renderer/app/api/ada/errors.js", - "id": "api.errors.ApiMethodNotYetImplementedError", + "file": "source/renderer/app/api/common/errors.js", + "id": "api.errors.GenericApiError", "start": { - "column": 35, - "line": 5 + "column": 19, + "line": 6 } }, { - "defaultMessage": "!!!Wallet you are trying to import already exists.", - "description": "\"Wallet you are trying to import already exists.\" error message.", + "defaultMessage": "!!!Incorrect wallet password.", + "description": "\"Incorrect wallet password.\" error message.", "end": { "column": 3, - "line": 14 + "line": 15 }, - "file": "source/renderer/app/api/ada/errors.js", - "id": "api.errors.WalletAlreadyImportedError", + "file": "source/renderer/app/api/common/errors.js", + "id": "api.errors.IncorrectPasswordError", "start": { - "column": 30, - "line": 10 + "column": 32, + "line": 11 } }, { - "defaultMessage": "!!!Your ADA could not be redeemed correctly.", - "description": "\"Your ADA could not be redeemed correctly.\" error message.", + "defaultMessage": "!!!There was a problem sending the support request.", + "description": "\"There was a problem sending the support request.\" error message", "end": { "column": 3, - "line": 19 + "line": 20 }, - "file": "source/renderer/app/api/ada/errors.js", - "id": "api.errors.RedeemAdaError", + "file": "source/renderer/app/api/common/errors.js", + "id": "api.errors.ReportRequestError", "start": { - "column": 18, - "line": 15 + "column": 22, + "line": 16 } }, { - "defaultMessage": "!!!Wallet could not be imported, please make sure you are providing a correct file.", - "description": "\"Wallet could not be imported, please make sure you are providing a correct file.\" error message.", + "defaultMessage": "!!!This API method is not yet implemented.", + "description": "\"This API method is not yet implemented.\" error message.", "end": { "column": 3, - "line": 24 + "line": 25 }, - "file": "source/renderer/app/api/ada/errors.js", - "id": "api.errors.WalletFileImportError", + "file": "source/renderer/app/api/common/errors.js", + "id": "api.errors.ApiMethodNotYetImplementedError", "start": { - "column": 25, - "line": 20 + "column": 35, + "line": 21 } }, { - "defaultMessage": "!!!Not enough money to make this transaction.", - "description": "\"Not enough money to make this transaction.\" error message.", + "defaultMessage": "!!!Forbidden Mnemonic: an example Mnemonic has been submitted. Please generate a fresh and private Mnemonic from a trusted source.", + "description": "\"Forbidden Mnemonic: an example Mnemonic has been submitted.\" error message", "end": { "column": 3, - "line": 29 + "line": 30 }, - "file": "source/renderer/app/api/ada/errors.js", - "id": "api.errors.NotEnoughMoneyToSendError", + "file": "source/renderer/app/api/common/errors.js", + "id": "api.errors.ForbiddenMnemonicError", "start": { - "column": 29, - "line": 25 + "column": 26, + "line": 26 } - }, + } + ], + "path": "source/renderer/app/api/common/errors.json" + }, + { + "descriptors": [ { "defaultMessage": "!!!It's not allowed to send money to the same address you are sending from. Make sure you have enough addresses with money in this account or send to a different address.", "description": "\"It's not allowed to send money to the same address you are sending from.\" error message.", "end": { "column": 3, - "line": 34 + "line": 9 }, - "file": "source/renderer/app/api/ada/errors.js", + "file": "source/renderer/app/api/transactions/errors.js", "id": "api.errors.NotAllowedToSendMoneyToSameAddressError", "start": { "column": 43, - "line": 30 + "line": 5 } }, { @@ -90,13 +95,41 @@ "description": "\"It is not allowed to send money to Ada redemption address.\" error message.", "end": { "column": 3, - "line": 39 + "line": 14 }, - "file": "source/renderer/app/api/ada/errors.js", + "file": "source/renderer/app/api/transactions/errors.js", "id": "api.errors.NotAllowedToSendMoneyToRedeemAddressError", "start": { "column": 45, - "line": 35 + "line": 10 + } + }, + { + "defaultMessage": "!!!Not enough money to make this transaction.", + "description": "\"Not enough money to make this transaction.\" error message.", + "end": { + "column": 3, + "line": 19 + }, + "file": "source/renderer/app/api/transactions/errors.js", + "id": "api.errors.NotEnoughMoneyToSendError", + "start": { + "column": 29, + "line": 15 + } + }, + { + "defaultMessage": "!!!Your ADA could not be redeemed correctly.", + "description": "\"Your ADA could not be redeemed correctly.\" error message.", + "end": { + "column": 3, + "line": 24 + }, + "file": "source/renderer/app/api/transactions/errors.js", + "id": "api.errors.RedeemAdaError", + "start": { + "column": 18, + "line": 20 } }, { @@ -104,13 +137,13 @@ "description": "\"All your funds are already at the address you are trying send money to.\" error message.", "end": { "column": 3, - "line": 44 + "line": 29 }, - "file": "source/renderer/app/api/ada/errors.js", + "file": "source/renderer/app/api/transactions/errors.js", "id": "api.errors.AllFundsAlreadyAtReceiverAddressError", "start": { "column": 41, - "line": 40 + "line": 25 } }, { @@ -118,78 +151,64 @@ "description": "\"Not enough Ada for fees. Try sending a smaller amount.\" error message", "end": { "column": 3, - "line": 49 + "line": 34 }, - "file": "source/renderer/app/api/ada/errors.js", + "file": "source/renderer/app/api/transactions/errors.js", "id": "api.errors.NotEnoughFundsForTransactionFeesError", "start": { "column": 41, - "line": 45 + "line": 30 } } ], - "path": "source/renderer/app/api/ada/errors.json" + "path": "source/renderer/app/api/transactions/errors.json" }, { "descriptors": [ { - "defaultMessage": "!!!An error occurred, please try again later.", - "description": "Generic error message.", - "end": { - "column": 3, - "line": 11 - }, - "file": "source/renderer/app/api/common.js", - "id": "api.errors.GenericApiError", - "start": { - "column": 19, - "line": 7 - } - }, - { - "defaultMessage": "!!!Incorrect wallet password.", - "description": "\"Incorrect wallet password.\" error message.", + "defaultMessage": "!!!Wallet you are trying to restore already exists.", + "description": "\"Wallet you are trying to restore already exists.\" error message.", "end": { "column": 3, - "line": 16 + "line": 9 }, - "file": "source/renderer/app/api/common.js", - "id": "api.errors.IncorrectPasswordError", + "file": "source/renderer/app/api/wallets/errors.js", + "id": "api.errors.WalletAlreadyRestoredError", "start": { - "column": 32, - "line": 12 + "column": 30, + "line": 5 } }, { - "defaultMessage": "!!!Wallet you are trying to restore already exists.", - "description": "\"Wallet you are trying to restore already exists.\" error message.", + "defaultMessage": "!!!Wallet you are trying to import already exists.", + "description": "\"Wallet you are trying to import already exists.\" error message.", "end": { "column": 3, - "line": 21 + "line": 14 }, - "file": "source/renderer/app/api/common.js", - "id": "api.errors.WalletAlreadyRestoredError", + "file": "source/renderer/app/api/wallets/errors.js", + "id": "api.errors.WalletAlreadyImportedError", "start": { "column": 30, - "line": 17 + "line": 10 } }, { - "defaultMessage": "!!!There was a problem sending the support request.", - "description": "\"There was a problem sending the support request.\" error message", + "defaultMessage": "!!!Wallet could not be imported, please make sure you are providing a correct file.", + "description": "\"Wallet could not be imported, please make sure you are providing a correct file.\" error message.", "end": { "column": 3, - "line": 26 + "line": 19 }, - "file": "source/renderer/app/api/common.js", - "id": "api.errors.ReportRequestError", + "file": "source/renderer/app/api/wallets/errors.js", + "id": "api.errors.WalletFileImportError", "start": { - "column": 22, - "line": 22 + "column": 25, + "line": 15 } } ], - "path": "source/renderer/app/api/common.json" + "path": "source/renderer/app/api/wallets/errors.json" }, { "descriptors": [ @@ -207,32 +226,18 @@ "line": 21 } }, - { - "defaultMessage": "!!!Connected - waiting for block syncing to start", - "description": "Message \"Connected - waiting for block syncing to start\" on the loading screen.", - "end": { - "column": 3, - "line": 30 - }, - "file": "source/renderer/app/components/loading/Loading.js", - "id": "loading.screen.waitingForSyncToStart", - "start": { - "column": 25, - "line": 26 - } - }, { "defaultMessage": "!!!Network connection lost - reconnecting", "description": "Message \"Network connection lost - reconnecting\" on the loading screen.", "end": { "column": 3, - "line": 35 + "line": 30 }, "file": "source/renderer/app/components/loading/Loading.js", "id": "loading.screen.reconnectingToNetworkMessage", "start": { "column": 16, - "line": 31 + "line": 26 } }, { @@ -240,13 +245,13 @@ "description": "Message \"Syncing blocks\" on the loading screen.", "end": { "column": 3, - "line": 40 + "line": 35 }, "file": "source/renderer/app/components/loading/Loading.js", "id": "loading.screen.syncingBlocksMessage", "start": { "column": 11, - "line": 36 + "line": 31 } }, { @@ -254,13 +259,13 @@ "description": "Report connecting issue text on the loading screen.", "end": { "column": 3, - "line": 45 + "line": 40 }, "file": "source/renderer/app/components/loading/Loading.js", "id": "loading.screen.reportIssue.connecting.text", "start": { "column": 29, - "line": 41 + "line": 36 } }, { @@ -268,13 +273,13 @@ "description": "Report syncing issue text on the loading screen.", "end": { "column": 3, - "line": 50 + "line": 45 }, "file": "source/renderer/app/components/loading/Loading.js", "id": "loading.screen.reportIssue.syncing.text", "start": { "column": 26, - "line": 46 + "line": 41 } }, { @@ -282,13 +287,13 @@ "description": "Report an issue button label on the loading .", "end": { "column": 3, - "line": 55 + "line": 50 }, "file": "source/renderer/app/components/loading/Loading.js", "id": "loading.screen.reportIssue.buttonLabel", "start": { "column": 26, - "line": 51 + "line": 46 } } ], @@ -301,13 +306,13 @@ "description": "Title of Sync error overlay", "end": { "column": 3, - "line": 17 + "line": 18 }, "file": "source/renderer/app/components/loading/SystemTimeErrorOverlay.js", "id": "systemTime.error.overlayTitle", "start": { "column": 16, - "line": 13 + "line": 14 } }, { @@ -315,13 +320,27 @@ "description": "Text of Sync error overlay", "end": { "column": 3, - "line": 22 + "line": 23 }, "file": "source/renderer/app/components/loading/SystemTimeErrorOverlay.js", "id": "systemTime.error.overlayText", "start": { "column": 15, - "line": 18 + "line": 19 + } + }, + { + "defaultMessage": "!!!more than 15 seconds", + "description": "Time offset text shown in case NTP service is unreachable", + "end": { + "column": 3, + "line": 28 + }, + "file": "source/renderer/app/components/loading/SystemTimeErrorOverlay.js", + "id": "systemTime.error.timeOffsetIndeterminable", + "start": { + "column": 28, + "line": 24 } }, { @@ -329,13 +348,27 @@ "description": "Link to Daedalus website FAQ page", "end": { "column": 3, - "line": 27 + "line": 33 }, "file": "source/renderer/app/components/loading/SystemTimeErrorOverlay.js", "id": "systemTime.error.problemSolutionLink", "start": { "column": 23, - "line": 23 + "line": 29 + } + }, + { + "defaultMessage": "!!!Check the time again", + "description": "Text of Check the time again button", + "end": { + "column": 3, + "line": 38 + }, + "file": "source/renderer/app/components/loading/SystemTimeErrorOverlay.js", + "id": "systemTime.error.onCheckTheTimeAgainLink", + "start": { + "column": 27, + "line": 34 } } ], @@ -1795,7 +1828,7 @@ "file": "source/renderer/app/components/wallet/ada-redemption/AdaRedemptionForm.js", "id": "wallet.redeem.dialog.walletPasswordPlaceholder", "start": { - "column": 29, + "column": 31, "line": 185 } }, @@ -1809,7 +1842,7 @@ "file": "source/renderer/app/components/wallet/ada-redemption/AdaRedemptionForm.js", "id": "wallet.redeem.dialog.walletPasswordLabel", "start": { - "column": 23, + "column": 25, "line": 190 } } @@ -2173,9 +2206,9 @@ "line": 66 }, "file": "source/renderer/app/components/wallet/file-import/WalletFileImportDialog.js", - "id": "wallet.file.import.dialog.walletPasswordLabel", + "id": "wallet.file.import.dialog.spendingPasswordLabel", "start": { - "column": 23, + "column": 25, "line": 62 } }, @@ -3290,13 +3323,13 @@ "description": "Transaction type shown for credit card payments.", "end": { "column": 3, - "line": 22 + "line": 26 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.type.card", "start": { "column": 8, - "line": 18 + "line": 22 } }, { @@ -3304,13 +3337,13 @@ "description": "Transaction type shown for {currency} transactions.", "end": { "column": 3, - "line": 27 + "line": 31 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.type", "start": { "column": 8, - "line": 23 + "line": 27 } }, { @@ -3318,13 +3351,13 @@ "description": "Transaction type shown for money exchanges between currencies.", "end": { "column": 3, - "line": 32 + "line": 36 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.type.exchange", "start": { "column": 12, - "line": 28 + "line": 32 } }, { @@ -3332,13 +3365,13 @@ "description": "Transaction assurance level.", "end": { "column": 3, - "line": 37 + "line": 41 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.assuranceLevel", "start": { "column": 18, - "line": 33 + "line": 37 } }, { @@ -3346,13 +3379,13 @@ "description": "Transaction confirmations.", "end": { "column": 3, - "line": 42 + "line": 46 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.confirmations", "start": { "column": 17, - "line": 38 + "line": 42 } }, { @@ -3360,13 +3393,13 @@ "description": "Transaction ID.", "end": { "column": 3, - "line": 47 + "line": 51 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.transactionId", "start": { "column": 17, - "line": 43 + "line": 47 } }, { @@ -3374,13 +3407,13 @@ "description": "Conversion rate.", "end": { "column": 3, - "line": 52 + "line": 56 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.conversion.rate", "start": { "column": 18, - "line": 48 + "line": 52 } }, { @@ -3388,13 +3421,13 @@ "description": "Label \"{currency} sent\" for the transaction.", "end": { "column": 3, - "line": 57 + "line": 61 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.sent", "start": { "column": 8, - "line": 53 + "line": 57 } }, { @@ -3402,13 +3435,13 @@ "description": "Label \"{currency} received\" for the transaction.", "end": { "column": 3, - "line": 62 + "line": 66 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.received", "start": { "column": 12, - "line": 58 + "line": 62 } }, { @@ -3416,13 +3449,13 @@ "description": "From address", "end": { "column": 3, - "line": 67 + "line": 71 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.address.from", "start": { "column": 15, - "line": 63 + "line": 67 } }, { @@ -3430,13 +3463,13 @@ "description": "From addresses", "end": { "column": 3, - "line": 72 + "line": 76 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.addresses.from", "start": { "column": 17, - "line": 68 + "line": 72 } }, { @@ -3444,13 +3477,13 @@ "description": "To address", "end": { "column": 3, - "line": 77 + "line": 81 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.address.to", "start": { "column": 13, - "line": 73 + "line": 77 } }, { @@ -3458,13 +3491,13 @@ "description": "To addresses", "end": { "column": 3, - "line": 82 + "line": 86 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.addresses.to", "start": { "column": 15, - "line": 78 + "line": 82 } }, { @@ -3472,13 +3505,13 @@ "description": "Transaction amount.", "end": { "column": 3, - "line": 87 + "line": 91 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.transactionAmount", "start": { "column": 21, - "line": 83 + "line": 87 } }, { @@ -3486,13 +3519,13 @@ "description": "Transaction assurance level \"low\".", "end": { "column": 3, - "line": 95 + "line": 99 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.assuranceLevel.low", "start": { - "column": 25, - "line": 91 + "column": 34, + "line": 95 } }, { @@ -3500,13 +3533,13 @@ "description": "Transaction assurance level \"medium\".", "end": { "column": 3, - "line": 100 + "line": 104 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.assuranceLevel.medium", "start": { - "column": 28, - "line": 96 + "column": 37, + "line": 100 } }, { @@ -3514,13 +3547,13 @@ "description": "Transaction assurance level \"high\".", "end": { "column": 3, - "line": 105 + "line": 109 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.assuranceLevel.high", "start": { - "column": 26, - "line": 101 + "column": 35, + "line": 105 } }, { @@ -3528,13 +3561,13 @@ "description": "Transaction state \"pending\"", "end": { "column": 3, - "line": 113 + "line": 117 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.state.pending", "start": { "column": 31, - "line": 109 + "line": 113 } }, { @@ -3542,13 +3575,13 @@ "description": "Transaction state \"pending\"", "end": { "column": 3, - "line": 118 + "line": 122 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.state.failed", "start": { "column": 30, - "line": 114 + "line": 118 } } ], diff --git a/source/renderer/app/i18n/locales/en-US.json b/source/renderer/app/i18n/locales/en-US.json index 67663aa393..d282812ca3 100644 --- a/source/renderer/app/i18n/locales/en-US.json +++ b/source/renderer/app/i18n/locales/en-US.json @@ -3,6 +3,7 @@ "ImageUploadWidget.dropFileHint": "Drop file here", "api.errors.AllFundsAlreadyAtReceiverAddressError": "All your funds are already at the address you are trying send money to.", "api.errors.ApiMethodNotYetImplementedError": "This API method is not yet implemented.", + "api.errors.ForbiddenMnemonicError": "Forbidden Mnemonic: an example Mnemonic has been submitted. Please generate a fresh and private Mnemonic from a trusted source.", "api.errors.GenericApiError": "An error occurred, please try again later.", "api.errors.IncorrectPasswordError": "Incorrect wallet password.", "api.errors.NotAllowedToSendMoneyToRedeemAddressError": "It is not allowed to send money to Ada redemption address.", @@ -78,7 +79,6 @@ "loading.screen.reportIssue.connecting.text": "Having trouble connecting to network?", "loading.screen.reportIssue.syncing.text": "Having trouble syncing?", "loading.screen.syncingBlocksMessage": "Syncing blocks", - "loading.screen.waitingForSyncToStart": "Connected - waiting for block syncing to start", "paper.wallet.create.certificate.completion.dialog.addressCopiedLabel": "copied", "paper.wallet.create.certificate.completion.dialog.addressInstructions": "To receive funds to your paper wallet simply share your wallet address with others.", "paper.wallet.create.certificate.completion.dialog.addressLabel": "Wallet address", @@ -162,9 +162,11 @@ "static.about.copyright": "Input Output HK Limited. Licensed under", "static.about.license": "MIT licence", "static.about.title": "Daedalus", + "systemTime.error.onCheckTheTimeAgainLink": "Check the time again", "systemTime.error.overlayText": "Attention, Daedalus is unable to sync with the blockchain because the time on your machine is different from the global time. Your time is off by {timeOffset}.
To synchronize the time and fix this issue, please visit the FAQ section of Daedalus website:", "systemTime.error.overlayTitle": "Unable to sync - incorrect time", "systemTime.error.problemSolutionLink": "daedaluswallet.io/faq", + "systemTime.error.timeOffsetIndeterminable": "more than {allowedTimeDifferenceInSeconds} seconds", "test.environment.developmentLabel": "Development", "test.environment.stagingLabel": "Staging", "test.environment.testnetLabel": "Testnet", @@ -204,12 +206,12 @@ "wallet.file.import.dialog.passwordSwitchLabel": "Password", "wallet.file.import.dialog.passwordSwitchPlaceholder": "Activate to create password", "wallet.file.import.dialog.repeatPasswordLabel": "Repeat password", + "wallet.file.import.dialog.spendingPasswordLabel": "Wallet password", "wallet.file.import.dialog.submitLabel": "Import wallet", "wallet.file.import.dialog.wallet.name.input.hint": "e.g: Shopping Wallet", "wallet.file.import.dialog.wallet.name.input.label": "Wallet name", "wallet.file.import.dialog.walletFileHint": "Click to choose file", "wallet.file.import.dialog.walletFileLabel": "Import file", - "wallet.file.import.dialog.walletPasswordLabel": "Wallet password", "wallet.navigation.receive": "Receive", "wallet.navigation.send": "Send", "wallet.navigation.settings": "Settings", diff --git a/source/renderer/app/i18n/locales/hr-HR.json b/source/renderer/app/i18n/locales/hr-HR.json index 3545055d08..a4dfca8d5c 100644 --- a/source/renderer/app/i18n/locales/hr-HR.json +++ b/source/renderer/app/i18n/locales/hr-HR.json @@ -3,6 +3,7 @@ "ImageUploadWidget.dropFileHint": "!!!Drop file here", "api.errors.AllFundsAlreadyAtReceiverAddressError": "!!!All your funds are already at the address you are trying send money to.", "api.errors.ApiMethodNotYetImplementedError": "!!!This API method is not yet implemented.", + "api.errors.ForbiddenMnemonicError": "!!!Forbidden Mnemonic: an example Mnemonic has been submitted. Please generate a fresh and private Mnemonic from a trusted source.", "api.errors.GenericApiError": "!!!An error occurred, please try again later.", "api.errors.IncorrectPasswordError": "!!!Incorrect wallet password.", "api.errors.NotAllowedToSendMoneyToRedeemAddressError": "!!!It is not allowed to send money to Ada redemption address.", @@ -78,7 +79,6 @@ "loading.screen.reportIssue.connecting.text": "!!!Having trouble connecting to network?", "loading.screen.reportIssue.syncing.text": "!!!Having trouble syncing?", "loading.screen.syncingBlocksMessage": "!!!Syncing blocks", - "loading.screen.waitingForSyncToStart": "!!!Connected - waiting for block syncing to start", "paper.wallet.create.certificate.completion.dialog.addressCopiedLabel": "!!!copied", "paper.wallet.create.certificate.completion.dialog.addressInstructions": "!!!To receive funds to your paper wallet simply share your wallet address with others.", "paper.wallet.create.certificate.completion.dialog.addressLabel": "!!!Wallet address", @@ -162,9 +162,11 @@ "static.about.copyright": "!!!Input Output HK Limited. Licensed under", "static.about.license": "!!!MIT licence", "static.about.title": "!!!Daedalus", + "systemTime.error.onCheckTheTimeAgainLink": "!!!Check the time again", "systemTime.error.overlayText": "!!!Attention, Daedalus is unable to sync with the blockchain because the time on your machine is different from the global time. You are 2 hours 12 minutes 54 seconds behind.
To synchronize the time and fix this issue, please visit the FAQ section of Daedalus website:", "systemTime.error.overlayTitle": "!!!Unable to sync - incorrect time", "systemTime.error.problemSolutionLink": "!!!daedaluswallet.io/faq", + "systemTime.error.timeOffsetIndeterminable": "!!!more than 15 seconds", "test.environment.developmentLabel": "!!!Development vx", "test.environment.stagingLabel": "!!!Staging vx", "test.environment.testnetLabel": "!!!Testnet vx", @@ -204,12 +206,12 @@ "wallet.file.import.dialog.passwordSwitchLabel": "!!!Password", "wallet.file.import.dialog.passwordSwitchPlaceholder": "!!!Activate to create password", "wallet.file.import.dialog.repeatPasswordLabel": "!!!Repeat password", + "wallet.file.import.dialog.spendingPasswordLabel": "!!!Wallet password", "wallet.file.import.dialog.submitLabel": "!!!Import wallet", "wallet.file.import.dialog.wallet.name.input.hint": "!!!e.g: Shopping Wallet", "wallet.file.import.dialog.wallet.name.input.label": "!!!Wallet name", "wallet.file.import.dialog.walletFileHint": "!!!Drop file here or click to choose", "wallet.file.import.dialog.walletFileLabel": "!!!Import file", - "wallet.file.import.dialog.walletPasswordLabel": "!!!Wallet password", "wallet.navigation.receive": "primi", "wallet.navigation.send": "pošalji", "wallet.navigation.settings": "postavke", diff --git a/source/renderer/app/i18n/locales/ja-JP.json b/source/renderer/app/i18n/locales/ja-JP.json index 420e60e064..f89a90da2b 100644 --- a/source/renderer/app/i18n/locales/ja-JP.json +++ b/source/renderer/app/i18n/locales/ja-JP.json @@ -3,6 +3,7 @@ "ImageUploadWidget.dropFileHint": "ファイルをここにドロップしてください", "api.errors.AllFundsAlreadyAtReceiverAddressError": "送ろうとしている資金は既に送金先のアドレスにて反映済みです。", "api.errors.ApiMethodNotYetImplementedError": "このAPIは未実装です。", + "api.errors.ForbiddenMnemonicError": "!!!Forbidden Mnemonic: an example Mnemonic has been submitted. Please generate a fresh and private Mnemonic from a trusted source.", "api.errors.GenericApiError": "エラーが発生しました。しばらくしてからもう一度お試しください。", "api.errors.IncorrectPasswordError": "無効なウォレットパスワード", "api.errors.NotAllowedToSendMoneyToRedeemAddressError": "Ada還元アドレスに送金することはできません。", @@ -78,7 +79,6 @@ "loading.screen.reportIssue.connecting.text": "接続に問題がありますか?", "loading.screen.reportIssue.syncing.text": "同期に問題がありますか?", "loading.screen.syncingBlocksMessage": "ブロック同期中", - "loading.screen.waitingForSyncToStart": "接続完了 - ブロック同期開始を待機中", "paper.wallet.create.certificate.completion.dialog.addressCopiedLabel": "コピーされました", "paper.wallet.create.certificate.completion.dialog.addressInstructions": "ペーパーウォレットを用いて資金を受け取る際は、下記のウォレットアドレスを共有してください。", "paper.wallet.create.certificate.completion.dialog.addressLabel": "ウォレットアドレス", @@ -162,9 +162,11 @@ "static.about.copyright": "Input Output HK Limited. Licensed under", "static.about.license": "MIT licence", "static.about.title": "ダイダロス", + "systemTime.error.onCheckTheTimeAgainLink": "時間を再度確認", "systemTime.error.overlayText": "注意:お使いのマシンの時刻が標準時刻と一致していないために、ダイダロスは同期することができません。 あなたのマシンの時間は {timeOffset} 遅れています。
この問題を解決するには、DaedalusのウェブサイトのFAQセクションを参照してください:", "systemTime.error.overlayTitle": "同期できません - 時刻の不一致", "systemTime.error.problemSolutionLink": "daedaluswallet.io/ja/faq", + "systemTime.error.timeOffsetIndeterminable": "{allowedTimeDifferenceInSeconds}秒以上", "test.environment.developmentLabel": "開発", "test.environment.stagingLabel": "ステージング", "test.environment.testnetLabel": "テストネット", @@ -204,12 +206,12 @@ "wallet.file.import.dialog.passwordSwitchLabel": "パスワード", "wallet.file.import.dialog.passwordSwitchPlaceholder": "パスワードを有効化する", "wallet.file.import.dialog.repeatPasswordLabel": "パスワードを再度入力してください", + "wallet.file.import.dialog.spendingPasswordLabel": "ウォレットパスワード", "wallet.file.import.dialog.submitLabel": "ウォレットをインポート", "wallet.file.import.dialog.wallet.name.input.hint": "例:ショッピングウォレット", "wallet.file.import.dialog.wallet.name.input.label": "ウォレット名", "wallet.file.import.dialog.walletFileHint": "クリックしてファイルを選択してください", "wallet.file.import.dialog.walletFileLabel": "ファイルをインポート", - "wallet.file.import.dialog.walletPasswordLabel": "ウォレットパスワード", "wallet.navigation.receive": "入金", "wallet.navigation.send": "送金", "wallet.navigation.settings": "設定", diff --git a/source/renderer/app/i18n/locales/ko-KR.json b/source/renderer/app/i18n/locales/ko-KR.json index c20864ea0a..cac3bd79af 100644 --- a/source/renderer/app/i18n/locales/ko-KR.json +++ b/source/renderer/app/i18n/locales/ko-KR.json @@ -3,6 +3,7 @@ "ImageUploadWidget.dropFileHint": "!!!Drop file here", "api.errors.AllFundsAlreadyAtReceiverAddressError": "!!!All your funds are already at the address you are trying send money to.", "api.errors.ApiMethodNotYetImplementedError": "!!!This API method is not yet implemented.", + "api.errors.ForbiddenMnemonicError": "!!!Forbidden Mnemonic: an example Mnemonic has been submitted. Please generate a fresh and private Mnemonic from a trusted source.", "api.errors.GenericApiError": "!!!An error occurred, please try again later.", "api.errors.IncorrectPasswordError": "!!!Incorrect wallet password.", "api.errors.NotAllowedToSendMoneyToRedeemAddressError": "!!!It is not allowed to send money to Ada redemption address.", @@ -78,7 +79,6 @@ "loading.screen.reportIssue.connecting.text": "!!!Having trouble connecting to network?", "loading.screen.reportIssue.syncing.text": "!!!Having trouble syncing?", "loading.screen.syncingBlocksMessage": "!!!Syncing blocks", - "loading.screen.waitingForSyncToStart": "!!!Connected - waiting for block syncing to start", "paper.wallet.create.certificate.completion.dialog.addressCopiedLabel": "!!!copied", "paper.wallet.create.certificate.completion.dialog.addressInstructions": "!!!To receive funds to your paper wallet simply share your wallet address with others.", "paper.wallet.create.certificate.completion.dialog.addressLabel": "!!!Wallet address", @@ -162,9 +162,11 @@ "static.about.copyright": "!!!Input Output HK Limited. Licensed under", "static.about.license": "!!!MIT licence", "static.about.title": "!!!Daedalus", + "systemTime.error.onCheckTheTimeAgainLink": "!!!Check the time again", "systemTime.error.overlayText": "!!!Attention, Daedalus is unable to sync with the blockchain because the time on your machine is different from the global time. You are 2 hours 12 minutes 54 seconds behind.
To synchronize the time and fix this issue, please visit the FAQ section of Daedalus website:", "systemTime.error.overlayTitle": "!!!Unable to sync - incorrect time", "systemTime.error.problemSolutionLink": "!!!daedaluswallet.io/faq", + "systemTime.error.timeOffsetIndeterminable": "!!!more than 15 seconds", "test.environment.developmentLabel": "!!!Development vx", "test.environment.stagingLabel": "!!!Staging vx", "test.environment.testnetLabel": "!!!Testnet vx", @@ -204,12 +206,12 @@ "wallet.file.import.dialog.passwordSwitchLabel": "!!!Password", "wallet.file.import.dialog.passwordSwitchPlaceholder": "!!!Activate to create password", "wallet.file.import.dialog.repeatPasswordLabel": "!!!Repeat password", + "wallet.file.import.dialog.spendingPasswordLabel": "!!!Wallet password", "wallet.file.import.dialog.submitLabel": "!!!Import wallet", "wallet.file.import.dialog.wallet.name.input.hint": "!!!e.g: Shopping Wallet", "wallet.file.import.dialog.wallet.name.input.label": "!!!Wallet name", "wallet.file.import.dialog.walletFileHint": "!!!Drop file here or click to choose", "wallet.file.import.dialog.walletFileLabel": "!!!Import file", - "wallet.file.import.dialog.walletPasswordLabel": "!!!Wallet password", "wallet.navigation.receive": "!!!Receive", "wallet.navigation.send": "!!!Send", "wallet.navigation.settings": "!!!Settings", diff --git a/source/renderer/app/i18n/locales/zh-CN.json b/source/renderer/app/i18n/locales/zh-CN.json index c20864ea0a..cac3bd79af 100644 --- a/source/renderer/app/i18n/locales/zh-CN.json +++ b/source/renderer/app/i18n/locales/zh-CN.json @@ -3,6 +3,7 @@ "ImageUploadWidget.dropFileHint": "!!!Drop file here", "api.errors.AllFundsAlreadyAtReceiverAddressError": "!!!All your funds are already at the address you are trying send money to.", "api.errors.ApiMethodNotYetImplementedError": "!!!This API method is not yet implemented.", + "api.errors.ForbiddenMnemonicError": "!!!Forbidden Mnemonic: an example Mnemonic has been submitted. Please generate a fresh and private Mnemonic from a trusted source.", "api.errors.GenericApiError": "!!!An error occurred, please try again later.", "api.errors.IncorrectPasswordError": "!!!Incorrect wallet password.", "api.errors.NotAllowedToSendMoneyToRedeemAddressError": "!!!It is not allowed to send money to Ada redemption address.", @@ -78,7 +79,6 @@ "loading.screen.reportIssue.connecting.text": "!!!Having trouble connecting to network?", "loading.screen.reportIssue.syncing.text": "!!!Having trouble syncing?", "loading.screen.syncingBlocksMessage": "!!!Syncing blocks", - "loading.screen.waitingForSyncToStart": "!!!Connected - waiting for block syncing to start", "paper.wallet.create.certificate.completion.dialog.addressCopiedLabel": "!!!copied", "paper.wallet.create.certificate.completion.dialog.addressInstructions": "!!!To receive funds to your paper wallet simply share your wallet address with others.", "paper.wallet.create.certificate.completion.dialog.addressLabel": "!!!Wallet address", @@ -162,9 +162,11 @@ "static.about.copyright": "!!!Input Output HK Limited. Licensed under", "static.about.license": "!!!MIT licence", "static.about.title": "!!!Daedalus", + "systemTime.error.onCheckTheTimeAgainLink": "!!!Check the time again", "systemTime.error.overlayText": "!!!Attention, Daedalus is unable to sync with the blockchain because the time on your machine is different from the global time. You are 2 hours 12 minutes 54 seconds behind.
To synchronize the time and fix this issue, please visit the FAQ section of Daedalus website:", "systemTime.error.overlayTitle": "!!!Unable to sync - incorrect time", "systemTime.error.problemSolutionLink": "!!!daedaluswallet.io/faq", + "systemTime.error.timeOffsetIndeterminable": "!!!more than 15 seconds", "test.environment.developmentLabel": "!!!Development vx", "test.environment.stagingLabel": "!!!Staging vx", "test.environment.testnetLabel": "!!!Testnet vx", @@ -204,12 +206,12 @@ "wallet.file.import.dialog.passwordSwitchLabel": "!!!Password", "wallet.file.import.dialog.passwordSwitchPlaceholder": "!!!Activate to create password", "wallet.file.import.dialog.repeatPasswordLabel": "!!!Repeat password", + "wallet.file.import.dialog.spendingPasswordLabel": "!!!Wallet password", "wallet.file.import.dialog.submitLabel": "!!!Import wallet", "wallet.file.import.dialog.wallet.name.input.hint": "!!!e.g: Shopping Wallet", "wallet.file.import.dialog.wallet.name.input.label": "!!!Wallet name", "wallet.file.import.dialog.walletFileHint": "!!!Drop file here or click to choose", "wallet.file.import.dialog.walletFileLabel": "!!!Import file", - "wallet.file.import.dialog.walletPasswordLabel": "!!!Wallet password", "wallet.navigation.receive": "!!!Receive", "wallet.navigation.send": "!!!Send", "wallet.navigation.settings": "!!!Settings", diff --git a/source/renderer/app/routes-config.js b/source/renderer/app/routes-config.js index 3133fb0534..9242b4c1d4 100644 --- a/source/renderer/app/routes-config.js +++ b/source/renderer/app/routes-config.js @@ -3,6 +3,7 @@ export const ROUTES = { ROOT: '/', STAKING: '/staking', ADA_REDEMPTION: '/ada-redemption', + NETWORK_STATUS: '/network-status', PAPER_WALLET_CREATE_CERTIFICATE: '/paper-wallet/create-certificate', PROFILE: { LANGUAGE_SELECTION: '/profile/language-selection', diff --git a/source/renderer/app/stores/AppStore.js b/source/renderer/app/stores/AppStore.js index 2fd551d8e5..b62817074b 100644 --- a/source/renderer/app/stores/AppStore.js +++ b/source/renderer/app/stores/AppStore.js @@ -6,6 +6,7 @@ import LocalizableError from '../i18n/LocalizableError'; import { buildRoute } from '../utils/routing'; import { OPEN_ABOUT_DIALOG_CHANNEL } from '../../../common/ipc-api/open-about-dialog'; import { GO_TO_ADA_REDEMPTION_SCREEN_CHANNEL } from '../../../common/ipc-api/go-to-ada-redemption-screen'; +import { GO_TO_NETWORK_STATUS_SCREEN_CHANNEL } from '../../../common/ipc-api/go-to-network-status-screen'; import { GET_GPU_STATUS } from '../../../common/ipc-api'; import { ROUTES } from '../routes-config'; import environment from '../../../common/environment'; @@ -24,12 +25,14 @@ export default class AppStore extends Store { this.actions.app.getGpuStatus.listen(this._getGpuStatus); ipcRenderer.on(OPEN_ABOUT_DIALOG_CHANNEL, this._openAboutDialog); ipcRenderer.on(GO_TO_ADA_REDEMPTION_SCREEN_CHANNEL, this._goToAdaRedemptionScreen); + ipcRenderer.on(GO_TO_NETWORK_STATUS_SCREEN_CHANNEL, this._goToNetworkStatusScreen); ipcRenderer.on(GET_GPU_STATUS.SUCCESS, this._onGetGpuStatusSuccess); } teardown() { ipcRenderer.removeListener(OPEN_ABOUT_DIALOG_CHANNEL, this._openAboutDialog); ipcRenderer.removeListener(GO_TO_ADA_REDEMPTION_SCREEN_CHANNEL, this._goToAdaRedemptionScreen); + ipcRenderer.removeListener(GO_TO_NETWORK_STATUS_SCREEN_CHANNEL, this._goToNetworkStatusScreen); } @computed get currentRoute(): string { @@ -77,4 +80,13 @@ export default class AppStore extends Store { } }; + @computed get isNetworkStatusPage(): boolean { + return this.currentRoute === ROUTES.NETWORK_STATUS; + } + + @action _goToNetworkStatusScreen = () => { + const route = this.isNetworkStatusPage ? ROUTES.ROOT : ROUTES.NETWORK_STATUS; + this.actions.router.goToRoute.trigger({ route }); + }; + } diff --git a/source/renderer/app/stores/NetworkStatusStore.js b/source/renderer/app/stores/NetworkStatusStore.js index 1e349c1ebb..2c528fe733 100644 --- a/source/renderer/app/stores/NetworkStatusStore.js +++ b/source/renderer/app/stores/NetworkStatusStore.js @@ -3,235 +3,319 @@ import { observable, action, computed, runInAction } from 'mobx'; import moment from 'moment'; import Store from './lib/Store'; import Request from './lib/LocalizedRequest'; +import { + ALLOWED_TIME_DIFFERENCE, + MAX_ALLOWED_STALL_DURATION, + NETWORK_STATUS_REQUEST_TIMEOUT, + NETWORK_STATUS_POLL_INTERVAL, + SYSTEM_TIME_POLL_INTERVAL, +} from '../config/timingConfig'; +import { UNSYNCED_BLOCKS_ALLOWED } from '../config/numbersConfig'; import { Logger } from '../../../common/logging'; -import type { GetSyncProgressResponse, GetLocalTimeDifferenceResponse } from '../api/common'; -import environment from '../../../common/environment'; +import type { GetNetworkStatusResponse } from '../api/nodes/types'; +import type { NodeQueryParams } from '../api/nodes/requests/getNodeInfo'; // To avoid slow reconnecting on store reset, we cache the most important props let cachedState = null; -// Maximum number of out-of-sync blocks above which we consider to be out-of-sync -const OUT_OF_SYNC_BLOCKS_LIMIT = 6; -const SYNC_PROGRESS_INTERVAL = 2000; -const TIME_DIFF_POLL_INTERVAL = 30 * 60 * 1000; // 30 minutes -const ALLOWED_TIME_DIFFERENCE = 15 * 1000000; // 15 seconds -const ALLOWED_NETWORK_DIFFICULTY_STALL = 2 * 60 * 1000; // 2 minutes - -const STARTUP_STAGES = { +// DEFINE CONSTANTS ------------------------- +const NODE_STATUS = { CONNECTING: 0, SYNCING: 1, RUNNING: 2, }; +// END CONSTANTS ---------------------------- export default class NetworkStatusStore extends Store { + // Initialize store properties _startTime = Date.now(); - _startupStage = STARTUP_STAGES.CONNECTING; - _lastNetworkDifficultyChange = 0; - _syncProgressPollInterval: ?number = null; - _updateLocalTimeDifferencePollInterval: ?number = null; + _systemTime = Date.now(); + _nodeStatus = NODE_STATUS.CONNECTING; + _networkStatusPollingInterval: ?number = null; + _systemTimeChangeCheckPollingInterval: ?number = null; + + // Initialize store observables - @observable isConnected = false; + // Internal Node states + /* eslint-disable indent */ + @observable isNodeResponding = false; // Is 'true' as long we are receiving node Api responses + @observable isNodeSubscribed = false; // Is 'true' in case node is subscribed to the network + @observable isNodeSyncing = false; // Is 'true' in case we are receiving blocks and not stalling + @observable isNodeTimeCorrect = true; // Is 'true' in case local and global time are in sync + @observable isNodeInSync = false; // Is 'true' if node is syncing and local/network block height + // difference is within the allowed limit + /* eslint-enabme indent */ @observable hasBeenConnected = false; - @observable localDifficulty = 0; - @observable networkDifficulty = 0; - @observable localTimeDifference = 0; - @observable syncProgressRequest: Request = new Request( - // Use the sync progress for target API - this.api[environment.API].getSyncProgress + @observable syncProgress = null; + @observable initialLocalHeight = null; + @observable localBlockHeight = 0; + @observable networkBlockHeight = 0; + @observable mostRecentBlockTimestamp = 0; // milliseconds + @observable localTimeDifference: ?number = 0; // microseconds + @observable isSystemTimeChanged = false; // Tracks system time change event + @observable getNetworkStatusRequest: Request = new Request( + this.api.ada.getNetworkStatus ); - @observable localTimeDifferenceRequest: Request = new Request( - this.api.ada.getLocalTimeDifference + @observable forceCheckTimeDifferenceRequest: Request = new Request( + this.api.ada.getNetworkStatus ); - @observable _localDifficultyStartedWith = null; - - @action initialize() { - super.initialize(); - if (cachedState !== null) Object.assign(this, cachedState); - } + // DEFINE STORE METHODS setup() { this.registerReactions([ - this._updateSyncProgressWhenDisconnected, - this._updateLocalTimeDifferenceWhenConnected, + this._updateNetworkStatusWhenDisconnected, + this._updateLocalTimeDifferenceWhenSystemTimeChanged, ]); - // Setup polling intervals - this._syncProgressPollInterval = setInterval( - this._updateSyncProgress, SYNC_PROGRESS_INTERVAL + // Setup network status polling interval + this._networkStatusPollingInterval = setInterval( + this._updateNetworkStatus, NETWORK_STATUS_POLL_INTERVAL + ); + + // Setup system time change polling interval + this._systemTimeChangeCheckPollingInterval = setInterval( + this._updateSystemTime, SYSTEM_TIME_POLL_INTERVAL ); - if (environment.isAdaApi()) { - this._updateLocalTimeDifferencePollInterval = setInterval( - this._updateLocalTimeDifference, TIME_DIFF_POLL_INTERVAL - ); - } } teardown() { super.teardown(); // Teardown polling intervals - if (this._syncProgressPollInterval) { - clearInterval(this._syncProgressPollInterval); + if (this._networkStatusPollingInterval) { + clearInterval(this._networkStatusPollingInterval); } - if (this._updateLocalTimeDifferencePollInterval) { - clearInterval(this._updateLocalTimeDifferencePollInterval); + if (this._systemTimeChangeCheckPollingInterval) { + clearInterval(this._systemTimeChangeCheckPollingInterval); } + // Save current state into the cache cachedState = { - isConnected: this.isConnected, hasBeenConnected: this.hasBeenConnected, - localDifficulty: this.localDifficulty, - networkDifficulty: this.networkDifficulty, + localBlockHeight: this.localBlockHeight, + networkBlockHeight: this.networkBlockHeight, }; } - @computed get isConnecting(): boolean { - // until we start receiving network difficulty messages we are not connected to node - return !this.isConnected; - } + _updateNetworkStatusWhenDisconnected = async () => { + if (!this.isConnected) await this._updateNetworkStatus(); + }; + + _updateLocalTimeDifferenceWhenSystemTimeChanged = async () => { + if (this.isSystemTimeChanged) { + Logger.debug('System time change detected'); + await this._updateNetworkStatus({ force_ntp_check: true }); + } + }; - @computed get hasBlockSyncingStarted(): boolean { - return this.networkDifficulty >= 1; + _getStartupTimeDelta() { + return Date.now() - this._startTime; } - @computed get relativeSyncPercentage(): number { - if (this.networkDifficulty > 0 && this._localDifficultyStartedWith !== null) { - const relativeLocal = this.localDifficulty - this._localDifficultyStartedWith; - const relativeNetwork = this.networkDifficulty - this._localDifficultyStartedWith; - // In case node is in sync after first local difficulty messages - // local and network difficulty will be the same (0) - Logger.debug('Network difficulty: ' + this.networkDifficulty); - Logger.debug('Local difficulty: ' + this.localDifficulty); - Logger.debug('Relative local difficulty: ' + relativeLocal); - Logger.debug('Relative network difficulty: ' + relativeNetwork); - - if (relativeLocal >= relativeNetwork) return 100; - return relativeLocal / relativeNetwork * 100; - } - return 0; + // DEFINE ACTIONS + @action initialize() { + super.initialize(); + if (cachedState !== null) Object.assign(this, cachedState); } - @computed get relativeSyncBlocksDifference(): number { - if (this.networkDifficulty > 0 && this._localDifficultyStartedWith !== null) { - const relativeLocal = this.localDifficulty - this._localDifficultyStartedWith; - const relativeNetwork = this.networkDifficulty - this._localDifficultyStartedWith; - // In case node is in sync after first local difficulty messages - // local and network difficulty will be the same (0) - Logger.debug('Network difficulty: ' + this.networkDifficulty); - Logger.debug('Local difficulty: ' + this.localDifficulty); - Logger.debug('Relative local difficulty: ' + relativeLocal); - Logger.debug('Relative network difficulty: ' + relativeNetwork); - - if (relativeLocal >= relativeNetwork) return 0; - return relativeNetwork - relativeLocal; + @action _updateSystemTime = () => { + // In order to detect system time changes (e.g. user updates machine's date/time) + // we are checking current system time and comparing the difference to the last known value. + // If the difference is larger than the polling interval plus a safety margin of 100% + // we can be sure the user has update the time. + const systemTimeDifference = Math.abs(moment(Date.now()).diff(moment(this._systemTime))); + this.isSystemTimeChanged = Math.floor(systemTimeDifference / SYSTEM_TIME_POLL_INTERVAL) > 1; + this._systemTime = Date.now(); + }; + + @action _updateNetworkStatus = async (queryParams?: NodeQueryParams) => { + const isForcedTimeDifferenceCheck = !!queryParams; + + // Prevent network status requests in case there is an already executing + // forced time difference check unless we are trying to run another + // forced time difference check in which case we need to wait for it to finish + if (this.forceCheckTimeDifferenceRequest.isExecuting) { + if (isForcedTimeDifferenceCheck) { + await this.forceCheckTimeDifferenceRequest; + } else { + return; + } } - return 0; - } - @computed get syncPercentage(): number { - if (this.networkDifficulty > 0) { - if (this.localDifficulty >= this.networkDifficulty) return 100; - return this.localDifficulty / this.networkDifficulty * 100; + if (isForcedTimeDifferenceCheck) { + // Set most recent block timestamp into the future as a guard + // against system time changes - e.g. if system time was set into the past + this.mostRecentBlockTimestamp = Date.now() + NETWORK_STATUS_REQUEST_TIMEOUT; } - return 0; - } - @computed get isSystemTimeCorrect(): boolean { - if (!environment.isAdaApi()) return true; - // We assume that system time is correct by default - if (!this.localTimeDifferenceRequest.wasExecuted) return true; - // Compare time difference if we have a result - return this.localTimeDifference <= ALLOWED_TIME_DIFFERENCE; - } + // Record connection status before running network status call + const wasConnected = this.isConnected; - @computed get isSyncing(): boolean { - return !this.isConnecting && this.hasBlockSyncingStarted && !this.isSynced; - } + try { + let networkStatus: GetNetworkStatusResponse; + if (isForcedTimeDifferenceCheck) { + networkStatus = await this.forceCheckTimeDifferenceRequest.execute(queryParams).promise; + } else { + networkStatus = await this.getNetworkStatusRequest.execute().promise; + } - @computed get isSynced(): boolean { - return ( - !this.isConnecting && - this.hasBlockSyncingStarted && - this.relativeSyncBlocksDifference <= OUT_OF_SYNC_BLOCKS_LIMIT - ); - } + const { + subscriptionStatus, + syncProgress, + blockchainHeight, + localBlockchainHeight, + localTimeDifference, + } = networkStatus; - @action _updateSyncProgress = async () => { - try { - const difficulty = await this.syncProgressRequest.execute().promise; - runInAction('update difficulties', () => { - // We are connected, move on to syncing stage - if (this._startupStage === STARTUP_STAGES.CONNECTING) { - Logger.info( - `========== Connected after ${this._getStartupTimeDelta()} milliseconds ==========` - ); - this._startupStage = STARTUP_STAGES.SYNCING; + // We got response which means node is responding + runInAction('update isNodeResponding', () => { + this.isNodeResponding = true; + }); + + // Node is subscribed in case it is subscribed to at least one other node in the network + runInAction('update isNodeSubscribed', () => { + const nodeIPs = Object.values(subscriptionStatus || {}); + this.isNodeSubscribed = nodeIPs.includes('subscribed'); + }); + + // System time is correct if local time difference is below allowed threshold + runInAction('update localTimeDifference and isNodeTimeCorrect', () => { + this.localTimeDifference = localTimeDifference; + this.isNodeTimeCorrect = ( + this.localTimeDifference !== null && // If we receive 'null' it means NTP check failed + this.localTimeDifference <= ALLOWED_TIME_DIFFERENCE + ); + }); + + if (this._nodeStatus === NODE_STATUS.CONNECTING && this.isNodeSubscribed) { + // We are connected for the first time, move on to syncing stage + this._nodeStatus = NODE_STATUS.SYNCING; + Logger.info( + `========== Connected after ${this._getStartupTimeDelta()} milliseconds ==========` + ); + } + + // Update sync progress + runInAction('update syncProgress', () => { + this.syncProgress = syncProgress; + }); + + runInAction('update block heights', () => { + if (this.initialLocalHeight === null) { + // If initial local block height isn't set, mark the first + // result as the 'starting' height for the sync progress + this.initialLocalHeight = localBlockchainHeight; + Logger.debug('Initial local block height: ' + JSON.stringify(localBlockchainHeight)); } - // If we haven't set local difficulty before, mark the first - // result as 'start' difficulty for the sync progress - if (this._localDifficultyStartedWith === null) { - this._localDifficultyStartedWith = difficulty.localDifficulty; - Logger.debug('Initial difficulty: ' + JSON.stringify(difficulty)); + + // Update the local block height on each request + this.localBlockHeight = localBlockchainHeight; + Logger.debug('Local blockchain height: ' + localBlockchainHeight); + + // Update the network block height on each request + const hasStartedReceivingBlocks = blockchainHeight > 0; + const lastBlockchainHeight = this.networkBlockHeight; + this.networkBlockHeight = blockchainHeight; + if (hasStartedReceivingBlocks) { + Logger.debug('Network blockchain height: ' + blockchainHeight); + } + + // Check if the network's block height has ceased to change + const isBlockchainHeightIncreasing = ( + hasStartedReceivingBlocks && + this.networkBlockHeight > lastBlockchainHeight + ); + if ( + isBlockchainHeightIncreasing || // New block detected + this.mostRecentBlockTimestamp > Date.now() || // Guard against future timestamps + !this.isNodeTimeCorrect // Guard against incorrect system time + ) { + this.mostRecentBlockTimestamp = Date.now(); // Record latest block timestamp } - // Update the local difficulty on each request - this.localDifficulty = difficulty.localDifficulty; - Logger.debug('Local difficulty changed: ' + this.localDifficulty); - // Check if network difficulty is stalled (e.g. unchanged for more than 2 minutes) - // e.g. in case there is no Internet connection Api will send the last known value - if (this.networkDifficulty !== difficulty.networkDifficulty) { - if (!this.isConnected) this.isConnected = true; - this._lastNetworkDifficultyChange = Date.now(); - } else if (this.isConnected) { - const currentNetworkDifficultyStall = moment(Date.now()).diff( - moment(this._lastNetworkDifficultyChange) + const timeSinceLastBlock = moment(Date.now()).diff(moment(this.mostRecentBlockTimestamp)); + const isBlockchainHeightStalling = timeSinceLastBlock > MAX_ALLOWED_STALL_DURATION; + + // Node is syncing in case we are receiving blocks and they are not stalling + runInAction('update isNodeSyncing', () => { + this.isNodeSyncing = isBlockchainHeightIncreasing || !isBlockchainHeightStalling; + }); + + runInAction('update isNodeInSync', () => { + const remainingUnsyncedBlocks = this.networkBlockHeight - this.localBlockHeight; + this.isNodeInSync = ( + this.isNodeSyncing && + remainingUnsyncedBlocks <= UNSYNCED_BLOCKS_ALLOWED ); - if (currentNetworkDifficultyStall > ALLOWED_NETWORK_DIFFICULTY_STALL) { - this.isConnected = false; - if (!this.hasBeenConnected) this.hasBeenConnected = true; - } + }); + + if (hasStartedReceivingBlocks) { + const initialLocalHeight = this.initialLocalHeight || 0; + const blocksSyncedSinceStart = this.localBlockHeight - initialLocalHeight; + const totalUnsyncedBlocksAtStart = this.networkBlockHeight - initialLocalHeight; + Logger.debug('Total unsynced blocks at node start: ' + totalUnsyncedBlocksAtStart); + Logger.debug('Blocks synced since node start: ' + blocksSyncedSinceStart); } - // Update the network difficulty on each request - this.networkDifficulty = difficulty.networkDifficulty; }); - Logger.debug('Network difficulty changed: ' + this.networkDifficulty); - if (this._startupStage === STARTUP_STAGES.SYNCING && this.isSynced) { - Logger.info(`========== Synced after ${this._getStartupTimeDelta()} milliseconds ==========`); - this._startupStage = STARTUP_STAGES.RUNNING; + if (this._nodeStatus === NODE_STATUS.SYNCING && this.isNodeInSync) { + // We are synced for the first time, move on to running stage + this._nodeStatus = NODE_STATUS.RUNNING; this.actions.networkStatus.isSyncedAndReady.trigger(); + Logger.info(`========== Synced after ${this._getStartupTimeDelta()} milliseconds ==========`); + } + + if (wasConnected !== this.isConnected) { + if (!this.isConnected) { + if (!this.hasBeenConnected) { + runInAction('update hasBeenConnected', () => this.hasBeenConnected = true); + } + Logger.debug('Connection Lost. Reconnecting...'); + } else if (this.hasBeenConnected) { + Logger.debug('Connection Restored'); + } } } catch (error) { - // If the sync progress request fails, switch to disconnected state + // Node is not responding, switch to disconnected state runInAction('update connected status', () => { - if (this.isConnected) { - this.isConnected = false; - if (!this.hasBeenConnected) this.hasBeenConnected = true; + this.isNodeResponding = false; + this.isNodeSubscribed = false; + this.isNodeSyncing = false; + this.isNodeInSync = false; + if (wasConnected) { + if (!this.hasBeenConnected) { + runInAction('update hasBeenConnected', () => this.hasBeenConnected = true); + } + Logger.debug('Connection Lost. Reconnecting...'); } }); - Logger.debug('Connection Lost. Reconnecting...'); } }; - @action _updateLocalTimeDifference = async () => { - if (!this.isConnected) return; - try { - const response = await this.localTimeDifferenceRequest.execute().promise; - runInAction('update time difference', () => (this.localTimeDifference = response)); - } catch (error) { - runInAction('update time difference', () => (this.localTimeDifference = 0)); - } + forceCheckLocalTimeDifference = async () => { + await this._updateNetworkStatus({ force_ntp_check: true }); }; - _updateLocalTimeDifferenceWhenConnected = async () => { - if (this.isConnected) await this._updateLocalTimeDifference(); - }; + // DEFINE COMPUTED VALUES + @computed get isConnected(): boolean { + return this.isNodeResponding && this.isNodeSubscribed && this.isNodeSyncing; + } - _updateSyncProgressWhenDisconnected = async () => { - if (!this.isConnected) await this._updateSyncProgress(); - }; + @computed get isSystemTimeCorrect(): boolean { + return this.isNodeTimeCorrect; + } - _getStartupTimeDelta() { - return Date.now() - this._startTime; + @computed get isSynced(): boolean { + return this.isConnected && this.isNodeInSync && this.isNodeTimeCorrect; + } + + @computed get syncPercentage(): number { + const { networkBlockHeight, localBlockHeight } = this; + if (networkBlockHeight >= 1) { + if (localBlockHeight >= networkBlockHeight) { return 100; } + return localBlockHeight / networkBlockHeight * 100; + } + return 0; } + } diff --git a/source/renderer/app/stores/SidebarStore.js b/source/renderer/app/stores/SidebarStore.js index 3cfcc4f4c5..027c1cf8e0 100644 --- a/source/renderer/app/stores/SidebarStore.js +++ b/source/renderer/app/stores/SidebarStore.js @@ -5,7 +5,7 @@ import { ipcRenderer } from 'electron'; import Store from './lib/Store'; import environment from '../../../common/environment'; import { sidebarConfig } from '../config/sidebarConfig'; -import { syncStateTags } from '../domains/Wallet'; +import { WalletSyncStateTags } from '../domains/Wallet'; import { GO_TO_ADA_REDEMPTION_SCREEN_CHANNEL } from '../../../common/ipc-api/go-to-ada-redemption-screen'; import { formattedWalletAmount } from '../utils/formatters'; import type { SidebarWalletType } from '../types/sidebarTypes'; @@ -44,7 +44,7 @@ export default class SidebarStore extends Store { title: w.name, info: formattedWalletAmount(w.amount), isConnected: networkStatus.isConnected, - isRestoreActive: get(w, 'syncState.tag') === syncStateTags.RESTORING, + isRestoreActive: get(w, 'syncState.tag') === WalletSyncStateTags.RESTORING, restoreProgress: get(w, 'syncState.data.percentage.quantity', 0), })); } diff --git a/source/renderer/app/stores/TransactionsStore.js b/source/renderer/app/stores/TransactionsStore.js index 6e45a21e9b..04978ae750 100644 --- a/source/renderer/app/stores/TransactionsStore.js +++ b/source/renderer/app/stores/TransactionsStore.js @@ -4,7 +4,7 @@ import _ from 'lodash'; import Store from './lib/Store'; import CachedRequest from './lib/LocalizedCachedRequest'; import WalletTransaction from '../domains/WalletTransaction'; -import type { GetTransactionsResponse } from '../api/common'; +import type { GetTransactionsResponse } from '../api/transactions/types'; import environment from '../../../common/environment'; export type TransactionSearchOptionsStruct = { @@ -15,7 +15,7 @@ export type TransactionSearchOptionsStruct = { export default class TransactionsStore extends Store { - INITIAL_SEARCH_LIMIT = 1000; + INITIAL_SEARCH_LIMIT = null; // 'null' value stands for 'load all' SEARCH_LIMIT_INCREASE = 500; SEARCH_SKIP = 0; RECENT_TRANSACTIONS_LIMIT = 5; diff --git a/source/renderer/app/stores/WalletSettingsStore.js b/source/renderer/app/stores/WalletSettingsStore.js index 932f937806..055512f3a3 100644 --- a/source/renderer/app/stores/WalletSettingsStore.js +++ b/source/renderer/app/stores/WalletSettingsStore.js @@ -2,13 +2,13 @@ import { observable, action } from 'mobx'; import Store from './lib/Store'; import globalMessages from '../i18n/global-messages'; -import { assuranceModeOptions } from '../types/transactionAssuranceTypes'; +import { WalletAssuranceModeOptions } from '../domains/Wallet'; export default class WalletSettingsStore extends Store { WALLET_ASSURANCE_LEVEL_OPTIONS = [ - { value: assuranceModeOptions.NORMAL, label: globalMessages.assuranceLevelNormal }, - { value: assuranceModeOptions.STRICT, label: globalMessages.assuranceLevelStrict }, + { value: WalletAssuranceModeOptions.NORMAL, label: globalMessages.assuranceLevelNormal }, + { value: WalletAssuranceModeOptions.STRICT, label: globalMessages.assuranceLevelStrict }, ]; @observable walletFieldBeingEdited = null; diff --git a/source/renderer/app/stores/WalletStore.js b/source/renderer/app/stores/WalletStore.js index bbd2f60f8d..0c6e5a41f0 100644 --- a/source/renderer/app/stores/WalletStore.js +++ b/source/renderer/app/stores/WalletStore.js @@ -6,7 +6,6 @@ import Wallet from '../domains/Wallet'; import Request from './lib/LocalizedRequest'; import { buildRoute, matchRoute } from '../utils/routing'; import { ROUTES } from '../routes-config'; -import type { GetWalletRecoveryPhraseResponse } from '../api/common'; import environment from '../../../common/environment'; /** @@ -27,10 +26,10 @@ export default class WalletsStore extends Store { @observable isRestoreActive: boolean = false; @observable lastDiscardedAntivirusRestorationSlowdownNotificationWalletId: ?string = null; - _newWalletDetails: { name: string, mnemonic: string, password: ?string } = { + _newWalletDetails: { name: string, mnemonic: string, spendingPassword: ?string } = { name: '', mnemonic: '', - password: null, + spendingPassword: null, }; setup() { @@ -45,11 +44,11 @@ export default class WalletsStore extends Store { _create = async (params: { name: string, - password: ?string, + spendingPassword: ?string, }) => { Object.assign(this._newWalletDetails, params); try { - const recoveryPhrase: ?GetWalletRecoveryPhraseResponse = await ( + const recoveryPhrase: ?Array = await ( this.getWalletRecoveryPhraseRequest.execute().promise ); if (recoveryPhrase != null) { diff --git a/source/renderer/app/stores/ada/AdaRedemptionStore.js b/source/renderer/app/stores/ada/AdaRedemptionStore.js index 8b1345e29e..2e80a21ce1 100644 --- a/source/renderer/app/stores/ada/AdaRedemptionStore.js +++ b/source/renderer/app/stores/ada/AdaRedemptionStore.js @@ -4,9 +4,10 @@ import { ipcRenderer } from 'electron'; import { isString } from 'lodash'; import Store from '../lib/Store'; import Request from '../lib/LocalizedRequest'; +import WalletTransaction from '../../domains/WalletTransaction'; import { Logger } from '../../../../common/logging'; +import { encryptPassphrase } from '../../api/utils'; import { matchRoute } from '../../utils/routing'; -import WalletTransaction from '../../domains/WalletTransaction'; import { PARSE_REDEMPTION_CODE } from '../../../../common/ipc-api'; import { InvalidMnemonicError, @@ -15,11 +16,11 @@ import { } from '../../i18n/errors'; import { DECIMAL_PLACES_IN_ADA } from '../../config/numbersConfig'; import LocalizableError from '../../i18n/LocalizableError'; -import Wallet from '../../domains/Wallet'; import { ROUTES } from '../../routes-config'; -import type { RedeemPaperVendedAdaResponse } from '../../api/ada/index'; import type { RedemptionTypeChoices } from '../../types/redemptionTypes'; import { ADA_REDEMPTION_TYPES } from '../../types/redemptionTypes'; +import type { RedeemAdaParams } from '../../api/transactions/requests/redeemAda'; +import type { RedeemPaperVendedAdaParams } from '../../api/transactions/requests/redeemPaperVendedAda'; export default class AdaRedemptionStore extends Store { @@ -37,9 +38,9 @@ export default class AdaRedemptionStore extends Store { @observable error: ?LocalizableError = null; @observable amountRedeemed: number = 0; @observable showAdaRedemptionSuccessMessage: boolean = false; - @observable redeemAdaRequest: Request = new Request(this.api.ada.redeemAda); + @observable redeemAdaRequest: Request = new Request(this.api.ada.redeemAda); // eslint-disable-next-line - @observable redeemPaperVendedAdaRequest: Request = new Request(this.api.ada.redeemPaperVendedAda); + @observable redeemPaperVendedAdaRequest: Request = new Request(this.api.ada.redeemPaperVendedAda); @observable isRedemptionDisclaimerAccepted = false; setup() { @@ -79,9 +80,9 @@ export default class AdaRedemptionStore extends Store { this.api.ada.isValidRedemptionMnemonic(mnemonic) ); - isValidPaperVendRedemptionKey = ( - mnemonic: string - ) => this.api.ada.isValidPaperVendRedemptionKey(mnemonic); + isValidPaperVendRedemptionKey = (mnemonic: string) => ( + this.api.ada.isValidPaperVendRedemptionKey(mnemonic) + ); @computed get isAdaRedemptionPage(): boolean { return matchRoute(ROUTES.ADA_REDEMPTION, this.stores.app.currentRoute); @@ -204,60 +205,62 @@ export default class AdaRedemptionStore extends Store { this.decryptionKey = null; }); - _redeemAda = async ({ walletId, walletPassword } : { + _redeemAda = async ({ walletId, spendingPassword } : { walletId: string, - walletPassword: ?string, + spendingPassword: ?string, }) => { - runInAction(() => { - this.walletId = walletId; - }); - const accountId = await this.stores.ada.addresses.getAccountIdByWalletId(walletId); - if (!accountId) throw new Error('Active account required before redeeming Ada.'); + runInAction(() => this.walletId = walletId); - this.redeemAdaRequest.execute({ - redemptionCode: this.redemptionCode, - accountId, - walletPassword - }) - .then(action((transaction: WalletTransaction) => { - this._reset(); - this.actions.ada.adaRedemption.adaSuccessfullyRedeemed.trigger({ - walletId, - amount: transaction.amount.toFormat(DECIMAL_PLACES_IN_ADA), - }); - })) - .catch(action((error) => { - this.error = error; - })); + const accountIndex = await this.stores.ada.addresses.getAccountIndexByWalletId(walletId); + if (!accountIndex) throw new Error('Active account required before redeeming Ada.'); + + try { + const transaction: WalletTransaction = await this.redeemAdaRequest.execute({ + accountIndex, + redemptionCode: this.redemptionCode, + spendingPassword: spendingPassword && encryptPassphrase(spendingPassword) + }); + this._reset(); + this.actions.ada.adaRedemption.adaSuccessfullyRedeemed.trigger({ + walletId, + amount: transaction.amount.toFormat(DECIMAL_PLACES_IN_ADA), + }); + } catch (error) { + runInAction(() => this.error = error); + } }; - _redeemPaperVendedAda = async ({ walletId, shieldedRedemptionKey, walletPassword } : { + _redeemPaperVendedAda = async ({ walletId, shieldedRedemptionKey, spendingPassword } : { walletId: string, shieldedRedemptionKey: string, - walletPassword: ?string, + spendingPassword: ?string, }) => { - this.walletId = walletId; - const accountId = await this.stores.ada.addresses.getAccountIdByWalletId(walletId); - if (!accountId) throw new Error('Active account required before redeeming Ada.'); + runInAction(() => this.walletId = walletId); + + const accountIndex = await this.stores.ada.addresses.getAccountIndexByWalletId(walletId); + if (!accountIndex) throw new Error('Active account required before redeeming Ada.'); try { - const transaction = await this.redeemPaperVendedAdaRequest.execute({ - shieldedRedemptionKey, - mnemonics: this.passPhrase, - accountId, - walletPassword, + const transaction: WalletTransaction = await this.redeemPaperVendedAdaRequest.execute({ + mnemonics: this.passPhrase && this.passPhrase.split(' '), + accountIndex, + redemptionCode: shieldedRedemptionKey, + spendingPassword: spendingPassword && encryptPassphrase(spendingPassword) }); this._reset(); this.actions.ada.adaRedemption.adaSuccessfullyRedeemed.trigger({ walletId, - amount: transaction.amount.toFormat(DECIMAL_PLACES_IN_ADA), + amount: transaction.amount.toFormat(DECIMAL_PLACES_IN_ADA) }); } catch (error) { runInAction(() => this.error = error); } }; - _onAdaSuccessfullyRedeemed = action(({ walletId, amount }) => { + _onAdaSuccessfullyRedeemed = action(({ walletId, amount } : { + walletId: string, + amount: number, + }) => { Logger.debug('ADA successfully redeemed for wallet: ' + walletId); this.stores.ada.wallets.goToWalletRoute(walletId); this.amountRedeemed = amount; diff --git a/source/renderer/app/stores/ada/AdaTransactionsStore.js b/source/renderer/app/stores/ada/AdaTransactionsStore.js index 6d77ad3c48..9461335991 100644 --- a/source/renderer/app/stores/ada/AdaTransactionsStore.js +++ b/source/renderer/app/stores/ada/AdaTransactionsStore.js @@ -6,6 +6,12 @@ import { isValidAmountInLovelaces } from '../../utils/validations'; import TransactionsStore from '../TransactionsStore'; import { transactionTypes } from '../../domains/WalletTransaction'; +type TransactionFeeRequest = { + walletId: string, + address: string, + amount: number, +}; + export default class AdaTransactionsStore extends TransactionsStore { @computed get unconfirmedAmount(): UnconfirmedAmount { @@ -38,10 +44,19 @@ export default class AdaTransactionsStore extends TransactionsStore { return unconfirmedAmount; } - calculateTransactionFee = async (walletId: string, receiver: string, amount: string) => { - const accountId = await this.stores.ada.addresses.getAccountIdByWalletId(walletId); - if (!accountId) throw new Error('Active account required before calculating transaction fees.'); - return this.api.ada.calculateTransactionFee({ sender: accountId, receiver, amount }); + calculateTransactionFee = async (transactionFeeRequest: TransactionFeeRequest) => { + const { walletId } = transactionFeeRequest; + const accountIndex = await this.stores.ada.addresses.getAccountIndexByWalletId(walletId); + + if (!accountIndex) { + throw new Error('Active account required before calculating transaction fees.'); + } + return this.api.ada.calculateTransactionFee( + { + ...transactionFeeRequest, + accountIndex, + } + ); }; validateAmount = (amountInLovelaces: string): Promise => ( diff --git a/source/renderer/app/stores/ada/AdaWalletSettingsStore.js b/source/renderer/app/stores/ada/AdaWalletSettingsStore.js index 430013f7f8..9205992799 100644 --- a/source/renderer/app/stores/ada/AdaWalletSettingsStore.js +++ b/source/renderer/app/stores/ada/AdaWalletSettingsStore.js @@ -2,17 +2,16 @@ import { observable, action } from 'mobx'; import { findIndex, merge } from 'lodash'; import WalletSettingsStore from '../WalletSettingsStore'; +import Wallet from '../../domains/Wallet'; import Request from '../lib/LocalizedRequest'; import type { WalletExportToFileParams } from '../../actions/ada/wallet-settings-actions'; -import type { ExportWalletToFileResponse } from '../../api/ada/index'; -import type { UpdateWalletPasswordResponse, UpdateWalletResponse } from '../../api/common'; export default class EtcWalletSettingsStore extends WalletSettingsStore { /* eslint-disable max-len */ - @observable updateWalletRequest: Request = new Request(this.api.ada.updateWallet); - @observable updateWalletPasswordRequest: Request = new Request(this.api.ada.updateWalletPassword); - @observable exportWalletToFileRequest: Request = new Request(this.api.ada.exportWalletToFile); + @observable updateWalletRequest: Request = new Request(this.api.ada.updateWallet); + @observable updateWalletPasswordRequest: Request = new Request(this.api.ada.updateWalletPassword); + @observable exportWalletToFileRequest: Request> = new Request(this.api.ada.exportWalletToFile); /* eslint-enable max-len */ setup() { @@ -37,11 +36,19 @@ export default class EtcWalletSettingsStore extends WalletSettingsStore { @action _updateWalletField = async ({ field, value }: { field: string, value: string }) => { const activeWallet = this.stores.ada.wallets.active; if (!activeWallet) return; + const { id: walletId, name, assurance } = activeWallet; const walletData = { walletId, name, assurance }; walletData[field] = value; - const wallet = await this.updateWalletRequest.execute(walletData).promise; + + const wallet = await this.updateWalletRequest.execute({ + walletId: walletData.walletId, + name: walletData.name, + assuranceLevel: walletData.assurance + }).promise; + if (!wallet) return; + await this.stores.ada.wallets.walletsRequest.patch(result => { const walletIndex = findIndex(result, { id: walletId }); // TODO: revert to this original update after full transition to V1 Api diff --git a/source/renderer/app/stores/ada/AdaWalletsStore.js b/source/renderer/app/stores/ada/AdaWalletsStore.js index 1e16d650c8..12b67671f9 100644 --- a/source/renderer/app/stores/ada/AdaWalletsStore.js +++ b/source/renderer/app/stores/ada/AdaWalletsStore.js @@ -3,6 +3,7 @@ import { observable, action, computed, runInAction } from 'mobx'; import { get, chunk, find } from 'lodash'; import WalletStore from '../WalletStore'; import Wallet from '../../domains/Wallet'; +import WalletTransaction from '../../domains/WalletTransaction'; import { MAX_ADA_WALLETS_COUNT } from '../../config/numbersConfig'; import { matchRoute, buildRoute } from '../../utils/routing'; import { i18nContext } from '../../utils/i18nContext'; @@ -12,33 +13,22 @@ import { mnemonicToSeedHex } from '../../utils/crypto'; import { downloadPaperWalletCertificate } from '../../utils/paperWalletPdfGenerator'; import type { walletExportTypeChoices } from '../../types/walletExportTypes'; import type { WalletImportFromFileParams } from '../../actions/ada/wallets-actions'; -import type { ImportWalletFromFileResponse } from '../../api/ada/index'; -import type { - CreateTransactionResponse, CreateWalletResponse, DeleteWalletResponse, - GetWalletsResponse, RestoreWalletResponse, - GetWalletRecoveryPhraseResponse, -} from '../../api/common'; -import type { - GetWalletCertificateAdditionalMnemonicsResponse, - GetWalletCertificateRecoveryPhraseResponse, - GetWalletRecoveryPhraseFromCertificateResponse, -} from '../../api/ada/types'; export default class AdaWalletsStore extends WalletStore { // REQUESTS /* eslint-disable max-len */ - @observable walletsRequest: Request = new Request(this.api.ada.getWallets); - @observable importFromFileRequest: Request = new Request(this.api.ada.importWalletFromFile); - @observable createWalletRequest: Request = new Request(this.api.ada.createWallet); + @observable walletsRequest: Request> = new Request(this.api.ada.getWallets); + @observable importFromFileRequest: Request = new Request(this.api.ada.importWalletFromFile); + @observable createWalletRequest: Request = new Request(this.api.ada.createWallet); @observable getWalletAddressesRequest: Request = new Request(this.api.ada.getAddresses); - @observable deleteWalletRequest: Request = new Request(this.api.ada.deleteWallet); - @observable sendMoneyRequest: Request = new Request(this.api.ada.createTransaction); - @observable getWalletRecoveryPhraseRequest: Request = new Request(this.api.ada.getWalletRecoveryPhrase); - @observable getWalletCertificateAdditionalMnemonicsRequest: Request = new Request(this.api.ada.getWalletCertificateAdditionalMnemonics); - @observable getWalletCertificateRecoveryPhraseRequest: Request = new Request(this.api.ada.getWalletCertificateRecoveryPhrase); - @observable getWalletRecoveryPhraseFromCertificateRequest: Request = new Request(this.api.ada.getWalletRecoveryPhraseFromCertificate); - @observable restoreRequest: Request = new Request(this.api.ada.restoreWallet); + @observable deleteWalletRequest: Request = new Request(this.api.ada.deleteWallet); + @observable sendMoneyRequest: Request = new Request(this.api.ada.createTransaction); + @observable getWalletRecoveryPhraseRequest: Request> = new Request(this.api.ada.getWalletRecoveryPhrase); + @observable getWalletCertificateAdditionalMnemonicsRequest: Request> = new Request(this.api.ada.getWalletCertificateAdditionalMnemonics); + @observable getWalletCertificateRecoveryPhraseRequest: Request> = new Request(this.api.ada.getWalletCertificateRecoveryPhrase); + @observable getWalletRecoveryPhraseFromCertificateRequest: Request> = new Request(this.api.ada.getWalletRecoveryPhraseFromCertificate); + @observable restoreRequest: Request = new Request(this.api.ada.restoreWallet); /* eslint-enable max-len */ @observable walletExportType: walletExportTypeChoices = 'paperWallet'; @@ -73,18 +63,21 @@ export default class AdaWalletsStore extends WalletStore { walletBackup.finishWalletBackup.listen(this._finishCreation); } - _sendMoney = async (transactionDetails: { + _sendMoney = async ({ receiver, amount, password }: { receiver: string, amount: string, password: ?string, }) => { const wallet = this.active; if (!wallet) throw new Error('Active wallet required before sending.'); - const accountId = await this.stores.ada.addresses.getAccountIdByWalletId(wallet.id); - if (!accountId) throw new Error('Active account required before sending.'); + const accountIndex = await this.stores.ada.addresses.getAccountIndexByWalletId(wallet.id); + await this.sendMoneyRequest.execute({ - ...transactionDetails, - sender: accountId, + address: receiver, + amount: parseInt(amount, 10), + spendingPassword: password, + accountIndex, + walletId: wallet.id, }); this.refreshWalletsData(); this.actions.dialogs.closeActiveDialog.trigger(); @@ -197,7 +190,7 @@ export default class AdaWalletsStore extends WalletStore { const spendingPassword = mnemonicToSeedHex(certificatePassword.join(' ')); // Unscramble 18-word wallet certificate mnemonic to 12-word mnemonic - const unscrambledRecoveryPhrase: GetWalletRecoveryPhraseFromCertificateResponse = await ( + const unscrambledRecoveryPhrase: Array = await ( this.getWalletRecoveryPhraseFromCertificateRequest.execute({ passphrase: spendingPassword, scrambledInput: scrambledInput.join(' '), @@ -217,9 +210,9 @@ export default class AdaWalletsStore extends WalletStore { }; @action _importWalletFromFile = async (params: WalletImportFromFileParams) => { - const { filePath, walletName, walletPassword } = params; + const { filePath, walletName, spendingPassword } = params; const importedWallet = await this.importFromFileRequest.execute({ - filePath, walletName, walletPassword, + filePath, walletName, spendingPassword, }).promise; if (!importedWallet) throw new Error('Imported wallet was not received correctly'); await this._patchWalletRequestWithNewWallet(importedWallet); @@ -277,12 +270,12 @@ export default class AdaWalletsStore extends WalletStore { this._updateCertificateCreationState(true); // Generate wallet recovery phrase - const recoveryPhrase: GetWalletRecoveryPhraseResponse = await ( + const recoveryPhrase: Array = await ( this.getWalletRecoveryPhraseRequest.execute().promise ); // Generate 9-words (additional) mnemonic - const additionalMnemonicWords: GetWalletCertificateAdditionalMnemonicsResponse = await ( + const additionalMnemonicWords: Array = await ( this.getWalletCertificateAdditionalMnemonicsRequest.execute().promise ); this.additionalMnemonicWords = additionalMnemonicWords.join(' '); @@ -292,7 +285,7 @@ export default class AdaWalletsStore extends WalletStore { this.walletCertificatePassword = spendingPassword; // Generate paper wallet scrambled mnemonic - const walletCertificateRecoveryPhrase: GetWalletCertificateRecoveryPhraseResponse = await ( + const walletCertificateRecoveryPhrase: Array = await ( this.getWalletCertificateRecoveryPhraseRequest.execute({ passphrase: spendingPassword, input: recoveryPhrase.join(' '), diff --git a/source/renderer/app/stores/ada/AddressesStore.js b/source/renderer/app/stores/ada/AddressesStore.js index 8a136baf01..d1047d358e 100644 --- a/source/renderer/app/stores/ada/AddressesStore.js +++ b/source/renderer/app/stores/ada/AddressesStore.js @@ -1,16 +1,15 @@ // @flow -import { observable, computed, action, runInAction } from 'mobx'; import _ from 'lodash'; +import { observable, computed, action, runInAction } from 'mobx'; import Store from '../lib/Store'; import CachedRequest from '../lib/LocalizedCachedRequest'; import Request from '../lib/LocalizedRequest'; -import WalletAddress from '../../domains/WalletAddress'; +import type { Address, Addresses, GetAddressesResponse } from '../../api/addresses/types'; import LocalizableError from '../../i18n/LocalizableError'; -import type { GetAddressesResponse, CreateAddressResponse } from '../../api/ada/index'; export default class AddressesStore extends Store { - @observable lastGeneratedAddress: ?WalletAddress = null; + @observable lastGeneratedAddress: ?Address = null; @observable addressesRequests: Array<{ walletId: string, allRequest: CachedRequest @@ -19,7 +18,7 @@ export default class AddressesStore extends Store { // REQUESTS /* eslint-disable max-len */ - @observable createAddressRequest: Request = new Request(this.api.ada.createAddress); + @observable createAddressRequest: Request
= new Request(this.api.ada.createAddress); /* eslint-disable max-len */ setup() { @@ -28,13 +27,15 @@ export default class AddressesStore extends Store { actions.resetErrors.listen(this._resetErrors); } - _createAddress = async (params: { walletId: string, password: ?string }) => { + _createAddress = async (params: { walletId: string, spendingPassword?: string }) => { try { - const { walletId, password } = params; - const accountId = await this.getAccountIdByWalletId(walletId); - const address: ?CreateAddressResponse = await this.createAddressRequest.execute({ - accountId, password + const { walletId, spendingPassword } = params; + const accountIndex = await this.getAccountIndexByWalletId(walletId); + + const address: ?Address = await this.createAddressRequest.execute({ + accountIndex, spendingPassword, walletId }).promise; + if (address != null) { this._refreshAddresses(); runInAction('set last generated address and reset error', () => { @@ -47,7 +48,7 @@ export default class AddressesStore extends Store { } }; - @computed get all(): Array { + @computed get all(): Addresses { const wallet = this.stores.ada.wallets.active; if (!wallet) return []; const result = this._getAddressesAllRequest(wallet.id).result; @@ -61,7 +62,7 @@ export default class AddressesStore extends Store { return result ? result.addresses.length > 0 : false; } - @computed get active(): ?WalletAddress { + @computed get active(): ?Address { if (this.lastGeneratedAddress) return this.lastGeneratedAddress; const wallet = this.stores.ada.wallets.active; if (!wallet) return; @@ -91,9 +92,9 @@ export default class AddressesStore extends Store { this.error = null; }; - getAccountIdByWalletId = async (walletId: string): Promise => { - const result = await this._getAddressesAllRequest(walletId); - return result ? result.accountId : null; + getAccountIndexByWalletId = async (walletId: string): Promise => { + const result = await this.api.ada.getAddresses({ walletId }); + return result ? result.accountIndex : null; }; getAddressesByWalletId = async (walletId: string): Promise> => { diff --git a/source/renderer/app/stores/ada/NodeUpdateStore.js b/source/renderer/app/stores/ada/NodeUpdateStore.js index 9296c42255..dd17991e4f 100644 --- a/source/renderer/app/stores/ada/NodeUpdateStore.js +++ b/source/renderer/app/stores/ada/NodeUpdateStore.js @@ -2,9 +2,7 @@ import { observable, action, runInAction } from 'mobx'; import Store from '../lib/Store'; import Request from '../lib/LocalizedRequest'; -import type { - NextUpdateResponse, PostponeUpdateResponse, ApplyUpdateResponse -} from '../../api/ada/index'; +import type { NodeSoftware } from '../../api/nodes/types'; import { NODE_UPDATE_POLL_INTERVAL } from '../../config/timingConfig'; export default class NodeUpdateStore extends Store { @@ -17,9 +15,9 @@ export default class NodeUpdateStore extends Store { // REQUESTS /* eslint-disable max-len */ - @observable nextUpdateRequest: Request = new Request(this.api.ada.nextUpdate); - @observable postponeUpdateRequest: Request = new Request(this.api.ada.postponeUpdate); - @observable applyUpdateRequest: Request = new Request(this.api.ada.applyUpdate); + @observable nextUpdateRequest: Request = new Request(this.api.ada.nextUpdate); + @observable postponeUpdateRequest: Request> = new Request(this.api.ada.postponeUpdate); + @observable applyUpdateRequest: Request> = new Request(this.api.ada.applyUpdate); /* eslint-disable max-len */ setup() { diff --git a/source/renderer/app/types/transactionAssuranceTypes.js b/source/renderer/app/types/transactionAssuranceTypes.js deleted file mode 100644 index 71c0e310d3..0000000000 --- a/source/renderer/app/types/transactionAssuranceTypes.js +++ /dev/null @@ -1,27 +0,0 @@ -// @flow -export type AssuranceModeOption = 'CWANormal' | 'CWAStrict'; -export type AssuranceMode = { low: number, medium: number }; -export type AssuranceLevel = 'low' | 'medium' | 'high'; - -export const assuranceModeOptions: { - NORMAL: AssuranceModeOption, STRICT: AssuranceModeOption, -} = { - NORMAL: 'CWANormal', STRICT: 'CWAStrict', -}; - -export const assuranceModes: { NORMAL: AssuranceMode, STRICT: AssuranceMode } = { - NORMAL: { - low: 3, - medium: 9, - }, - STRICT: { - low: 5, - medium: 15, - } -}; - -export const assuranceLevels: { - LOW: AssuranceLevel, MEDIUM: AssuranceLevel, HIGH: AssuranceLevel, -} = { - LOW: 'low', MEDIUM: 'medium', HIGH: 'high', -}; diff --git a/source/renderer/app/utils/formatters.js b/source/renderer/app/utils/formatters.js index 1fd851dd82..17ef1d373a 100644 --- a/source/renderer/app/utils/formatters.js +++ b/source/renderer/app/utils/formatters.js @@ -1,6 +1,6 @@ // @flow import BigNumber from 'bignumber.js'; -import { DECIMAL_PLACES_IN_ADA } from '../config/numbersConfig'; +import { DECIMAL_PLACES_IN_ADA, LOVELACES_PER_ADA } from '../config/numbersConfig'; export const formattedWalletAmount = ( amount: BigNumber, @@ -26,3 +26,7 @@ export const formattedAmountToNaturalUnits = (amount: string): string => { export const formattedAmountWithoutTrailingZeros = (amount: string): string => ( amount.replace(/0+$/, '').replace(/\.$/, '') ); + +export const formattedAmountToLovelace = (amount: string): number => ( + parseInt(formattedAmountToBigNumber(amount).times(LOVELACES_PER_ADA), 10) +); diff --git a/source/common/redemption-key-validation.js b/source/renderer/app/utils/redemption-key-validation.js similarity index 90% rename from source/common/redemption-key-validation.js rename to source/renderer/app/utils/redemption-key-validation.js index b127ae2842..e1c9067a22 100644 --- a/source/common/redemption-key-validation.js +++ b/source/renderer/app/utils/redemption-key-validation.js @@ -1,12 +1,13 @@ +// @flow import bs58 from 'bs58'; // Convert base64url into base64 -function toRfc4648(str) { +function toRfc4648(str: string) { return str.replace(new RegExp('_', 'g'), '/').replace(new RegExp('-', 'g'), '+'); } // Checks is input string valid base64 or base64url -function isValidBase64Url(code) { +function isValidBase64Url(code: string) { try { // Note that atob is defined in Electron render process where its being used // This won't work in Node @@ -23,7 +24,7 @@ function isValidBase64Url(code) { // Implements: "Valid redemption key should be base64 and base64url decodable, // it should end with '=' and it should be 44 chars long." -export const isValidRedemptionKey = (code) => ( +export const isValidRedemptionKey = (code: string) => ( // Return true if: // * its base64 or base64url decodable - ada vending state mess // * ends with '=' - base64 padds string with optional '=' or two '=' signs. @@ -39,7 +40,7 @@ export const isValidRedemptionKey = (code) => ( ); // Implements: "Valid paper vend redemption key should be base58 decodable 32 byte stream." -export const isValidPaperVendRedemptionKey = (code) => { +export const isValidPaperVendRedemptionKey = (code: string) => { try { return bs58.decode(code).length === 32; } catch (err) { diff --git a/storybook/stories/WalletScreens.stories.js b/storybook/stories/WalletScreens.stories.js index 315bc54da3..b1c5f8678d 100644 --- a/storybook/stories/WalletScreens.stories.js +++ b/storybook/stories/WalletScreens.stories.js @@ -23,7 +23,7 @@ import WalletSendForm from '../../source/renderer/app/components/wallet/WalletSe import WalletReceive from '../../source/renderer/app/components/wallet/WalletReceive'; import WalletTransactionsList from '../../source/renderer/app/components/wallet/transactions/WalletTransactionsList'; import WalletSettings from '../../source/renderer/app/components/wallet/WalletSettings'; -import { assuranceModeOptions } from '../../source/renderer/app/types/transactionAssuranceTypes'; +import { WalletAssuranceModeOptions } from '../../source/renderer/app/domains/Wallet'; import ChangeWalletPasswordDialog from '../../source/renderer/app/components/wallet/settings/ChangeWalletPasswordDialog'; import DeleteWalletConfirmationDialog from '../../source/renderer/app/components/wallet/settings/DeleteWalletConfirmationDialog'; import ExportWalletToFileDialog from '../../source/renderer/app/components/wallet/settings/ExportWalletToFileDialog'; @@ -144,7 +144,7 @@ storiesOf('WalletScreens', module) activeField={null} assuranceLevels={[ { - value: assuranceModeOptions.NORMAL, + value: WalletAssuranceModeOptions.NORMAL, label: { id: 'global.assuranceLevel.normal', defaultMessage: '!!!Normal', @@ -152,7 +152,7 @@ storiesOf('WalletScreens', module) } }, { - value: assuranceModeOptions.STRICT, + value: WalletAssuranceModeOptions.STRICT, label: { id: 'global.assuranceLevel.strict', defaultMessage: '!!!Strict', @@ -181,7 +181,7 @@ storiesOf('WalletScreens', module) onStartEditing={() => {}} onStopEditing={() => {}} openDialogAction={() => {}} - walletAssurance={assuranceModeOptions.NORMAL} + walletAssurance={WalletAssuranceModeOptions.NORMAL} walletName={text('Wallet Name', 'Wallet Name')} walletPasswordUpdateDate={moment().subtract(1, 'month').toDate()} changeWalletPasswordDialog={ diff --git a/storybook/stories/support/StoryProvider.js b/storybook/stories/support/StoryProvider.js index 45b14b554f..f706ec5cb2 100644 --- a/storybook/stories/support/StoryProvider.js +++ b/storybook/stories/support/StoryProvider.js @@ -6,7 +6,7 @@ import { observable, computed, runInAction } from 'mobx'; import BigNumber from 'bignumber.js'; import moment from 'moment'; import actions from '../../../source/renderer/app/actions'; -import { assuranceModeOptions } from '../../../source/renderer/app/types/transactionAssuranceTypes.js'; +import { WalletAssuranceModeOptions } from '../../../source/renderer/app/domains/Wallet'; type Props = { children: Node, @@ -17,7 +17,7 @@ const WALLETS = [ id: '0', name: 'No Password', amount: new BigNumber(66.998), - assurance: assuranceModeOptions.NORMAL, + assurance: WalletAssuranceModeOptions.NORMAL, hasPassword: false, passwordUpdateDate: new Date() }, @@ -25,7 +25,7 @@ const WALLETS = [ id: '1', name: 'With Password', amount: new BigNumber(0), - assurance: assuranceModeOptions.NORMAL, + assurance: WalletAssuranceModeOptions.NORMAL, hasPassword: true, passwordUpdateDate: moment().subtract(1, 'month').toDate() } diff --git a/yarn.lock b/yarn.lock index d69a8f2214..ceda696499 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8,13 +8,13 @@ "@babel/code-frame@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz#2a02643368de80916162be70865c97774f3adbd9" + resolved "http://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz#2a02643368de80916162be70865c97774f3adbd9" dependencies: "@babel/highlight" "7.0.0-beta.44" "@babel/generator@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42" + resolved "http://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42" dependencies: "@babel/types" "7.0.0-beta.44" jsesc "^2.5.1" @@ -24,7 +24,7 @@ "@babel/helper-function-name@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz#e18552aaae2231100a6e485e03854bc3532d44dd" + resolved "http://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz#e18552aaae2231100a6e485e03854bc3532d44dd" dependencies: "@babel/helper-get-function-arity" "7.0.0-beta.44" "@babel/template" "7.0.0-beta.44" @@ -32,19 +32,19 @@ "@babel/helper-get-function-arity@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz#d03ca6dd2b9f7b0b1e6b32c56c72836140db3a15" + resolved "http://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz#d03ca6dd2b9f7b0b1e6b32c56c72836140db3a15" dependencies: "@babel/types" "7.0.0-beta.44" "@babel/helper-split-export-declaration@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz#c0b351735e0fbcb3822c8ad8db4e583b05ebd9dc" + resolved "http://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz#c0b351735e0fbcb3822c8ad8db4e583b05ebd9dc" dependencies: "@babel/types" "7.0.0-beta.44" "@babel/highlight@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.44.tgz#18c94ce543916a80553edcdcf681890b200747d5" + resolved "http://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.44.tgz#18c94ce543916a80553edcdcf681890b200747d5" dependencies: chalk "^2.0.0" esutils "^2.0.2" @@ -56,7 +56,7 @@ "@babel/template@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f" + resolved "http://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f" dependencies: "@babel/code-frame" "7.0.0-beta.44" "@babel/types" "7.0.0-beta.44" @@ -65,7 +65,7 @@ "@babel/traverse@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966" + resolved "http://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966" dependencies: "@babel/code-frame" "7.0.0-beta.44" "@babel/generator" "7.0.0-beta.44" @@ -80,7 +80,7 @@ "@babel/types@7.0.0-beta.44": version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757" + resolved "http://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757" dependencies: esutils "^2.0.2" lodash "^4.2.0" @@ -313,8 +313,8 @@ react-treebeard "^2.1.0" "@types/node@^7.0.18": - version "7.0.69" - resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.69.tgz#709629340708ec0e1845559bf5c0c88d26d31dca" + version "7.0.70" + resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.70.tgz#688aaeb6e6d374ed016c4dc2c46de695859d6887" "@types/webpack-env@^1.13.2": version "1.13.6" @@ -1131,7 +1131,7 @@ babel-helper-hoist-variables@^6.24.1: babel-helper-is-nodes-equiv@^0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz#34e9b300b1479ddd98ec77ea0bbe9342dfe39684" + resolved "http://registry.npmjs.org/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz#34e9b300b1479ddd98ec77ea0bbe9342dfe39684" babel-helper-is-react-class@^1.0.0: version "1.0.0" @@ -1431,55 +1431,55 @@ babel-plugin-react-intl@2.3.1: babel-plugin-syntax-async-functions@^6.8.0: version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + resolved "http://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" babel-plugin-syntax-async-generators@^6.5.0: version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a" + resolved "http://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a" babel-plugin-syntax-class-constructor-call@^6.18.0: version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz#9cb9d39fe43c8600bec8146456ddcbd4e1a76416" + resolved "http://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz#9cb9d39fe43c8600bec8146456ddcbd4e1a76416" babel-plugin-syntax-class-properties@^6.8.0: version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" + resolved "http://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" babel-plugin-syntax-decorators@^6.1.18, babel-plugin-syntax-decorators@^6.13.0: version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b" + resolved "http://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b" babel-plugin-syntax-do-expressions@^6.8.0: version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz#5747756139aa26d390d09410b03744ba07e4796d" + resolved "http://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz#5747756139aa26d390d09410b03744ba07e4796d" babel-plugin-syntax-dynamic-import@^6.18.0: version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" + resolved "http://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" babel-plugin-syntax-exponentiation-operator@^6.8.0: version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + resolved "http://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" babel-plugin-syntax-export-extensions@^6.8.0: version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721" + resolved "http://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721" babel-plugin-syntax-flow@^6.18.0, babel-plugin-syntax-flow@^6.8.0: version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" + resolved "http://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" babel-plugin-syntax-function-bind@^6.8.0: version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz#48c495f177bdf31a981e732f55adc0bdd2601f46" + resolved "http://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz#48c495f177bdf31a981e732f55adc0bdd2601f46" babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + resolved "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" babel-plugin-syntax-object-rest-spread@^6.8.0: version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + resolved "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" babel-plugin-syntax-trailing-function-commas@^6.22.0, babel-plugin-syntax-trailing-function-commas@^6.8.0: version "6.22.0" @@ -2145,7 +2145,7 @@ babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: babylon@7.0.0-beta.44: version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d" + resolved "http://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d" babylon@^6.18.0: version "6.18.0" @@ -2567,15 +2567,15 @@ buffer-xor@^1.0.3: buffer@^4.3.0: version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + resolved "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" isarray "^1.0.0" buffer@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.0.tgz#53cf98241100099e9eeae20ee6d51d21b16e541e" + version "5.2.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -2678,12 +2678,12 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000880" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000880.tgz#e6d7ba9a6602086f6fc124b91eeacbb70c55b2c4" + version "1.0.30000883" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000883.tgz#976f22d6a9be119b342d5ce6c7ee98fc6e0bc94a" caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805, caniuse-lite@^1.0.30000844: - version "1.0.30000880" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000880.tgz#b7b6ceaf739e17d0dda0d89426cba4be16d07bb0" + version "1.0.30000883" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000883.tgz#597c1eabfb379bd9fbeaa778632762eb574706ac" capture-stack-trace@^1.0.0: version "1.0.0" @@ -2731,7 +2731,7 @@ chainsaw@~0.1.0: chalk@0.5.1: version "0.5.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" + resolved "http://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" dependencies: ansi-styles "^1.1.0" escape-string-regexp "^1.0.0" @@ -2741,7 +2741,7 @@ chalk@0.5.1: chalk@1.1.3, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + resolved "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: ansi-styles "^2.2.1" escape-string-regexp "^1.0.2" @@ -2759,7 +2759,7 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4 chalk@~0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" + resolved "http://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" dependencies: ansi-styles "~1.0.0" has-color "~0.1.0" @@ -3643,12 +3643,6 @@ d@1: dependencies: es5-ext "^0.10.9" -d@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309" - dependencies: - es5-ext "~0.10.2" - damerau-levenshtein@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514" @@ -4005,11 +3999,11 @@ duplexify@^3.4.2, duplexify@^3.6.0: stream-shift "^1.0.0" duration@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/duration/-/duration-0.2.0.tgz#5f9c4dfaafff655de986112efe25c5978dd85146" + version "0.2.1" + resolved "https://registry.yarnpkg.com/duration/-/duration-0.2.1.tgz#c87477ac08c2e540954b730094a5cb33bb1ae3d4" dependencies: - d "~0.1.1" - es5-ext "~0.10.2" + d "1" + es5-ext "~0.10.23" each-props@^1.3.0: version "1.3.2" @@ -4178,8 +4172,8 @@ electron-store@1.3.0: conf "^1.3.0" electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.30, electron-to-chromium@^1.3.47: - version "1.3.61" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.61.tgz#a8ac295b28d0f03d85e37326fd16b6b6b17a1795" + version "1.3.62" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.62.tgz#2e8e2dc070c800ec8ce23ff9dfcceb585d6f9ed8" electron-unhandled@1.1.0: version "1.1.0" @@ -4340,7 +4334,7 @@ es-to-primitive@^1.1.1: is-date-object "^1.0.1" is-symbol "^1.0.1" -es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2: +es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.23: version "0.10.46" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.46.tgz#efd99f67c5a7ec789baa3daa7f79870388f7f572" dependencies: @@ -4856,7 +4850,7 @@ extend@^3.0.0, extend@~3.0.0, extend@~3.0.1, extend@~3.0.2: external-editor@^2.0.4: version "2.2.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" + resolved "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" dependencies: chardet "^0.4.0" iconv-lite "^0.4.17" @@ -5611,7 +5605,7 @@ glogg@^1.0.0: got@^6.7.1: version "6.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" + resolved "http://registry.npmjs.org/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" dependencies: create-error-class "^3.0.0" duplexer3 "^0.1.4" @@ -7207,8 +7201,8 @@ make-dir@^1.0.0: pify "^3.0.0" make-error@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.4.tgz#19978ed575f9e9545d2ff8c13e33b5d18a67d535" + version "1.3.5" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" make-iterator@^1.0.0: version "1.0.1" @@ -7431,15 +7425,15 @@ minimatch@3.0.3: minimist@0.0.8: version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + resolved "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" minimist@~0.0.1: version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" minipass@^2.2.1, minipass@^2.3.3: version "2.3.4" @@ -7485,7 +7479,7 @@ mixin-object@^2.0.1: mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" @@ -7527,7 +7521,7 @@ mobx@3.1.7: moment@2.21.0: version "2.21.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" + resolved "http://registry.npmjs.org/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" moment@^2.21.0: version "2.22.2" @@ -9768,7 +9762,7 @@ request@^2.45.0, request@^2.79.0, request@^2.81.0, request@^2.83.0, request@^2.8 request@~2.79.0: version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" + resolved "http://registry.npmjs.org/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" dependencies: aws-sign2 "~0.6.0" aws4 "^1.2.1" @@ -9964,8 +9958,8 @@ rx@2.3.24: resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7" rxjs@^5.1.1: - version "5.5.11" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.11.tgz#f733027ca43e3bec6b994473be4ab98ad43ced87" + version "5.5.12" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" dependencies: symbol-observable "1.0.1" @@ -11701,7 +11695,7 @@ web3-utils@1.0.0-beta.30: webdriverio@4.12.0: version "4.12.0" - resolved "https://registry.yarnpkg.com/webdriverio/-/webdriverio-4.12.0.tgz#e340def272183c8168a4dd0b382322f9d7bee10d" + resolved "http://registry.npmjs.org/webdriverio/-/webdriverio-4.12.0.tgz#e340def272183c8168a4dd0b382322f9d7bee10d" dependencies: archiver "~2.1.0" babel-runtime "^6.26.0" @@ -11821,8 +11815,8 @@ webpack-sources@1.0.1: source-map "~0.5.3" webpack-sources@^1.0.1, webpack-sources@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54" + version "1.2.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.2.0.tgz#18181e0d013fce096faf6f8e6d41eeffffdceac2" dependencies: source-list-map "^2.0.0" source-map "~0.6.1"