Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multiple wallets synced with master #635

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion locale/texts.pot
Original file line number Diff line number Diff line change
Expand Up @@ -2768,4 +2768,4 @@ msgstr ""

#: src/components/atomic-swap/ProposalBalanceTable.js:62
msgid "Receiving"
msgstr ""
msgstr ""
56 changes: 51 additions & 5 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import SentryPermission from './screens/SentryPermission';
import UnknownTokens from './screens/UnknownTokens';
import Signin from './screens/Signin';
import LockedWallet from './screens/LockedWallet';
import ChooseWallet from './screens/ChooseWallet';
import NewWallet from './screens/NewWallet';
import WalletType from './screens/WalletType';
import SoftwareWalletWarning from './screens/SoftwareWalletWarning';
Expand All @@ -45,8 +46,9 @@ import RequestErrorModal from './components/RequestError';
import { GlobalModalContext, MODAL_TYPES } from './components/GlobalModal';
import createRequestInstance from './api/axiosInstance';
import hathorLib from '@hathor/wallet-lib';
import { IPC_RENDERER } from './constants';
import AllAddresses from './screens/AllAddresses';
import AddressList from './screens/AddressList';
import { IPC_RENDERER, LEDGER_ENABLED } from './constants';
import NFTList from './screens/NFTList';
import { resetNavigateTo, updateLedgerClosed } from './actions/index';
import { WALLET_STATUS } from './sagas/wallet';
Expand Down Expand Up @@ -80,7 +82,17 @@ function Root() {
useEffect(() => {
if (ledgerClosed) {
LOCAL_STORE.lock();
navigate('/locked/');
wallet.removeHardwareWalletFromStorage();

// If there are other wallets, go to screen to choose wallet
const firstWallet = wallet.getFirstWalletPrefix();
if (firstWallet) {
wallet.setWalletPrefix(firstWallet);
navigate('/choose_wallet');
} else {
wallet.setWalletPrefix(null);
navigate('/wallet_type/');
}
}
}, [ledgerClosed]);

Expand All @@ -91,6 +103,22 @@ function Root() {
* - Ledger event handlers
*/
useEffect(() => {
// When Ledger device loses connection or the app is closed
if (this.props.ledgerClosed && !prevProps.ledgerClosed) {
wallet.removeHardwareWalletFromStorage();

// If there are other wallets, go to screen to choose wallet
const firstWallet = wallet.getFirstWalletPrefix();
if (firstWallet) {
wallet.setWalletPrefix(firstWallet);
this.props.history.push('/choose_wallet');
} else {
wallet.setWalletPrefix(null);
this.props.history.push('/wallet_type/');
}
}

// ---
// Detect a previous instalation and migrate
storageUtils.migratePreviousLocalStorage();

Expand Down Expand Up @@ -215,6 +243,7 @@ function Root() {
<Route path="/signin" element={<StartedComponent children={ <Signin />} loaded={false} />} />
<Route path="/hardware_wallet" element={<StartedComponent children={ <StartHardwareWallet /> } loaded={false} />} />
<Route path="/locked" element={<DefaultComponent children={<LockedWallet />} />} />
<Route path="/choose_wallet" element={<DefaultComponent children={<ChooseWallet />} />} />
<Route path="/welcome" element={<Welcome />} />
<Route path="/loading_addresses" element={<LoadingAddresses />} />
<Route path="/permission" element={<SentryPermission />} />
Expand All @@ -233,7 +262,7 @@ function LoadedWalletComponent({ children }) {

// If was closed and is loaded we need to redirect to locked screen
if ((!isServerScreen) && (LOCAL_STORE.wasClosed() || LOCAL_STORE.isLocked()) && (!LOCAL_STORE.isHardwareWallet())) {
return <Navigate to={ '/locked/' } />;
return <Redirect to={{ pathname: '/choose_wallet/' }} />;
}

// We allow server screen to be shown from locked screen to allow the user to
Expand Down Expand Up @@ -303,18 +332,29 @@ function StartedComponent({children, loaded: routeRequiresWalletToBeLoaded}) {
const isServerScreen = location.pathname === '/server';
// Wallet is locked, go to locked screen
if (LOCAL_STORE.isLocked() && !isServerScreen && !LOCAL_STORE.isHardwareWallet()) {
return <Navigate to={ '/locked/' } replace />;
return <Redirect to={{pathname: '/choose_wallet/'}}/>;
}

// Route requires the wallet to be loaded, render it
if (routeRequiresWalletToBeLoaded || isServerScreen) {
return <LoadedWalletComponent children={children} />;
}

// XXX: Wallet reseted?
const firstWallet = wallet.getFirstWalletPrefix();
if (firstWallet && LOCAL_STORE.prefix === '') {
return <Redirect to={{pathname: '/choose_wallet/'}}/>;
}

// Route does not require wallet to be loaded. Redirect to wallet "home" screen
return <Navigate to="/wallet/" replace />;
}

const firstWallet = wallet.getFirstWalletPrefix();
if (firstWallet && hathorLib.storage.store.prefix === '') {
return <Redirect to={{pathname: '/choose_wallet/'}}/>;
}

// Wallet is not loaded, but it is still loading addresses. Go to the loading screen
if (loadingAddresses) {
const location = useLocation();
Expand Down Expand Up @@ -378,7 +418,13 @@ function DefaultComponent({ children }) {
LOCAL_STORE.isHardwareWallet()) {
// This will redirect the page to Wallet Type screen
LOCAL_STORE.cleanWallet();
return <Navigate to={ '/wallet_type/' } />;
wallet.removeHardwareWalletFromStorage();
hathorLib.wallet.unlock();
const firstWallet = wallet.getFirstWalletPrefix();
if (firstWallet) {
return <Redirect to={{ pathname: '/choose_wallet/' }} />;
}
return <Redirect to={{ pathname: '/wallet_type/' }} />;
}

// Render the navigation top bar, the component and the error handling modal
Expand Down
7 changes: 6 additions & 1 deletion src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,12 @@ export const metadataLoaded = data => ({ type: 'metadata_loaded', payload: data
/**
* Remove token metadata after unregister token
*/
export const removeTokenMetadata = data => ({ type: 'remove_token_metadata', payload: data });
export const removeTokenMetadata = data => ({ type: "remove_token_metadata", payload: data });

/**
* Set the wallet name during start process
*/
export const setInitWalletName = name => ({type: "set_init_wallet_name", payload: name});

/**
* Partially update history and balance
Expand Down
5 changes: 5 additions & 0 deletions src/components/ModalResetAllData.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ class ModalResetAllData extends React.Component {
componentDidMount() {
$('#confirmResetModal').modal('show');
$('#confirmResetModal').on('hidden.bs.modal', this.props.onClose);
// when the modal closes, make sures the fields are reset
// XXX: Check if hide and hidden expressions are redundant
$('#confirmResetModal').on('hide.bs.modal', () => {
this.onDismiss();
});
}

componentWillUnmount() {
Expand Down
2 changes: 2 additions & 0 deletions src/components/Navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { t } from 'ttag';
import logo from '../assets/images/hathor-white-logo.png';
import Version from './Version';
import ServerStatus from './ServerStatus';
import WalletStatus from './WalletStatus';
import helpers from '../utils/helpers';
import { useSelector } from 'react-redux';
import { FEATURE_TOGGLE_DEFAULTS, NANO_CONTRACTS_FEATURE_TOGGLE } from '../constants';
Expand Down Expand Up @@ -73,6 +74,7 @@ function Navigation() {
</li>}
</ul>
<div className="navbar-right d-flex flex-row align-items-center navigation-search">
<WalletStatus />
<ServerStatus />
<Version />
</div>
Expand Down
7 changes: 5 additions & 2 deletions src/components/TokenBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,11 @@ export default function TokenBar () {
*/
const lockWallet = () => {
LOCAL_STORE.lock();
navigate('/locked/');
};
if (hathorLib.wallet.isHardwareWallet()) {
wallet.removeHardwareWalletFromStorage();
}
navigate('/choose_wallet/');
}

/**
* Called when user clicks to go to settings, then redirects to settings screen
Expand Down
2 changes: 1 addition & 1 deletion src/components/TokenHistory.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class TokenHistory extends React.Component {
* firstHash {string} ID of the first transaction being shown
* lastHash {string} ID of the last transaction being shown
* reference {string} ID of the reference transaction used when clicking on pagination button
* direction {string} 'previous' or 'next', dependending on which pagination button the user has clicked
* direction {string} 'previous' or 'next', depending on which pagination button the user has clicked
* transactions {Array} List of transactions to be shown in the screen
* shouldFetch {Boolean} If should fetch more history (when the fetch returns 0 elements, should be set to false)
*/
Expand Down
32 changes: 32 additions & 0 deletions src/components/WalletStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright (c) Hathor Labs and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import { t } from 'ttag'

import hathorLib from '@hathor/wallet-lib';

/**
* Component that shows the current wallet name in use
*
* @memberof Components
*/
function WalletStatus(props) {
if (hathorLib.wallet.isHardwareWallet()) {
return null;
}
const listOfWallets = hathorLib.storage.store.getListOfWallets();
const walletName = listOfWallets[hathorLib.storage.store.prefix].name;
return (
<div className="d-flex flex-column version-wrapper align-items-center">
<span>{t`Current Wallet`}</span>
<span>{walletName}</span>
</div>
);
}

export default WalletStatus;
6 changes: 5 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@ export const LEDGER_MAX_VERSION = '2.0.0';
*/
export const LEDGER_FIRST_CUSTOM_TOKEN_COMPATIBLE_VERSION = '1.1.0'


/**
* Wallet service URLs
*/
Expand Down Expand Up @@ -286,3 +285,8 @@ export const colors = {
* when changing the address of a nano contract
*/
export const NANO_UPDATE_ADDRESS_LIST_COUNT = 5;

/**
* Hardware wallet name on storage
*/
export const HARDWARE_WALLET_NAME = '$HARDWARE%'
27 changes: 27 additions & 0 deletions src/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Copyright (c) Hathor Labs and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

export class WalletDoesNotExistError extends Error {
constructor(prefix) {
super(`Wallet does not exist: ${prefix}`);
this.name = "WalletDoesNotExistError";
}
};

export class WalletAlreadyExistError extends Error {
constructor() {
super('Prefix already in use');
this.name = "WalletAlreadyExistError";
}
};

export class InvalidWalletNameError extends Error {
constructor() {
super('Invalid wallet name');
this.name = "InvalidWalletNameError";
}
};
90 changes: 90 additions & 0 deletions src/featureFlags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import events from 'events';
import { UnleashClient } from 'unleash-proxy-client';
import {
UNLEASH_URL,
UNLEASH_CLIENT_KEY,
UNLEASH_POLLING_INTERVAL,
} from './constants';

const IGNORE_WALLET_SERVICE_FLAG = 'featureFlags:ignoreWalletServiceFlag';

export class FeatureFlags extends events.EventEmitter {
constructor(userId, network) {
super();

this.userId = userId;
this.network = network;
this.walletServiceFlag = `wallet-service-wallet-desktop-${this.network}.rollout`;
this.walletServiceEnabled = null;
this.client = new UnleashClient({
url: UNLEASH_URL,
clientKey: UNLEASH_CLIENT_KEY,
refreshInterval: UNLEASH_POLLING_INTERVAL,
appName: `wallet-service-wallet-desktop`,
});

this.client.on('update', () => {
// Get current flag
const walletServiceEnabled = this.client.isEnabled(this.walletServiceFlag);

// We should only emit an update if we already had a value on the instance
// and if the value has changed
if (this.walletServiceEnabled !== null && (
this.walletServiceEnabled !== walletServiceEnabled
)) {
this.walletServiceEnabled = walletServiceEnabled;
this.emit('wallet-service-enabled', walletServiceEnabled);
}
});
}

/**
* Uses the Hathor Unleash Server and Proxy to determine if the
* wallet should use the WalletService facade or the old facade
*
* @params {string} userId An user identifier (e.g. the firstAddress)
* @params {string} network The network name ('mainnet' or 'testnet')
*
* @return {boolean} The result from the unleash feature flag
*/
async shouldUseWalletService() {
try {
const shouldIgnore = await localStorage.getItem(IGNORE_WALLET_SERVICE_FLAG);
if (shouldIgnore) {
return false;
}
this.client.updateContext({ userId: this.userId });

// Start polling for feature flag updates
await this.client.start();

// start() method will have already called the fetchToggles, so the flag should be enabled
const isWalletServiceEnabled = this.client.isEnabled(this.walletServiceFlag);
this.walletServiceEnabled = isWalletServiceEnabled;

return this.walletServiceEnabled;
} catch (e) {
// If our feature flag service is unavailable, we default to the
// old facade
return false;
}
}

/**
* Sets the ignore flag on the storage to persist it between app restarts
*/
async ignoreWalletServiceFlag() {
await localStorage.setItem(IGNORE_WALLET_SERVICE_FLAG, 'true');
this.walletServiceEnabled = false;

// Stop the client from polling
this.client.stop();
}

/**
* Removes the ignore flag from the storage
*/
static clearIgnoreWalletServiceFlag() {
localStorage.removeItem(IGNORE_WALLET_SERVICE_FLAG);
}
}
Loading
Loading