diff --git a/package.json b/package.json index d7271798f..84e2bd465 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "selfkey-identity-wallet", "productName": "SelfKey Identity Wallet", - "version": "1.8.1", + "version": "1.8.2", "description": "The Official SelfKey Identity Wallet for Desktop", "browser": [ "chrome" diff --git a/src/common/app/index.js b/src/common/app/index.js index f003a0bd4..d40e4ac6a 100644 --- a/src/common/app/index.js +++ b/src/common/app/index.js @@ -10,6 +10,7 @@ import { Logger } from 'common/logger'; import { featureIsEnabled } from 'common/feature-flags'; import { kycOperations } from '../kyc'; import { schedulerOperations } from '../scheduler'; +import { createWalletOperations } from '../create-wallet'; const log = new Logger('app-redux'); @@ -74,7 +75,9 @@ export const appTypes = { APP_UNLOCK_WALLET: 'app/unlock/wallet', APP_SET_KEYSTORE_VALUE: 'app/keystore/SET', LOAD_KEYSTORE_VALUE: 'app/keystore/LOAD', - APP_SEED_UNLOCK_START: 'app/seed/unlock' + APP_SEED_UNLOCK_START: 'app/seed/unlock', + APP_SEED_GENERATE: 'app/seed/generate', + APP_RESET: 'app/reset' }; const appActions = { @@ -133,6 +136,10 @@ const appActions = { setKeystoreValue: payload => ({ type: appTypes.APP_SET_KEYSTORE_VALUE, payload + }), + resetAppAction: payload => ({ + type: appTypes.APP_RESET, + payload }) }; @@ -181,6 +188,7 @@ const unlockWalletOperation = (wallet, type) => async dispatch => { throw error; } finally { dispatch(appActions.setSeedAction(null)); + dispatch(createWalletOperations.setPasswordAction('')); dispatch(appActions.setHardwareWalletsAction([])); } }; @@ -227,10 +235,10 @@ const unlockWalletWithNewFile = (filePath, password) => async dispatch => { } }; -const unlockWalletWithPrivateKey = privateKey => async dispatch => { +const unlockWalletWithPrivateKey = (privateKey, password) => async dispatch => { const walletService = getGlobalContext().walletService; try { - const wallet = await walletService.unlockWalletWithPrivateKey(privateKey); + const wallet = await walletService.unlockWalletWithPrivateKey(privateKey, password); await dispatch(appOperations.unlockWalletOperation(wallet, 'privateKey')); await dispatch(push('/main/dashboard')); } catch (error) { @@ -282,6 +290,13 @@ const unlockWalletWithPublicKey = (address, path) => async (dispatch, getState) } }; +const generateSeedPhraseOperation = () => async (dispatch, getState) => { + const { walletService } = getGlobalContext(); + const seed = walletService.generateSeedPhrase(); + await dispatch(appActions.setSeedAction(seed)); + return seed; +}; + const startSeedUnlockOperation = seed => async dispatch => { await dispatch(appActions.setSeedAction(seed)); await dispatch(loadHDWalletsOperation()); @@ -482,7 +497,8 @@ const operations = { installUpdate, unlockWalletOperation, loadKeystoreValue, - startSeedUnlockOperation + startSeedUnlockOperation, + generateSeedPhraseOperation }; const appOperations = { @@ -556,6 +572,10 @@ const appOperations = { startSeedUnlockOperation: createAliasedAction( appTypes.APP_SEED_UNLOCK_START, operations.startSeedUnlockOperation + ), + generateSeedPhraseOperation: createAliasedAction( + appTypes.APP_SEED_GENERATE, + operations.generateSeedPhraseOperation ) }; @@ -615,6 +635,10 @@ const setSeedReducer = (state, action) => { return { ...state, seed: action.payload }; }; +const appResetReducer = (state, action) => { + return { ...initialState }; +}; + const appReducers = { setWalletsReducer, setWalletsLoadingReducer, @@ -629,7 +653,8 @@ const appReducers = { setAutoUpdateProgressReducer, setAutoUpdateDownloadedReducer, setKeystoreValueReducer, - setSeedReducer + setSeedReducer, + appResetReducer }; const reducer = (state = initialState, action) => { @@ -662,6 +687,8 @@ const reducer = (state = initialState, action) => { return appReducers.setAutoUpdateDownloadedReducer(state, action); case appTypes.APP_SET_KEYSTORE_VALUE: return appReducers.setKeystoreValueReducer(state, action); + case appTypes.APP_RESET: + return appReducers.appResetReducer(state, action); } return state; }; diff --git a/src/common/wallet-connect/index.js b/src/common/wallet-connect/index.js index 9d6f2ee76..d7a212770 100644 --- a/src/common/wallet-connect/index.js +++ b/src/common/wallet-connect/index.js @@ -105,11 +105,11 @@ export const operations = { ); await dispatch(push('/wallet-connect/sign-message')); }, - signMessageDenyOperation: () => async (dispatch, getState) => { + signMessageDenyOperation: error => async (dispatch, getState) => { try { const wc = walletConnectSelectors.selectWalletConnect(getState()); const { walletConnectService } = getGlobalContext(); - walletConnectService.rejectRequest(wc.requestId); + walletConnectService.rejectRequest(wc.requestId, error); await dispatch(walletConnectOperations.resetSessionAction()); await dispatch(push('/main/dashboard')); } catch (error) { @@ -151,7 +151,7 @@ export const operations = { await dispatch(push('/main/hd-error')); } } - walletConnectService.rejectRequest(wc.requestId, error); + await dispatch(walletConnectOperations.signMessageDenyOperation(error)); return; } @@ -185,11 +185,11 @@ export const operations = { ); await dispatch(push('/wallet-connect/transaction')); }, - transactionDenyOperation: () => async (dispatch, getState) => { + transactionDenyOperation: error => async (dispatch, getState) => { try { const wc = walletConnectSelectors.selectWalletConnect(getState()); const { walletConnectService } = getGlobalContext(); - walletConnectService.rejectRequest(wc.requestId); + walletConnectService.rejectRequest(wc.requestId, error); await dispatch(walletConnectOperations.resetSessionAction()); await dispatch(push('/main/dashboard')); } catch (error) { @@ -245,7 +245,7 @@ export const operations = { await dispatch(push('/main/hd-error')); } } - walletConnectService.rejectRequest(wc.requestId, error); + await dispatch(walletConnectOperations.transactionDenyOperation(error)); return; } diff --git a/src/main/blockchain/web3-service.js b/src/main/blockchain/web3-service.js index f32aa3bb8..3c25c8fc2 100644 --- a/src/main/blockchain/web3-service.js +++ b/src/main/blockchain/web3-service.js @@ -304,6 +304,20 @@ export class Web3Service extends EventEmitter { return this.getTransactionStatus(tx); } + async getNextNonce(address, opt) { + opt = opt || {}; + let block = 'pending'; + + if (opt.block) { + block = opt.block; + } + + let nonce = await this.web3.eth.getTransactionCount(address, block); + if (nonce <= this.nonce) { + return this.nonce; + } + } + async sendSignedTransaction(contactMethodInstance, contractAdress, args, wallet) { let opts = { ...(args || [])[0] }; if (!opts.from) { @@ -333,10 +347,7 @@ export class Web3Service extends EventEmitter { }); } let data = contactMethodInstance.encodeABI(); - let nonce = await this.web3.eth.getTransactionCount(opts.from, 'pending'); - if (nonce === this.nonce) { - nonce++; - } + let nonce = await this.getNextNonce(); let rawTx = { nonce: this.web3.utils.toHex(nonce), to: contractAdress, diff --git a/src/main/main.js b/src/main/main.js index abc263731..e8ec8590c 100644 --- a/src/main/main.js +++ b/src/main/main.js @@ -58,9 +58,10 @@ if (!gotTheLock) { } else { electron.app.on('second-instance', (event, commandLine, workingDirectory) => { // Someone tried to run a second instance, we should focus our window. - if (electron.app.win) { - if (electron.app.win.isMinimized()) electron.app.win.restore(); - electron.app.win.focus(); + const { mainWindow } = getGlobalContext(); + if (mainWindow) { + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.focus(); } }); } diff --git a/src/main/wallet-connect/wallet-connect-service.js b/src/main/wallet-connect/wallet-connect-service.js index cb4297e40..7a485f418 100644 --- a/src/main/wallet-connect/wallet-connect-service.js +++ b/src/main/wallet-connect/wallet-connect-service.js @@ -1,18 +1,43 @@ import WalletConnect from '@walletconnect/client'; -import { convertHexToNumber } from '@walletconnect/utils'; import { Logger } from 'common/logger'; import { walletConnectOperations } from '../../common/wallet-connect'; import { Identity } from '../platform/identity'; import { getWallet } from '../../common/wallet/selectors'; import { identitySelectors } from 'common/identity'; +import EthUtils from '../../common/utils/eth-utils'; const log = new Logger('WalletConnectService'); export class WalletConnectService { HANDLER_NAME = 'wallet-connect'; - constructor({ config, store }) { + constructor({ config, store, mainWindow, web3Service }) { this.config = config; this.store = store; + this.mainWindow = mainWindow; + this.web3Service = web3Service; + } + + focusWindow() { + if (this.mainWindow) { + if (this.mainWindow.isMinimized()) this.mainWindow.restore(); + + let attempts = 0; + let timeout = null; + const refocus = () => { + if ((this.mainWindow.isFocused() || attempts > 5) && timeout) { + clearTimeout(timeout); + timeout = null; + return; + } + this.mainWindow.setFocusable(true); + this.mainWindow.moveTop(); + this.mainWindow.focus(); + this.mainWindow.flashFrame(true); + attempts++; + timeout = setTimeout(refocus, 1000); + }; + refocus(); + } } async handleUrlCommand(cmd) { @@ -40,7 +65,7 @@ export class WalletConnectService { this.peerMeta = peerMeta; this.peerId = peerId; - + this.focusWindow(); this.store.dispatch(walletConnectOperations.sessionRequestOperation(peerId, peerMeta)); }); @@ -94,21 +119,20 @@ export class WalletConnectService { handlePersonalSignRequest({ id, method, params }) { const [message] = params; - + this.focusWindow(); this.store.dispatch( walletConnectOperations.signMessageOperation(id, this.peerMeta, this.peerId, message) ); } - handleTransaction({ id, method, params }) { + async handleTransaction({ id, method, params }) { const rawTx = params[0]; - + rawTx.nonce = await this.web3Service.getNextNonce(rawTx.from); const tx = { ...rawTx }; - tx.gas = convertHexToNumber(tx.gas); - tx.gasPrice = convertHexToNumber(tx.gasPrice); - tx.nonce = convertHexToNumber(tx.nonce || ''); - tx.value = convertHexToNumber(tx.value || ''); - + tx.gas = EthUtils.hexToDecimal(tx.gas); + tx.gasPrice = EthUtils.hexToDecimal(tx.gasPrice); + if (tx.value) tx.value = EthUtils.hexToDecimal(tx.value); + this.focusWindow(); this.store.dispatch( walletConnectOperations.transactionOperation( id, diff --git a/src/main/wallet/wallet-service.js b/src/main/wallet/wallet-service.js index f6fd2d8c2..4b5c15c26 100644 --- a/src/main/wallet/wallet-service.js +++ b/src/main/wallet/wallet-service.js @@ -83,6 +83,13 @@ export class WalletService { return wallet; } + async updateKeystorePath(id, keystoreFilePath) { + return Wallet.updateKeyStorePath({ + id, + keystoreFilePath + }); + } + async createWalletWithPassword(password) { const account = this.web3Service.createAccount(password); this.web3Service.setDefaultAccount(account); @@ -157,7 +164,7 @@ export class WalletService { return newWallet; } - async unlockWalletWithPrivateKey(privateKey) { + async unlockWalletWithPrivateKey(privateKey, password) { if (!privateKey.startsWith('0x')) { privateKey = Buffer.from(privateKey).toString('hex'); } @@ -167,14 +174,19 @@ export class WalletService { const account = this.web3Service.privateKeyToAccount(privateKey); this.web3Service.setDefaultAccount(account); - + let keystoreFilePath; let wallet = await Wallet.findByPublicKey(account.address); - + if ((!wallet && password) || (wallet && password && !wallet.keystoreFilePath)) { + keystoreFilePath = await this.saveAccountToKeystore(account, password); + } if (!wallet) { wallet = await this.createWallet({ address: account.address, + keystoreFilePath, profile: 'local' }); + } else if (keystoreFilePath) { + wallet = await this.updateKeystorePath(wallet.id, keystoreFilePath); } const newWallet = { ...wallet, @@ -256,6 +268,10 @@ export class WalletService { return this._getWallets(page, accountsQuantity, 'trezor'); } + generateSeedPhrase() { + return HDWallet.generateMnemonic(); + } + async getHDWalletAccounts(seed, offset, limit) { const wallet = await HDWallet.createFromMnemonic(seed); const accounts = wallet.getAccounts(offset, limit); diff --git a/src/main/wallet/wallet.js b/src/main/wallet/wallet.js index b1155de48..2b06a160f 100644 --- a/src/main/wallet/wallet.js +++ b/src/main/wallet/wallet.js @@ -117,6 +117,11 @@ export class Wallet extends BaseModel { return wallet; } + static async updateKeyStorePath({ id, keystoreFilePath }) { + let wallet = await this.query().patchAndFetchById(id, { keystoreFilePath }); + return wallet; + } + async hasSignedUpTo(websiteUrl) { let logins = await this.$relatedQuery('loginAttempts') .where({ diff --git a/src/renderer/app.jsx b/src/renderer/app.jsx index 4cdf2ac3f..71492c731 100644 --- a/src/renderer/app.jsx +++ b/src/renderer/app.jsx @@ -15,6 +15,8 @@ import CreatePassword from './wallet/create/password'; import NoConnection from './no-connection'; import PasswordConfirmation from './wallet/create/password/confirmation-container'; import BackupAddress from './wallet/create/backup-address'; +import BackupHDWallet from './wallet/create/backup-hd-container'; +import ConfirmHDWallet from './wallet/create/confirm-hd-container'; import BackupPK from './wallet/create/backup-pk'; import Main from './wallet/main'; import Unlock from './wallet/unlock'; @@ -71,6 +73,8 @@ class AppContainerComponent extends PureComponent { + + diff --git a/src/renderer/dashboard/buy-key-popup-modal.jsx b/src/renderer/dashboard/buy-key-popup-modal.jsx index 70641ea58..71d094c28 100644 --- a/src/renderer/dashboard/buy-key-popup-modal.jsx +++ b/src/renderer/dashboard/buy-key-popup-modal.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { Grid, List, ListItem, Typography, Divider } from '@material-ui/core'; import { withStyles } from '@material-ui/styles'; -import { PaymentIcon, Copy } from 'selfkey-ui'; +import { PaymentIcon, Copy, primary } from 'selfkey-ui'; const styles = theme => ({ address: { @@ -32,6 +32,11 @@ const styles = theme => ({ '& a': { textDecoration: 'none', color: '#FFFFFF' + }, + '& a:hover': { + '& p': { + color: primary + } } }, exchanges: { diff --git a/src/renderer/home/index.jsx b/src/renderer/home/index.jsx index 9dd624881..2a327fe69 100644 --- a/src/renderer/home/index.jsx +++ b/src/renderer/home/index.jsx @@ -69,6 +69,7 @@ const unlockWalletLink = React.forwardRef((props, ref) => ( )); class Home extends PureComponent { componentDidMount() { + this.props.dispatch(appOperations.resetAppAction()); this.props.dispatch(appOperations.loadWalletsOperation()); this.props.dispatch(tokensOperations.loadTokensOperation()); } diff --git a/src/renderer/individual/dashboard/applications-tab.jsx b/src/renderer/individual/dashboard/applications-tab.jsx index ed65ce543..217ce86c5 100644 --- a/src/renderer/individual/dashboard/applications-tab.jsx +++ b/src/renderer/individual/dashboard/applications-tab.jsx @@ -43,7 +43,7 @@ const styles = theme => ({ paddingRight: '10px' }, label: { - minWidth: '130px', + minWidth: '150px', paddingRight: '20px' }, statusInfoWrap: { @@ -94,6 +94,9 @@ const styles = theme => ({ }, statusName: { marginLeft: '8px' + }, + rpLink: { + marginTop: '1em' } }); @@ -416,6 +419,24 @@ class IndividualApplicationsTabComponent extends PureComponent { + {getRpInfo(item.rpName, 'name') === 'KeyFI' && + item.currentStatus === 2 && ( + + + + + )} diff --git a/src/renderer/kyc/application/application-status.jsx b/src/renderer/kyc/application/application-status.jsx index d936e32a9..e4d066bdc 100644 --- a/src/renderer/kyc/application/application-status.jsx +++ b/src/renderer/kyc/application/application-status.jsx @@ -23,24 +23,26 @@ const styles = theme => ({ } }); -export const ApplicationStatusCompleted = withStyles(styles)(({ classes, statusAction }) => ( - - - - - Your application was successful - - - {statusAction ? ( - - +export const ApplicationStatusCompleted = withStyles(styles)( + ({ classes, statusAction, completedButtonText = 'Manage Applications' }) => ( + + + + + Your application was successful + - ) : null} - - -)); + {statusAction ? ( + + + + ) : null} + + + ) +); export const ApplicationStatusRejected = withStyles(styles)(({ classes, statusAction }) => ( @@ -117,7 +119,15 @@ const statusComponent = { }; export const ApplicationStatusBar = withStyles(styles)( - ({ classes, status, contact, statusAction, loading = false, barStyle }) => { + ({ + classes, + status, + contact, + statusAction, + loading = false, + barStyle, + completedButtonText = 'Manage Applications' + }) => { if (!statusComponent.hasOwnProperty(status) || loading) { return null; } @@ -130,7 +140,11 @@ export const ApplicationStatusBar = withStyles(styles)( alignItems="flex-start" className={`${classes.status} ${barStyle}`} > - + ); } diff --git a/src/renderer/kyc/application/current-application-container.jsx b/src/renderer/kyc/application/current-application-container.jsx index 30016a817..e82352125 100644 --- a/src/renderer/kyc/application/current-application-container.jsx +++ b/src/renderer/kyc/application/current-application-container.jsx @@ -14,6 +14,7 @@ class CurrentApplicationComponent extends PureComponent { showEditAttribute: false, agreementError: false, agreementValue: false, + blockedJurisdictionError: false, editAttribute: {}, typeId: -1, isDocument: false, @@ -85,6 +86,37 @@ class CurrentApplicationComponent extends PureComponent { return; } + // Future improvement: Load blocked jurisdictions from vendor/product airtable + if (currentApplication.relyingPartyName === 'keyfi') { + const req = this.props.requirements.filter( + req => + req.type.url === + 'http://platform.selfkey.org/schema/attribute/nationality.json' || + req.type.url === + 'http://platform.selfkey.org/schema/attribute/country-of-residency.json' + ); + const jurisdictionError = req.reduce((acc, curr) => { + if (acc) return acc; + const attribute = selected[`_${curr.uiId}`] || curr.options[0]; + // Ignore optional empty attributes + if (!attribute && !curr.required) { + return false; + } + if (attribute.data.value.country === 'US') { + return true; + } + return false; + }, false); + + if (jurisdictionError) { + this.setState({ + blockedJurisdictionError: + 'Apologies, the jurisdiction you selected is not currently eligible.' + }); + return; + } + } + this.setState({ loading: true }); if (this.props.existingApplicationId) { this.props.dispatch(push('/main/selfkeyIdApplications')); @@ -107,10 +139,12 @@ class CurrentApplicationComponent extends PureComponent { this.setState({ selected: { ...selected, [uiId]: item } }); }; handleEdit = (item, identityId) => { + const { selected } = this.state; const attrName = `${identityId || ''}_${item.uiId}`; this.setState({ showEditAttribute: true, - editAttribute: this.state.selected[attrName] || item.options[0] + editAttribute: selected[attrName] || item.options[0], + selected: { ...selected, [attrName]: null } }); }; handleAdd = (item, identityId) => { @@ -162,6 +196,7 @@ class CurrentApplicationComponent extends PureComponent { addItem={this.handleAdd} existingApplicationId={existingApplicationId} loading={this.state.loading} + blockedJurisdictionError={this.state.blockedJurisdictionError} /> {this.state.showCreateAttribute && ( { if (!relyingParty || !currentApplication || !requirements) return ( @@ -140,6 +141,13 @@ export const CurrentApplicationPopup = withStyles(styles)( ) : null} + {blockedJurisdictionError ? ( + + + Error: The jurisdiction you selected is not currently eligible. + + + ) : null} diff --git a/src/renderer/marketplace/common/blocked-jurisdiction.jsx b/src/renderer/marketplace/common/blocked-jurisdiction.jsx new file mode 100644 index 000000000..a3469341c --- /dev/null +++ b/src/renderer/marketplace/common/blocked-jurisdiction.jsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { Grid, Typography, Button } from '@material-ui/core'; +import { withStyles } from '@material-ui/styles'; +import { Alert } from '../../common'; + +const styles = theme => ({ + container: { + marginBottom: '5px', + width: '100%' + }, + status: { + alignItems: 'center', + backgroundColor: '#262F39', + '& p': { + display: 'inline-block' + }, + '& svg': { + verticalAlign: 'middle' + } + }, + actionButton: { + textAlign: 'right' + }, + alertWrap: { + alignItems: 'center', + display: 'flex' + } +}); + +export const BlockedJurisdiction = withStyles(styles)( + ({ + classes, + text = 'Apologies, the jurisdiction you selected is not currently eligible.', + onActionClick, + actionButtonText = 'Information' + }) => ( +
+ + + + {text} + + + {onActionClick ? ( + + + + ) : null} + +
+ ) +); + +export default BlockedJurisdiction; diff --git a/src/renderer/marketplace/keyfi/checkout/keyfi-checkout-container.jsx b/src/renderer/marketplace/keyfi/checkout/keyfi-checkout-container.jsx index 429e2336b..b702eb744 100644 --- a/src/renderer/marketplace/keyfi/checkout/keyfi-checkout-container.jsx +++ b/src/renderer/marketplace/keyfi/checkout/keyfi-checkout-container.jsx @@ -16,6 +16,7 @@ import { marketplaceSelectors } from 'common/marketplace'; import { KeyFiCheckout } from './keyfi-checkout'; import { MarketplaceKeyFiComponent } from '../common/marketplace-keyfi-component'; import { identitySelectors } from 'common/identity'; +import { RESIDENCY_ATTRIBUTE, NATIONALITY_ATTRIBUTE } from 'common/identity/constants'; const styles = theme => ({}); const CRYPTOCURRENCY = config.constants.primaryToken; @@ -119,7 +120,9 @@ class MarketplaceKeyFiCheckoutContainerComponent extends MarketplaceKeyFiCompone onStatusActionClick = () => { const { rp } = this.props; if (rp && rp.authenticated && this.userHasApplied()) { - if (this.applicationCompleted() || this.applicationWasRejected()) { + if (this.applicationCompleted()) { + window.openExternal(null, 'https://keyfi.com'); + } else if (this.applicationWasRejected()) { this.props.dispatch(push(this.manageApplicationsRoute())); } else if (this.applicationRequiresAdditionalDocuments()) { this.redirectToKYCC(rp); @@ -148,6 +151,7 @@ class MarketplaceKeyFiCheckoutContainerComponent extends MarketplaceKeyFiCompone onSelectCrypto={this.onSelectCrypto} applicationStatus={this.getApplicationStatus()} onStatusAction={this.onStatusActionClick} + isBlockedJurisdiction={this.props.isBlockedJurisdiction} /> ); } @@ -159,6 +163,17 @@ const mapStateToProps = (state, props) => { const primaryToken = { ...props, cryptoCurrency: config.constants.primaryToken }; const identity = identitySelectors.selectIdentity(state); + + // Block US users + const residencyAndNationalityAttributes = identitySelectors.selectAttributesByUrl(state, { + identityId: identity.id, + attributeTypeUrls: [NATIONALITY_ATTRIBUTE, RESIDENCY_ATTRIBUTE] + }); + + const isBlockedJurisdiction = residencyAndNationalityAttributes.some( + attr => attr.data.value.country === 'US' + ); + const product = marketplaceSelectors.selectInventoryItemBySku( state, 'keyfi_kyc', @@ -172,6 +187,7 @@ const mapStateToProps = (state, props) => { product, application, identity, + isBlockedJurisdiction, vendor: marketplaceSelectors.selectVendorById(state, vendorId), ...getLocale(state), ...getFiatCurrency(state), diff --git a/src/renderer/marketplace/keyfi/checkout/keyfi-checkout.jsx b/src/renderer/marketplace/keyfi/checkout/keyfi-checkout.jsx index 2bacdc9a7..7facfc8a9 100644 --- a/src/renderer/marketplace/keyfi/checkout/keyfi-checkout.jsx +++ b/src/renderer/marketplace/keyfi/checkout/keyfi-checkout.jsx @@ -4,6 +4,7 @@ import { Grid, Typography, Button } from '@material-ui/core'; import { MarketplaceKycRequirements } from '../../common/marketplace-kyc-requirements'; import { BackButton, DefiIcon } from 'selfkey-ui'; import { ApplicationStatusBar } from '../../../kyc/application/application-status'; +import { BlockedJurisdiction } from '../../common/blocked-jurisdiction'; const styles = theme => ({ pageContent: { @@ -249,7 +250,8 @@ const KeyFiCheckout = withStyles(styles)( cryptoCurrency, applicationStatus, onStatusAction, - primaryToken + primaryToken, + isBlockedJurisdiction }) => ( @@ -305,7 +307,9 @@ const KeyFiCheckout = withStyles(styles)( variant="contained" size="large" onClick={onStartClick} - disabled={!!applicationStatus || loading} + disabled={ + !!applicationStatus || loading || isBlockedJurisdiction + } className={classes.ctabutton} > @@ -352,11 +356,16 @@ const KeyFiCheckout = withStyles(styles)( + {isBlockedJurisdiction && ( + + )} + diff --git a/src/renderer/wallet-connect/transaction-component.jsx b/src/renderer/wallet-connect/transaction-component.jsx index 5d7f0cf1e..b56c96588 100644 --- a/src/renderer/wallet-connect/transaction-component.jsx +++ b/src/renderer/wallet-connect/transaction-component.jsx @@ -10,6 +10,10 @@ const useStyles = makeStyles({ height: 50, background: 'transparent' }, + data: { + width: 300, + overflowWrap: 'break-word' + }, actions: { marginTop: 20 } @@ -67,11 +71,12 @@ export const TransactionComponent = ({ onCancel, peerMeta, address, method, tx,
)} - Value: {tx.value} WEI + Value: {tx.value || 0} WEI {!!tx.data && ( - - Data: {tx.data} + + Data: + {tx.data} )} diff --git a/src/renderer/wallet/create/backup-hd-container.jsx b/src/renderer/wallet/create/backup-hd-container.jsx new file mode 100644 index 000000000..60a00ecca --- /dev/null +++ b/src/renderer/wallet/create/backup-hd-container.jsx @@ -0,0 +1,53 @@ +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import BackupHDPhrase from './backup-hd'; +import { useDispatch, useSelector } from 'react-redux'; +import { push } from 'connected-react-router'; +import { appOperations, appSelectors } from 'common/app'; + +const goBackConfirmPassword = React.forwardRef((props, ref) => ( + +)); + +export const BackupHDWalletContainer = () => { + const [copied, setCopied] = useState(false); + + const dispatch = useDispatch(); + const seed = useSelector(appSelectors.selectSeed); + + useEffect( + () => { + if (!seed) { + dispatch(appOperations.generateSeedPhraseOperation()); + } + }, + [seed] + ); + + const handleCancel = e => { + e.preventDefault(); + dispatch(push('/home')); + }; + + const handleNext = e => { + e.preventDefault(); + dispatch(push('/confirmHDWallet')); + }; + + const handleCopy = () => { + setCopied(true); + }; + + return ( + + ); +}; + +export default BackupHDWalletContainer; diff --git a/src/renderer/wallet/create/backup-hd.jsx b/src/renderer/wallet/create/backup-hd.jsx index a8a67b814..bbf02b46e 100644 --- a/src/renderer/wallet/create/backup-hd.jsx +++ b/src/renderer/wallet/create/backup-hd.jsx @@ -4,6 +4,7 @@ import { PasswordIcon } from 'selfkey-ui'; import { withStyles } from '@material-ui/styles'; import { Popup } from '../../common'; import { PropTypes } from 'prop-types'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; const styles = theme => ({ icon: { @@ -42,6 +43,7 @@ class BackupHDPhraseComponent extends PureComponent { render() { const { classes, + copied, backComponent, onNextClick, onCancelClick, @@ -87,14 +89,16 @@ class BackupHDPhraseComponent extends PureComponent { {w} ))} - + + + @@ -146,10 +150,13 @@ export const BackupHDPhrase = withStyles(styles)(BackupHDPhraseComponent); BackupHDPhrase.displayName = 'BackupHDPhrase'; BackupHDPhrase.propTypes = { + copied: PropTypes.bool, + seedPhrase: PropTypes.array.isRequired, onNextClick: PropTypes.func.isRequired, - - backComponent: PropTypes.element + backComponent: PropTypes.object +}; +BackupHDPhrase.defaultProps = { + copied: false }; -BackupHDPhrase.defaultProps = {}; export default BackupHDPhrase; diff --git a/src/renderer/wallet/create/confirm-hd-container.jsx b/src/renderer/wallet/create/confirm-hd-container.jsx new file mode 100644 index 000000000..3726a32c1 --- /dev/null +++ b/src/renderer/wallet/create/confirm-hd-container.jsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react'; +import { Link } from 'react-router-dom'; +import _ from 'lodash'; +import ConfirmHDPhrase from './confirm-hd'; +import { useDispatch, useSelector } from 'react-redux'; +import { push } from 'connected-react-router'; +import { appSelectors, appOperations } from 'common/app'; + +const goBackConfirmPassword = React.forwardRef((props, ref) => ( + +)); + +export const ConfirmHDWalletContainer = () => { + const seed = useSelector(appSelectors.selectSeed); + + const [selected, setSelected] = useState([]); + const [shuffled] = useState(_.shuffle((seed || '').split(' '))); + const [error, setError] = useState(null); + + // const seed = useSelector(appSelectors.selectSeedPhrase); + + const dispatch = useDispatch(); + + const handleCancel = e => { + e.preventDefault(); + dispatch(push('/backupHDWallet')); + }; + + const handleNext = e => { + e.preventDefault(); + let newError = null; + if (seed !== selected.join(' ')) { + newError = 'Incorrect seed phrase'; + } + + setError(newError); + + if (newError) { + setSelected([]); + return; + } + + dispatch(appOperations.startSeedUnlockOperation(seed)); + }; + + const handleClear = () => { + setSelected([]); + }; + + const handleSelectWord = w => { + setSelected([...selected, w]); + }; + + return ( + + ); +}; + +export default ConfirmHDWalletContainer; diff --git a/src/renderer/wallet/create/confirm-hd.jsx b/src/renderer/wallet/create/confirm-hd.jsx index 3906539ed..94f66b1f0 100644 --- a/src/renderer/wallet/create/confirm-hd.jsx +++ b/src/renderer/wallet/create/confirm-hd.jsx @@ -159,6 +159,10 @@ class ConfirmDPhraseComponent extends PureComponent { variant="contained" onClick={onNextClick} className={classes.next} + disabled={ + selectedSeedPhrase.length !== + shuffledSeedPhrase.length + } size="large" > Continue diff --git a/src/renderer/wallet/create/password/confirmation-container.jsx b/src/renderer/wallet/create/password/confirmation-container.jsx index 6ecc821c7..2d2a8d7b8 100644 --- a/src/renderer/wallet/create/password/confirmation-container.jsx +++ b/src/renderer/wallet/create/password/confirmation-container.jsx @@ -8,6 +8,7 @@ import { connect } from 'react-redux'; import { getGlobalContext } from 'common/context'; import PasswordConfirmation from './confirmation-component'; +import { featureIsEnabled } from 'common/feature-flags'; const goBackCreatePassword = React.forwardRef((props, ref) => ( @@ -29,6 +30,10 @@ class PasswordConfirmationContainer extends PureComponent { handleNext = async e => { e && e.preventDefault(); if (this.props.firstPassword === this.state.password) { + if (featureIsEnabled('hdWallet')) { + await this.props.dispatch(push('/backupHDWallet')); + return; + } getGlobalContext().matomoService.trackEvent( 'wallet_setup', 'password_create', diff --git a/src/renderer/wallet/create/password/password-component.jsx b/src/renderer/wallet/create/password/password-component.jsx index e0f3b2a57..8dc1a2da7 100644 --- a/src/renderer/wallet/create/password/password-component.jsx +++ b/src/renderer/wallet/create/password/password-component.jsx @@ -94,7 +94,7 @@ class PasswordComponent extends PureComponent { Protect your SelfKey Identity Wallet and Ethereum address with a password. Your address is like a bank account number on the blockchain, - used to send and receive Ether or tokens. This password is isRequired to + used to send and receive Ether or tokens. This password is required to unlock your wallet.
diff --git a/src/renderer/wallet/unlock/select-address.jsx b/src/renderer/wallet/unlock/select-address.jsx index c80b93031..48b32565c 100644 --- a/src/renderer/wallet/unlock/select-address.jsx +++ b/src/renderer/wallet/unlock/select-address.jsx @@ -12,6 +12,7 @@ import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'; import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'; import { connect } from 'react-redux'; import { appOperations, appSelectors } from 'common/app'; +import { createWalletSelectors } from 'common/create-wallet'; import { push } from 'connected-react-router'; import { Popup } from '../../common'; @@ -129,7 +130,10 @@ class SelectAddress extends PureComponent { handleSelectedAddress = async () => { if (this.state.selectedPrivateKey) { await this.props.dispatch( - appOperations.unlockWalletWithPrivateKeyOperation(this.state.selectedPrivateKey) + appOperations.unlockWalletWithPrivateKeyOperation( + this.state.selectedPrivateKey, + this.props.password + ) ); return; } @@ -299,8 +303,10 @@ class SelectAddress extends PureComponent { const mapStateToProps = (state, props) => { const app = appSelectors.selectApp(state); + const cp = createWalletSelectors.selectCreateWallet(state); return { - hardwareWallets: app.hardwareWallets + hardwareWallets: app.hardwareWallets, + password: cp.password }; }; diff --git a/stories/kyc.stories.js b/stories/kyc.stories.js index 77d52c446..f34b11062 100644 --- a/stories/kyc.stories.js +++ b/stories/kyc.stories.js @@ -74,6 +74,14 @@ storiesOf('KYC/Application Status bar', module) .add('completed', () => ( )) + .add('completed with custom action', () => ( + + )) + .add('progress', () => ( )) diff --git a/stories/marketplace-common.stories.js b/stories/marketplace-common.stories.js index 69218771c..b9b0bec62 100644 --- a/stories/marketplace-common.stories.js +++ b/stories/marketplace-common.stories.js @@ -19,6 +19,7 @@ import MarketplaceDIDRequired from '../src/renderer/marketplace/selfkey-did-requ import KeyFiWidget from '../src/renderer/marketplace/keyfi/widget/keyfi-widget'; import KeyFiCheckout from '../src/renderer/marketplace/keyfi/checkout/keyfi-checkout'; import KeyFiPaymentComplete from '../src/renderer/marketplace/keyfi/checkout/keyfi-payment-complete'; +import { BlockedJurisdiction } from '../src/renderer/marketplace/common/blocked-jurisdiction'; import KYCRequirementData from './__fixtures__/kyc-requirements-data'; const paymentCheckoutData = { @@ -68,6 +69,13 @@ storiesOf('Marketplace Common', module) onEnterDid={action('did associate')} onClose={action('did close')} /> + )) + .add('Blocked Jurisdiction', () => ( + )); const resumeItemSets = [