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 = [