@kazeblockchain/kazejs
Version:
Javascript libraries for Kaze wallet
231 lines (212 loc) • 7.29 kB
JavaScript
import fs from 'fs'
import Account from './Account'
import { DEFAULT_WALLET, DEFAULT_SCRYPT } from '../consts'
import logger from '../logging'
const log = logger('wallet')
/**
* @typedef WalletFile
* @param {string} name
* @param {WalletScryptParams} scrypt
* @param {WalletAccount[]} accounts
* @param {object} extra
*/
/**
* @typedef WalletScryptParams
* @param {number} n - Must be power of 2. 2^8 - 2^64
* @param {number} r - 1 - 256
* @param {number} p - 1 - 256
*/
/**
* @typedef WalletAccount
* @param {string} address - Address of account.
* @param {string} label - Label of account.
* @param {boolean} isDefault - Is the default change account.
* @param {boolean} lock - Is not allowed to spend funds.
* @param {string} key - Encrypted WIF of account.
* @param {object|null} contract - Contract details (applicable to contract address).
* @param {object} extra - Any extra information.
*/
/**
* Wallet class to read and integrate a Wallet file into the library. This class is responsible for ensuring that the Wallet File is read correctly and usable by the library.
* @param {WalletFile} file - Wallet file
* @param {string} file.name - Name of wallet
* @param {WalletScryptParams} file.scrypt - Scrypt parameters
* @param {WalletAccount[]} file.accounts - Accounts in wallet
* @param {object} file.extra - Extra information of wallet.
*/
class Wallet {
constructor ({ name = 'myWallet', version = DEFAULT_WALLET.version, scrypt = DEFAULT_SCRYPT, accounts = [], extra = null } = DEFAULT_WALLET) {
/** @type {string} */
this.name = name
/** @type {string} */
this.version = version
/** @type {ScryptParams} */
this.scrypt = {
n: scrypt.n || scrypt.cost,
r: scrypt.r || scrypt.blockSize,
p: scrypt.p || scrypt.parallel
}
/** @type {Account[]} */
this.accounts = []
for (const acct of accounts) {
this.addAccount(acct)
}
/** @type {object|null} */
this.extra = extra
log.info(`New Wallet created: ${this.name}`)
}
get [Symbol.toStringTag] () {
return 'Wallet'
}
/**
* Returns the default Account according to the following rules:
* 1. First Account where isDefault is true.
* 2. First Account with a decrypted private key.
* 3. First Account with an encrypted private key.
* 4. First Account in the array.
* Throws error if no accounts available.
* @return {Account} Account
*/
get defaultAccount () {
if (this.accounts.length === 0) throw new Error('No accounts available in this Wallet!')
for (const acct of this.accounts) {
if (acct.isDefault) return acct
}
for (const acct of this.accounts) {
if (acct._privateKey || acct._WIF) return acct
}
for (const acct of this.accounts) {
if (acct.encrypted) return acct
}
return this.accounts[0]
}
/**
* Imports a Wallet through a JSON string
* @param {string} - JSON string
* @return {Wallet}
*/
static import (jsonString) {
const walletJson = JSON.parse(jsonString)
return new Wallet(walletJson)
}
/**
* Reads a Wallet file sync.
* @param {string} filepath - Relative path from cwd
* @return {Wallet}
*/
static readFile (filepath) {
log.info(`Importing wallet from file: ${filepath}`)
return this.import(fs.readFileSync(filepath, 'utf8'))
}
/**
* Adds an account.
* @param {Account|WalletAccount} acct - Account or WalletAccount object.
* @return {number} Index position of Account in array.
*/
addAccount (acct) {
const index = this.accounts.length
if (!(acct instanceof Account)) {
acct = new Account(acct)
}
this.accounts.push(acct)
try {
const address = acct.address
log.info(`Added Account: ${address} to Wallet ${this.name}`)
} catch (err) {
log.warn(`Encrypted account added to Wallet ${this.name}. You will not be able to export this wallet without first decrypting this account`)
}
return index
}
/**
* Attempts to decrypt Account at index in array.
* @param {number} index - Index of Account in array.
* @param {string} keyphrase - keyphrase
* @return {boolean} Decryption success/failure
*/
decrypt (index, keyphrase) {
if (index < 0) throw new Error('Index cannot be negative!')
if (index >= this.accounts.length) throw new Error('Index cannot larger than Accounts array!')
try {
this.accounts[index].decrypt(keyphrase, this.scrypt)
return true
} catch (err) { return false }
}
/**
* Attempts to decrypt all accounts with keyphrase.
* @param {string} keyphrase
* @return {boolean[]} Each boolean represents if that Account has been decrypted successfully.
*/
decryptAll (keyphrase) {
const results = []
this.accounts.map((acct, i) => {
results.push(this.decrypt(i, keyphrase))
})
log.info(`decryptAll for Wallet ${this.name}: ${results.reduce((c, p) => { return p + (c ? '1' : '0') }, '')}`)
return results
}
/**
* Attempts to encrypt Account at index in array.
* @param {number} index - Index of Account in array.
* @param {string} keyphrase
* @return {boolean} Encryption success/failure
*/
encrypt (index, keyphrase) {
if (index < 0) throw new Error('Index cannot be negative!')
if (index >= this.accounts.length) throw new Error('Index cannot larger than Accounts array!')
try {
this.accounts[index].encrypt(keyphrase, this.scrypt)
return true
} catch (err) { return false }
}
/**
* Attempts to encrypt all accounts with keyphrase.
* @param {string} keyphrase
* @return {boolean[]} Each boolean represents if that Account has been encrypted successfully.
*/
encryptAll (keyphrase) {
const results = []
this.accounts.map((acct, i) => {
results.push(this.encrypt(i, keyphrase))
})
log.info(`decryptAll for Wallet ${this.name}: ${results.reduce((c, p) => { return p + (c ? '1' : '0') }, '')}`)
return results
}
/**
* Export this class as a object
* @return {object}
*/
export () {
return {
name: this.name,
version: this.version,
scrypt: this.scrypt,
accounts: this.accounts.map((acct) => acct.export()),
extra: this.extra
}
}
/**
* Set Account at index in array to be default account.
* @param {number} index - The index of the Account in accounts array.
* @return this
*/
setDefault (index) {
for (let i = 0; i < this.accounts.length; i++) {
this.accounts[i].isDefault = i === index
}
log.info(`Set Account: ${this.accounts[index]} as default for Wallet ${this.name}`)
}
/**
* Writes the Wallet file to a file.
* @param {string} filepath
* @return {Promise<boolean>} write success / failure
*/
writeFile (filepath) {
log.info(`Exporting wallet file to: ${filepath}`)
return fs.writeFile(filepath, JSON.stringify(this.export()), (err) => {
if (err) throw err
log.info('Wallet file written!')
return true
})
}
}
export default Wallet