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

Add HW multisig support - Closes #3954 #3997

Merged
merged 27 commits into from
Jan 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5ab7cbc
Move sign tx functions to utils
reyraa Dec 5, 2021
b3595a4
Use Sign function in multisigTransactionSigned action
reyraa Dec 5, 2021
80dbe20
Adapt signTransaction to use the new Sign function
reyraa Dec 5, 2021
b94497d
Disambiguate the function name
reyraa Dec 5, 2021
4236eeb
Fix validation in case members are all optional
reyraa Dec 5, 2021
7844fbf
Update the use of signMultisigTransaction for signing with second pas…
reyraa Dec 5, 2021
8fd37b9
Fix noop warnings
reyraa Dec 6, 2021
b891116
Fix unrecognized properties
reyraa Dec 6, 2021
571a7dd
Remove logs
reyraa Dec 27, 2021
f241932
Remove unused imports
reyraa Dec 27, 2021
cee2bfe
Fix the signature index
reyraa Dec 27, 2021
7c143eb
Use constants
reyraa Dec 27, 2021
cbaadbc
Fix broken unit tests
reyraa Dec 27, 2021
81f9c86
Hide second pass input for HW accounts
reyraa Dec 27, 2021
4172f05
Do not show second pass for HW
reyraa Dec 29, 2021
25531a1
Remove networkIdentifier from parameters
reyraa Dec 29, 2021
68220ed
:recycle: Handle multi-signature inclusion logic for HW
Jan 3, 2022
546b9ac
:recycle: Refactor HW updateTransactionSignatures logic
Jan 3, 2022
08edb03
Fix normal tx signature using HW
reyraa Jan 6, 2022
34b8a9f
Update unit tests
reyraa Jan 6, 2022
a539bfa
Fix failing unit tests
reyraa Jan 7, 2022
be1ea1f
fixed failing test on search.feature
eniolam1000752 Jan 7, 2022
6ebd163
Add signature pending step to multisg registration
reyraa Jan 9, 2022
4e50e48
Copy sender signature to members slot
reyraa Jan 9, 2022
96e4f08
Update unit tests
reyraa Jan 9, 2022
326abd1
Fix eslint issues
reyraa Jan 9, 2022
0b88772
:recycle: Handle public key check to always be buffer
Jan 9, 2022
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
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