Skip to content

Commit

Permalink
Merge pull request #3997 from LiskHQ/3954-add-hw-multisig-support
Browse files Browse the repository at this point in the history
Add HW multisig support - Closes #3954
  • Loading branch information
ManuGowda authored Jan 10, 2022
2 parents fde24ca + 0b88772 commit 52ace16
Show file tree
Hide file tree
Showing 22 changed files with 373 additions and 273 deletions.
8 changes: 4 additions & 4 deletions src/components/screens/dashboard/newsFeed/newsParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ const NewsParser = ({ children }) => {
const REGEX_USER = /\B(@[a-zA-Z0-9_]+)/g; // regex for @users
const REGEX_HASHTAG = /\B(#[A-Za-z0-9-_]+)/g; // regex for #hashtags
const textWithHashtag = reactStringReplace(Html5Entities.decode(children), REGEX_HASHTAG,
(hashtag, i) => (
(hashtag, index, offset) => (
<a
key={hashtag + i}
key={hashtag + index + offset}
href={`https://twitter.com/hashtag/${hashtag.slice(1)}`}
target="_blank"
className="hashtag"
>
{hashtag}
</a>
));
const textWithUsers = reactStringReplace(textWithHashtag, REGEX_USER, (user, i) => (
const textWithUsers = reactStringReplace(textWithHashtag, REGEX_USER, (user, index, offset) => (
<a
key={user + i}
key={user + index + offset}
href={`https://twitter.com/${user.slice(1)}`}
target="_blank"
className="user"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import LoadingIcon from '../loadingIcon';
import styles from './selectAccount.css';

const Tab = ({
tabName, tabId, accountsList,
name, id, accountsList,
onSaveNameAccounts, onSelectAccount,
}) => (
<div name={tabName} id={tabId} className={`${styles.deviceContainer} ${`tab-${tabId}`} hw-container`}>
<div name={name} id={id} className={`${styles.deviceContainer} ${`tab-${id}`} hw-container`}>
{accountsList.map((data) => (
<AccountCard
key={`hw-account-tabId-${data.index}`}
Expand Down Expand Up @@ -170,22 +170,22 @@ class SelectAccount extends React.Component {
? (
<TabsContainer name="main-tabs">
<Tab
tabName={t('Active')}
tabId="active"
name={t('Active')}
id="active"
accountsList={nonEmptyAccounts}
onSaveNameAccounts={this.onSaveNameAccounts}
onSelectAccount={this.onSelectAccount}
/>
<Tab
tabName={t('Empty')}
tabId="empty"
name={t('Empty')}
id="empty"
accountsList={emptyAccounts}
onSaveNameAccounts={this.onSaveNameAccounts}
onSelectAccount={this.onSelectAccount}
/>
<Tab
tabName={t('Pending reclaim ({{numOfAccounts}})', { numOfAccounts: reclaimAccounts.length })}
tabId="reclaim"
name={t('Pending reclaim ({{numOfAccounts}})', { numOfAccounts: reclaimAccounts.length })}
id="reclaim"
accountsList={reclaimAccounts}
onSaveNameAccounts={this.onSaveNameAccounts}
onSelectAccount={this.onSelectAccount}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ export const DelegateDetails = ({
export const RoundState = ({
activeTab, state, isBanned, t, time,
}) => {
// if (activeTab === 'active') console.log('lastBlock', lastBlock);
if (state === undefined) {
return (
<span className={`${getRoundStateClass(activeTab)} ${styles.noEllipsis} ${styles.statusIconsContainer}`}>-</span>
Expand Down
3 changes: 2 additions & 1 deletion src/components/screens/multiSignature/form/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const validators = [
message: t => t('Either change the optional member to mandatory or define more optional members.'),
},
{
pattern: (mandatory, optional) => mandatory.length === 0 && optional.length > 0,
pattern: (mandatory, optional, signatures) =>
mandatory.length === 0 && optional.length === signatures,
message: t => t('All members can not be optional. Consider changing them to mandatory.'),
},
{
Expand Down
8 changes: 5 additions & 3 deletions src/components/screens/multiSignature/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import React from 'react';
import { withRouter } from 'react-router-dom';

import MultiStep from '../../shared/multiStep';
import { removeSearchParamsFromUrl } from '../../../utils/searchParams';
import Dialog from '../../toolbox/dialog/dialog';
import TransactionSignature from '@shared/transactionSignature';
import MultiStep from '@shared/multiStep';
import { removeSearchParamsFromUrl } from '@utils/searchParams';
import Dialog from '@toolbox/dialog/dialog';

import Form from './form';
import Summary from './summary';
Expand All @@ -25,6 +26,7 @@ const MultiSignature = ({ history }) => {
>
<Form />
<Summary />
<TransactionSignature />
<Status />
</MultiStep>
</Dialog>
Expand Down
2 changes: 0 additions & 2 deletions src/components/screens/multiSignature/summary/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import Summary from './summary';

const mapStateToProps = state => ({
account: getActiveTokenAccount(state),
signedTransaction: state.transactions.signedTransaction,
txSignatureError: state.transactions.txSignatureError,
});

const mapDispatchToProps = {
Expand Down
39 changes: 14 additions & 25 deletions src/components/screens/multiSignature/summary/summary.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useEffect } from 'react';
import React from 'react';
import { MODULE_ASSETS_NAME_ID_MAP } from '@constants';
import TransactionInfo from '@shared/transactionInfo';
import { isEmpty } from '@utils/helpers';
import Box from '@toolbox/box';
import BoxContent from '@toolbox/box/content';
import BoxFooter from '@toolbox/box/footer';
Expand All @@ -22,39 +21,29 @@ const Summary = ({
prevStep,
nextStep,
multisigGroupRegistered,
signedTransaction,
txSignatureError,
}) => {
// eslint-disable-next-line max-statements
const onConfirm = () => {
multisigGroupRegistered({
fee,
mandatoryKeys,
optionalKeys,
numberOfSignatures,
nextStep({
rawTransaction: {
fee: String(fee),
mandatoryKeys,
optionalKeys,
numberOfSignatures,
},
actionFunction: multisigGroupRegistered,
statusInfo: {
mandatoryKeys,
optionalKeys,
numberOfSignatures,
},
});
};

const goBack = () => {
prevStep({ mandatoryKeys, optionalKeys, numberOfSignatures });
};

useEffect(() => {
// success
if (!isEmpty(signedTransaction)) {
nextStep({
transactionInfo: signedTransaction,
});
}
}, [signedTransaction]);

useEffect(() => {
// error
if (txSignatureError) {
nextStep({ error: txSignatureError });
}
}, [txSignatureError]);

return (
<section className={styles.wrapper}>
<Box className={styles.container}>
Expand Down
17 changes: 14 additions & 3 deletions src/components/screens/multiSignature/summary/summary.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { mount } from 'enzyme';
import * as hwManagerAPI from '@utils/hwManager';
import Summary from './summary';
import accounts from '../../../../../test/constants/accounts';
import flushPromises from '../../../../../test/unit-test-utils/flushPromises';

const mockTransaction = {
fee: 0.02,
Expand Down Expand Up @@ -56,8 +55,20 @@ describe('Multisignature summary component', () => {

it('Should call props.nextStep', async () => {
wrapper.find('button.confirm').simulate('click');
await flushPromises();
expect(props.multisigGroupRegistered).toHaveBeenCalledWith(mockTransaction);
expect(props.nextStep).toHaveBeenCalledWith({
rawTransaction: {
fee: String(props.fee),
mandatoryKeys: props.mandatoryKeys,
optionalKeys: props.optionalKeys,
numberOfSignatures: props.numberOfSignatures,
},
actionFunction: props.multisigGroupRegistered,
statusInfo: {
mandatoryKeys: props.mandatoryKeys,
optionalKeys: props.optionalKeys,
numberOfSignatures: props.numberOfSignatures,
},
});
});

it('Should call props.prevStep', () => {
Expand Down
12 changes: 6 additions & 6 deletions src/components/screens/wallet/explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,24 @@ const Wallet = ({
pending={[]}
activeToken={activeToken}
discreetMode={discreetMode}
tabName={t('Transactions')}
tabId="transactions"
name={t('Transactions')}
id="transactions"
address={address}
/>
{activeToken !== 'BTC' ? (
<VotesTab
history={history}
address={address}
tabName={t('Voting')}
tabId="voting"
name={t('Voting')}
id="voting"
/>
) : null}
{isDelegate
? (
<DelegateTab
tabClassName="delegate-statistics"
tabName={t('Delegate profile')}
tabId="delegateProfile"
name={t('Delegate profile')}
id="delegateProfile"
account={account.data}
/>
)
Expand Down
16 changes: 8 additions & 8 deletions src/components/screens/wallet/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,24 +59,24 @@ const Wallet = ({ t, history }) => {
confirmedLength={confirmed.length}
activeToken={activeToken}
discreetMode={discreetMode}
tabName={t('Transactions')}
tabId="Transactions"
name={t('Transactions')}
id="Transactions"
address={address}
/>
{activeToken !== 'BTC' ? (
<VotesTab
history={history}
address={address}
tabName={t('Votes')}
tabId="votes"
name={t('Votes')}
id="votes"
/>
) : null}
{isDelegate
? (
<DelegateTab
tabClassName="delegate-statistics"
tabName={t('Delegate profile')}
tabId="delegateProfile"
name={t('Delegate profile')}
id="delegateProfile"
account={account.info[activeToken]}
/>
)
Expand All @@ -85,8 +85,8 @@ const Wallet = ({ t, history }) => {
? (
<MultiSignatureTab
// tabClassName="delegate-statistics"
tabName={t('Multisignatures')}
tabId="multiSignatures"
name={t('Multisignatures')}
id="multiSignatures"
/>
)
: null} */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const TransactionResult = (props) => {
&& props.status.code !== txStatusTypes.broadcastSuccess
&& (
props.transactions.signedTransaction.signatures.length > 1
|| props.status.code === txStatusTypes.multisigSignaturePartialSuccess
|| props.account.summary.isMultisignature
|| props.account.summary.publicKey !== props.transactions.signedTransaction.senderPublicKey.toString('hex')
);
Expand Down
4 changes: 3 additions & 1 deletion src/components/shared/transactionSummary/footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ const Footer = ({
}) => {
const isMultisignature = !!account.keys?.numberOfSignatures;
const hasSecondPass = account.keys?.numberOfSignatures === 2
&& account.keys.mandatoryKeys.length === 2 && account.keys.optionalKeys.length === 0;
&& account.keys.mandatoryKeys.length === 2
&& account.keys.optionalKeys.length === 0
&& !account.hwInfo;
const [inputStatus, setInputStatus] = useState(hasSecondPass ? 'hidden' : 'notRequired');

return (
Expand Down
14 changes: 7 additions & 7 deletions src/components/toolbox/tabsContainer/tabsContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class TabsContainer extends React.Component {
// eslint-disable-next-line class-methods-use-this
filterChildren(children) {
const _children = (Array.isArray(children) && children.filter(c => c)) || [children];
return _children.filter(tab => !!tab.props.tabId);
return _children.filter(tab => !!tab.props.id);
}

shouldComponentUpdate(nextProps, nextState) {
Expand All @@ -29,7 +29,7 @@ class TabsContainer extends React.Component {

/* istanbul ignore next */
if (nextTabs.length !== currentTabs.length) {
const activeTab = (nextTabs.length > 1 && (this.props.activeTab || nextTabs[0].props.tabId)) || '';
const activeTab = (nextTabs.length > 1 && (this.props.activeTab || nextTabs[0].props.id)) || '';
this.setState({ activeTab });
return false;
}
Expand All @@ -47,7 +47,7 @@ class TabsContainer extends React.Component {

this.setState({
activeTab: (React.Children.count(children) > 1
&& (tab || this.props.activeTab || children[0].props.tabId))
&& (tab || this.props.activeTab || children[0].props.id))
|| '',
});
}
Expand All @@ -60,17 +60,17 @@ class TabsContainer extends React.Component {
<div className={styles.wrapper} name={this.props.name}>
<Switcher
options={React.Children.map(children.filter(React.isValidElement), tab => ({
name: tab.props.tabName,
value: tab.props.tabName,
id: tab.props.tabId,
name: tab.props.name,
value: tab.props.name,
id: tab.props.id,
}))}
active={activeTab}
/>
<div className={styles.contentHolder}>
{React.Children.map(children, tab => (
React.isValidElement(tab)
&& (
<div className={`${tab.props.tabId === activeTab ? styles.active : ''}`}>
<div className={`${tab.props.id === activeTab ? styles.active : ''}`}>
{ tab }
</div>
)
Expand Down
Loading

0 comments on commit 52ace16

Please sign in to comment.