UNPKG

laksa-wallet

Version:

Wallet instance for laksa

612 lines (479 loc) 19.2 kB
/** * This source code is being disclosed to you solely for the purpose of your participation in * testing Zilliqa and Laksa. You may view, compile and run the code for that purpose and pursuant to * the protocols and algorithms that are programmed into, and intended by, the code. You may * not do anything else with the code without express permission from Zilliqa Research Pte. Ltd., * including modifying or publishing the code (or any part of it), and developing or forming * another public or private blockchain network. This source code is provided ‘as is’ and no * warranties are given as to title or non-infringement, merchantability or fitness for purpose * and, to the extent permitted by law, all liability for your use of the code is disclaimed. * Some programs in this code are governed by the GNU General Public License v3.0 (available at * https://www.gnu.org/licenses/gpl-3.0.en.html) (‘GPLv3’). The programs that are governed by * GPLv3.0 are those programs that are located in the folders src/depends and tests/depends * and which include a reference to GPLv3 in their program files. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('laksa-utils'), require('immutable'), require('bip39'), require('hdkey'), require('laksa-account')) : typeof define === 'function' && define.amd ? define(['exports', 'laksa-utils', 'immutable', 'bip39', 'hdkey', 'laksa-account'], factory) : (factory((global.Laksa = {}),global.laksaUtils,global.immutable,global.bip39,global.hdkey,global.account)); }(this, (function (exports,laksaUtils,immutable,bip39,hdkey,account) { 'use strict'; bip39 = bip39 && bip39.hasOwnProperty('default') ? bip39['default'] : bip39; hdkey = hdkey && hdkey.hasOwnProperty('default') ? hdkey['default'] : hdkey; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classPrivateFieldGet(receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } var descriptor = privateMap.get(receiver); if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; } function _classPrivateFieldSet(receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } var descriptor = privateMap.get(receiver); if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } return value; } const ENCRYPTED = 'ENCRYPTED'; const encryptedBy = { ACCOUNT: 'account', WALLET: 'wallet' }; class Wallet { constructor(messenger) { _defineProperty(this, "defaultAccount", void 0); _accounts2.set(this, { writable: true, value: immutable.Map({ accounts: immutable.List([]) }) }); _defineProperty(this, "createAccount", () => { const accountInstance = new account.Account(this.messenger); const accountObject = accountInstance.createAccount(); return this.addAccount(accountObject); }); _defineProperty(this, "createBatchAccounts", number => { if (!laksaUtils.isNumber(number) || laksaUtils.isNumber(number) && number === 0) throw new Error('number has to be >0 Number'); const Batch = []; for (let i = 0; i < number; i += 1) { Batch.push(this.createAccount()); } return Batch; }); _defineProperty(this, "exportAccountByAddress", async (address, password, options = { level: 1024 }) => { const accountToExport = this.getAccountByAddress(address); if (accountToExport) { const result = await accountToExport.toFile(password, options); return result; } else { return false; } }); _defineProperty(this, "importAccountFromPrivateKey", privateKey => { const accountInstance = new account.Account(this.messenger); const accountObject = accountInstance.importAccount(privateKey); return this.addAccount(accountObject); }); _defineProperty(this, "importAccountFromKeyStore", async (keyStore, password) => { const accountInstance = new account.Account(this.messenger); const accountObject = await accountInstance.fromFile(keyStore, password); return this.addAccount(accountObject); }); _defineProperty(this, "removeOneAccountByAddress", address => { if (!laksaUtils.isAddress(address)) throw new Error('address is not correct'); const addressRef = this.getAccountByAddress(address); if (addressRef !== undefined) { const currentArray = _classPrivateFieldGet(this, _accounts2).get('accounts').toArray(); delete currentArray[addressRef.index]; if (this.signer !== undefined && addressRef.address === this.signer.address) { this.signer = undefined; this.defaultAccount = undefined; } _classPrivateFieldSet(this, _accounts2, _classPrivateFieldGet(this, _accounts2).set('accounts', immutable.List(currentArray))); _classPrivateFieldSet(this, _accounts2, _classPrivateFieldGet(this, _accounts2).delete(address)); this.updateLength(); } this.updateLength(); }); _defineProperty(this, "getAccountByAddress", address => { if (!laksaUtils.isAddress(address)) throw new Error('address is not correct'); return _classPrivateFieldGet(this, _accounts2).get(address); }); _defineProperty(this, "getAccountByIndex", index => { if (!laksaUtils.isNumber(index)) throw new Error('index is not correct'); const address = _classPrivateFieldGet(this, _accounts2).get('accounts').get(index); if (address !== undefined) { return this.getAccountByAddress(address); } else return undefined; }); _defineProperty(this, "getWalletAccounts", () => { return this.getIndexKeys().map(index => { const accountFound = this.getAccountByIndex(parseInt(index, 10)); return accountFound || false; }).filter(d => !!d); }); _defineProperty(this, "cleanAllAccounts", () => { this.getIndexKeys().forEach(index => this.removeOneAccountByIndex(parseInt(index, 10))); return true; }); this.length = 0; this.messenger = messenger; this.signer = this.defaultAccount || undefined; } get accounts() { return _classPrivateFieldGet(this, _accounts2).get('accounts').toArray(); } set accounts(value) { if (value !== undefined) { throw new Error('you should not set "accounts" directly, use internal functions'); } } generateMnemonic() { return bip39.generateMnemonic(); } importAccountFromMnemonic(phrase, index) { if (!this.isValidMnemonic(phrase)) { throw new Error(`Invalid mnemonic phrase: ${phrase}`); } const seed = bip39.mnemonicToSeed(phrase); const hdKey = hdkey.fromMasterSeed(seed); const childKey = hdKey.derive(`m/44'/313'/0'/0/${index}`); const privateKey = childKey.privateKey.toString('hex'); return this.importAccountFromPrivateKey(privateKey); } isValidMnemonic(phrase) { if (phrase.trim().split(/\s+/g).length < 12) { return false; } return bip39.validateMnemonic(phrase); } defaultSetSigner() { if (this.getWalletAccounts().length === 1 && this.signer === undefined) { this.setSigner(this.getWalletAccounts()[0]); } } /** * @function {updateLength} * @return {number} {wallet account counts} */ updateLength() { this.length = this.getIndexKeys().length; } /** * @function {getIndexKeys} * @return {Array<string>} {index keys to the wallet} */ getIndexKeys() { const isCorrectKeys = n => /^\d+$/i.test(n) && parseInt(n, 10) <= 9e20; const arrays = _classPrivateFieldGet(this, _accounts2).get('accounts').toArray(); return Object.keys(arrays).filter(isCorrectKeys); } /** * @function {getCurrentMaxIndex} * @return {number} {max index to the wallet} */ getCurrentMaxIndex() { const diff = (a, b) => { return b - a; }; // const sorted = R.sort(diff, keyList) const sorted = this.getIndexKeys().sort(diff); return sorted[0] === undefined ? -1 : parseInt(sorted[0], 10); } /** * @function {addAccount} * @param {Account} accountObject {account object} * @return {Account} {account object} */ addAccount(accountObject) { if (!laksaUtils.isObject(accountObject)) throw new Error('account Object is not correct'); if (this.getAccountByAddress(accountObject.address)) return false; const newAccountObject = accountObject; newAccountObject.createTime = new Date(); newAccountObject.index = this.getCurrentMaxIndex() + 1; const objectKey = newAccountObject.address; const newIndex = newAccountObject.index; let newArrays = _classPrivateFieldGet(this, _accounts2).get('accounts'); newArrays = newArrays.set(newIndex, objectKey); _classPrivateFieldSet(this, _accounts2, _classPrivateFieldGet(this, _accounts2).set(objectKey, newAccountObject)); _classPrivateFieldSet(this, _accounts2, _classPrivateFieldGet(this, _accounts2).set('accounts', immutable.List(newArrays))); // this.#_accounts = this.#_accounts.concat(newArrays) this.updateLength(); this.defaultSetSigner(); return newAccountObject; } /** * @function {createAccount} * @return {Account} {account object} */ /** * @function {importAccountsFromPrivateKeyList} * @param {Array<PrivateKey>} privateKeyList {list of private keys} * @return {Array<Account>} {array of accounts} */ importAccountsFromPrivateKeyList(privateKeyList) { if (!laksaUtils.isArray(privateKeyList)) throw new Error('privateKeyList has to be Array<String>'); const Imported = []; for (let i = 0; i < privateKeyList.length; i += 1) { Imported.push(this.importAccountFromPrivateKey(privateKeyList[i])); } return Imported; } //------- /** * @function {removeOneAccountByAddress} * @param {Address} address {account address} * @return {undefined} {} */ /** * @function {removeOneAccountByIndex} * @param {number} index {index of account} * @return {undefined} {} */ removeOneAccountByIndex(index) { if (!laksaUtils.isNumber(index)) throw new Error('index is not correct'); const addressRef = this.getAccountByIndex(index); if (addressRef !== undefined && addressRef.address) { this.removeOneAccountByAddress(addressRef.address); } } //--------- /** * @function {getAccountByAddress} * @param {Address} address {account address} * @return {Account} {account object} */ /** * @function {getWalletAddresses} * @return {Array<Address>} {array of address} */ getWalletAddresses() { return this.getIndexKeys().map(index => { const accountFound = this.getAccountByIndex(parseInt(index, 10)); if (accountFound) { return accountFound.address; } return false; }).filter(d => !!d); } /** * @function {getWalletPublicKeys} * @return {Array<PublicKey>} {array of public Key} */ getWalletPublicKeys() { return this.getIndexKeys().map(index => { const accountFound = this.getAccountByIndex(parseInt(index, 10)); if (accountFound) { return accountFound.publicKey; } return false; }).filter(d => !!d); } /** * @function {getWalletPrivateKeys} * @return {Array<PrivateKey>} {array of private key} */ getWalletPrivateKeys() { return this.getIndexKeys().map(index => { const accountFound = this.getAccountByIndex(parseInt(index, 10)); if (accountFound) { return accountFound.privateKey; } return false; }).filter(d => !!d); } /** * @function getWalletAccounts * @return {Array<Account>} {array of account} */ // ----------- /** * @function {updateAccountByAddress} * @param {Address} address {account address} * @param {Account} newObject {account object to be updated} * @return {boolean} {is successful} */ updateAccountByAddress(address, newObject) { if (!laksaUtils.isAddress(address)) throw new Error('address is not correct'); if (!laksaUtils.isObject(newObject)) throw new Error('new account Object is not correct'); const newAccountObject = newObject; newAccountObject.updateTime = new Date(); _classPrivateFieldSet(this, _accounts2, _classPrivateFieldGet(this, _accounts2).update(address, () => newAccountObject)); return true; } // ----------- /** * @function {cleanAllAccountsw} * @return {boolean} {is successful} */ // ----------- /** * @function {encryptAllAccounts} * @param {string} password {password} * @param {object} options {encryption options} * @return {type} {description} */ async encryptAllAccounts(password, options) { const keys = this.getIndexKeys(); const results = []; for (const index of keys) { const accountObject = this.getAccountByIndex(parseInt(index, 10)); if (accountObject) { const { address } = accountObject; const things = this.encryptAccountByAddress(address, password, options, encryptedBy.WALLET); results.push(things); } } await Promise.all(results); } /** * @function {decryptAllAccounts} * @param {string} password {decrypt password} * @return {type} {description} */ async decryptAllAccounts(password) { const keys = this.getIndexKeys(); const results = []; for (const index of keys) { const accountObject = this.getAccountByIndex(parseInt(index, 10)); if (accountObject) { const { address, LastEncryptedBy } = accountObject; if (LastEncryptedBy === encryptedBy.WALLET) { const things = this.decryptAccountByAddress(address, password, encryptedBy.WALLET); results.push(things); } } } await Promise.all(results); } /** * @function {encryptAccountByAddress} * @param {Address} address {account address} * @param {string} password {password string for encryption} * @param {object} options {encryption options} * @param {Symbol} by {Symbol that encrypted by} * @return {boolean} {status} */ async encryptAccountByAddress(address, password, options, by) { const accountObject = this.getAccountByAddress(address); if (accountObject !== undefined) { const { crypto } = accountObject; if (crypto === undefined) { let encryptedObject = {}; if (typeof accountObject.encrypt === 'function') { encryptedObject = await accountObject.encrypt(password, options); } else { const newAccount = new account.Account(this.messenger); const tempAccount = newAccount.importAccount(accountObject.privateKey); encryptedObject = await tempAccount.encrypt(password, options); } encryptedObject.LastEncryptedBy = by || encryptedBy.ACCOUNT; const updateStatus = this.updateAccountByAddress(address, encryptedObject); if (updateStatus === true) { return encryptedObject; } else return false; } } return false; } /** * @function {decryptAccountByAddress} * @param {Address} address {account address} * @param {string} password {password string to decrypt} * @param {Symbol} by {Symbol that decrypted by} * @return {boolean} {status} */ async decryptAccountByAddress(address, password, by) { const accountObject = this.getAccountByAddress(address); if (accountObject !== undefined) { const { crypto } = accountObject; if (laksaUtils.isObject(crypto)) { let decryptedObject = {}; if (typeof accountObject.decrypt === 'function') { decryptedObject = await accountObject.decrypt(password); } else { const decryptedTempObject = await account.decryptAccount(accountObject, password); const newAccount = new account.Account(this.messenger); decryptedObject = newAccount.importAccount(decryptedTempObject.privateKey); } decryptedObject.LastEncryptedBy = by || encryptedBy.ACCOUNT; const updateStatus = this.updateAccountByAddress(address, decryptedObject); if (updateStatus === true) { return decryptedObject; } else return false; } } return false; } /** * @function {setSigner} * @param {Account} obj {account object} * @return {Wallet} {wallet instance} */ setSigner(obj) { if (laksaUtils.isString(obj)) { this.signer = this.getAccountByAddress(obj); this.defaultAccount = this.getAccountByAddress(obj); } else if (laksaUtils.isObject(obj) && laksaUtils.isAddress(obj.address)) { this.signer = this.getAccountByAddress(obj.address); this.defaultAccount = this.getAccountByAddress(obj.address); } return this; } // sign method for Transaction bytes /** * @function {sign} * @param {Transaction} tx {transaction bytes} * @return {Transaction} {signed transaction object} */ async sign(tx, { address, password }) { if (!this.signer && address === undefined) { throw new Error('This signer is not found or address is not defined'); } try { const signerAccount = this.getAccountByAddress(address === undefined ? this.signer : address); const result = await signerAccount.signTransaction(tx, password); return result; } catch (err) { throw err; } } } var _accounts2 = new WeakMap(); exports.Wallet = Wallet; exports.ENCRYPTED = ENCRYPTED; exports.encryptedBy = encryptedBy; Object.defineProperty(exports, '__esModule', { value: true }); })));