Add functionalities to multi-sig wallet (#3729)

* WIP Sending tokens in multi-sig wallet

* Working Token transfer for multi-sig wallet #3282

* Add operation hash to transfer modal

* Add existing wallet from address #3282

* Wallet delete redirect to Wallets/Accounts #3282

* Rightly check balance in Transfer // Get all accounts balances #3282

* Fix linting

* Better Header UI for Wallet

* Use the `~` webpack alias

* Use Webpack `~` alias
This commit is contained in:
Nicolas Gotchac
2016-12-07 12:47:44 +01:00
committed by Jaco Greeff
parent be90245ecb
commit 8dbd56888d
36 changed files with 857 additions and 322 deletions

View File

@@ -239,11 +239,7 @@ export default class Details extends Component {
}
renderTokenSelect () {
const { balance, images, tag, wallet } = this.props;
if (wallet) {
return null;
}
const { balance, images, tag } = this.props;
return (
<TokenSelect

View File

@@ -16,7 +16,11 @@
import { observable, computed, action, transaction } from 'mobx';
import BigNumber from 'bignumber.js';
import { uniq } from 'lodash';
import { wallet as walletAbi } from '~/contracts/abi';
import { bytesToHex } from '~/api/util/format';
import Contract from '~/api/contract';
import ERRORS from './errors';
import { ERROR_CODES } from '~/api/transport/error';
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '../../util/constants';
@@ -71,6 +75,9 @@ export default class TransferStore {
gasLimit = null;
onClose = null;
senders = null;
sendersBalances = null;
isWallet = false;
wallet = null;
@@ -108,19 +115,23 @@ export default class TransferStore {
constructor (api, props) {
this.api = api;
const { account, balance, gasLimit, senders, onClose } = props;
const { account, balance, gasLimit, senders, onClose, newError, sendersBalances } = props;
this.account = account;
this.balance = balance;
this.gasLimit = gasLimit;
this.onClose = onClose;
this.isWallet = account && account.wallet;
this.newError = newError;
if (this.isWallet) {
this.wallet = props.wallet;
this.walletContract = new Contract(this.api, walletAbi);
}
if (senders) {
this.senders = senders;
this.sendersBalances = sendersBalances;
this.senderError = ERRORS.requireSender;
}
}
@@ -217,6 +228,10 @@ export default class TransferStore {
this.txhash = txhash;
this.busyState = 'Your transaction has been posted to the network';
});
if (this.isWallet) {
return this._attachWalletOperation(txhash);
}
})
.catch((error) => {
this.sending = false;
@@ -224,6 +239,34 @@ export default class TransferStore {
});
}
@action _attachWalletOperation = (txhash) => {
let ethSubscriptionId = null;
return this.api.subscribe('eth_blockNumber', () => {
this.api.eth
.getTransactionReceipt(txhash)
.then((tx) => {
if (!tx) {
return;
}
const logs = this.walletContract.parseEventLogs(tx.logs);
const operations = uniq(logs
.filter((log) => log && log.params && log.params.operation)
.map((log) => bytesToHex(log.params.operation.value)));
if (operations.length > 0) {
this.operation = operations[0];
}
this.api.unsubscribe(ethSubscriptionId);
ethSubscriptionId = null;
});
}).then((subId) => {
ethSubscriptionId = subId;
});
}
@action _onUpdateAll = (valueAll) => {
this.valueAll = valueAll;
this.recalculateGas();
@@ -355,19 +398,29 @@ export default class TransferStore {
}
@action recalculate = () => {
const { account, balance } = this;
const { account } = this;
if (!account || !balance) {
if (!account || !this.balance) {
return;
}
const balance = this.senders
? this.sendersBalances[this.sender]
: this.balance;
if (!balance) {
return;
}
const { gas, gasPrice, tag, valueAll, isEth } = this;
const gasTotal = new BigNumber(gasPrice || 0).mul(new BigNumber(gas || 0));
const balance_ = balance.tokens.find((b) => tag === b.token.tag);
const availableEth = new BigNumber(balance.tokens[0].value);
const available = new BigNumber(balance_.value);
const format = new BigNumber(balance_.token.format || 1);
const senderBalance = this.balance.tokens.find((b) => tag === b.token.tag);
const available = new BigNumber(senderBalance.value);
const format = new BigNumber(senderBalance.token.format || 1);
let { value, valueError } = this;
let totalEth = gasTotal;
@@ -409,26 +462,52 @@ export default class TransferStore {
return this._getTransferMethod().postTransaction(options, values);
}
estimateGas () {
const { options, values } = this._getTransferParams(true);
return this._getTransferMethod(true).estimateGas(options, values);
_estimateGas (forceToken = false) {
const { options, values } = this._getTransferParams(true, forceToken);
return this._getTransferMethod(true, forceToken).estimateGas(options, values);
}
_getTransferMethod (gas = false) {
estimateGas () {
if (this.isEth || !this.isWallet) {
return this._estimateGas();
}
return Promise
.all([
this._estimateGas(true),
this._estimateGas()
])
.then((results) => results[0].plus(results[1]));
}
_getTransferMethod (gas = false, forceToken = false) {
const { isEth, isWallet } = this;
if (isEth && !isWallet) {
if (isEth && !isWallet && !forceToken) {
return gas ? this.api.eth : this.api.parity;
}
if (isWallet) {
if (isWallet && !forceToken) {
return this.wallet.instance.execute;
}
return this.token.contract.instance.transfer;
}
_getTransferParams (gas = false) {
_getData (gas = false) {
const { isEth, isWallet } = this;
if (!isWallet || isEth) {
return this.data && this.data.length ? this.data : '';
}
const func = this._getTransferMethod(gas, true);
const { options, values } = this._getTransferParams(gas, true);
return this.token.contract.getCallData(func, options, values);
}
_getTransferParams (gas = false, forceToken = false) {
const { isEth, isWallet } = this;
const to = (isEth && !isWallet) ? this.recipient
@@ -446,27 +525,30 @@ export default class TransferStore {
options.gas = MAX_GAS_ESTIMATION;
}
if (isEth && !isWallet) {
if (isEth && !isWallet && !forceToken) {
options.value = this.api.util.toWei(this.value || 0);
if (this.data && this.data.length) {
options.data = this.data;
}
options.data = this._getData(gas);
return { options, values: [] };
}
const values = isWallet
? [
this.recipient,
this.api.util.toWei(this.value || 0),
this.data || ''
]
: [
this.recipient,
new BigNumber(this.value || 0).mul(this.token.format).toFixed(0)
if (isWallet && !forceToken) {
const to = isEth ? this.recipient : this.token.contract.address;
const value = isEth ? this.api.util.toWei(this.value || 0) : new BigNumber(0);
const values = [
to, value,
this._getData(gas)
];
return { options, values };
}
const values = [
this.recipient,
new BigNumber(this.value || 0).mul(this.token.format).toFixed(0)
];
return { options, values };
}

View File

@@ -18,6 +18,7 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { observer } from 'mobx-react';
import { pick } from 'lodash';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
@@ -25,7 +26,7 @@ import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
import { newError } from '~/ui/Errors/actions';
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '~/ui';
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash, Input } from '~/ui';
import { nullableProptype } from '~/util/proptypes';
import Details from './Details';
@@ -45,10 +46,10 @@ class Transfer extends Component {
gasLimit: PropTypes.object.isRequired,
images: PropTypes.object.isRequired,
account: PropTypes.object,
senders: nullableProptype(PropTypes.object),
sendersBalances: nullableProptype(PropTypes.object),
account: PropTypes.object,
balance: PropTypes.object,
balances: PropTypes.object,
wallet: PropTypes.object,
onClose: PropTypes.func
}
@@ -133,6 +134,25 @@ class Transfer extends Component {
return (
<CompletedStep>
<TxHash hash={ txhash } />
{
this.store.operation
? (
<div>
<br />
<p>
This transaction needs confirmation from other owners.
<Input
style={ { width: '50%', margin: '0 auto' } }
value={ this.store.operation }
label='operation hash'
readOnly
allowCopy
/>
</p>
</div>
)
: null
}
</CompletedStep>
);
}
@@ -277,7 +297,9 @@ function mapStateToProps (initState, initProps) {
return (state) => {
const { gasLimit } = state.nodeStatus;
return { gasLimit, wallet, senders };
const sendersBalances = senders ? pick(state.balances.balances, Object.keys(senders)) : null;
return { gasLimit, wallet, senders, sendersBalances };
};
}