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

WIP: multisig #549

Closed
wants to merge 1 commit into from
Closed
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 app/background/node/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export class NodeService extends EventEmitter {

this.networkName = networkName;
this.network = network;
this.apiKey = await this.getAPIKey();
this.apiKey = null;// await this.getAPIKey();
this.noDns = await this.getNoDns();
this.spv = await this.getSpvMode();
}
Expand Down
1 change: 1 addition & 0 deletions app/background/wallet/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,5 @@ export const clientStub = ipcRendererInjector => makeClient(ipcRendererInjector,
'isReady',
'createClaim',
'sendClaim',
'addSharedKey'
]);
18 changes: 16 additions & 2 deletions app/background/wallet/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class WalletService {
this.node = plugin;
this.network = plugin.network;
this.networkName = this.network.type;
this.walletApiKey = apiKey;
this.walletApiKey = null;//apiKey;

dispatchToMainWindow({
type: SET_WALLET_NETWORK,
Expand Down Expand Up @@ -342,7 +342,14 @@ class WalletService {
});
};

createNewWallet = async (name, passphrase, isLedger, xPub) => {
createNewWallet = async (
name,
passphrase,
isLedger,
xPub,
m,
n
) => {
this.setWallet(name);

let res;
Expand All @@ -360,6 +367,8 @@ class WalletService {
passphrase,
watchOnly: false,
mnemonic: mnemonic.getPhrase().trim(),
m,
n
});
}

Expand Down Expand Up @@ -800,6 +809,10 @@ class WalletService {
);
};

addSharedKey = async (account, xpub) => {
return this.client.addSharedKey(this.name, account, xpub);
};

