openethereum/js/packages/dapp-wallet/WalletSettings/walletSettingsStore.js

373 lines
9.0 KiB
JavaScript
Raw Normal View History

// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { noop } from 'lodash';
import { observable, computed, action, transaction } from 'mobx';
import BigNumber from 'bignumber.js';
import { validateUint, validateAddress } from '@parity/shared/util/validation';
import { DEFAULT_GAS, MAX_GAS_ESTIMATION } from '@parity/shared/util/constants';
const STEPS = {
EDIT: { title: 'wallet settings' },
CONFIRMATION: { title: 'confirmation' }
};
export default class WalletSettingsStore {
accounts = {};
onClose = noop;
@observable fromString = false;
@observable requests = [];
@observable step = null;
@observable wallet = {
owners: null,
require: null,
dailylimit: null,
sender: ''
};
@observable errors = {
owners: null,
require: null,
dailylimit: null,
sender: null
};
@computed get stage () {
return this.stepsKeys.findIndex((k) => k === this.step);
}
@computed get hasErrors () {
return !!Object.keys(this.errors).find((key) => !!this.errors[key]);
}
@computed get stepsKeys () {
return this.steps.map((s) => s.key);
}
@computed get steps () {
return Object
.keys(STEPS)
.map((key) => {
return {
...STEPS[key],
key
};
});
}
@action
changesFromString (json) {
try {
const data = JSON.parse(json);
const changes = data.map((datum) => {
const [ type, valueStr ] = datum.split(';');
let value;
if (/^#BN#/.test(valueStr)) {
value = new BigNumber(valueStr.replace(/^#BN#/, ''), 16);
} else {
try {
value = JSON.parse(valueStr);
} catch (e) {
value = valueStr;
}
}
return { type, value };
});
this.changes = changes;
} catch (error) {
if (!(error instanceof SyntaxError)) {
console.error('changes from string', error);
}
this.changes = [];
}
}
changesToString () {
const changes = this.changes.map((change) => {
const { type, value } = change;
const valueStr = (value && typeof value.plus === 'function')
? '#BN#' + value.toString(16)
: JSON.stringify(value);
return [
type,
valueStr
].join(';');
});
return JSON.stringify(changes);
}
get changes () {
const changes = [];
const prevDailylimit = new BigNumber(this.initialWallet.dailylimit);
const nextDailylimit = new BigNumber(this.wallet.dailylimit);
const prevRequire = new BigNumber(this.initialWallet.require);
const nextRequire = new BigNumber(this.wallet.require);
if (!prevDailylimit.equals(nextDailylimit)) {
changes.push({
type: 'dailylimit',
initial: prevDailylimit,
value: nextDailylimit
});
}
if (!prevRequire.equals(nextRequire)) {
changes.push({
type: 'require',
initial: prevRequire,
value: nextRequire
});
}
const prevOwners = this.initialWallet.owners;
const nextOwners = this.wallet.owners;
const ownersToRemove = prevOwners.filter((owner) => !nextOwners.includes(owner));
const ownersToAdd = nextOwners.filter((owner) => !prevOwners.includes(owner));
const ownersChangeCount = Math.min(ownersToRemove.length, ownersToAdd.length);
for (let i = 0; i < ownersChangeCount; i++) {
changes.push({
type: 'change_owner',
value: {
from: ownersToRemove[i],
to: ownersToAdd[i]
}
});
}
ownersToRemove.slice(ownersChangeCount).forEach((owner) => {
changes.push({
type: 'remove_owner',
value: owner
});
});
ownersToAdd.slice(ownersChangeCount).forEach((owner) => {
changes.push({
type: 'add_owner',
value: owner
});
});
return changes;
}
set changes (changes) {
transaction(() => {
this.wallet.dailylimit = this.initialWallet.dailylimit;
this.wallet.require = this.initialWallet.require;
this.wallet.owners = this.initialWallet.owners.slice();
changes.forEach((change) => {
const { type, value } = change;
switch (type) {
case 'dailylimit':
this.wallet.dailylimit = value;
break;
case 'require':
this.wallet.require = value;
break;
case 'change_owner':
this.wallet.owners = this.wallet.owners
.filter((owner) => owner !== value.from)
.concat(value.to);
break;
case 'remove_owner':
this.wallet.owners = this.wallet.owners.filter((owner) => owner !== value);
break;
case 'add_owner':
this.wallet.owners.push(value);
break;
}
});
});
}
constructor (api, { onClose, wallet }) {
this.api = api;
this.step = this.stepsKeys[0];
this.walletInstance = wallet.instance;
this.initialWallet = {
address: wallet.address,
owners: wallet.owners,
require: wallet.require,
dailylimit: wallet.dailylimit.limit
};
transaction(() => {
this.wallet.owners = wallet.owners;
this.wallet.require = wallet.require;
this.wallet.dailylimit = wallet.dailylimit.limit;
this.validateWallet(this.wallet);
});
this.onClose = onClose;
}
@action onNext = () => {
const stepIndex = this.stepsKeys.findIndex((k) => k === this.step) + 1;
this.step = this.stepsKeys[stepIndex];
}
@action onChange = (_wallet) => {
const newWallet = Object.assign({}, this.wallet, _wallet);
this.validateWallet(newWallet);
}
@action onOwnersChange = (owners) => {
this.onChange({ owners });
}
@action onRequireChange = (require) => {
this.onChange({ require });
}
@action onSenderChange = (_, sender) => {
this.onChange({ sender });
}
@action onDailylimitChange = (dailylimit) => {
this.onChange({ dailylimit });
}
@action onModificationsStringChange = (event, value) => {
this.changesFromString(value);
if (this.changes && this.changes.length > 0) {
this.fromString = true;
} else {
this.fromString = false;
}
}
@action send = () => {
const changes = this.changes;
const walletInstance = this.walletInstance;
Promise.all(changes.map((change) => this.sendChange(change, walletInstance)));
this.onClose();
}
@action sendChange = (change, walletInstance) => {
const { method, values } = this.getChangeMethod(change, walletInstance);
const options = {
from: this.wallet.sender,
to: this.initialWallet.address,
gas: MAX_GAS_ESTIMATION
};
return method
.estimateGas(options, values)
.then((gasEst) => {
let gas = gasEst;
if (gas.gt(DEFAULT_GAS)) {
gas = gas.mul(1.2);
}
options.gas = gas;
return method.postTransaction(options, values);
});
}
getChangeMethod = (change, walletInstance) => {
if (change.type === 'require') {
return {
method: walletInstance.changeRequirement,
values: [ change.value ]
};
}
if (change.type === 'dailylimit') {
return {
method: walletInstance.setDailyLimit,
values: [ change.value ]
};
}
if (change.type === 'add_owner') {
return {
method: walletInstance.addOwner,
values: [ change.value ]
};
}
if (change.type === 'change_owner') {
return {
method: walletInstance.changeOwner,
values: [ change.value.from, change.value.to ]
};
}
if (change.type === 'remove_owner') {
return {
method: walletInstance.removeOwner,
values: [ change.value ]
};
}
}
@action validateWallet = (_wallet) => {
const senderValidation = validateAddress(_wallet.sender);
const requireValidation = validateUint(_wallet.require);
const dailylimitValidation = validateUint(_wallet.dailylimit);
const errors = {
sender: senderValidation.addressError,
require: requireValidation.valueError,
dailylimit: dailylimitValidation.valueError
};
const wallet = {
..._wallet,
sender: senderValidation.address,
require: requireValidation.value,
dailylimit: dailylimitValidation.value
};
transaction(() => {
this.wallet = wallet;
this.errors = errors;
});
}
}