// 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 . import { noop } from 'lodash'; import { observable, computed, action, transaction } from 'mobx'; import React from 'react'; import { FormattedMessage } from 'react-intl'; import Contract from '@parity/api/contract'; import { toWei } from '@parity/api/util/wei'; import Contracts from '@parity/shared/contracts'; import { wallet as walletAbi } from '@parity/shared/contracts/abi'; import { wallet as walletCode, walletLibrary as walletLibraryCode, walletLibraryRegKey, fullWalletCode } from '@parity/shared/contracts/code/wallet'; import { validateUint, validateAddress, validateName } from '@parity/shared/util/validation'; import { deploy } from '@parity/shared/util/tx'; import WalletsUtils from '@parity/shared/util/wallets'; const STEPS = { TYPE: { title: ( ) }, DETAILS: { title: ( ) }, INFO: { title: ( ) } }; export default class CreateWalletStore { @observable step = null; @observable txhash = null; @observable walletType = 'MULTISIG'; @observable wallet = { account: '', address: '', owners: [], required: 1, daylimit: toWei(1), name: '', description: '' }; @observable errors = { account: null, address: null, owners: null, required: null, daylimit: null, name: null }; onClose = noop; onSetRequest = noop; @computed get stage () { return this.stepsKeys.findIndex((k) => k === this.step); } @computed get hasErrors () { return !!Object.keys(this.errors) .filter((errorKey) => { if (this.walletType === 'WATCH') { return ['address', 'name'].includes(errorKey); } return errorKey !== 'address'; }) .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 }; }) .filter((step) => this.walletType === 'WATCH' || step.key !== 'INFO'); } constructor (api, { accounts, onClose, onSetRequest }) { this.api = api; this.step = this.stepsKeys[0]; this.wallet.account = Object.values(accounts)[0].address; this.validateWallet(this.wallet); this.onClose = onClose; this.onSetRequest = onSetRequest; } @action onTypeChange = (type) => { this.walletType = type; this.validateWallet(this.wallet); } @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 onAdd = () => { if (this.hasErrors) { return; } const walletContract = new Contract(this.api, walletAbi).at(this.wallet.address); return Promise .all([ WalletsUtils.fetchRequire(walletContract), WalletsUtils.fetchOwners(walletContract), WalletsUtils.fetchDailylimit(walletContract) ]) .then(([ require, owners, dailylimit ]) => { transaction(() => { this.wallet.owners = owners; this.wallet.required = require.toNumber(); this.wallet.dailylimit = dailylimit.limit; this.wallet = this.getWalletWithMeta(this.wallet); }); return this.addWallet(this.wallet); }); } @action onCreate = () => { if (this.hasErrors) { return; } const { account, owners, required, daylimit } = this.wallet; Contracts .get() .registry .lookupAddress(walletLibraryRegKey) .catch(() => { return null; // exception when registry is not available }) .then((address) => { console.warn('WalletLibrary address in registry', address); if (!address || /^(0x)?0*$/.test(address)) { return null; } // Check that it's actually the expected code return this.api.eth .getCode(address) .then((code) => { const strippedCode = code.replace(/^0x/, ''); // The actual deployed code is included in the wallet // library code (which might have some more data) if (walletLibraryCode.indexOf(strippedCode) >= 0) { return address; } return null; }); }) .then((address) => { let code = fullWalletCode; if (address) { const walletLibraryAddress = address.replace(/^0x/, '').toLowerCase(); code = walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress); } else { console.warn('wallet library has not been found in the registry'); } const options = { data: code, from: account }; const contract = this.api.newContract(walletAbi); this.wallet = this.getWalletWithMeta(this.wallet); this.onClose(); return deploy(contract, options, [ owners, required, daylimit ]) .then((requestId) => { const metadata = { ...this.wallet.metadata, deployment: true }; this.onSetRequest(requestId, { metadata }, false); }); }); } @action addWallet = (wallet) => { const { address, name, metadata } = wallet; return Promise .all([ this.api.parity.setAccountName(address, name), this.api.parity.setAccountMeta(address, metadata) ]) .then(() => { this.step = 'INFO'; }); } getWalletWithMeta = (wallet) => { const { name, description } = wallet; const metadata = { abi: walletAbi, wallet: true, timestamp: Date.now(), deleted: false, tags: [ 'wallet' ], description, name }; return { ...wallet, metadata }; } @action validateWallet = (_wallet) => { const addressValidation = validateAddress(_wallet.address); const accountValidation = validateAddress(_wallet.account); const requiredValidation = validateUint(_wallet.required); const daylimitValidation = validateUint(_wallet.daylimit); const nameValidation = validateName(_wallet.name); const errors = { address: addressValidation.addressError, account: accountValidation.addressError, required: requiredValidation.valueError, daylimit: daylimitValidation.valueError, name: nameValidation.nameError }; const wallet = { ..._wallet, address: addressValidation.address, account: accountValidation.address, required: requiredValidation.value, daylimit: daylimitValidation.value, name: nameValidation.name }; transaction(() => { this.wallet = wallet; this.errors = errors; }); } }