isLocked = () => this._ledgerProxy(
() => false,
async () => {
Expand Down Expand Up @@ -1687,6 +1700,7 @@ const methods = {
isReady: service.isReady,
createClaim: service.createClaim,
sendClaim: service.sendClaim,
addSharedKey: service.addSharedKey,
};

export async function start(server) {
Expand Down
1 change: 1 addition & 0 deletions app/components/ReceiveModal/receive.scss
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
font-size: 0.7rem;
font-family: 'Roboto Mono', monospace;
font-weight: 500;
margin: 5px;
}

&__disclaimer {
Expand Down
40 changes: 37 additions & 3 deletions app/components/Sidebar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const nodeClient = clientStub(() => require('electron').ipcRenderer);
rescanHeight: state.wallet.rescanHeight,
address: state.wallet.address,
updateAvailable: state.app.updateAvailable,
accountInfo: state.wallet.accountInfo,
}),
dispatch => ({

Expand All @@ -51,6 +52,7 @@ class Sidebar extends Component {
network: PropTypes.string.isRequired,
address: PropTypes.string.isRequired,
updateAvailable: PropTypes.object,
accountInfo: PropTypes.object.isRequired,
};

static contextType = I18nContext;
Expand All @@ -71,14 +73,46 @@ class Sidebar extends Component {

renderNav() {
const {t} = this.context;
const title = this.props.walletWatchOnly
? `Ledger Wallet (${this.props.walletId})`
: `Wallet (${this.props.walletId})`;
const {watchOnly, type, initialized} = this.props.accountInfo;

let title = 'Wallet';
if (watchOnly)
title = 'Ledger Wallet';
else if (type === 'multisig')
title = 'Multisig Wallet';
title += ` (${this.props.walletId})`;

if (!initialized) {
return(
<React.Fragment>
<div className="sidebar__section">{title}</div>
<div className="sidebar__actions">
<NavLink
className="sidebar__action"
to="/multisig"
activeClassName="sidebar__action--selected"
>
⚠️ Multisig
</NavLink>
</div>
</React.Fragment>
);
}

return (
<React.Fragment>
<div className="sidebar__section">{title}</div>
<div className="sidebar__actions">
{
type === 'multisig' &&
<NavLink
className="sidebar__action"
to="/multisig"
activeClassName="sidebar__action--selected"
>
Multisig
</NavLink>
}
<NavLink
to="/account"
className={isActive => `sidebar__action ${isActive ? "sidebar__action--selected" : ''}`}
Expand Down
11 changes: 8 additions & 3 deletions app/components/Topbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import * as walletActions from '../../ducks/walletActions';
spendableBalance: state.wallet.balance.spendable,
walletId: state.wallet.wid,
walletWatchOnly: state.wallet.watchOnly,
accountInfo: state.wallet.accountInfo,
};
},
dispatch => ({
Expand All @@ -47,6 +48,7 @@ class Topbar extends Component {
lockWallet: PropTypes.func.isRequired,
spendableBalance: PropTypes.number,
walletWatchOnly: PropTypes.bool.isRequired,
accountInfo: PropTypes.object.isRequired,
};

static contextType = I18nContext;
Expand Down Expand Up @@ -121,9 +123,12 @@ class Topbar extends Component {

const { spendableBalance, walletId } = this.props;
const { isShowingSettingMenu } = this.state;
const walletName = this.props.walletWatchOnly
? `${walletId} (Ledger)`
: walletId;
const {watchOnly, type} = this.props.accountInfo;
let walletName = walletId;
if (watchOnly)
walletName += ' (Ledger)';
else if (type === 'multisig')
walletName += ' (Multisig)';

return (
<div
Expand Down
3 changes: 3 additions & 0 deletions app/ducks/walletActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const setWallet = opts => {
changeDepth,
receiveDepth,
accountKey,
accountInfo = {},
} = opts;

return {
Expand All @@ -49,6 +50,7 @@ export const setWallet = opts => {
changeDepth,
receiveDepth,
accountKey,
accountInfo
},
};
};
Expand Down Expand Up @@ -102,6 +104,7 @@ export const fetchWallet = () => async (dispatch, getState) => {
changeDepth: accountInfo.changeDepth,
receiveDepth: accountInfo.receiveDepth,
accountKey: accountInfo.accountKey,
accountInfo, // TODO: remove all the above crap and just pass account object
}));
};

Expand Down
1 change: 1 addition & 0 deletions app/ducks/walletReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export default function walletReducer(state = getInitialState(), {type, payload}
receiveDepth: payload.receiveDepth,
accountKey: payload.accountKey,
initialized: typeof payload.initialized === 'undefined' ? state.initialized : payload.initialized,
accountInfo: payload.accountInfo,
};
case SET_BALANCE:
return {
Expand Down
7 changes: 7 additions & 0 deletions app/pages/Account/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const analytics = aClientStub(() => require("electron").ipcRenderer);
isFetching: state.wallet.isFetching,
network: state.wallet.network,
hnsPrice: state.node.hnsPrice,
accountInfo: state.wallet.accountInfo,
}),
(dispatch) => ({
fetchWallet: () => dispatch(walletActions.fetchWallet()),
Expand Down Expand Up @@ -62,6 +63,7 @@ export default class Account extends Component {
finalizeMany: PropTypes.func.isRequired,
renewMany: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
accountInfo: PropTypes.object.isRequired,
};

static contextType = I18nContext;
Expand All @@ -86,6 +88,11 @@ export default class Account extends Component {
constructor(props) {
super(props);
this.updateStatsAndBalance = throttle(this.updateStatsAndBalance, 15000, { trailing: true });

if ( this.props.accountInfo.type === 'multisig'
&& !this.props.accountInfo.initialized) {
this.props.history.push('/multisig');
}
}

componentDidMount() {
Expand Down
7 changes: 7 additions & 0 deletions app/pages/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import Exchange from '../Exchange';
import SignMessage from "../SignMessage";
import VerifyMessage from "../VerifyMessage";
import {fetchLocale, initHip2, checkForUpdates} from "../../ducks/app";
import Multisig from "../Multisig";
import {I18nContext} from "../../utils/i18n";
const connClient = cClientStub(() => require('electron').ipcRenderer);
const settingClient = sClientStub(() => require('electron').ipcRenderer);
Expand Down Expand Up @@ -251,6 +252,12 @@ class App extends Component {
path="/exchange"
render={this.routeRenderer(t('headingExchange'), Exchange, true)}
/>
<ProtectedRoute
isLocked={this.props.isLocked}
wallets={this.props.wallets}
path="/multisig"
render={this.routeRenderer('Multisig', Multisig, true)}
/>
<Redirect to="/login" />
</Switch>
</>
Expand Down
137 changes: 137 additions & 0 deletions app/pages/Multisig/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import { connect } from "react-redux";
import {clientStub as wClientStub} from "../../background/wallet/client";
import * as networks from "hsd/lib/protocol/networks";
import { clientStub as aClientStub } from "../../background/analytics/client";
import * as walletActions from "../../ducks/walletActions";
import { showError, showSuccess } from "../../ducks/notifications";
import * as nameActions from "../../ducks/names";
import * as nodeActions from "../../ducks/node";
import {HeaderItem, HeaderRow, Table, TableItem, TableRow} from "../../components/Table";
import CopyButton from "../../components/CopyButton";

// I'm getting pretty tired of creating brand new stylesheets
// for every single view!
import "../../components/NameClaimModal/name-claim-modal.scss";

const walletClient = wClientStub(() => require('electron').ipcRenderer);


@withRouter
@connect(
(state) => ({
accountInfo: state.wallet.accountInfo,
wallet: state.wallet,
}),
(dispatch) => ({
fetchWallet: () => dispatch(walletActions.fetchWallet()),
})
)
export default class Multisig extends Component {
static propTypes = {
accountInfo: PropTypes.object.isRequired,
wallet: PropTypes.object.isRequired,
fetchWallet: PropTypes.func.isRequired,
};

constructor(props) {
super(props);
}

render() {
return (
<div className="name-claim__claim-content">
<div>
Multisig wallet policy: {this.props.accountInfo.m}-of-{this.props.accountInfo.n}
</div>
<Table className="name-claim__claim-content__txt-table">
<HeaderRow>
<HeaderItem width="25%">Signer</HeaderItem>
<HeaderItem>Account Key (xpub)</HeaderItem>
</HeaderRow>

<TableRow>
<TableItem width="25%">{this.props.wallet.wid} (me)</TableItem>
<TableItem className="wrap">
{this.props.accountInfo.accountKey} <CopyButton content={this.props.accountInfo.accountKey} />
</TableItem>
</TableRow>

{this.renderOtherSigners()}

</Table>
</div>
);
}

renderOtherSigners() {
const rows = this.props.accountInfo.n - 1; // Our own key is already there
const keys = this.props.accountInfo.keys;
const out = [];

for (let i = 0; i < rows; i++) {
let key = keys[i];

if (!key) {
key =
<KeyInput
fetchWallet={this.props.fetchWallet}
/>
}

out.push(
<TableRow>
<TableItem width="25%">Signer #{i+2}</TableItem>
<TableItem>
{key}
</TableItem>
</TableRow>
);
}

return out;
}
}

class KeyInput extends Component {
static propTypes = {
fetchWallet: PropTypes.func,
}

state = {
errorMessage: null,
}

async addKey(xpub) {
this.setState({errorMessage: null})
try {
await walletClient.addSharedKey('default', xpub); // account, xpub
await this.props.fetchWallet();
} catch (e) {console.log(e)
this.setState({errorMessage: e.message});
}
}

handleChange = () => {
this.setState({errorMessage: null});
}

render() {
return (
<div>
<input
type="text"
className="name-claim__name-input-group__input"
onChange={this.handleChange}
onKeyDown={e => e.key === 'Enter' && this.addKey(e.target.value)}
placeholder="xpub..."
/>
<div className="login_password_error">
{this.state.errorMessage}
</div>
</div>
);
}
}
Loading