@colony/purser-software
Version:
A javascript library to interact with a software Ethereum wallet, based on the ethers.js library
272 lines (246 loc) • 10 kB
JavaScript
import _regeneratorRuntime from "@babel/runtime/regenerator";
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
import { Wallet as EthersWallet } from 'ethers/wallet';
import { isValidMnemonic, fromMnemonic } from 'ethers/utils/hdnode';
import { isSecretStorageWallet } from 'ethers/utils/json-wallet';
import { derivationPathSerializer, userInputValidator } from '@colony/purser-core/helpers';
import { objectToErrorString, getRandomValues, warning } from '@colony/purser-core/utils';
import { PATH, CHAIN_IDS } from '@colony/purser-core/defaults';
import SoftwareWallet from './class';
import { REQUIRED_PROPS as REQUIRED_PROPS_SOFTWARE } from './defaults';
import { staticMethods as messages } from './messages';
/**
* Open an existing wallet
* Using either `mnemonic`, `private key` or `encrypted keystore`
*
* This will try to extract the private key from a mnemonic (if available),
* and create a new SoftwareWallet instance using whichever key is available.
* (the on passed in or the one extracted from the mnemonic).
*
* @TODO Reduce code repetition
*
* With some clever refactoring we might be able to only call the SoftwareWallet
* constructor a single time for all three methods of opening the wallet
*
* @method open
*
* @param {string} password Optional password used to generate an encrypted keystore
* @param {string} privateKey Private key to open the wallet with
* @param {string} mnemonic Mnemonic string to open the wallet with
* @param {string} keystore JSON formatted keystore string to open the wallet with.
* Only works if you also send in a password
* @param {number} chainId The id of the network to use, defaults to mainnet (1)
*
* All the above params are sent in as props of an {WalletArgumentsType} object.
*
* @return {WalletType} A new wallet object (or undefined) if somehwere along
* the line an error is thrown.
*/
export var open =
/*#__PURE__*/
function () {
var _ref = _asyncToGenerator(
/*#__PURE__*/
_regeneratorRuntime.mark(function _callee() {
var argumentObject,
password,
privateKey,
mnemonic,
keystore,
_argumentObject$chain,
chainId,
extractedPrivateKey,
derivationPath,
keystoreWallet,
mnemonicWallet,
privateKeyWallet,
_args = arguments;
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
argumentObject = _args.length > 0 && _args[0] !== undefined ? _args[0] : {};
/*
* Validate the trasaction's object input
*/
userInputValidator({
firstArgument: argumentObject,
requiredEither: REQUIRED_PROPS_SOFTWARE.OPEN_WALLET
});
password = argumentObject.password, privateKey = argumentObject.privateKey, mnemonic = argumentObject.mnemonic, keystore = argumentObject.keystore, _argumentObject$chain = argumentObject.chainId, chainId = _argumentObject$chain === void 0 ? CHAIN_IDS.HOMESTEAD : _argumentObject$chain;
/*
* @TODO Re-add use ability to control derivation path
* When opening the wallet. But only if this proves to be a needed feature.
*/
derivationPath = derivationPathSerializer({
change: PATH.CHANGE,
addressIndex: PATH.INDEX
});
_context.prev = 4;
if (!(keystore && isSecretStorageWallet(keystore) && password)) {
_context.next = 13;
break;
}
_context.next = 8;
return EthersWallet.fromEncryptedJson(keystore, password);
case 8:
keystoreWallet = _context.sent;
/*
* Set the keystore and password props on the instance object.
*
* So that we can make use of them inside the SoftwareWallet
* constructor, as the Ethers Wallet instance object will
* be passed down.
*
* @TODO Better passing of values
*
* This needs to be refactored to pass values to the SoftwareWallet
* class in a less repetitious way
*/
keystoreWallet.keystore = keystore;
keystoreWallet.password = password;
keystoreWallet.chainId = chainId;
return _context.abrupt("return", new SoftwareWallet(keystoreWallet));
case 13:
/*
* @TODO Detect if existing but not valid mnemonic, and warn the user
*/
if (mnemonic && isValidMnemonic(mnemonic)) {
mnemonicWallet = fromMnemonic(mnemonic).derivePath(derivationPath);
extractedPrivateKey = mnemonicWallet.privateKey;
}
/*
* @TODO Detect if existing but not valid private key, and warn the user
*/
privateKeyWallet = new EthersWallet(privateKey || extractedPrivateKey);
/*
* Set the mnemonic and password props on the instance object.
*
* So that we can make use of them inside the SoftwareWallet
* constructor, as the Ethers Wallet instance object will
* be passed down.
*
* @TODO Better passing of values
*
* This needs to be refactored to pass values to the SoftwareWallet
* class in a less repetitious way
*/
privateKeyWallet.password = password;
privateKeyWallet.chainId = chainId;
/*
* @NOTE mnemonic prop was renamed due to naming conflict with getter-only
* ethers prop
*/
privateKeyWallet.originalMnemonic = mnemonic;
return _context.abrupt("return", new SoftwareWallet(privateKeyWallet));
case 21:
_context.prev = 21;
_context.t0 = _context["catch"](4);
throw new Error("".concat(messages.open, " ").concat(objectToErrorString({
password: password,
privateKey: privateKey,
mnemonic: mnemonic,
keystore: keystore
}), " Error: ").concat(_context.t0.message));
case 24:
case "end":
return _context.stop();
}
}
}, _callee, this, [[4, 21]]);
}));
return function open() {
return _ref.apply(this, arguments);
};
}();
/**
* Create a new wallet.
*
* This will use EtherWallet's `createRandom()` (with defaults and entropy)
* and use the resulting private key to instantiate a new SoftwareWallet.
*
* @method create
*
* @param {Uint8Array} entropy An unsigned 8bit integer Array to provide extra randomness
* @param {string} password Optional password used to generate an encrypted keystore
* @param {number} chainId The id of the network to use, defaults to mainnet (1)
*
* All the above params are sent in as props of an {WalletArgumentsType} object.
*
* @return {WalletType} A new wallet object
*/
export var create =
/*#__PURE__*/
function () {
var _ref2 = _asyncToGenerator(
/*#__PURE__*/
_regeneratorRuntime.mark(function _callee2() {
var argumentObject,
password,
_argumentObject$entro,
entropy,
_argumentObject$chain2,
chainId,
basicWallet,
_args2 = arguments;
return _regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
argumentObject = _args2.length > 0 && _args2[0] !== undefined ? _args2[0] : {};
/*
* Validate the trasaction's object input
*/
userInputValidator({
firstArgument: argumentObject
});
password = argumentObject.password, _argumentObject$entro = argumentObject.entropy, entropy = _argumentObject$entro === void 0 ? getRandomValues(new Uint8Array(65536)) : _argumentObject$entro, _argumentObject$chain2 = argumentObject.chainId, chainId = _argumentObject$chain2 === void 0 ? CHAIN_IDS.HOMESTEAD : _argumentObject$chain2;
_context2.prev = 3;
if (!entropy || entropy && !(entropy instanceof Uint8Array)) {
warning(messages.noEntrophy);
basicWallet = EthersWallet.createRandom();
} else {
basicWallet = EthersWallet.createRandom({
extraEntropy: entropy
});
}
/*
* Set the password prop on the instance object.
*
* So that we can make use of them inside the SoftwareWallet
* constructor, as the Ethers Wallet instance object will
* be passed down.
*
* @TODO Better passing of values
*
* This needs to be refactored to pass values to the SoftwareWallet
* class in a less repetitious way
*/
basicWallet.password = password;
basicWallet.chainId = chainId;
/*
* @NOTE mnemonic prop was renamed due to naming conflict with getter-only
* ethers prop
*/
basicWallet.originalMnemonic = basicWallet.mnemonic;
return _context2.abrupt("return", new SoftwareWallet(basicWallet));
case 11:
_context2.prev = 11;
_context2.t0 = _context2["catch"](3);
throw new Error("".concat(messages.create, " Error: ").concat(_context2.t0.message));
case 14:
case "end":
return _context2.stop();
}
}
}, _callee2, this, [[3, 11]]);
}));
return function create() {
return _ref2.apply(this, arguments);
};
}();
var softwareWallet = {
open: open,
create: create
};
export default softwareWallet;