Navigation Menu

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

Implement send LSK with Hardware Wallet Trezor Model T - Closes #1845 #1849 #1847 #1949

Merged
merged 6 commits into from Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 10 additions & 10 deletions app/src/ledger.js
Expand Up @@ -32,7 +32,7 @@ TransportNodeHid.setListenDevicesDebug((msg, ...args) => {
*/
let busy = false;
TransportNodeHid.setListenDevicesPollingSkip(() => busy);
let ledgerPath = null;
let hwDevice = null;
const getLedgerAccount = (index = 0) => {
const ledgerAccount = new LedgerAccount();
ledgerAccount.coinIndex(SupportedCoin.LISK);
Expand Down Expand Up @@ -85,14 +85,14 @@ const ledgerObserver = {
const liskAccount = await getLiskAccount(device.path);
const ledgerDevice = createLedgerHWDevice(liskAccount, device.path);
addConnectedDevices(ledgerDevice);
ledgerPath = device.path;
win.send({ event: 'ledgerConnected', value: null });
hwDevice = ledgerDevice;
win.send({ event: 'hwConnected', value: { model: ledgerDevice.model } });
}
} else if (type === 'remove') {
if (ledgerPath) {
removeConnectedDeviceByPath(ledgerPath);
ledgerPath = null;
win.send({ event: 'ledgerDisconnected', value: null });
if (hwDevice) {
removeConnectedDeviceByPath(hwDevice.path);
win.send({ event: 'hwDisconnected', value: { model: hwDevice.model } });
hwDevice = null;
}
}
}
Expand All @@ -104,7 +104,7 @@ const syncDevices = () => {
try {
observableListen = TransportNodeHid.listen(ledgerObserver);
} catch (e) {
ledgerPath = null;
hwDevice = null;
syncDevices();
}
};
Expand Down Expand Up @@ -174,8 +174,8 @@ export const executeLedgerCommand = (device, command) =>

// // eslint-disable-next-line arrow-body-style
createCommand('ledgerCommand', (command) => {
if (ledgerPath) {
return TransportNodeHid.open(ledgerPath)
if (hwDevice) {
return TransportNodeHid.open(hwDevice.path)
.then(async (transport) => { // eslint-disable-line max-statements
busy = true;
try {
Expand Down
6 changes: 0 additions & 6 deletions app/src/main.js
Expand Up @@ -101,12 +101,6 @@ ipcMain.on('proxyCredentialsEntered', (event, username, password) => {
global.myTempFunction(username, password);
});


// ipcMain.on('ledgerConnected', () => {
// console.log('ledgerConnected');
// store.dispatch({ type: actionTypes.connectHardwareWallet });
// });

// ToDo - enable this feature when it is implemented in the new design
// ipcMain.on('set-locale', (event, locale) => {
// const langCode = locale.substr(0, 2);
Expand Down
4 changes: 2 additions & 2 deletions app/src/trezor.js
Expand Up @@ -110,7 +110,7 @@ list.on('connect', (device) => {
logDebug(`Disconnected device ${device.features.label}`);
const trezorDevice = getDeviceById(deviceId);
if (trezorDevice) {
win.send({ event: 'trezorDisconnected', value: trezorDevice });
win.send({ event: 'hwDisconnected', value: { model: trezorDevice.model } });
removeConnectedDeviceByID(trezorDevice.deviceId);
}
});
Expand Down Expand Up @@ -144,7 +144,7 @@ list.on('connect', (device) => {
logDebug(`Adding Device ${device.features.label} to connected devices.`);
const trezorDevice = createTrezorHWDevice(device.features);
addConnectedDevices(trezorDevice);
win.send({ event: 'trezorConnected', value: trezorDevice });
win.send({ event: 'hwConnected', value: { model: trezorDevice.model } });
});

// This gets called on general error of the devicelist (no transport, etc)
Expand Down
4 changes: 2 additions & 2 deletions i18n/locales/en/common.json
Expand Up @@ -88,9 +88,10 @@
"Confirm": "Confirm",
"Confirm (Fee: 1 LSK)": "Confirm (Fee: 1 LSK)",
"Confirm (Fee: {{fee}} LSK)": "Confirm (Fee: {{fee}} LSK)",
"Confirm on Ledger": "Confirm on Ledger",
"Confirm on {{deviceModel}}": "Confirm on {{deviceModel}}",
"Confirm to register your second passphrase on the blockchain.": "Confirm to register your second passphrase on the blockchain.",
"Confirm transaction on Ledger Nano S": "Confirm transaction on Ledger Nano S",
"Confirm transaction on {{deviceModel}}": "Confirm transaction on {{deviceModel}}",
"Confirm transfer": "Confirm transfer",
"Confirm vote on Ledger Nano S": "Confirm vote on Ledger Nano S",
"Confirm your name": "Confirm your name",
Expand Down Expand Up @@ -552,7 +553,6 @@
"Your Trezor Device is not initialized. Please do it with trezor software.": "Your Trezor Device is not initialized. Please do it with trezor software.",
"Your Trezor Model T has an old Firmware. Please update it.": "Your Trezor Model T has an old Firmware. Please update it.",
"Your Trezor One has an old Firmware. Please update it.": "Your Trezor One has an old Firmware. Please update it.",
"Your account has been created!": "Your account has been created!",
"Your delegate name is being registered": "Your delegate name is being registered",
"Your passphrase is both your login and passphrase to your Lisk Hub. It is provided during account registration.": "Your passphrase is both your login and passphrase to your Lisk Hub. It is provided during account registration.",
"Your passphrase is used to access your Lisk ID.": "Your passphrase is used to access your Lisk ID.",
Expand Down
1 change: 0 additions & 1 deletion jest.config.js
Expand Up @@ -16,7 +16,6 @@ module.exports = {
'src/components/register/register.test.js',
'src/components/transactions/votedDelegates.test.js',
'src/components/voteUrlProcessor/index.test.js',
'src/store/middlewares/login.test.js',
'src/store/reducers/liskService.test.js',
'src/store/middlewares/socket.test.js',
'src/store/middlewares/peers.test.js',
Expand Down
13 changes: 8 additions & 5 deletions src/components/hwWallet/trezorLogin.js
Expand Up @@ -34,6 +34,7 @@ class TrezorLogin extends React.Component {
loginType: null,
publicKey: null,
address: null,
deviceId: null,
};

if (ipc) {
Expand Down Expand Up @@ -66,6 +67,8 @@ class TrezorLogin extends React.Component {
loginType,
publicKey,
address: extractAddress(publicKey),
deviceId,
deviceModel: devices[0].model,
});

// Retrieve Address with verification
Expand All @@ -91,7 +94,7 @@ class TrezorLogin extends React.Component {
network: this.props.network,
hwInfo: {
device: this.props.device,
deviceId: this.props.device.deviceId,
deviceId: this.state.deviceId,
derivationIndex: 0,
},
});
Expand All @@ -102,6 +105,7 @@ class TrezorLogin extends React.Component {
async componentDidMount() {
this.setState({ isLoading: true });
const devices = await getDeviceList();

setTimeout(async () => {
const output = await displayAccounts({
liskAPIClient: this.props.liskAPIClient,
Expand All @@ -124,20 +128,19 @@ class TrezorLogin extends React.Component {

selectAccount(ledgerAccount, index) {
Piwik.trackingEvent('TrezorLogin', 'button', 'Select account');
// set active peer
this.props.liskAPIClientSet({
publicKey: ledgerAccount.publicKey,
network: this.props.network,
hwInfo: { // Use pubKey[0] first 10 char as device id
deviceId: ledgerAccount.publicKey.substring(0, 10),
hwInfo: {
deviceId: this.state.deviceId,
derivationIndex: index,
deviceModel: this.state.deviceModel,
},
});
}

async addAccount() {
Piwik.trackingEvent('TrezorLogin', 'button', 'Add account');
console.log(this.state.hwAccounts);
const devices = await getDeviceList();

if (this.state.hwAccounts[this.state.hwAccounts.length - 1].isInitialized) {
Expand Down
41 changes: 21 additions & 20 deletions src/components/sendV2/summary/summary.js
Expand Up @@ -144,17 +144,18 @@ class Summary extends React.Component {
}

render() {
const { account, fields, t } = this.props;
const { secondPassphrase, isHardwareWalletConnected } = this.state;
let isBtnDisabled = secondPassphrase.hasSecondPassphrase && secondPassphrase.isValid !== '' && !secondPassphrase.isValid;
isBtnDisabled = !isBtnDisabled && isHardwareWalletConnected;

const confirmBtnMessage = isHardwareWalletConnected
? this.props.t('Confirm on Ledger')
: this.props.t('Send {{amount}} LSK', { amount: this.props.fields.amount.value });
? t('Confirm on {{deviceModel}}', { deviceModel: account.hwInfo.deviceModel })
: t('Send {{amount}} LSK', { amount: fields.amount.value });

const title = isHardwareWalletConnected
? this.props.t('Confirm transaction on Ledger Nano S')
: this.props.t('Transaction summary');
? t('Confirm transaction on {{deviceModel}}', { deviceModel: account.hwInfo.deviceModel })
: t('Transaction summary');

return (
<div className={`${styles.wrapper} summary`}>
Expand All @@ -164,60 +165,60 @@ class Summary extends React.Component {

<div className={`${styles.content} summary-content`}>
<div className={styles.row}>
<label>{this.props.t('Recipient')}</label>
<label>{t('Recipient')}</label>
<div className={styles.account}>
<AccountVisual address={this.props.fields.recipient.address} size={25} />
<AccountVisual address={fields.recipient.address} size={25} />
<label className={`${styles.information} recipient-confirm`}>
{this.props.fields.recipient.title || this.props.fields.recipient.address}
{fields.recipient.title || fields.recipient.address}
</label>
<span className={`${styles.secondText} ${styles.accountSecondText}`}>
{this.props.fields.recipient.address}
{fields.recipient.address}
</span>
</div>
</div>

<div className={styles.row}>
<label>{this.props.t('Amount')}</label>
<label>{t('Amount')}</label>
<label className={`${styles.information} ${styles.amount} amount-summary`}>
{`${this.props.fields.amount.value} ${this.props.t('LSK')}`}
<ConverterV2 className={`${styles.secondText} ${styles.amountSecondText}`} value={this.props.fields.amount.value} />
{`${fields.amount.value} ${t('LSK')}`}
<ConverterV2 className={`${styles.secondText} ${styles.amountSecondText}`} value={fields.amount.value} />
</label>
</div>

<div className={styles.row}>
<label>{this.props.t('Message')}</label>
<p className={`${styles.information} reference`}>{this.props.fields.reference.value}</p>
<label>{t('Message')}</label>
<p className={`${styles.information} reference`}>{fields.reference.value}</p>
</div>

<div className={styles.row}>
<label className={styles.transactionFee}>
{this.props.t('Transaction fee')}
{t('Transaction fee')}
<Tooltip
className={'showOnTop'}
title={this.props.t('Transaction fee')}
title={t('Transaction fee')}
footer={
<a href={links.transactionFee}
rel="noopener noreferrer"
target="_blank">
{this.props.t('Read More')}
{t('Read More')}
</a>
}
>
<p className={styles.tooltipText}>
{
this.props.t(`Every transaction needs to be confirmed and forged into Lisks blockchain network.
t(`Every transaction needs to be confirmed and forged into Lisks blockchain network.
Such operations require hardware resources and because of that there is a small fee for processing those.`)
}
</p>
</Tooltip>
</label>
<span>{this.props.t('{{fee}} LSK', { fee: fromRawLsk(fees.send) })}</span>
<span>{t('{{fee}} LSK', { fee: fromRawLsk(fees.send) })}</span>
</div>

{
secondPassphrase.hasSecondPassphrase
? <div className={`${styles.row} ${styles.passphrase} summary-second-passphrase`}>
<label>{this.props.t('Second passphrase')}</label>
<label>{t('Second passphrase')}</label>
<PassphraseInputV2
isSecondPassphrase={secondPassphrase.hasSecondPassphrase}
secondPPFeedback={secondPassphrase.feedback}
Expand All @@ -238,7 +239,7 @@ class Summary extends React.Component {
</PrimaryButtonV2>

<TertiaryButtonV2 className={`${styles.editBtn} on-prevStep`} onClick={this.prevStep}>
{this.props.t('Edit transaction')}
{t('Edit transaction')}
</TertiaryButtonV2>
</footer>
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/components/sendV2/summary/summary.test.js
Expand Up @@ -51,6 +51,9 @@ describe('Summary', () => {
account: {
address: accounts['second passphrase account'].address,
secondPublicKey: accounts['second passphrase account'].secondPublicKey,
hwInfo: {
deviceModel: 'Ledger Nano S',
},
},
failedTransactions: '',
pendingTransactions: [],
Expand Down
75 changes: 75 additions & 0 deletions src/store/middlewares/hwWallet.js
@@ -0,0 +1,75 @@
// istanbul ignore file
import actionTypes from '../../constants/actions';
import { accountLoggedOut } from '../../actions/account';
import { dialogDisplayed, dialogHidden } from '../../actions/dialog';
import { successToastDisplayed, errorToastDisplayed, infoToastDisplayed } from '../../actions/toaster';
import { HW_MSG } from '../../constants/hwConstants';
import Alert from '../../components/dialog/alert';

const hwWalletMiddleware = store => next => (action) => {
const { ipc } = window;

if (action.type === actionTypes.storeCreated && ipc) {
const util = require('util');

store.dispatch({
type: actionTypes.settingsUpdated,
data: { isHarwareWalletConnected: false },
});

ipc.on('hwConnected', (event, { model }) => {
store.dispatch({
type: actionTypes.settingsUpdated,
data: { isHarwareWalletConnected: true },
});

store.dispatch(successToastDisplayed({ label: `${model} connected` }));
});

ipc.on('hwDisconnected', (event, { model }) => {
const state = store.getState();
const { account } = state;

if (account.address) {
store.dispatch(dialogDisplayed({
childComponent: Alert,
childComponentProps: {
title: 'You are disconnected',
text: `There is no connection to the ${model}. Please check the cables if it happened by accident.`,
closeDialog: () => {
store.dispatch(dialogHidden());
location.reload(); // eslint-disable-line
},
},
}));

store.dispatch(accountLoggedOut());
}

store.dispatch({
type: actionTypes.settingsUpdated,
data: { isHarwareWalletConnected: false },
});

store.dispatch(successToastDisplayed({ label: `${model} disconnected` }));
});

ipc.on('trezorButtonCallback', (event, data) => {
store.dispatch(infoToastDisplayed({
label: util.format(HW_MSG.TREZOR_ASK_FOR_CONFIRMATION, data),
}));
});

ipc.on('trezorParamMessage', (event, data) => {
store.dispatch(infoToastDisplayed({ label: HW_MSG[data] }));
});

ipc.on('trezorError', () => {
store.dispatch(errorToastDisplayed({ label: HW_MSG.ERROR_OR_DEVICE_IS_NOT_CONNECTED }));
});
}

next(action);
};

export default hwWalletMiddleware;
14 changes: 7 additions & 7 deletions src/store/middlewares/index.js
Expand Up @@ -3,22 +3,22 @@ import peersMiddleware from './peers';
import accountMiddleware from './account';
import loadingBarMiddleware from './loadingBar';
import offlineMiddleware from './offline';
import loginMiddleware from './login';
import hwWalletMiddleware from './hwWallet';
// ToDo : enable this one when you solve the problem with multi account management
// import notificationMiddleware from './notification';
import votingMiddleware from './voting';
import followedAccountsMiddleware from './followedAccounts';
import socketMiddleware from './socket';

export default [
thunk,
peersMiddleware,
socketMiddleware,
// notificationMiddleware,
accountMiddleware,
followedAccountsMiddleware,
hwWalletMiddleware,
loadingBarMiddleware,
offlineMiddleware,
loginMiddleware,
// notificationMiddleware,
peersMiddleware,
socketMiddleware,
thunk,
votingMiddleware,
followedAccountsMiddleware,
];