openethereum/js/packages/dapp-accounts/CreateAccount/store.js
2017-07-24 17:48:25 +02:00

514 lines
13 KiB
JavaScript

// 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 { action, computed, observable, transaction } from 'mobx';
import apiutil from '@parity/api/util';
import ERRORS from './errors';
const FAKEPATH = 'C:\\fakepath\\';
const STAGE_SELECT_TYPE = 0;
const STAGE_CREATE = 1;
const STAGE_INFO = 2;
const STAGE_CONFIRM_BACKUP = 3;
export default class Store {
@observable accounts = null;
@observable address = null;
@observable createType = 'fromNew';
@observable description = '';
@observable gethAccountsAvailable = [];
@observable gethAddresses = [];
@observable gethImported = [];
@observable isBusy = false;
@observable isWindowsPhrase = false;
@observable name = '';
@observable nameError = ERRORS.noName;
@observable password = '';
@observable passwordHint = '';
@observable passwordRepeat = '';
@observable phrase = '';
@observable backupPhraseAddress = null;
@observable phraseBackedUp = '';
@observable qrAddress = null;
@observable rawKey = '';
@observable rawKeyError = ERRORS.nokey;
@observable stage = STAGE_SELECT_TYPE;
@observable vaultName = '';
@observable walletFile = '';
@observable walletFileError = ERRORS.noFile;
@observable walletJson = '';
constructor (api, accounts, isTest, loadGeth = true) {
this._api = api;
this.accounts = Object.assign({}, accounts);
this.isTest = isTest;
if (loadGeth) {
this.loadAvailableGethAccounts();
}
}
@computed get canCreate () {
switch (this.createType) {
case 'fromGeth':
return this.gethAddresses.length !== 0;
case 'fromJSON':
case 'fromPresale':
return !(this.nameError || this.walletFileError);
case 'fromNew':
return !(this.nameError || this.passwordRepeatError) && this.hasAddress && this.hasPhrase;
case 'fromPhrase':
return !(this.nameError || this.passwordRepeatError || this.passPhraseError);
case 'fromQr':
return this.qrAddressValid && !this.nameError;
case 'fromRaw':
return !(this.nameError || this.passwordRepeatError || this.rawKeyError);
default:
return false;
}
}
@computed get passPhraseError () {
return !this.isTest && this.phrase.length === 0;
}
@computed get hasAddress () {
return !!(this.address);
}
@computed get hasPhrase () {
return this.phrase.length !== 0;
}
@computed get passwordRepeatError () {
return this.password === this.passwordRepeat
? null
: ERRORS.noMatchPassword;
}
@computed get backupPhraseError () {
return !this.backupPhraseAddress || this.address === this.backupPhraseAddress
? null
: ERRORS.noMatchBackupPhrase;
}
@computed get phraseBackedUpError () {
return this.phraseBackedUp === 'I have written down the phrase'
? null
: ERRORS.noMatchPhraseBackedUp;
}
@computed get qrAddressValid () {
console.log('qrValid', this.qrAddress, this._api.util.isAddressValid(this.qrAddress));
return this._api.util.isAddressValid(this.qrAddress);
}
@action clearPhrase = () => {
transaction(() => {
this.phrase = '';
this.phraseBackedUp = '';
});
}
@action clearErrors = () => {
transaction(() => {
this.address = '';
this.description = '';
this.password = '';
this.passwordRepeat = '';
this.phrase = '';
this.backupPhraseAddress = null;
this.phraseBackedUp = '';
this.name = '';
this.nameError = ERRORS.noName;
this.qrAddress = null;
this.rawKey = '';
this.rawKeyError = null;
this.vaultName = '';
this.walletFile = '';
this.walletFileError = null;
this.walletJson = '';
});
}
@action selectGethAccount = (address) => {
if (this.gethAddresses.includes(address)) {
this.gethAddresses = this.gethAddresses.filter((_address) => _address !== address);
} else {
this.gethAddresses = [address].concat(this.gethAddresses.peek());
}
}
@action setAddress = (address) => {
this.address = address;
}
@action setBusy = (isBusy) => {
this.isBusy = isBusy;
}
@action setCreateType = (createType) => {
this.clearErrors();
this.createType = createType;
}
@action setDescription = (description) => {
this.description = description;
}
@action setGethAccountsAvailable = (gethAccountsAvailable) => {
this.gethAccountsAvailable = [].concat(gethAccountsAvailable);
}
@action setGethImported = (gethImported) => {
this.gethImported = gethImported;
}
@action setQrAddress = (qrAddress) => {
if (qrAddress && qrAddress.substr(0, 2) !== '0x') {
qrAddress = `0x${qrAddress}`;
}
// FIXME: Current native signer encoding is not 100% for EIP-55, lowercase for now
this.qrAddress = qrAddress && this._api.util
? this._api.util.toChecksumAddress(qrAddress.toLowerCase())
: qrAddress;
}
@action setVaultName = (vaultName) => {
this.vaultName = vaultName;
}
@action setWindowsPhrase = (isWindowsPhrase = false) => {
this.isWindowsPhrase = isWindowsPhrase;
}
@action setName = (name) => {
let nameError = null;
if (!name || !name.trim().length) {
nameError = ERRORS.noName;
}
transaction(() => {
this.name = name;
this.nameError = nameError;
});
}
@action setBackupPhraseAddress = (address) => {
this.backupPhraseAddress = address;
}
@action computeBackupPhraseAddress = () => {
return this._api.parity.phraseToAddress(this.phrase)
.then(address => {
this.setBackupPhraseAddress(address);
return address !== this.address;
})
.catch((error) => {
console.error('createAccount', error);
throw error;
});
}
@action setPhraseBackedUp = (backedUp) => {
this.phraseBackedUp = backedUp;
}
@action setPassword = (password) => {
this.password = password;
}
@action setPasswordHint = (passwordHint) => {
this.passwordHint = passwordHint;
}
@action setPasswordRepeat = (passwordRepeat) => {
this.passwordRepeat = passwordRepeat;
}
@action setPhrase = (phrase) => {
const recoveryPhrase = phrase
.toLowerCase() // wordlists are lowercase
.trim() // remove whitespace at both ends
.replace(/\s/g, ' ') // replace any whitespace with single space
.replace(/ +/g, ' '); // replace multiple spaces with a single space
const phraseParts = recoveryPhrase
.split(' ')
.map((part) => part.trim())
.filter((part) => part.length);
this.phrase = phraseParts.join(' ');
this.backupPhraseAddress = null;
}
@action setRawKey = (rawKey) => {
let rawKeyError = null;
if (!rawKey || !rawKey.trim().length) {
rawKeyError = ERRORS.noKey;
} else if (rawKey.substr(0, 2) !== '0x' || rawKey.substr(2).length !== 64 || !apiutil.isHex(rawKey)) {
rawKeyError = ERRORS.invalidKey;
}
transaction(() => {
this.rawKey = rawKey;
this.rawKeyError = rawKeyError;
});
}
@action setStage = (stage) => {
this.stage = stage;
}
@action setWalletFile = (walletFile) => {
transaction(() => {
this.walletFile = walletFile.replace(FAKEPATH, '');
this.walletFileError = ERRORS.noFile;
this.walletJson = null;
});
}
@action setWalletJson = (walletJson) => {
transaction(() => {
this.walletFileError = null;
this.walletJson = walletJson;
});
}
@action nextStage = () => {
if (this.stage === 0) {
this.clearErrors();
}
this.stage++;
}
@action prevStage = () => {
this.stage--;
}
createAccount = (vaultStore) => {
if (!this.canCreate) {
return false;
}
this.setBusy(true);
return this
._createAccount()
.then(() => {
if (vaultStore && this.vaultName && this.vaultName.length) {
return vaultStore.moveAccount(this.vaultName, this.address);
}
return true;
})
.then(() => {
this.setBusy(false);
})
.catch((error) => {
this.setBusy(false);
throw error;
});
}
_createAccount = () => {
switch (this.createType) {
case 'fromGeth':
return this.createAccountFromGeth();
case 'fromJSON':
case 'fromPresale':
return this.createAccountFromWallet();
case 'fromNew':
case 'fromPhrase':
return this.createAccountFromPhrase();
case 'fromQr':
return this.createAccountFromQr();
case 'fromRaw':
return this.createAccountFromRaw();
default:
throw new Error(`Cannot create account for ${this.createType}`);
}
}
createAccountFromGeth = (timestamp = Date.now()) => {
return this._api.parity
.importGethAccounts(this.gethAddresses.peek())
.then((gethImported) => {
console.log('createAccountFromGeth', gethImported);
this.setName('Geth Import');
this.setDescription('Imported from Geth keystore');
this.setGethImported(gethImported);
return Promise.all(gethImported.map((address) => {
return this.setupMeta(address, timestamp);
}));
})
.catch((error) => {
console.error('createAccountFromGeth', error);
throw error;
});
}
createAccountFromPhrase = (timestamp = Date.now()) => {
let formattedPhrase = this.phrase;
if (this.isWindowsPhrase && this.createType === 'fromPhrase') {
formattedPhrase = this.phrase
.split(' ') // get the words
.map((word) => word === 'misjudged' ? word : `${word}\r`) // add \r after each (except last in dict)
.join(' '); // re-create string
}
return this._api.parity
.newAccountFromPhrase(formattedPhrase, this.password)
.then((address) => {
this.setAddress(address);
return this.setupMeta(address, timestamp);
})
.catch((error) => {
console.error('createAccount', error);
throw error;
});
}
createAccountFromQr = (timestamp = Date.now()) => {
this.setAddress(this.qrAddress);
return this.setupMeta(this.qrAddress, timestamp, { external: true });
}
createAccountFromRaw = (timestamp = Date.now()) => {
return this._api.parity
.newAccountFromSecret(this.rawKey, this.password)
.then((address) => {
this.setAddress(address);
return this.setupMeta(address, timestamp);
})
.catch((error) => {
console.error('createAccount', error);
throw error;
});
}
createAccountFromWallet = (timestamp = Date.now()) => {
return this._api.parity
.newAccountFromWallet(this.walletJson, this.password)
.then((address) => {
this.setAddress(address);
return this.setupMeta(address, timestamp);
})
.catch((error) => {
console.error('createAccount', error);
throw error;
});
}
setupMeta = (address, timestamp = Date.now(), extra = {}) => {
const meta = Object.assign({}, extra, {
description: this.description,
passwordHint: this.passwordHint,
timestamp
});
return this._api.parity
.setAccountName(address, this.name)
.then(() => this._api.parity.setAccountMeta(address, meta));
}
createIdentities = () => {
return Promise
.all([
this._api.parity.generateSecretPhrase(),
this._api.parity.generateSecretPhrase(),
this._api.parity.generateSecretPhrase(),
this._api.parity.generateSecretPhrase(),
this._api.parity.generateSecretPhrase(),
this._api.parity.generateSecretPhrase(),
this._api.parity.generateSecretPhrase()
])
.then((phrases) => {
return Promise
.all(phrases.map((phrase) => this._api.parity.phraseToAddress(phrase)))
.then((addresses) => {
return phrases.reduce((accounts, phrase, index) => {
const address = addresses[index];
accounts[address] = {
address,
phrase
};
return accounts;
}, {});
});
})
.catch((error) => {
console.error('createIdentities', error);
throw error;
});
}
loadAvailableGethAccounts () {
return this._api.parity
.listGethAccounts()
.then((_addresses) => {
const addresses = (_addresses || []).filter((address) => !this.accounts[address]);
return Promise
.all(addresses.map((address) => this._api.eth.getBalance(address)))
.then((balances) => {
this.setGethAccountsAvailable(addresses.map((address, index) => {
return {
address,
balance: apiutil.fromWei(balances[index]).toFormat(5)
};
}));
});
})
.catch((error) => {
console.warn('loadAvailableGethAccounts', error);
});
}
}
export {
STAGE_INFO,
STAGE_CONFIRM_BACKUP,
STAGE_CREATE,
STAGE_SELECT_TYPE
};