From 74d73f4433781d84f1b833bd74094f9815f54a44 Mon Sep 17 00:00:00 2001 From: Kyle Zsembery Date: Fri, 11 Sep 2020 15:19:03 +1000 Subject: [PATCH 01/15] Fix the operator label for service nodes list --- .../service_node/service_node_list.vue | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/service_node/service_node_list.vue b/src/components/service_node/service_node_list.vue index e521d043..868d2cd6 100644 --- a/src/components/service_node/service_node_list.vue +++ b/src/components/service_node/service_node_list.vue @@ -13,8 +13,9 @@ {{ getRole(node) }} • - • {{ $t("strings.contribution") }}: + {{ $t("strings.contribution") }}: + + {{ $t("strings.serviceNodeDetails.minContribution") }}: {{ getMinContribution(node) }} LOKI • {{ $t("strings.serviceNodeDetails.maxContribution") }}: {{ openForContributionLoki(node) }} LOKI @@ -22,7 +23,7 @@ - {{ getFee(node) }} + {{ getFee(node) }} { + const primary = state.gateway.wallet.address_list.primary[0]; + return (primary && primary.address) || null; + } + }), methods: { nodeWithMinContribution(node) { const nodeWithMinContribution = { ...node, minContribution: this.getMinContribution(node) }; @@ -112,9 +120,10 @@ export default { // don't show a role if the user is not an operator or contributor let role = ""; const opAddress = node.operator_address; - if (node.operator_address === this.our_address) { + if (opAddress === this.our_address) { role = "strings.operator"; } else if (node.ourContributionAmount && opAddress !== this.our_address) { + // if we're not the operator and we have a contribution amount role = "strings.contributor"; } return this.$t(role); From 2decb9b05415d4369f267e6fabfd115b932a2944 Mon Sep 17 00:00:00 2001 From: Kyle Zsembery Date: Mon, 14 Sep 2020 09:43:12 +1000 Subject: [PATCH 02/15] Add better error handling for a transfer rpc call --- .../main-process/modules/wallet-rpc.js | 46 +++++++++++-------- src/pages/wallet/send.vue | 1 + 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src-electron/main-process/modules/wallet-rpc.js b/src-electron/main-process/modules/wallet-rpc.js index c0c5ddfd..b4a56789 100644 --- a/src-electron/main-process/modules/wallet-rpc.js +++ b/src-electron/main-process/modules/wallet-rpc.js @@ -1292,30 +1292,38 @@ export class WalletRPC { params.payment_id = payment_id; } - this.sendRPC(rpc_endpoint, params).then(data => { - if (data.hasOwnProperty("error")) { - let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + this.sendRPC(rpc_endpoint, params) + .then(data => { + if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) { + let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + this.sendGateway("set_tx_status", { + code: -1, + message: error, + sending: false + }); + return; + } + // update state to show a confirm popup + this.sendGateway("set_tx_status", { + code: 1, + i18n: "strings.awaitingConfirmation", + sending: false, + txData: { + amountList: data.result.amount_list, + metadataList: data.result.tx_metadata_list, + feeList: data.result.fee_list, + priority: data.params.priority, + destinations: data.params.destinations + } + }); + }) + .catch(err => { this.sendGateway("set_tx_status", { code: -1, - message: error, + message: err.message, sending: false }); - return; - } - // update state to show a confirm popup - this.sendGateway("set_tx_status", { - code: 1, - i18n: "strings.awaitingConfirmation", - sending: false, - txData: { - amountList: data.result.amount_list, - metadataList: data.result.tx_metadata_list, - feeList: data.result.fee_list, - priority: data.params.priority, - destinations: data.params.destinations - } }); - }); }; crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", cryptoCallback); diff --git a/src/pages/wallet/send.vue b/src/pages/wallet/send.vue index b39ec2d5..dfc7f2be 100644 --- a/src/pages/wallet/send.vue +++ b/src/pages/wallet/send.vue @@ -366,6 +366,7 @@ export default { note }; + // Commit the transaction this.$gateway.send("wallet", "relay_tx", relayTxData); }, // helper for constructing a dialog for confirming transactions From 12d7df3c3d8c146d4bdb3d04b5996cc1d2d9fac8 Mon Sep 17 00:00:00 2001 From: Kyle Zsembery Date: Mon, 14 Sep 2020 11:06:01 +1000 Subject: [PATCH 03/15] Fix error handling for second case --- src-electron/main-process/modules/wallet-rpc.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src-electron/main-process/modules/wallet-rpc.js b/src-electron/main-process/modules/wallet-rpc.js index b4a56789..53b75f16 100644 --- a/src-electron/main-process/modules/wallet-rpc.js +++ b/src-electron/main-process/modules/wallet-rpc.js @@ -1295,7 +1295,12 @@ export class WalletRPC { this.sendRPC(rpc_endpoint, params) .then(data => { if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) { - let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + let error = ""; + if (data.error) { + error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + } else { + error = `Incorrect result from ${rpc_endpoint} RPC call`; + } this.sendGateway("set_tx_status", { code: -1, message: error, From 68ca92eabdf7f1bd6a31e3eee983bcf805b78e58 Mon Sep 17 00:00:00 2001 From: Kyle Zsembery Date: Mon, 14 Sep 2020 11:14:50 +1000 Subject: [PATCH 04/15] safer condition --- src-electron/main-process/modules/wallet-rpc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-electron/main-process/modules/wallet-rpc.js b/src-electron/main-process/modules/wallet-rpc.js index 53b75f16..eb3eed4f 100644 --- a/src-electron/main-process/modules/wallet-rpc.js +++ b/src-electron/main-process/modules/wallet-rpc.js @@ -1296,7 +1296,7 @@ export class WalletRPC { .then(data => { if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) { let error = ""; - if (data.error) { + if (data.error && data.error.message) { error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); } else { error = `Incorrect result from ${rpc_endpoint} RPC call`; From ae2586f2488ec46bf78e91520cb6e1d4f9cc3cc8 Mon Sep 17 00:00:00 2001 From: kylezs Date: Wed, 16 Sep 2020 12:41:42 +1000 Subject: [PATCH 05/15] fix a few bugs, support reserved service nodes, confirm tx sweep all (#179) * fix a few bugs, support reserved service nodes better, confirm tx for sweep all working better * send and sweep all confirmTx bug fixed, min/max on Service nodes added --- .prettierrc.js | 5 - .stylintrc | 1 - .../main-process/modules/wallet-rpc.js | 1300 ++++++++++------- src/components/confirm_tx_dialog.vue | 75 + .../service_node/service_node_contribute.vue | 42 +- .../service_node/service_node_details.vue | 85 +- .../service_node/service_node_list.vue | 64 +- .../service_node/service_node_staking.vue | 174 ++- .../service_node/service_node_unlock.vue | 28 +- src/i18n/en-us.js | 47 +- src/mixins/confirm_dialog_mixin.js | 21 + src/mixins/service_node_mixin.js | 27 + src/pages/wallet/send.vue | 121 +- 13 files changed, 1297 insertions(+), 693 deletions(-) delete mode 100644 .prettierrc.js create mode 100644 src/components/confirm_tx_dialog.vue create mode 100644 src/mixins/confirm_dialog_mixin.js create mode 100644 src/mixins/service_node_mixin.js diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index 18d7aa75..00000000 --- a/.prettierrc.js +++ /dev/null @@ -1,5 +0,0 @@ -// Must be included inside .stylintrc, otherwise will conflict and cause weird -// linting errors -module.exports = { - printWidth: 120 -}; diff --git a/.stylintrc b/.stylintrc index c623d6a8..1f6643e5 100644 --- a/.stylintrc +++ b/.stylintrc @@ -32,5 +32,4 @@ "valid": true, "zeroUnits": "never", "zIndexNormalize": false, - "printWidth": 120 } diff --git a/src-electron/main-process/modules/wallet-rpc.js b/src-electron/main-process/modules/wallet-rpc.js index eb3eed4f..08fd025f 100644 --- a/src-electron/main-process/modules/wallet-rpc.js +++ b/src-electron/main-process/modules/wallet-rpc.js @@ -103,7 +103,11 @@ export class WalletRPC { this.wallet_dir = path.join(this.dirs[net_type], "wallets"); args.push("--wallet-dir", this.wallet_dir); - const log_file = path.join(this.dirs[net_type], "logs", "wallet-rpc.log"); + const log_file = path.join( + this.dirs[net_type], + "logs", + "wallet-rpc.log" + ); args.push("--log-file", log_file); if (net_type === "testnet") { @@ -125,13 +129,20 @@ export class WalletRPC { this.hostname = "127.0.0.1"; this.port = options.wallet.rpc_bind_port; - const rpcExecutable = process.platform === "win32" ? "loki-wallet-rpc.exe" : "loki-wallet-rpc"; + const rpcExecutable = + process.platform === "win32" + ? "loki-wallet-rpc.exe" + : "loki-wallet-rpc"; // eslint-disable-next-line no-undef const rpcPath = path.join(__ryo_bin, rpcExecutable); // Check if the rpc exists if (!fs.existsSync(rpcPath)) { - reject(new Error("Failed to find Loki Wallet RPC. Please make sure you anti-virus has not removed it.")); + reject( + new Error( + "Failed to find Loki Wallet RPC. Please make sure you anti-virus has not removed it." + ) + ); return; } @@ -140,8 +151,13 @@ export class WalletRPC { .catch(() => "closed") .then(status => { if (status === "closed") { - const options = process.platform === "win32" ? {} : { detached: true }; - this.walletRPCProcess = child_process.spawn(rpcPath, args, options); + const options = + process.platform === "win32" ? {} : { detached: true }; + this.walletRPCProcess = child_process.spawn( + rpcPath, + args, + options + ); this.walletRPCProcess.stdout.on("data", data => { process.stdout.write(`Wallet: ${data}`); @@ -176,7 +192,9 @@ export class WalletRPC { }); } }); - this.walletRPCProcess.on("error", err => process.stderr.write(`Wallet: ${err}`)); + this.walletRPCProcess.on("error", err => + process.stderr.write(`Wallet: ${err}`) + ); this.walletRPCProcess.on("close", code => { process.stderr.write(`Wallet: exited with code ${code} \n`); this.walletRPCProcess = null; @@ -193,7 +211,11 @@ export class WalletRPC { clearInterval(intrvl); resolve(); } else { - if (this.walletRPCProcess && data.error.cause && data.error.cause.code === "ECONNREFUSED") { + if ( + this.walletRPCProcess && + data.error.cause && + data.error.cause.code === "ECONNREFUSED" + ) { // Ignore } else { clearInterval(intrvl); @@ -251,7 +273,9 @@ export class WalletRPC { params.password, params.seed, params.refresh_type, - params.refresh_type == "date" ? params.refresh_start_date : params.refresh_start_height + params.refresh_type == "date" + ? params.refresh_start_date + : params.refresh_start_height ); break; @@ -263,7 +287,9 @@ export class WalletRPC { params.address, params.viewkey, params.refresh_type, - params.refresh_type == "date" ? params.refresh_start_date : params.refresh_start_height + params.refresh_type == "date" + ? params.refresh_start_date + : params.refresh_start_height ); break; @@ -280,7 +306,12 @@ export class WalletRPC { break; case "stake": - this.stake(params.password, params.amount, params.key, params.destination); + this.stake( + params.password, + params.amount, + params.key, + params.destination + ); break; case "register_service_node": @@ -292,14 +323,31 @@ export class WalletRPC { break; case "unlock_stake": - this.unlockStake(params.password, params.service_node_key, params.confirmed || false); + this.unlockStake( + params.password, + params.service_node_key, + params.confirmed || false + ); break; case "transfer": - this.transfer(params.password, params.amount, params.address, params.payment_id, params.priority); + this.transfer( + params.password, + params.amount, + params.address, + params.payment_id, + params.priority, + // return false if undefined + !!params.isSweepAll + ); break; case "relay_tx": - this.relayTransaction(params.metadataList, params.isBlink, params.addressSave, params.note); + this.relayTransaction( + params.metadataList, + params.isBlink, + params.addressSave, + params.note + ); break; case "purchase_lns": this.purchaseLNS( @@ -329,7 +377,12 @@ export class WalletRPC { break; case "check_transaction": - this.checkTransactionProof(params.signature, params.txid, params.address, params.message); + this.checkTransactionProof( + params.signature, + params.txid, + params.address, + params.message + ); break; case "add_address_book": @@ -344,7 +397,9 @@ export class WalletRPC { break; case "delete_address_book": - this.deleteAddressBook(params.hasOwnProperty("index") ? params.index : false); + this.deleteAddressBook( + params.hasOwnProperty("index") ? params.index : false + ); break; case "save_tx_notes": @@ -391,15 +446,25 @@ export class WalletRPC { } // We need to check if the hash generated with an empty string is the same as the password_hash we are storing - crypto.pbkdf2("", this.auth[2], 1000, 64, "sha512", (err, password_hash) => { - if (err) { - this.sendGateway("set_has_password", false); - return; - } + crypto.pbkdf2( + "", + this.auth[2], + 1000, + 64, + "sha512", + (err, password_hash) => { + if (err) { + this.sendGateway("set_has_password", false); + return; + } - // If the pass hash doesn't match empty string then we don't have a password - this.sendGateway("set_has_password", this.wallet_state.password_hash !== password_hash.toString("hex")); - }); + // If the pass hash doesn't match empty string then we don't have a password + this.sendGateway( + "set_has_password", + this.wallet_state.password_hash !== password_hash.toString("hex") + ); + } + ); } validateAddress(address) { @@ -441,7 +506,9 @@ export class WalletRPC { } // store hash of the password so we can check against it later when requesting private keys, or for sending txs - this.wallet_state.password_hash = crypto.pbkdf2Sync(password, this.auth[2], 1000, 64, "sha512").toString("hex"); + this.wallet_state.password_hash = crypto + .pbkdf2Sync(password, this.auth[2], 1000, 64, "sha512") + .toString("hex"); this.wallet_state.name = filename; this.wallet_state.open = true; @@ -449,7 +516,13 @@ export class WalletRPC { }); } - restoreWallet(filename, password, seed, refresh_type, refresh_start_timestamp_or_height) { + restoreWallet( + filename, + password, + seed, + refresh_type, + refresh_start_timestamp_or_height + ) { if (refresh_type == "date") { // Convert timestamp to 00:00 and move back a day // Core code also moved back some amount of blocks @@ -492,7 +565,9 @@ export class WalletRPC { } // store hash of the password so we can check against it later when requesting private keys, or for sending txs - this.wallet_state.password_hash = crypto.pbkdf2Sync(password, this.auth[2], 1000, 64, "sha512").toString("hex"); + this.wallet_state.password_hash = crypto + .pbkdf2Sync(password, this.auth[2], 1000, 64, "sha512") + .toString("hex"); this.wallet_state.name = filename; this.wallet_state.open = true; @@ -500,7 +575,14 @@ export class WalletRPC { }); } - restoreViewWallet(filename, password, address, viewkey, refresh_type, refresh_start_timestamp_or_height) { + restoreViewWallet( + filename, + password, + address, + viewkey, + refresh_type, + refresh_start_timestamp_or_height + ) { if (refresh_type == "date") { // Convert timestamp to 00:00 and move back a day // Core code also moved back some amount of blocks @@ -516,7 +598,14 @@ export class WalletRPC { } }); } else { - this.restoreViewWallet(filename, password, address, viewkey, "height", height); + this.restoreViewWallet( + filename, + password, + address, + viewkey, + "height", + height + ); } }); return; @@ -541,7 +630,9 @@ export class WalletRPC { } // store hash of the password so we can check against it later when requesting private keys, or for sending txs - this.wallet_state.password_hash = crypto.pbkdf2Sync(password, this.auth[2], 1000, 64, "sha512").toString("hex"); + this.wallet_state.password_hash = crypto + .pbkdf2Sync(password, this.auth[2], 1000, 64, "sha512") + .toString("hex"); this.wallet_state.name = filename; this.wallet_state.open = true; @@ -555,9 +646,15 @@ export class WalletRPC { // trim off suffix if exists if (import_path.endsWith(".keys")) { - import_path = import_path.substring(0, import_path.length - ".keys".length); + import_path = import_path.substring( + 0, + import_path.length - ".keys".length + ); } else if (import_path.endsWith(".address.txt")) { - import_path = import_path.substring(0, import_path.length - ".address.txt".length); + import_path = import_path.substring( + 0, + import_path.length - ".address.txt".length + ); } if (!fs.existsSync(import_path)) { @@ -583,7 +680,9 @@ export class WalletRPC { try { fs.copySync(import_path, destination, { errorOnExist: true }); if (fs.existsSync(import_path + ".keys")) { - fs.copySync(import_path + ".keys", destination + ".keys", { errorOnExist: true }); + fs.copySync(import_path + ".keys", destination + ".keys", { + errorOnExist: true + }); } } catch (e) { this.sendGateway("set_wallet_error", { @@ -601,7 +700,8 @@ export class WalletRPC { .then(data => { if (data.hasOwnProperty("error")) { if (fs.existsSync(destination)) fs.unlinkSync(destination); - if (fs.existsSync(destination + ".keys")) fs.unlinkSync(destination + ".keys"); + if (fs.existsSync(destination + ".keys")) + fs.unlinkSync(destination + ".keys"); this.sendGateway("set_wallet_error", { status: data.error }); @@ -672,7 +772,10 @@ export class WalletRPC { } this.saveWallet().then(() => { - let address_txt_path = path.join(this.wallet_dir, filename + ".address.txt"); + let address_txt_path = path.join( + this.wallet_dir, + filename + ".address.txt" + ); if (!fs.existsSync(address_txt_path)) { fs.writeFile(address_txt_path, wallet.info.address, "utf8", () => { this.listWallets(); @@ -699,7 +802,10 @@ export class WalletRPC { return; } - let address_txt_path = path.join(this.wallet_dir, filename + ".address.txt"); + let address_txt_path = path.join( + this.wallet_dir, + filename + ".address.txt" + ); if (!fs.existsSync(address_txt_path)) { this.sendRPC("get_address", { account_index: 0 }).then(data => { if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) { @@ -712,7 +818,9 @@ export class WalletRPC { } // store hash of the password so we can check against it later when requesting private keys, or for sending txs - this.wallet_state.password_hash = crypto.pbkdf2Sync(password, this.auth[2], 1000, 64, "sha512").toString("hex"); + this.wallet_state.password_hash = crypto + .pbkdf2Sync(password, this.auth[2], 1000, 64, "sha512") + .toString("hex"); this.wallet_state.name = filename; this.wallet_state.open = true; @@ -810,7 +918,8 @@ export class WalletRPC { } this.wallet_state.balance = wallet.info.balance = n.result.balance; - this.wallet_state.unlocked_balance = wallet.info.unlocked_balance = n.result.unlocked_balance; + this.wallet_state.unlocked_balance = wallet.info.unlocked_balance = + n.result.unlocked_balance; this.sendGateway("set_wallet_data", { info: wallet.info }); @@ -849,8 +958,15 @@ export class WalletRPC { async updateLocalLNSRecords() { try { - const addressData = await this.sendRPC("get_address", { account_index: 0 }, 5000); - if (addressData.hasOwnProperty("error") || !addressData.hasOwnProperty("result")) { + const addressData = await this.sendRPC( + "get_address", + { account_index: 0 }, + 5000 + ); + if ( + addressData.hasOwnProperty("error") || + !addressData.hasOwnProperty("result") + ) { return; } @@ -859,19 +975,24 @@ export class WalletRPC { const addresses = results.map(a => a.address).filter(a => !!a); if (addresses.length === 0) return; - const records = await this.backend.daemon.getLNSRecordsForOwners(addresses); + const records = await this.backend.daemon.getLNSRecordsForOwners( + addresses + ); // We need to ensure that we decrypt any incoming records that we already have const currentRecords = this.wallet_state.lnsRecords; const recordsToUpdate = { ...this.purchasedNames }; const newRecords = records.map(record => { // If we have a new record or we haven't decrypted our current record then we should return the new record - const current = currentRecords.find(c => c.name_hash === record.name_hash); + const current = currentRecords.find( + c => c.name_hash === record.name_hash + ); if (!current || !current.name) return record; // We need to check if we need to re-decrypt the record. // This is only necessary if the encrypted_value changed. - const needsToUpdate = current.encrypted_value !== record.encrypted_value; + const needsToUpdate = + current.encrypted_value !== record.encrypted_value; if (needsToUpdate) { const { name, type } = current; recordsToUpdate[name] = type; @@ -914,7 +1035,9 @@ export class WalletRPC { // Update our current records with the new decrypted record const currentRecords = this.wallet_state.lnsRecords; - const isOurRecord = currentRecords.find(c => c.name_hash === record.name_hash); + const isOurRecord = currentRecords.find( + c => c.name_hash === record.name_hash + ); if (!isOurRecord) return null; const newRecords = currentRecords.map(current => { @@ -950,7 +1073,11 @@ export class WalletRPC { if (!record || !record.encrypted_value) return null; // Decrypt the value if possible - const value = await this.decryptLNSValue(type, lowerCaseName, record.encrypted_value); + const value = await this.decryptLNSValue( + type, + lowerCaseName, + record.encrypted_value + ); return { name, @@ -969,7 +1096,9 @@ export class WalletRPC { }); if (data.hasOwnProperty("error")) { - let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + let error = + data.error.message.charAt(0).toUpperCase() + + data.error.message.slice(1); throw new Error(error); } @@ -991,7 +1120,9 @@ export class WalletRPC { }); if (data.hasOwnProperty("error")) { - let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + let error = + data.error.message.charAt(0).toUpperCase() + + data.error.message.slice(1); throw new Error(error); } @@ -1003,112 +1134,130 @@ export class WalletRPC { } stake(password, amount, service_node_key, destination) { - crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => { - if (err) { - this.sendGateway("set_snode_status", { - stake: { - code: -1, - i18n: "notification.errors.internalError", - sending: false - } - }); - return; - } - if (!this.isValidPasswordHash(password_hash)) { - this.sendGateway("set_snode_status", { - stake: { - code: -1, - i18n: "notification.errors.invalidPassword", - sending: false - } - }); - return; - } - - amount = (parseFloat(amount) * 1e9).toFixed(0); - - this.sendRPC("stake", { - amount, - destination, - service_node_key - }).then(data => { - if (data.hasOwnProperty("error")) { - let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + crypto.pbkdf2( + password, + this.auth[2], + 1000, + 64, + "sha512", + (err, password_hash) => { + if (err) { this.sendGateway("set_snode_status", { stake: { code: -1, - message: error, + i18n: "notification.errors.internalError", + sending: false + } + }); + return; + } + if (!this.isValidPasswordHash(password_hash)) { + this.sendGateway("set_snode_status", { + stake: { + code: -1, + i18n: "notification.errors.invalidPassword", sending: false } }); return; } - // Update the new snode list - this.backend.daemon.updateServiceNodes(); + amount = (parseFloat(amount) * 1e9).toFixed(0); - this.sendGateway("set_snode_status", { - stake: { - code: 0, - i18n: "notification.positive.stakeSuccess", - sending: false + this.sendRPC("stake", { + amount, + destination, + service_node_key + }).then(data => { + if (data.hasOwnProperty("error")) { + let error = + data.error.message.charAt(0).toUpperCase() + + data.error.message.slice(1); + this.sendGateway("set_snode_status", { + stake: { + code: -1, + message: error, + sending: false + } + }); + return; } - }); - }); - }); - } - registerSnode(password, register_service_node_str) { - crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => { - if (err) { - this.sendGateway("set_snode_status", { - registration: { - code: -1, - i18n: "notification.errors.internalError", - sending: false - } - }); - return; - } + // Update the new snode list + this.backend.daemon.updateServiceNodes(); - if (!this.isValidPasswordHash(password_hash)) { - this.sendGateway("set_snode_status", { - registration: { - code: -1, - i18n: "notification.errors.invalidPassword", - sending: false - } + this.sendGateway("set_snode_status", { + stake: { + code: 0, + i18n: "notification.positive.stakeSuccess", + sending: false + } + }); }); - return; } + ); + } - this.sendRPC("register_service_node", { - register_service_node_str - }).then(data => { - if (data.hasOwnProperty("error")) { - const error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + registerSnode(password, register_service_node_str) { + crypto.pbkdf2( + password, + this.auth[2], + 1000, + 64, + "sha512", + (err, password_hash) => { + if (err) { this.sendGateway("set_snode_status", { registration: { code: -1, - message: error, + i18n: "notification.errors.internalError", sending: false } }); return; } - // Update the new snode list - this.backend.daemon.updateServiceNodes(); + if (!this.isValidPasswordHash(password_hash)) { + this.sendGateway("set_snode_status", { + registration: { + code: -1, + i18n: "notification.errors.invalidPassword", + sending: false + } + }); + return; + } - this.sendGateway("set_snode_status", { - registration: { - code: 0, - i18n: "notification.positive.registerServiceNodeSuccess", - sending: false + this.sendRPC("register_service_node", { + register_service_node_str + }).then(data => { + if (data.hasOwnProperty("error")) { + const error = + data.error.message.charAt(0).toUpperCase() + + data.error.message.slice(1); + this.sendGateway("set_snode_status", { + registration: { + code: -1, + message: error, + sending: false + } + }); + return; } + + // Update the new snode list + this.backend.daemon.updateServiceNodes(); + + this.sendGateway("set_snode_status", { + registration: { + code: 0, + i18n: "notification.positive.registerServiceNodeSuccess", + sending: false + } + }); }); - }); - }); + } + ); } async updateServiceNodeList() { @@ -1128,67 +1277,76 @@ export class WalletRPC { }; // Unlock code 0 means success, 1 means can unlock, -1 means error - crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => { - if (err) { - sendError("notification.errors.internalError"); - return; - } + crypto.pbkdf2( + password, + this.auth[2], + 1000, + 64, + "sha512", + (err, password_hash) => { + if (err) { + sendError("notification.errors.internalError"); + return; + } - if (!this.isValidPasswordHash(password_hash)) { - sendError("notification.errors.invalidPassword"); - return; - } + if (!this.isValidPasswordHash(password_hash)) { + sendError("notification.errors.invalidPassword"); + return; + } - const sendRPC = path => { - return this.sendRPC(path, { - service_node_key - }).then(data => { - if (data.hasOwnProperty("error")) { - const error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); - sendError(error, false); - return null; - } + const sendRPC = path => { + return this.sendRPC(path, { + service_node_key + }).then(data => { + if (data.hasOwnProperty("error")) { + const error = + data.error.message.charAt(0).toUpperCase() + + data.error.message.slice(1); + sendError(error, false); + return null; + } - if (!data.hasOwnProperty("result")) { - sendError("notification.errors.failedServiceNodeUnlock"); - return null; - } + if (!data.hasOwnProperty("result")) { + sendError("notification.errors.failedServiceNodeUnlock"); + return null; + } - return data.result; - }); - }; + return data.result; + }); + }; - if (confirmed) { - sendRPC("request_stake_unlock").then(data => { - if (!data) return; + if (confirmed) { + sendRPC("request_stake_unlock").then(data => { + if (!data) return; - const unlock = { - code: data.unlocked ? 0 : -1, - message: data.msg, - sending: false - }; + const unlock = { + code: data.unlocked ? 0 : -1, + message: data.msg, + sending: false + }; - // Update the new snode list - if (data.unlocked) { - this.backend.daemon.updateServiceNodes(); - } + // Update the new snode list + if (data.unlocked) { + this.backend.daemon.updateServiceNodes(); + } - this.sendGateway("set_snode_status", { unlock }); - }); - } else { - sendRPC("can_request_stake_unlock").then(data => { - if (!data) return; + this.sendGateway("set_snode_status", { unlock }); + }); + } else { + sendRPC("can_request_stake_unlock").then(data => { + if (!data) return; - const unlock = { - code: data.can_unlock ? 1 : -1, - message: data.msg, - sending: false - }; + const unlock = { + code: data.can_unlock ? 1 : -1, + message: data.msg, + sending: false + }; - this.sendGateway("set_snode_status", { unlock }); - }); + this.sendGateway("set_snode_status", { unlock }); + }); + } } - }); + ); } // submits the transaction to the blockchain, irreversible from here @@ -1235,7 +1393,12 @@ export class WalletRPC { }); if (address_book.hasOwnProperty("save") && address_book.save) { - this.addAddressBook(address, payment_id, address_book.description, address_book.name); + this.addAddressBook( + address, + payment_id, + address_book.description, + address_book.name + ); } return; } @@ -1249,7 +1412,7 @@ export class WalletRPC { // prepares params and provides a "confirm" popup to allow the user to check // send address and tx fees before sending - transfer(password, amount, address, payment_id, priority) { + transfer(password, amount, address, payment_id, priority, isSweepAll) { const cryptoCallback = (err, password_hash) => { if (err) { this.sendGateway("set_tx_status", { @@ -1270,12 +1433,10 @@ export class WalletRPC { amount = (parseFloat(amount) * 1e9).toFixed(0); - let sweep_all = amount == this.wallet_state.unlocked_balance; - - const rpc_endpoint = sweep_all ? "sweep_all" : "transfer_split"; - const rpcSpecificParams = sweep_all + const rpc_endpoint = isSweepAll ? "sweep_all" : "transfer_split"; + const rpcSpecificParams = isSweepAll ? { - address: address, + address, account_index: 0 } : { @@ -1297,7 +1458,9 @@ export class WalletRPC { if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) { let error = ""; if (data.error && data.error.message) { - error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + error = + data.error.message.charAt(0).toUpperCase() + + data.error.message.slice(1); } else { error = `Incorrect result from ${rpc_endpoint} RPC call`; } @@ -1314,10 +1477,14 @@ export class WalletRPC { i18n: "strings.awaitingConfirmation", sending: false, txData: { + // for a sweep all + address: data.params.address, + isSweepAll: rpc_endpoint === "sweep_all", amountList: data.result.amount_list, metadataList: data.result.tx_metadata_list, feeList: data.result.fee_list, priority: data.params.priority, + // for a "send" tx destinations: data.params.destinations } }); @@ -1339,55 +1506,64 @@ export class WalletRPC { const _owner = owner.trim() === "" ? null : owner; const backup_owner = backupOwner.trim() === "" ? null : backupOwner; - crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => { - if (err) { - this.sendGateway("set_lns_status", { - code: -1, - i18n: "notification.errors.internalError", - sending: false - }); - return; - } - if (!this.isValidPasswordHash(password_hash)) { - this.sendGateway("set_lns_status", { - code: -1, - i18n: "notification.errors.invalidPassword", - sending: false - }); - return; - } - - const params = { - type, - owner: _owner, - backup_owner, - name: _name, - value - }; - - this.sendRPC("lns_buy_mapping", params).then(data => { - if (data.hasOwnProperty("error")) { - let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + crypto.pbkdf2( + password, + this.auth[2], + 1000, + 64, + "sha512", + (err, password_hash) => { + if (err) { + this.sendGateway("set_lns_status", { + code: -1, + i18n: "notification.errors.internalError", + sending: false + }); + return; + } + if (!this.isValidPasswordHash(password_hash)) { this.sendGateway("set_lns_status", { code: -1, - message: error, + i18n: "notification.errors.invalidPassword", sending: false }); return; } - this.purchasedNames[name.trim()] = type; + const params = { + type, + owner: _owner, + backup_owner, + name: _name, + value + }; - // Fetch new records and then get the decrypted record for the one we just inserted - setTimeout(() => this.updateLocalLNSRecords(), 5000); + this.sendRPC("lns_buy_mapping", params).then(data => { + if (data.hasOwnProperty("error")) { + let error = + data.error.message.charAt(0).toUpperCase() + + data.error.message.slice(1); + this.sendGateway("set_lns_status", { + code: -1, + message: error, + sending: false + }); + return; + } - this.sendGateway("set_lns_status", { - code: 0, - i18n: "notification.positive.namePurchased", - sending: false + this.purchasedNames[name.trim()] = type; + + // Fetch new records and then get the decrypted record for the one we just inserted + setTimeout(() => this.updateLocalLNSRecords(), 5000); + + this.sendGateway("set_lns_status", { + code: 0, + i18n: "notification.positive.namePurchased", + sending: false + }); }); - }); - }); + } + ); } updateLNSMapping(password, type, name, value, owner, backupOwner) { @@ -1395,72 +1571,85 @@ export class WalletRPC { const _owner = owner.trim() === "" ? null : owner; const backup_owner = backupOwner.trim() === "" ? null : backupOwner; - crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => { - if (err) { - this.sendGateway("set_lns_status", { - code: -1, - i18n: "notification.errors.internalError", - sending: false - }); - return; - } - if (!this.isValidPasswordHash(password_hash)) { - this.sendGateway("set_lns_status", { - code: -1, - i18n: "notification.errors.invalidPassword", - sending: false - }); - return; - } - - const params = { - type, - owner: _owner, - backup_owner, - name: _name, - value - }; - - this.sendRPC("lns_update_mapping", params).then(data => { - if (data.hasOwnProperty("error")) { - let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + crypto.pbkdf2( + password, + this.auth[2], + 1000, + 64, + "sha512", + (err, password_hash) => { + if (err) { this.sendGateway("set_lns_status", { code: -1, - message: error, + i18n: "notification.errors.internalError", + sending: false + }); + return; + } + if (!this.isValidPasswordHash(password_hash)) { + this.sendGateway("set_lns_status", { + code: -1, + i18n: "notification.errors.invalidPassword", sending: false }); return; } - this.purchasedNames[name.trim()] = type; - - // Fetch new records and then get the decrypted record for the one we just inserted - setTimeout(() => this.updateLocalLNSRecords(), 5000); + const params = { + type, + owner: _owner, + backup_owner, + name: _name, + value + }; - // Optimistically update our record - const { lnsRecords } = this.wallet_state; - const newRecords = lnsRecords.map(record => { - if (record.type === type && record.name && record.name.toLowerCase() === _name) { - return { - ...record, - owner: _owner, - backup_owner, - value - }; + this.sendRPC("lns_update_mapping", params).then(data => { + if (data.hasOwnProperty("error")) { + let error = + data.error.message.charAt(0).toUpperCase() + + data.error.message.slice(1); + this.sendGateway("set_lns_status", { + code: -1, + message: error, + sending: false + }); + return; } - return record; - }); - this.wallet_state.lnsRecords = newRecords; - this.sendGateway("set_wallet_data", { lnsRecords: newRecords }); + this.purchasedNames[name.trim()] = type; + + // Fetch new records and then get the decrypted record for the one we just inserted + setTimeout(() => this.updateLocalLNSRecords(), 5000); + + // Optimistically update our record + const { lnsRecords } = this.wallet_state; + const newRecords = lnsRecords.map(record => { + if ( + record.type === type && + record.name && + record.name.toLowerCase() === _name + ) { + return { + ...record, + owner: _owner, + backup_owner, + value + }; + } - this.sendGateway("set_lns_status", { - code: 0, - i18n: "notification.positive.lnsRecordUpdated", - sending: false + return record; + }); + this.wallet_state.lnsRecords = newRecords; + this.sendGateway("set_wallet_data", { lnsRecords: newRecords }); + + this.sendGateway("set_lns_status", { + code: 0, + i18n: "notification.positive.lnsRecordUpdated", + sending: false + }); }); - }); - }); + } + ); } proveTransaction(txid, address, message) { @@ -1482,7 +1671,9 @@ export class WalletRPC { this.sendRPC(rpc_endpoint, params).then(data => { if (data.hasOwnProperty("error")) { - let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + let error = + data.error.message.charAt(0).toUpperCase() + + data.error.message.slice(1); this.sendGateway("set_prove_transaction_status", { code: -1, message: error, @@ -1522,7 +1713,9 @@ export class WalletRPC { this.sendRPC(rpc_endpoint, params).then(data => { if (data.hasOwnProperty("error")) { - let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1); + let error = + data.error.message.charAt(0).toUpperCase() + + data.error.message.slice(1); this.sendGateway("set_check_transaction_status", { code: -1, message: error, @@ -1551,49 +1744,56 @@ export class WalletRPC { } getPrivateKeys(password) { - crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => { - if (err) { - this.sendGateway("set_wallet_data", { - secret: { - mnemonic: "notification.errors.internalError", - spend_key: -1, - view_key: -1 - } - }); - return; - } - if (!this.isValidPasswordHash(password_hash)) { - this.sendGateway("set_wallet_data", { - secret: { - mnemonic: "notification.errors.invalidPassword", - spend_key: -1, - view_key: -1 + crypto.pbkdf2( + password, + this.auth[2], + 1000, + 64, + "sha512", + (err, password_hash) => { + if (err) { + this.sendGateway("set_wallet_data", { + secret: { + mnemonic: "notification.errors.internalError", + spend_key: -1, + view_key: -1 + } + }); + return; + } + if (!this.isValidPasswordHash(password_hash)) { + this.sendGateway("set_wallet_data", { + secret: { + mnemonic: "notification.errors.invalidPassword", + spend_key: -1, + view_key: -1 + } + }); + return; + } + Promise.all([ + this.sendRPC("query_key", { key_type: "mnemonic" }), + this.sendRPC("query_key", { key_type: "spend_key" }), + this.sendRPC("query_key", { key_type: "view_key" }) + ]).then(data => { + let wallet = { + secret: { + mnemonic: "", + spend_key: "", + view_key: "" + } + }; + for (let n of data) { + if (n.hasOwnProperty("error") || !n.hasOwnProperty("result")) { + continue; + } + wallet.secret[n.params.key_type] = n.result.key; } + + this.sendGateway("set_wallet_data", wallet); }); - return; } - Promise.all([ - this.sendRPC("query_key", { key_type: "mnemonic" }), - this.sendRPC("query_key", { key_type: "spend_key" }), - this.sendRPC("query_key", { key_type: "view_key" }) - ]).then(data => { - let wallet = { - secret: { - mnemonic: "", - spend_key: "", - view_key: "" - } - }; - for (let n of data) { - if (n.hasOwnProperty("error") || !n.hasOwnProperty("result")) { - continue; - } - wallet.secret[n.params.key_type] = n.result.key; - } - - this.sendGateway("set_wallet_data", wallet); - }); - }); + ); } getAddressList() { @@ -1635,7 +1835,8 @@ export class WalletRPC { if (address_balance.address_index == address.address_index) { address.balance = address_balance.balance; address.unlocked_balance = address_balance.unlocked_balance; - address.num_unspent_outputs = address_balance.num_unspent_outputs; + address.num_unspent_outputs = + address_balance.num_unspent_outputs; break; } } @@ -1654,7 +1855,11 @@ export class WalletRPC { wallet.address_list.unused = wallet.address_list.unused.slice(0, 10); if (wallet.address_list.unused.length < num_unused_addresses) { - for (let n = wallet.address_list.unused.length; n < num_unused_addresses; n++) { + for ( + let n = wallet.address_list.unused.length; + n < num_unused_addresses; + n++ + ) { this.sendRPC("create_address", { account_index: 0 }).then(data => { @@ -1691,18 +1896,37 @@ export class WalletRPC { } }; - const types = ["in", "out", "pending", "failed", "pool", "miner", "snode", "gov", "stake"]; + const types = [ + "in", + "out", + "pending", + "failed", + "pool", + "miner", + "snode", + "gov", + "stake" + ]; types.forEach(type => { if (data.result.hasOwnProperty(type)) { - wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result[type]); + wallet.transactions.tx_list = wallet.transactions.tx_list.concat( + data.result[type] + ); } }); for (let i = 0; i < wallet.transactions.tx_list.length; i++) { if (/^0*$/.test(wallet.transactions.tx_list[i].payment_id)) { wallet.transactions.tx_list[i].payment_id = ""; - } else if (/^0*$/.test(wallet.transactions.tx_list[i].payment_id.substring(16))) { - wallet.transactions.tx_list[i].payment_id = wallet.transactions.tx_list[i].payment_id.substring(0, 16); + } else if ( + /^0*$/.test(wallet.transactions.tx_list[i].payment_id.substring(16)) + ) { + wallet.transactions.tx_list[ + i + ].payment_id = wallet.transactions.tx_list[i].payment_id.substring( + 0, + 16 + ); } } @@ -1758,9 +1982,15 @@ export class WalletRPC { }); for (const entry of addresses) { - const list = entry.starred ? wallet.address_list.address_book_starred : wallet.address_list.address_book; + const list = entry.starred + ? wallet.address_list.address_book_starred + : wallet.address_list.address_book; const hasAddress = list.find(a => { - return a.address === entry.address && a.name === entry.name && a.payment_id === entry.payment_id; + return ( + a.address === entry.address && + a.name === entry.name && + a.payment_id === entry.payment_id + ); }); if (!hasAddress) { list.push(entry); @@ -1784,7 +2014,14 @@ export class WalletRPC { } } - addAddressBook(address, payment_id = null, description = "", name = "", starred = false, index = false) { + addAddressBook( + address, + payment_id = null, + description = "", + name = "", + starred = false, + index = false + ) { if (index !== false) { this.sendRPC("delete_address_book", { index: index }).then(() => { this.addAddressBook(address, payment_id, description, name, starred); @@ -1825,111 +2062,144 @@ export class WalletRPC { } exportKeyImages(password, filename = null) { - crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => { - if (err) { - this.sendGateway("show_notification", { - type: "negative", - i18n: "notification.errors.internalError", - timeout: 2000 - }); - return; - } - if (!this.isValidPasswordHash(password_hash)) { - this.sendGateway("show_notification", { - type: "negative", - i18n: "notification.errors.invalidPassword", - timeout: 2000 - }); - return; - } + crypto.pbkdf2( + password, + this.auth[2], + 1000, + 64, + "sha512", + (err, password_hash) => { + if (err) { + this.sendGateway("show_notification", { + type: "negative", + i18n: "notification.errors.internalError", + timeout: 2000 + }); + return; + } + if (!this.isValidPasswordHash(password_hash)) { + this.sendGateway("show_notification", { + type: "negative", + i18n: "notification.errors.invalidPassword", + timeout: 2000 + }); + return; + } - if (filename == null) { - filename = path.join(this.wallet_data_dir, "images", this.wallet_state.name, "key_image_export"); - } else { - filename = path.join(filename, "key_image_export"); - } + if (filename == null) { + filename = path.join( + this.wallet_data_dir, + "images", + this.wallet_state.name, + "key_image_export" + ); + } else { + filename = path.join(filename, "key_image_export"); + } - const onError = () => - this.sendGateway("show_notification", { - type: "negative", - i18n: "notification.errors.keyImages.exporting", - timeout: 2000 - }); + const onError = () => + this.sendGateway("show_notification", { + type: "negative", + i18n: "notification.errors.keyImages.exporting", + timeout: 2000 + }); - this.sendRPC("export_key_images") - .then(data => { - if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) { - onError(); - return; - } + this.sendRPC("export_key_images") + .then(data => { + if ( + data.hasOwnProperty("error") || + !data.hasOwnProperty("result") + ) { + onError(); + return; + } - if (data.result.signed_key_images) { - fs.outputJSONSync(filename, data.result.signed_key_images); - this.sendGateway("show_notification", { - i18n: ["notification.positive.keyImages.exported", { filename }], - timeout: 2000 - }); - } else { - this.sendGateway("show_notification", { - type: "warning", - textColor: "black", - i18n: "notification.warnings.noKeyImageExport", - timeout: 2000 - }); - } - }) - .catch(onError); - }); + if (data.result.signed_key_images) { + fs.outputJSONSync(filename, data.result.signed_key_images); + this.sendGateway("show_notification", { + i18n: [ + "notification.positive.keyImages.exported", + { filename } + ], + timeout: 2000 + }); + } else { + this.sendGateway("show_notification", { + type: "warning", + textColor: "black", + i18n: "notification.warnings.noKeyImageExport", + timeout: 2000 + }); + } + }) + .catch(onError); + } + ); } importKeyImages(password, filename = null) { - crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => { - if (err) { - this.sendGateway("show_notification", { - type: "negative", - i18n: "notification.errors.internalError", - timeout: 2000 - }); - return; - } - if (!this.isValidPasswordHash(password_hash)) { - this.sendGateway("show_notification", { - type: "negative", - i18n: "notification.errors.invalidPassword", - timeout: 2000 - }); - return; - } + crypto.pbkdf2( + password, + this.auth[2], + 1000, + 64, + "sha512", + (err, password_hash) => { + if (err) { + this.sendGateway("show_notification", { + type: "negative", + i18n: "notification.errors.internalError", + timeout: 2000 + }); + return; + } + if (!this.isValidPasswordHash(password_hash)) { + this.sendGateway("show_notification", { + type: "negative", + i18n: "notification.errors.invalidPassword", + timeout: 2000 + }); + return; + } - if (filename == null) { - filename = path.join(this.wallet_data_dir, "images", this.wallet_state.name, "key_image_export"); - } + if (filename == null) { + filename = path.join( + this.wallet_data_dir, + "images", + this.wallet_state.name, + "key_image_export" + ); + } - const onError = i18n => - this.sendGateway("show_notification", { - type: "negative", - i18n, - timeout: 2000 - }); + const onError = i18n => + this.sendGateway("show_notification", { + type: "negative", + i18n, + timeout: 2000 + }); - fs.readJSON(filename) - .then(signed_key_images => { - this.sendRPC("import_key_images", { - signed_key_images - }).then(data => { - if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) { - onError("notification.errors.keyImages.importing"); - return; - } + fs.readJSON(filename) + .then(signed_key_images => { + this.sendRPC("import_key_images", { + signed_key_images + }).then(data => { + if ( + data.hasOwnProperty("error") || + !data.hasOwnProperty("result") + ) { + onError("notification.errors.keyImages.importing"); + return; + } - this.sendGateway("show_notification", { - i18n: "notification.positive.keyImages.imported", - timeout: 2000 + this.sendGateway("show_notification", { + i18n: "notification.positive.keyImages.imported", + timeout: 2000 + }); }); - }); - }) - .catch(() => onError("notification.errors.keyImages.reading")); - }); + }) + .catch(() => onError("notification.errors.keyImages.reading")); + } + ); } copyOldGuiWallets(wallets) { @@ -2063,15 +2333,27 @@ export class WalletRPC { password_protected: null }; - if (fs.existsSync(path.join(this.wallet_dir, wallet_name + ".meta.json"))) { - let meta = fs.readFileSync(path.join(this.wallet_dir, wallet_name + ".meta.json"), "utf8"); + if ( + fs.existsSync(path.join(this.wallet_dir, wallet_name + ".meta.json")) + ) { + let meta = fs.readFileSync( + path.join(this.wallet_dir, wallet_name + ".meta.json"), + "utf8" + ); if (meta) { meta = JSON.parse(meta); wallet_data.address = meta.address; wallet_data.password_protected = meta.password_protected; } - } else if (fs.existsSync(path.join(this.wallet_dir, wallet_name + ".address.txt"))) { - let address = fs.readFileSync(path.join(this.wallet_dir, wallet_name + ".address.txt"), "utf8"); + } else if ( + fs.existsSync( + path.join(this.wallet_dir, wallet_name + ".address.txt") + ) + ) { + let address = fs.readFileSync( + path.join(this.wallet_dir, wallet_name + ".address.txt"), + "utf8" + ); if (address) { wallet_data.address = address; } @@ -2094,15 +2376,26 @@ export class WalletRPC { } for (var i = 0; i < legacy_paths.length; i++) { try { - let legacy_config_path = path.join(legacy_paths[i], "config", "wallet_info.json"); + let legacy_config_path = path.join( + legacy_paths[i], + "config", + "wallet_info.json" + ); if (this.net_type === "test") { - legacy_config_path = path.join(legacy_paths[i], "testnet", "config", "wallet_info.json"); + legacy_config_path = path.join( + legacy_paths[i], + "testnet", + "config", + "wallet_info.json" + ); } if (!fs.existsSync(legacy_config_path)) { continue; } - let legacy_config = JSON.parse(fs.readFileSync(legacy_config_path, "utf8")); + let legacy_config = JSON.parse( + fs.readFileSync(legacy_config_path, "utf8") + ); let legacy_wallet_path = legacy_config.wallet_filepath; if (!fs.existsSync(legacy_wallet_path)) { continue; @@ -2110,7 +2403,10 @@ export class WalletRPC { let legacy_address = ""; if (fs.existsSync(legacy_wallet_path + ".address.txt")) { - legacy_address = fs.readFileSync(legacy_wallet_path + ".address.txt", "utf8"); + legacy_address = fs.readFileSync( + legacy_wallet_path + ".address.txt", + "utf8" + ); } wallets.legacy.push({ path: legacy_wallet_path, @@ -2126,88 +2422,104 @@ export class WalletRPC { } changeWalletPassword(old_password, new_password) { - crypto.pbkdf2(old_password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => { - if (err) { - this.sendGateway("show_notification", { - type: "negative", - i18n: "notification.errors.internalError", - timeout: 2000 - }); - return; - } - if (!this.isValidPasswordHash(password_hash)) { - this.sendGateway("show_notification", { - type: "negative", - i18n: "notification.errors.invalidOldPassword", - timeout: 2000 - }); - return; - } - - this.sendRPC("change_wallet_password", { - old_password, - new_password - }).then(data => { - if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) { + crypto.pbkdf2( + old_password, + this.auth[2], + 1000, + 64, + "sha512", + (err, password_hash) => { + if (err) { this.sendGateway("show_notification", { type: "negative", - i18n: "notification.errors.changingPassword", + i18n: "notification.errors.internalError", + timeout: 2000 + }); + return; + } + if (!this.isValidPasswordHash(password_hash)) { + this.sendGateway("show_notification", { + type: "negative", + i18n: "notification.errors.invalidOldPassword", timeout: 2000 }); return; } - // store hash of the password so we can check against it later when requesting private keys, or for sending txs - this.wallet_state.password_hash = crypto - .pbkdf2Sync(new_password, this.auth[2], 1000, 64, "sha512") - .toString("hex"); + this.sendRPC("change_wallet_password", { + old_password, + new_password + }).then(data => { + if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) { + this.sendGateway("show_notification", { + type: "negative", + i18n: "notification.errors.changingPassword", + timeout: 2000 + }); + return; + } + + // store hash of the password so we can check against it later when requesting private keys, or for sending txs + this.wallet_state.password_hash = crypto + .pbkdf2Sync(new_password, this.auth[2], 1000, 64, "sha512") + .toString("hex"); - this.sendGateway("show_notification", { - i18n: "notification.positive.passwordUpdated", - timeout: 2000 + this.sendGateway("show_notification", { + i18n: "notification.positive.passwordUpdated", + timeout: 2000 + }); }); - }); - }); + } + ); } deleteWallet(password) { - crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => { - if (err) { - this.sendGateway("show_notification", { - type: "negative", - i18n: "notification.errors.internalError", - timeout: 2000 - }); - return; - } - if (!this.isValidPasswordHash(password_hash)) { - this.sendGateway("show_notification", { - type: "negative", - i18n: "notification.errors.invalidPassword", - timeout: 2000 - }); - return; - } + crypto.pbkdf2( + password, + this.auth[2], + 1000, + 64, + "sha512", + (err, password_hash) => { + if (err) { + this.sendGateway("show_notification", { + type: "negative", + i18n: "notification.errors.internalError", + timeout: 2000 + }); + return; + } + if (!this.isValidPasswordHash(password_hash)) { + this.sendGateway("show_notification", { + type: "negative", + i18n: "notification.errors.invalidPassword", + timeout: 2000 + }); + return; + } - this.sendGateway("show_loading", { - message: "Deleting wallet" - }); + this.sendGateway("show_loading", { + message: "Deleting wallet" + }); - let wallet_path = path.join(this.wallet_dir, this.wallet_state.name); - this.closeWallet().then(() => { - try { - if (fs.existsSync(wallet_path + ".keys")) fs.unlinkSync(wallet_path + ".keys"); - if (fs.existsSync(wallet_path + ".address.txt")) fs.unlinkSync(wallet_path + ".address.txt"); - if (fs.existsSync(wallet_path)) fs.unlinkSync(wallet_path); - } catch (e) { - console.warn(`Failed to delete wallet files: ${e}`); - } + let wallet_path = path.join(this.wallet_dir, this.wallet_state.name); + this.closeWallet().then(() => { + try { + if (fs.existsSync(wallet_path + ".keys")) + fs.unlinkSync(wallet_path + ".keys"); + if (fs.existsSync(wallet_path + ".address.txt")) + fs.unlinkSync(wallet_path + ".address.txt"); + if (fs.existsSync(wallet_path)) fs.unlinkSync(wallet_path); + } catch (e) { + console.warn(`Failed to delete wallet files: ${e}`); + } - this.listWallets(); - this.sendGateway("hide_loading"); - this.sendGateway("return_to_wallet_select"); - }); - }); + this.listWallets(); + this.sendGateway("hide_loading"); + this.sendGateway("return_to_wallet_select"); + }); + } + ); } async saveWallet() { diff --git a/src/components/confirm_tx_dialog.vue b/src/components/confirm_tx_dialog.vue new file mode 100644 index 00000000..557f1de8 --- /dev/null +++ b/src/components/confirm_tx_dialog.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/components/service_node/service_node_contribute.vue b/src/components/service_node/service_node_contribute.vue index 77864040..f443e120 100644 --- a/src/components/service_node/service_node_contribute.vue +++ b/src/components/service_node/service_node_contribute.vue @@ -6,13 +6,18 @@
{{ $t("titles.availableForContribution") }}
- +
-
+
{{ $t("strings.noServiceNodesCurrentlyAvailable") }}
- + @@ -37,28 +46,17 @@ export default { ServiceNodeList, ServiceNodeDetails }, + props: { + awaitingServiceNodes: { + type: Array, + required: true + } + }, computed: mapState({ - awaiting_service_nodes(state) { - const nodes = state.gateway.daemon.service_nodes.nodes; - const isAwaitingContribution = node => !node.active && !node.funded && node.requested_unlock_height === 0; - const compareFee = (n1, n2) => (this.getFeeDecimal(n1) > this.getFeeDecimal(n2) ? 1 : -1); - const awaitingContributionNodes = nodes.filter(isAwaitingContribution).map(n => { - return { - ...n, - awaitingContribution: true - }; - }); - awaitingContributionNodes.sort(compareFee); - return awaitingContributionNodes; - }, theme: state => state.gateway.app.config.appearance.theme, fetching: state => state.gateway.daemon.service_nodes.fetching }), methods: { - getFeeDecimal(node) { - const operatorPortion = node.portions_for_operator; - return (operatorPortion / 18446744073709551612) * 100; - }, scrollToTop() { window.scrollTo(0, 0); }, diff --git a/src/components/service_node/service_node_details.vue b/src/components/service_node/service_node_details.vue index 53ae2258..9d8ec3aa 100644 --- a/src/components/service_node/service_node_details.vue +++ b/src/components/service_node/service_node_details.vue @@ -15,7 +15,12 @@ :label="$t(actionI18n)" @click="action(node, $event)" /> - + @@ -29,27 +34,37 @@
- {{ $t("strings.serviceNodeDetails.stakingRequirement") }} + {{ + $t("strings.serviceNodeDetails.stakingRequirement") + }}
- +
- {{ $t("strings.serviceNodeDetails.totalContributed") }} + {{ + $t("strings.serviceNodeDetails.totalContributed") + }}
- +
- {{ $t("strings.serviceNodeDetails.registrationHeight") }} + {{ + $t("strings.serviceNodeDetails.registrationHeight") + }}
{{ node.registration_height }} @@ -59,7 +74,9 @@
- {{ $t("strings.serviceNodeDetails.operatorFee") }} + {{ + $t("strings.serviceNodeDetails.operatorFee") + }}
{{ operatorFee }} @@ -69,7 +86,9 @@
- {{ $t("strings.serviceNodeDetails.unlockHeight") }} + {{ + $t("strings.serviceNodeDetails.unlockHeight") + }}
{{ node.requested_unlock_height }} @@ -79,7 +98,9 @@
- {{ $t("strings.serviceNodeDetails.lastUptimeProof") }} + {{ + $t("strings.serviceNodeDetails.lastUptimeProof") + }}
{{ formatDate(node.last_uptime_proof * 1000) }} @@ -89,7 +110,9 @@
- {{ $t("strings.serviceNodeDetails.lastRewardBlockHeight") }} + {{ + $t("strings.serviceNodeDetails.lastRewardBlockHeight") + }}
{{ node.last_reward_block_height }} @@ -98,7 +121,11 @@
- {{ $t("strings.serviceNodeDetails.contributors") }}: + {{ + $t("strings.serviceNodeDetails.contributors") + }}: - {{ $t("strings.me") }} - {{ contributor.name }} - {{ contributor.address }} + {{ $t("strings.me") }} + {{ + contributor.name + }} + {{ + contributor.address + }} - {{ $t("strings.operator") }} • + {{ $t("strings.operator") }} • + {{ $t("strings.contribution") }}: - +
- + @@ -151,7 +194,9 @@ export default { } }, data() { - const menuItems = [{ action: "copyAddress", i18n: "menuItems.copyAddress" }]; + const menuItems = [ + { action: "copyAddress", i18n: "menuItems.copyAddress" } + ]; return { isVisible: false, @@ -180,7 +225,9 @@ export default { for (const contributor of this.node.contributors) { let values = { ...contributor }; - const address = address_book.find(a => a.address === contributor.address); + const address = address_book.find( + a => a.address === contributor.address + ); if (address) { const { name, description } = address; const separator = description === "" ? "" : " - "; diff --git a/src/components/service_node/service_node_list.vue b/src/components/service_node/service_node_list.vue index 868d2cd6..99ad75f5 100644 --- a/src/components/service_node/service_node_list.vue +++ b/src/components/service_node/service_node_list.vue @@ -8,22 +8,38 @@ > {{ $t("strings.serviceNodeDetails.snKey") }}: {{ node.service_node_pubkey }}{{ $t("strings.serviceNodeDetails.snKey") }}: + {{ node.service_node_pubkey }} - {{ getRole(node) }} • - - {{ $t("strings.contribution") }}: - + + {{ getRole(node) }} • + + {{ $t("strings.contribution") }}: + + + + + + {{ $t("strings.serviceNodeDetails.reserved") }} • - {{ $t("strings.serviceNodeDetails.minContribution") }}: {{ getMinContribution(node) }} LOKI • - {{ $t("strings.serviceNodeDetails.maxContribution") }}: {{ openForContributionLoki(node) }} LOKI + {{ $t("strings.serviceNodeDetails.minContribution") }}: + {{ getMinContribution(node) }} LOKI • + {{ $t("strings.serviceNodeDetails.maxContribution") }}: + {{ openForContributionLoki(node) }} LOKI - - {{ getFee(node) }} + + {{ + getFee(node) + }} node.total_reserved ? node.staking_requirement - node.total_reserved : 0; - return openContributionRemaining; - }, openForContributionLoki(node) { return (this.openForContribution(node) / 1e9).toFixed(4); }, @@ -117,7 +131,6 @@ export default { return this.$store.getters["gateway/isReady"]; }, getRole(node) { - // don't show a role if the user is not an operator or contributor let role = ""; const opAddress = node.operator_address; if (opAddress === this.our_address) { @@ -131,21 +144,12 @@ export default { getNumContributors(node) { return node.contributors.length; }, - getMinContribution(node) { - // This is calculated in the same way it is calculated on the LokiBlocks site - const openContributionRemaining = this.openForContribution(node); - const minContributionAtomicUnits = - !node.funded && node.contributors.length < MAX_NUMBER_OF_CONTRIBUTORS - ? openContributionRemaining / (MAX_NUMBER_OF_CONTRIBUTORS - node.contributors.length) - : 0; - const minContributionLoki = minContributionAtomicUnits / 1e9; - // ceiling to 4 decimal places - return minContributionLoki.toFixed(4); - }, getFee(node) { const operatorPortion = node.portions_for_operator; const percentageFee = (operatorPortion / 18446744073709551612) * 100; - return `${percentageFee.toFixed(2)}% ${this.$t("strings.transactions.fee")}`; + return `${percentageFee.toFixed(2)}% ${this.$t( + "strings.transactions.fee" + )}`; }, copyKey(key) { clipboard.writeText(key); diff --git a/src/components/service_node/service_node_staking.vue b/src/components/service_node/service_node_staking.vue index d9435320..17a5f5c2 100644 --- a/src/components/service_node/service_node_staking.vue +++ b/src/components/service_node/service_node_staking.vue @@ -3,11 +3,16 @@

{{ $t("strings.serviceNodeContributionDescription") }} - Loki {{ $t("strings.website") }}.

- + - - + {{ $t("buttons.all") }} + :label="$t('buttons.min')" + :disable="!areButtonsEnabled()" + @click="service_node.amount = minStake(service_node.key)" + /> +
- +
- - + +
@@ -61,7 +89,12 @@ import { required, decimal } from "vuelidate/lib/validators"; import { service_node_key, greater_than_zero } from "src/validators/common"; import LokiField from "components/loki_field"; import WalletPassword from "src/mixins/wallet_password"; +import ConfirmDialogMixin from "src/mixins/confirm_dialog_mixin"; import ServiceNodeContribute from "./service_node_contribute"; +import ServiceNodeMixin from "src/mixins/service_node_mixin"; + +// the case for doing nothing on a tx_status update +const DO_NOTHING = 10; export default { name: "ServiceNodeStaking", @@ -69,12 +102,16 @@ export default { LokiField, ServiceNodeContribute }, - mixins: [WalletPassword], + mixins: [WalletPassword, ConfirmDialogMixin, ServiceNodeMixin], data() { return { service_node: { key: "", - amount: 0 + amount: 0, + // the min and max are for that particular SN, + // start at min/max for the wallet + minStakeAmount: 0, + maxStakeAmount: this.unlocked_balance / 1e9 } }; }, @@ -95,6 +132,49 @@ export default { const wallet = state.gateway.wallet.info; const prefix = (wallet && wallet.address && wallet.address[0]) || "L"; return `${prefix}..`; + }, + awaiting_service_nodes(state) { + const nodes = state.gateway.daemon.service_nodes.nodes; + // a reserved node is one on which someone is a "contributor" of amount = 0 + const getOurContribution = node => + node.contributors.find( + c => c.address === this.our_address && c.amount > 0 + ); + const isAwaitingContribution = node => + !node.active && !node.funded && node.requested_unlock_height === 0; + const isAwaitingContributionNonReserved = node => + isAwaitingContribution(node) && !getOurContribution(node); + const isAwaitingContributionReserved = node => + isAwaitingContribution(node) && getOurContribution(node); + + // we want the reserved nodes sorted by fee at the top + const awaitingContributionNodesReserved = nodes + .filter(isAwaitingContributionReserved) + .map(n => { + return { + ...n, + awaitingContribution: true + }; + }); + const awaitingContributionNodesNonReserved = nodes + .filter(isAwaitingContributionNonReserved) + .map(n => { + return { + ...n, + awaitingContribution: true + }; + }); + + const compareFee = (n1, n2) => + this.getFeeDecimal(n1) > this.getFeeDecimal(n2) ? 1 : -1; + awaitingContributionNodesReserved.sort(compareFee); + awaitingContributionNodesNonReserved.sort(compareFee); + + const nodesForContribution = [ + ...awaitingContributionNodesReserved, + ...awaitingContributionNodesNonReserved + ]; + return nodesForContribution; } }), validations: { @@ -135,28 +215,6 @@ export default { } }, deep: true - }, - tx_status: { - handler(val, old) { - if (val.code == old.code) return; - switch (this.tx_status.code) { - case 0: - this.$q.notify({ - type: "positive", - timeout: 1000, - message: this.tx_status.message - }); - break; - case -1: - this.$q.notify({ - type: "negative", - timeout: 3000, - message: this.tx_status.message - }); - break; - } - }, - deep: true } }, methods: { @@ -170,6 +228,37 @@ export default { this.service_node.key = key; this.service_node.amount = minContribution; }, + minStake() { + const node = this.getNodeWithPubKey(); + return this.getMinContribution(node); + }, + maxStake() { + const node = this.getNodeWithPubKey(); + return this.openForContributionLoki(node); + }, + getFeeDecimal(node) { + const operatorPortion = node.portions_for_operator; + return (operatorPortion / 18446744073709551612) * 100; + }, + buildDialogFieldsSweep(txData) { + this.buildDialogFields(txData); + }, + getNodeWithPubKey() { + const key = this.service_node.key; + const nodeOfKey = this.awaiting_service_nodes.find( + n => n.service_node_pubkey === key + ); + if (!nodeOfKey) { + this.$q.notify({ + type: "negative", + timeout: 1000, + message: this.$t("notification.errors.invalidServiceNodeKey") + }); + return; + } else { + return nodeOfKey; + } + }, sweepAllWarning() { this.$q .dialog({ @@ -192,6 +281,13 @@ export default { .onDismiss(() => {}) .onCancel(() => {}); }, + areButtonsEnabled() { + // if we can find the service node key in the list of service nodes + const key = this.service_node.key; + return !!this.awaiting_service_nodes.find( + n => n.service_node_pubkey === key + ); + }, async sweepAll() { const { unlocked_balance } = this.info; @@ -215,12 +311,16 @@ export default { .onOk(password => { password = password || ""; this.$store.commit("gateway/set_tx_status", { - code: 1, + code: DO_NOTHING, message: "Sweeping all", sending: true }); const newTx = objectAssignDeep.noMutate(tx, { password }); - this.$gateway.send("wallet", "transfer", newTx); + const txWithSweepAll = { + ...newTx, + isSweepAll: true + }; + this.$gateway.send("wallet", "transfer", txWithSweepAll); }) .onDismiss(() => {}) .onCancel(() => {}); @@ -272,7 +372,7 @@ export default { noPasswordMessage: this.$t("dialog.stake.message"), ok: { label: this.$t("dialog.stake.ok"), - color: this.theme == "dark" ? "white" : "dark" + color: "primary" }, dark: this.theme == "dark", color: this.theme == "dark" ? "white" : "dark" diff --git a/src/components/service_node/service_node_unlock.vue b/src/components/service_node/service_node_unlock.vue index b394c210..13beccf2 100644 --- a/src/components/service_node/service_node_unlock.vue +++ b/src/components/service_node/service_node_unlock.vue @@ -5,7 +5,9 @@ {{ $t("titles.currentlyStakedNodes") }} - {{ $t("strings.serviceNodeStartStakingDescription") }} + {{ + $t("strings.serviceNodeStartStakingDescription") + }}
- + - +
@@ -57,8 +66,12 @@ export default { }, // just SNs the user has contributed to service_nodes(state) { - const nodes = state.gateway.daemon.service_nodes.nodes; - const getOurContribution = node => node.contributors.find(c => c.address === this.our_address); + let nodes = state.gateway.daemon.service_nodes.nodes; + // don't count reserved nodes in my stakes (where they are a contributor of amount 0) + const getOurContribution = node => + node.contributors.find( + c => c.address === this.our_address && c.amount > 0 + ); return nodes.filter(getOurContribution).map(n => { const ourContribution = getOurContribution(n); return { @@ -219,7 +232,10 @@ export default { }); }, getRole(node) { - const key = node.operator_address === this.our_address ? "strings.operator" : "strings.contributor"; + const key = + node.operator_address === this.our_address + ? "strings.operator" + : "strings.contributor"; return this.$t(key); }, getFee(node) { diff --git a/src/i18n/en-us.js b/src/i18n/en-us.js index 9b3f4b8b..1e6b9752 100644 --- a/src/i18n/en-us.js +++ b/src/i18n/en-us.js @@ -22,6 +22,8 @@ export default { import: "IMPORT", importWallet: "IMPORT WALLET | IMPORT WALLETS", lns: "LOKI NAME SERVICE", + max: "MAX", + min: "MIN", next: "NEXT", openWallet: "OPEN WALLET", purchase: "PURCHASE", @@ -62,12 +64,14 @@ export default { }, copyAddress: { title: "Copy address", - message: "There is a payment id associated with this address.\nBe sure to copy the payment id separately." + message: + "There is a payment id associated with this address.\nBe sure to copy the payment id separately." }, copyPrivateKeys: { // Copy {seedWords/viewKey/spendKey} title: "Copy {type}", - message: "Be careful who you send your private keys to as they control your funds.", + message: + "Be careful who you send your private keys to as they control your funds.", seedWords: "Seed Words", viewKey: "View Key", spendKey: "Spend Key" @@ -115,7 +119,8 @@ export default { }, rescan: { title: "Rescan wallet", - message: "Warning: Some information about previous transactions\nsuch as the recipient's address will be lost.", + message: + "Warning: Some information about previous transactions\nsuch as the recipient's address will be lost.", ok: "RESCAN" }, restart: { @@ -312,7 +317,8 @@ export default { }, errors: { banningPeer: "Error banning peer", - cannotAccessRemoteNode: "Could not access remote node, please try another remote node", + cannotAccessRemoteNode: + "Could not access remote node, please try another remote node", changingPassword: "Error changing password", copyWalletFail: "Failed to copy wallet", copyingPrivateKeys: "Error copying private keys", @@ -335,8 +341,10 @@ export default { invalidAmount: "Amount not valid", invalidBackupOwner: "Backup owner address not valid", invalidNameLength: "Name must be between 1 and 64 characters long", - invalidNameFormat: "Name may only contain alphanumerics, hyphens and underscore", - invalidNameHypenNotAllowed: "Name may only begin or end with alphanumerics or an underscore", + invalidNameFormat: + "Name may only contain alphanumerics, hyphens and underscore", + invalidNameHypenNotAllowed: + "Name may only begin or end with alphanumerics or an underscore", invalidOldPassword: "Invalid old password", invalidOwner: "Owner address not valid", invalidPassword: "Invalid password", @@ -346,7 +354,8 @@ export default { invalidRestoreDate: "Invalid restore date", invalidRestoreHeight: "Invalid restore height", invalidSeedLength: "Invalid seed word length", - invalidServiceNodeCommand: "Please enter the service node registration command", + invalidServiceNodeCommand: + "Please enter the service node registration command", invalidServiceNodeKey: "Service node key not valid", invalidSessionId: "Session ID not valid", invalidWalletPath: "Invalid wallet path", @@ -384,7 +393,8 @@ export default { mnemonicSeed: "25 (or 24) word mnemonic seed", pasteTransactionId: "Paste transaction ID", pasteTransactionProof: "Paste transaction proof", - proveOptionalMessage: "Optional message against which the signature is signed", + proveOptionalMessage: + "Optional message against which the signature is signed", recipientWalletAddress: "Recipient's wallet address", selectAFile: "Please select a file", sessionId: "The Session ID to link to Loki Name Service", @@ -442,7 +452,8 @@ export default { }, remote: { title: "Remote Daemon Only", - description: "Less security, wallet will connect to a remote node to make all transactions." + description: + "Less security, wallet will connect to a remote node to make all transactions." } }, destinationUnknown: "Destination Unknown", @@ -472,9 +483,11 @@ export default { proveTransactionDescription: "Generate a proof of your incoming/outgoing payment by supplying the transaction ID, the recipient address and an optional message.\nFor the case of outgoing payments, you can get a 'Spend Proof' that proves the authorship of a transaction. In this case, you don't need to specify the recipient address.", readingWalletList: "Reading wallet list", - recentIncomingTransactionsToAddress: "Recent incoming transactions to this address", + recentIncomingTransactionsToAddress: + "Recent incoming transactions to this address", recentTransactionsWithAddress: "Recent transactions with this address", - rescanModalDescription: "Select full rescan or rescan of spent outputs only.", + rescanModalDescription: + "Select full rescan or rescan of spent outputs only.", saveSeedWarning: "Please copy and save these in a secure location!", saveToAddressBook: "Save to address book", seedWords: "Seed words", @@ -483,17 +496,22 @@ export default { "Staking contributes to the safety of the Loki network. For your contribution, you earn LOKI. Once staked, you will have to wait either 15 or 30 days to have your Loki unlocked, depending on if a stake was unlocked by a contributor or the node was deregistered. To learn more about staking, please visit the", serviceNodeRegistrationDescription: 'Enter the {registerCommand} command produced by the daemon that is registering to become a Service Node using the "{prepareCommand}" command', - serviceNodeStartStakingDescription: "To start staking, please visit the Staking tab", - noServiceNodesCurrentlyAvailable: "There are currently no service nodes available for contribution", + serviceNodeStartStakingDescription: + "To start staking, please visit the Staking tab", + noServiceNodesCurrentlyAvailable: + "There are currently no service nodes available for contribution", serviceNodeDetails: { contributors: "Contributors", lastRewardBlockHeight: "Last reward block height", lastUptimeProof: "Last uptime proof", maxContribution: "Max contribution", + minContribution: "Min contribution", + min: "MIN", operatorFee: "Operator Fee", registrationHeight: "Registration height", unlockHeight: "Unlock height", + reserved: "Reserved", serviceNodeKey: "Service Node Key", snKey: "SN Key", stakingRequirement: "Staking requirement", @@ -535,7 +553,8 @@ export default { userNotUsedAddress: "You have not used this address", userUsedAddress: "You have used this address", viewKey: "View key", - viewOnlyMode: "View only mode. Please load full wallet in order to send coins.", + viewOnlyMode: + "View only mode. Please load full wallet in order to send coins.", website: "website" }, titles: { diff --git a/src/mixins/confirm_dialog_mixin.js b/src/mixins/confirm_dialog_mixin.js new file mode 100644 index 00000000..728e3b78 --- /dev/null +++ b/src/mixins/confirm_dialog_mixin.js @@ -0,0 +1,21 @@ +export default { + methods: { + buildDialogFields(val) { + const { feeList, amountList, destinations, metadataList, priority, isSweepAll, address } = val.txData; + const totalFees = feeList.reduce((a, b) => a + b, 0) / 1e9; + const totalAmount = amountList.reduce((a, b) => a + b, 0) / 1e9; + // If the tx is a sweep all, we're sending to the wallet's primary address + // a tx can be split, but only sent to one address + let destination = isSweepAll ? address : destinations[0].address; + const isBlink = [0, 2, 3, 4, 5].includes(priority) ? true : false; + const confirmFields = { + metadataList, + isBlink, + destination, + totalAmount, + totalFees + }; + return confirmFields; + } + } +}; diff --git a/src/mixins/service_node_mixin.js b/src/mixins/service_node_mixin.js new file mode 100644 index 00000000..e8a1b961 --- /dev/null +++ b/src/mixins/service_node_mixin.js @@ -0,0 +1,27 @@ +export default { + methods: { + getMinContribution(node) { + const MAX_NUMBER_OF_CONTRIBUTORS = 4; + // This is calculated in the same way it is calculated on the LokiBlocks site + const openContributionRemaining = this.openForContribution(node); + const minContributionAtomicUnits = + !node.funded && node.contributors.length < MAX_NUMBER_OF_CONTRIBUTORS + ? openContributionRemaining / + (MAX_NUMBER_OF_CONTRIBUTORS - node.contributors.length) + : 0; + const minContributionLoki = minContributionAtomicUnits / 1e9; + // ceiling to 4 decimal places + return minContributionLoki.toFixed(4); + }, + openForContribution(node) { + const openContributionRemaining = + node.staking_requirement > node.total_reserved + ? node.staking_requirement - node.total_reserved + : 0; + return openContributionRemaining; + }, + openForContributionLoki(node) { + return (this.openForContribution(node) / 1e9).toFixed(4); + } + } +}; diff --git a/src/pages/wallet/send.vue b/src/pages/wallet/send.vue index dfc7f2be..8f84960a 100644 --- a/src/pages/wallet/send.vue +++ b/src/pages/wallet/send.vue @@ -10,7 +10,10 @@
- +
- + - + {{ $t("buttons.contacts") }} @@ -67,7 +77,11 @@
- +
- - - -
{{ $t("dialog.confirmTransaction.title") }}
-
- -
-
- {{ $t("dialog.confirmTransaction.sendTo") }}: -
- {{ confirmFields.destination }} -
-
- {{ $t("strings.transactions.amount") }}: - {{ confirmFields.totalAmount }} Loki -
- {{ $t("strings.transactions.fee") }}: {{ confirmFields.totalFees }} Loki -
- {{ $t("dialog.confirmTransaction.priority") }}: - {{ confirmFields.translatedBlinkOrSlow }} -
-
- - - - -
-
+ @@ -186,6 +175,8 @@ import { required, decimal } from "vuelidate/lib/validators"; import { payment_id, address, greater_than_zero } from "src/validators/common"; import LokiField from "components/loki_field"; import WalletPassword from "src/mixins/wallet_password"; +import ConfirmDialogMixin from "src/mixins/confirm_dialog_mixin"; +import ConfirmTransactionDialog from "components/confirm_tx_dialog"; const objectAssignDeep = require("object-assign-deep"); // the case for doing nothing on a tx_status update @@ -193,9 +184,10 @@ const DO_NOTHING = 10; export default { components: { - LokiField + LokiField, + ConfirmTransactionDialog }, - mixins: [WalletPassword], + mixins: [WalletPassword, ConfirmDialogMixin], data() { let priorityOptions = [ { label: this.$t("strings.priorityOptions.blink"), value: 5 }, // Blink @@ -214,8 +206,13 @@ export default { } }, priorityOptions: priorityOptions, - confirmTransaction: false, - confirmFields: {} + confirmFields: { + metadataList: [], + isBlink: false, + totalAmount: -1, + destination: "", + totalFees: 0 + } }; }, computed: mapState({ @@ -233,7 +230,8 @@ export default { const wallet = state.gateway.wallet.info; const prefix = (wallet && wallet.address && wallet.address[0]) || "L"; return `${prefix}..`; - } + }, + confirmTransaction: state => state.gateway.tx_status.code === 1 }), validations: { newTx: { @@ -268,7 +266,7 @@ export default { case DO_NOTHING: break; case 1: - this.buildDialogFields(val); + this.buildDialogFieldsSend(val); break; case 0: this.$q.notify({ @@ -308,7 +306,10 @@ export default { } }, mounted() { - if (this.$route.path == "/wallet/send" && this.$route.query.hasOwnProperty("address")) { + if ( + this.$route.path == "/wallet/send" && + this.$route.query.hasOwnProperty("address") + ) { this.autoFill(this.$route.query); } }, @@ -317,25 +318,9 @@ export default { this.newTx.address = info.address; this.newTx.payment_id = info.payment_id; }, - buildDialogFields(val) { - this.confirmTransaction = true; - const { feeList, amountList, destinations, metadataList, priority } = val.txData; - const totalFees = feeList.reduce((a, b) => a + b, 0) / 1e9; - const totalAmount = amountList.reduce((a, b) => a + b, 0) / 1e9; - // a tx can be split, but only sent to one address - const destination = destinations[0].address; - - const isBlink = [0, 2, 3, 4, 5].includes(priority) ? true : false; - const blinkOrSlow = isBlink ? "strings.priorityOptions.blink" : "strings.priorityOptions.slow"; - const translatedBlinkOrSlow = this.$t(blinkOrSlow); - this.confirmFields = { - metadataList, - isBlink, - translatedBlinkOrSlow, - destination, - totalAmount, - totalFees - }; + buildDialogFieldsSend(txData) { + // build using mixin method + this.confirmFields = this.buildDialogFields(txData); }, onConfirmTransaction() { // put the loading spinner up @@ -369,7 +354,13 @@ export default { // Commit the transaction this.$gateway.send("wallet", "relay_tx", relayTxData); }, - // helper for constructing a dialog for confirming transactions + onCancelTransaction() { + this.$store.commit("gateway/set_tx_status", { + code: DO_NOTHING, + message: "Cancel the transaction from confirm dialog", + sending: false + }); + }, async send() { this.$v.newTx.$touch(); From e0a5d842276df5472b04f60ecc96e16dbe5a523c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 16 Sep 2020 12:50:07 +1000 Subject: [PATCH 06/15] Added build action --- .github/workflows/build.yml | 9 +++-- .github/workflows/release.yml | 69 +++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7d423db2..112ce621 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - development jobs: build: @@ -51,15 +52,15 @@ jobs: run: ls ./bin shell: bash - - name: Publish window and linux binaries + - name: Build window and linux binaries if: runner.os != 'macOS' - run: npm run release + run: npm run build env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Publish mac binaries + - name: Build mac binaries if: runner.os == 'macOS' - run: npm run release + run: npm run build env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} CSC_LINK: ${{ secrets.MAC_CERTIFICATE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..f38c54d3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,69 @@ +name: Loki Electron Wallet Release + +on: + push: + branches: + - master + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, macos-latest, ubuntu-latest] + steps: + - name: Checkout git repo + uses: actions/checkout@v1 + + - name: Install node + uses: actions/setup-node@v1 + with: + node-version: "11.9.0" + + - name: Install dependencies + run: npm install + + - name: Download lokid binaries + run: node ./build/download-binaries.js + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract zip binaries + if: runner.os != 'Linux' + run: unzip latest.zip + shell: bash + working-directory: ./downloads + + - name: Extract xz binaries + if: runner.os == 'Linux' + run: tar -xf latest.xz + shell: bash + working-directory: ./downloads + + - name: Move lokid binaries + run: | + find ./downloads -type f -name "lokid*" -exec cp '{}' ./bin \; + find ./downloads -type f -name "loki-wallet-rpc*" -exec cp '{}' ./bin \; + shell: bash + + - name: Verify binaries + run: ls ./bin + shell: bash + + - name: Publish window and linux binaries + if: runner.os != 'macOS' + run: npm run release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish mac binaries + if: runner.os == 'macOS' + run: npm run release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CSC_LINK: ${{ secrets.MAC_CERTIFICATE }} + CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} + SIGNING_APPLE_ID: ${{ secrets.SIGNING_APPLE_ID }} + SIGNING_APP_PASSWORD: ${{ secrets.SIGNING_APP_PASSWORD }} + SIGNING_TEAM_ID: ${{ secrets.SIGNING_TEAM_ID }} From e64bd76fff18779bb3a76b913761bb26efe00cb7 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Wed, 16 Sep 2020 13:00:46 +1000 Subject: [PATCH 07/15] Disable publishing on build --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46dc529f..4a9f1e7a 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "private": true, "scripts": { "dev": "quasar dev -m electron", - "build": "quasar build -m electron", + "build": "quasar build -m electron --publish=never", "release": "quasar build -m electron --publish=always", "lint": "eslint --fix .", "format": "prettier --write \"**/*.+(js|jsx|json|yml|yaml|css|md|vue)\"", From b7f06ecc35569a2834a8cce06ff60a983bea7544 Mon Sep 17 00:00:00 2001 From: kylezs Date: Thu, 17 Sep 2020 10:02:13 +1000 Subject: [PATCH 08/15] Bump node version, update README (#184) * Bump node version and update docs --- .nvmrc | 2 +- README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.nvmrc b/.nvmrc index ba9aff72..bfbf3ff1 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -11.9.0 +14.11.0 diff --git a/README.md b/README.md index 85c9504e..c9266275 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,10 @@ Please submit any changes as pull requests to the development branch, all change #### Commands ``` -nvm use 11.9.0 -npm install -g quasar-cli +nvm use 14.11.0 +npm install -g @quasar/cli git clone https://github.com/loki-project/loki-electron-gui-wallet -cd loki-electron-wallet +cd loki-electron-gui-wallet cp path_to_lokid_binaries/lokid bin/ cp path_to_lokid_binaries/loki-wallet-rpc bin/ npm install From 5157c767f5f77c0182f852b4ade9d22d28c3cce2 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 18 Sep 2020 09:55:14 +1000 Subject: [PATCH 09/15] Bump to version 1.4.6 --- package-lock.json | 19 ++++++++++++++----- package.json | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index ecbb1a01..10b896d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "loki-electron-wallet", - "version": "1.4.5", + "version": "1.4.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2784,9 +2784,9 @@ } }, "bl": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", - "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", "dev": true, "requires": { "buffer": "^5.5.0", @@ -2890,7 +2890,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz", "integrity": "sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==", - "dev": true + "dev": true, + "optional": true }, "boxen": { "version": "4.2.0", @@ -15456,6 +15457,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, + "optional": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -15474,6 +15476,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -15506,6 +15509,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, + "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -15518,6 +15522,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -15573,6 +15578,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" }, @@ -15582,6 +15588,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -15593,6 +15600,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, + "optional": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -15642,6 +15650,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, + "optional": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" diff --git a/package.json b/package.json index 4a9f1e7a..f47a2fd2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loki-electron-wallet", - "version": "1.4.5", + "version": "1.4.6", "description": "Modern GUI interface for Loki Currency", "productName": "Loki Electron Wallet", "repository": { From fa83971353e5afd4067eee08a1a86cbe883a3ad1 Mon Sep 17 00:00:00 2001 From: Mikunj Varsani Date: Fri, 18 Sep 2020 14:59:42 +1000 Subject: [PATCH 10/15] Fix asset downloading during build (#185) --- .github/workflows/build.yml | 18 ++++++++---- .github/workflows/release.yml | 14 +++++++-- .gitignore | 3 +- build/download-binaries.js | 54 ----------------------------------- downloads/download-asset.sh | 40 ++++++++++++++++++++++++++ quasar.conf.js | 11 +++++-- 6 files changed, 75 insertions(+), 65 deletions(-) delete mode 100644 build/download-binaries.js create mode 100755 downloads/download-asset.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 112ce621..10e438fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,28 +17,36 @@ jobs: - name: Checkout git repo uses: actions/checkout@v1 + # Read node version from `.nvmrc` file + - name: Read nvm rc + id: nvmrc + uses: browniebroke/read-nvmrc-action@v1 + - name: Install node uses: actions/setup-node@v1 with: - node-version: "11.9.0" + node-version: ${{ steps.nvmrc.outputs.node_version }} - name: Install dependencies run: npm install - name: Download lokid binaries - run: node ./build/download-binaries.js + run: ./download-asset.sh env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OS: ${{ runner.os }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + working-directory: ./downloads - name: Extract zip binaries if: runner.os != 'Linux' - run: unzip latest.zip + run: unzip latest shell: bash working-directory: ./downloads - name: Extract xz binaries if: runner.os == 'Linux' - run: tar -xf latest.xz + run: tar -xf latest shell: bash working-directory: ./downloads diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f38c54d3..8cd56790 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,18 +16,26 @@ jobs: - name: Checkout git repo uses: actions/checkout@v1 + # Read node version from `.nvmrc` file + - name: Read nvm rc + id: nvmrc + uses: browniebroke/read-nvmrc-action@v1 + - name: Install node uses: actions/setup-node@v1 with: - node-version: "11.9.0" + node-version: ${{ steps.nvmrc.outputs.node_version }} - name: Install dependencies run: npm install - name: Download lokid binaries - run: node ./build/download-binaries.js + run: ./download-asset.sh env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OS: ${{ runner.os }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + working-directory: ./downloads - name: Extract zip binaries if: runner.os != 'Linux' diff --git a/.gitignore b/.gitignore index e3ab8b55..a25455d9 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ bin/* .env -/downloads +downloads/* +!downloads/*.sh dev-app-update.yml diff --git a/build/download-binaries.js b/build/download-binaries.js deleted file mode 100644 index e2aa287f..00000000 --- a/build/download-binaries.js +++ /dev/null @@ -1,54 +0,0 @@ -const axios = require("axios").default; -const fs = require("fs-extra"); -const path = require("path"); - -async function download() { - const { platform, env } = process; - const repoUrl = "https://api.github.com/repos/loki-project/loki-core/releases/latest"; - try { - const pwd = process.cwd(); - const downloadDir = path.join(pwd, "downloads"); - await fs.ensureDir(downloadDir); - - const headers = { - "Content-Type": "application/json", - "User-Agent": "Loki-Electron-Wallet" - }; - if (env.GH_TOKEN) { - headers.Authorisation = `Bearer ${env.GH_TOKEN}`; - } - - const { data } = await axios.get(repoUrl, { headers }); - const { name } = data; - console.log("Latest release: " + name); - - const url = (data.assets || []) - .map(asset => asset["browser_download_url"]) - .find(url => { - if (platform === "darwin") { - return url.includes("osx") || url.includes("mac"); - } else if (platform === "win32") { - return url.includes("win") || url.includes("windows"); - } - return url.includes("linux"); - }); - - if (!url) { - throw new Error("Download url not found for " + process); - } - console.log("Downloading binary at url: " + url); - - const extension = path.extname(url); - const filePath = path.join(downloadDir, "latest" + extension); - const { data: artifact } = await axios.get(url, { - responseType: "stream" - }); - artifact.pipe(fs.createWriteStream(filePath)); - console.log("Downloaded binary to: " + filePath); - } catch (err) { - console.error("Failed to download file: " + err); - process.exit(1); - } -} - -download(); diff --git a/downloads/download-asset.sh b/downloads/download-asset.sh new file mode 100755 index 00000000..34a8c68e --- /dev/null +++ b/downloads/download-asset.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Source from: https://github.com/houqp/download-release-assets-action + +set -e + +if [ -z "$OS" ]; then + echo "OS must be set" + exit 1 +fi + +if [ -z "$RENAME" ]; then + RENAME="latest" +fi + +REPO="loki-project/loki-core" +RELEASE="latest" + +if [ "$OS" == "Linux" ]; then + FILE_NAME_REGEX="linux" +elif [ "$OS" == "Windows" ]; then + FILE_NAME_REGEX="win" +elif [ "$OS" == "macOS" ]; then + FILE_NAME_REGEX="osx" +else + echo "OS must be Linux, Windows or macOS" + exit 1 +fi + + +ASSET_URL=$(curl -sL --fail \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "https://api.github.com/repos/${REPO}/releases/${RELEASE}" \ + | jq -r ".assets | .[] | select(.name | test(\"${FILE_NAME_REGEX}\")) | .url") + +curl -sL --fail \ + -H "Accept: application/octet-stream" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -o "${RENAME}" \ + "$ASSET_URL" diff --git a/quasar.conf.js b/quasar.conf.js index 68a31311..5c0b89f1 100644 --- a/quasar.conf.js +++ b/quasar.conf.js @@ -158,7 +158,8 @@ module.exports = function() { appId: "com.loki-project.electron-wallet", productName: "Loki Electron Wallet", - copyright: "Copyright © 2018-2019 Loki Project, 2018 Ryo Currency Project", + copyright: + "Copyright © 2018-2020 Loki Project, 2018 Ryo Currency Project", afterSign: "build/notarize.js", artifactName: "loki-electron-wallet-${version}-${os}.${ext}", publish: "github", @@ -192,7 +193,13 @@ module.exports = function() { allowToChangeInstallationDirectory: true }, - files: ["!build/*.js", "!.env", "!dev-app-update.yml"], + files: [ + "!build/*.js", + "!.env", + "!dev-app-update.yml", + "!downloads/**", + "!dist/**" + ], extraResources: ["bin"] } From 34b64df5d29b8c23904984ec8322d435c1649bb3 Mon Sep 17 00:00:00 2001 From: kylezs Date: Mon, 21 Sep 2020 16:49:41 +1000 Subject: [PATCH 11/15] Better error handling after confirming tx, "send all" to use `sweep_all` RPC call. (#188) - Create new wallet when using default language fix --- .../main-process/modules/wallet-rpc.js | 32 +++++++++++-------- .../service_node/service_node_list.vue | 3 -- .../service_node/service_node_staking.vue | 9 +----- src/i18n/en-us.js | 2 -- src/pages/wallet-select/create.vue | 14 ++++++-- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src-electron/main-process/modules/wallet-rpc.js b/src-electron/main-process/modules/wallet-rpc.js index 08fd025f..a180d915 100644 --- a/src-electron/main-process/modules/wallet-rpc.js +++ b/src-electron/main-process/modules/wallet-rpc.js @@ -336,9 +336,7 @@ export class WalletRPC { params.amount, params.address, params.payment_id, - params.priority, - // return false if undefined - !!params.isSweepAll + params.priority ); break; case "relay_tx": @@ -1353,7 +1351,7 @@ export class WalletRPC { async relayTransaction(metadataList, isBlink, addressSave, note) { const { address, payment_id, address_book } = addressSave; let failed = false; - let errorMessage = ""; + let errorMessage = "Failed to relay transaction"; // submit each transaction individually for (const hex of metadataList) { @@ -1366,18 +1364,18 @@ export class WalletRPC { try { const data = await this.sendRPC("relay_tx", params); if (data.hasOwnProperty("error")) { - const errMsg = data.error.message; - const error = errMsg.charAt(0).toUpperCase() + errMsg.slice(1); - errorMessage = error; + errorMessage = data.error.message || errorMessage; failed = true; - return; - } - // save note to the new txid - if (data.hasOwnProperty("result")) { + break; + } else if (data.hasOwnProperty("result")) { const tx_hash = data.result.tx_hash; if (note && note !== "") { this.saveTxNotes(tx_hash, note); } + } else { + errorMessage = "Invalid format of relay_tx RPC return message"; + failed = true; + break; } } catch (e) { failed = true; @@ -1412,7 +1410,7 @@ export class WalletRPC { // prepares params and provides a "confirm" popup to allow the user to check // send address and tx fees before sending - transfer(password, amount, address, payment_id, priority, isSweepAll) { + transfer(password, amount, address, payment_id, priority) { const cryptoCallback = (err, password_hash) => { if (err) { this.sendGateway("set_tx_status", { @@ -1433,7 +1431,12 @@ export class WalletRPC { amount = (parseFloat(amount) * 1e9).toFixed(0); + // if sending "All" the funds, then we need to send all - fee (sweep_all) + // To be amended after the hardfork, v8. + // https://github.com/loki-project/loki-electron-gui-wallet/issues/181 + const isSweepAll = amount == this.wallet_state.unlocked_balance; const rpc_endpoint = isSweepAll ? "sweep_all" : "transfer_split"; + const rpcSpecificParams = isSweepAll ? { address, @@ -1471,15 +1474,16 @@ export class WalletRPC { }); return; } + // update state to show a confirm popup this.sendGateway("set_tx_status", { code: 1, i18n: "strings.awaitingConfirmation", sending: false, txData: { - // for a sweep all + // target address for a sweep all address: data.params.address, - isSweepAll: rpc_endpoint === "sweep_all", + isSweepAll: isSweepAll, amountList: data.result.amount_list, metadataList: data.result.tx_metadata_list, feeList: data.result.fee_list, diff --git a/src/components/service_node/service_node_list.vue b/src/components/service_node/service_node_list.vue index 99ad75f5..b3cf83e8 100644 --- a/src/components/service_node/service_node_list.vue +++ b/src/components/service_node/service_node_list.vue @@ -124,9 +124,6 @@ export default { }; return nodeWithMinContribution; }, - openForContributionLoki(node) { - return (this.openForContribution(node) / 1e9).toFixed(4); - }, is_ready() { return this.$store.getters["gateway/isReady"]; }, diff --git a/src/components/service_node/service_node_staking.vue b/src/components/service_node/service_node_staking.vue index 17a5f5c2..743445aa 100644 --- a/src/components/service_node/service_node_staking.vue +++ b/src/components/service_node/service_node_staking.vue @@ -240,9 +240,6 @@ export default { const operatorPortion = node.portions_for_operator; return (operatorPortion / 18446744073709551612) * 100; }, - buildDialogFieldsSweep(txData) { - this.buildDialogFields(txData); - }, getNodeWithPubKey() { const key = this.service_node.key; const nodeOfKey = this.awaiting_service_nodes.find( @@ -316,11 +313,7 @@ export default { sending: true }); const newTx = objectAssignDeep.noMutate(tx, { password }); - const txWithSweepAll = { - ...newTx, - isSweepAll: true - }; - this.$gateway.send("wallet", "transfer", txWithSweepAll); + this.$gateway.send("wallet", "transfer", newTx); }) .onDismiss(() => {}) .onCancel(() => {}); diff --git a/src/i18n/en-us.js b/src/i18n/en-us.js index 1e6b9752..f817280b 100644 --- a/src/i18n/en-us.js +++ b/src/i18n/en-us.js @@ -505,9 +505,7 @@ export default { lastRewardBlockHeight: "Last reward block height", lastUptimeProof: "Last uptime proof", maxContribution: "Max contribution", - minContribution: "Min contribution", - min: "MIN", operatorFee: "Operator Fee", registrationHeight: "Registration height", unlockHeight: "Unlock height", diff --git a/src/pages/wallet-select/create.vue b/src/pages/wallet-select/create.vue index 22f648d9..58ece8d1 100644 --- a/src/pages/wallet-select/create.vue +++ b/src/pages/wallet-select/create.vue @@ -1,7 +1,10 @@ @@ -79,7 +87,7 @@ export default { return { wallet: { name: "", - language: languageOptions[0], + language: languageOptions[0].value, password: "", password_confirm: "" }, From 419b6b2319d50a1c5e7ec660408c06f33b324857 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Tue, 22 Sep 2020 01:06:29 +0200 Subject: [PATCH 12/15] Update package.json (#189) Change author to "Loki Project" to match author of Session and Lokinet --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f47a2fd2..f2a9ba60 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "cordovaId": "com.lokinetwork.wallet", "author": { - "name": "Loki", + "name": "Loki Project", "email": "team@loki.network" }, "private": true, From 72155c38871b0ab29fce052383bef248a727f995 Mon Sep 17 00:00:00 2001 From: kylezs Date: Tue, 22 Sep 2020 15:20:24 +1000 Subject: [PATCH 13/15] Add bunyan logger for logging electron events to file (#190) * add bunyan logger * add logging for unhandled exception and unhandled rejection --- package-lock.json | 92 +++++++++--- package.json | 1 + src-electron/main-process/modules/backend.js | 141 +++++++++++++++---- src/components/language_select.vue | 4 +- 4 files changed, 189 insertions(+), 49 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10b896d6..1badd9d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2671,8 +2671,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -3003,7 +3002,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3248,6 +3246,17 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "bunyan": { + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.14.tgz", + "integrity": "sha512-LlahJUxXzZLuw/hetUQJmRgZ1LF6+cr5TPpRj6jf327AsiIq2jhYEH4oqUUkVKTor+9w2BT3oxVwhzE5lw9tcg==", + "requires": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" + } + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -4034,8 +4043,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -5230,6 +5238,15 @@ "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", "dev": true }, + "dtrace-provider": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", + "optional": true, + "requires": { + "nan": "^2.14.0" + } + }, "duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -8576,7 +8593,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -8585,8 +8601,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", @@ -10075,7 +10090,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -10181,8 +10195,7 @@ "moment": { "version": "2.27.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", - "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==", - "dev": true + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" }, "move-concurrently": { "version": "1.0.1", @@ -10236,11 +10249,45 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "optional": true, + "requires": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, + "requires": { + "glob": "^6.0.1" + } + } + } + }, "nan": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", - "dev": true + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" }, "nanomatch": { "version": "1.2.13", @@ -10267,6 +10314,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "optional": true + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -10907,7 +10960,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -11254,8 +11306,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -12986,6 +13037,12 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "optional": true + }, "safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", @@ -16617,8 +16674,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", diff --git a/package.json b/package.json index f2a9ba60..73a3d7bc 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "axios": "^0.18.1", + "bunyan": "^1.8.14", "electron-is-dev": "^1.0.1", "electron-updater": "^4.2.0", "electron-window-state": "^5.0.3", diff --git a/src-electron/main-process/modules/backend.js b/src-electron/main-process/modules/backend.js index eaa3c279..336b7552 100644 --- a/src-electron/main-process/modules/backend.js +++ b/src-electron/main-process/modules/backend.js @@ -5,6 +5,7 @@ import { dialog } from "electron"; import semver from "semver"; import axios from "axios"; import { version } from "../../../package.json"; +const bunyan = require("bunyan"); const WebSocket = require("ws"); const os = require("os"); @@ -24,6 +25,7 @@ export class Backend { this.config_file = null; this.config_data = {}; this.scee = new SCEE(); + this.log = null; } init(config) { @@ -142,7 +144,10 @@ export class Backend { data }; - let encrypted_data = this.scee.encryptString(JSON.stringify(message), this.token); + let encrypted_data = this.scee.encryptString( + JSON.stringify(message), + this.token + ); this.wss.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { @@ -185,14 +190,22 @@ export class Backend { case "quick_save_config": // save only partial config settings Object.keys(params).map(key => { - this.config_data[key] = Object.assign(this.config_data[key], params[key]); - }); - fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => { - this.send("set_app_data", { - config: params, - pending_config: params - }); + this.config_data[key] = Object.assign( + this.config_data[key], + params[key] + ); }); + fs.writeFile( + this.config_file, + JSON.stringify(this.config_data, null, 4), + "utf8", + () => { + this.send("set_app_data", { + config: params, + pending_config: params + }); + } + ); break; case "save_config_init": case "save_config": { @@ -208,12 +221,18 @@ export class Backend { } Object.keys(params).map(key => { - this.config_data[key] = Object.assign(this.config_data[key], params[key]); + this.config_data[key] = Object.assign( + this.config_data[key], + params[key] + ); }); const validated = Object.keys(this.defaults) .filter(k => k in this.config_data) - .map(k => [k, this.validate_values(this.config_data[k], this.defaults[k])]) + .map(k => [ + k, + this.validate_values(this.config_data[k], this.defaults[k]) + ]) .reduce((map, obj) => { map[obj[0]] = obj[1]; return map; @@ -225,19 +244,24 @@ export class Backend { ...validated }; - fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => { - if (data.method == "save_config_init") { - this.startup(); - } else { - this.send("set_app_data", { - config: this.config_data, - pending_config: this.config_data - }); - if (config_changed) { - this.send("settings_changed_reboot"); + fs.writeFile( + this.config_file, + JSON.stringify(this.config_data, null, 4), + "utf8", + () => { + if (data.method == "save_config_init") { + this.startup(); + } else { + this.send("set_app_data", { + config: this.config_data, + pending_config: this.config_data + }); + if (config_changed) { + this.send("settings_changed_reboot"); + } } } - }); + ); break; } case "init": @@ -255,7 +279,10 @@ export class Backend { } if (path) { - const baseUrl = net_type === "testnet" ? "https://lokitestnet.com" : "https://lokiblocks.com"; + const baseUrl = + net_type === "testnet" + ? "https://lokitestnet.com" + : "https://lokiblocks.com"; const url = `${baseUrl}/${path}/`; require("electron").shell.openExternal(url + params.id); } @@ -279,12 +306,18 @@ export class Backend { if (err) { this.send("show_notification", { type: "negative", - i18n: ["notification.errors.errorSavingItem", { item: params.type }], + i18n: [ + "notification.errors.errorSavingItem", + { item: params.type } + ], timeout: 2000 }); } else { this.send("show_notification", { - i18n: ["notification.positive.itemSaved", { item: params.type, filename }], + i18n: [ + "notification.positive.itemSaved", + { item: params.type, filename } + ], timeout: 2000 }); } @@ -317,6 +350,28 @@ export class Backend { } } + initLogger(logPath) { + let log = bunyan.createLogger({ + name: "log", + streams: [ + { + level: "debug", + path: path.join(logPath, "electron.log") + } + ] + }); + + this.log = log; + + process.on("uncaughtException", error => { + log.error("Unhandled Error", error); + }); + + process.on("unhandledRejection", error => { + log.error("Unhandled Promise Rejection", error); + }); + } + startup() { this.send("set_app_data", { remotes: this.remotes, @@ -344,14 +399,20 @@ export class Backend { if (!this.config_data.hasOwnProperty(key)) { this.config_data[key] = {}; } - this.config_data[key] = Object.assign(this.config_data[key], disk_config_data[key]); + this.config_data[key] = Object.assign( + this.config_data[key], + disk_config_data[key] + ); }); // here we may want to check if config data is valid, if not also send code -1 // i.e. check ports are integers and > 1024, check that data dir path exists, etc const validated = Object.keys(this.defaults) .filter(k => k in this.config_data) - .map(k => [k, this.validate_values(this.config_data[k], this.defaults[k])]) + .map(k => [ + k, + this.validate_values(this.config_data[k], this.defaults[k]) + ]) .reduce((map, obj) => { map[obj[0]] = obj[1]; return map; @@ -364,7 +425,12 @@ export class Backend { }; // save config file back to file, so updated options are stored on disk - fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => {}); + fs.writeFile( + this.config_file, + JSON.stringify(this.config_data, null, 4), + "utf8", + () => {} + ); this.send("set_app_data", { config: this.config_data, @@ -427,6 +493,9 @@ export class Backend { fs.mkdirpSync(log_dir); } + console.log("About to init the logger with log_dir: " + log_dir); + this.initLogger(log_dir); + this.daemon = new Daemon(this); this.walletd = new WalletRPC(this); @@ -605,7 +674,11 @@ export class Backend { // Replace any invalid value with default values validate_values(values, defaults) { - const isDictionary = v => typeof v === "object" && v !== null && !(v instanceof Array) && !(v instanceof Date); + const isDictionary = v => + typeof v === "object" && + v !== null && + !(v instanceof Array) && + !(v instanceof Date); const modified = { ...values }; // Make sure we have valid defaults @@ -616,7 +689,10 @@ export class Backend { if (!(key in defaults)) continue; const defaultValue = defaults[key]; - const invalidDefault = defaultValue === null || defaultValue === undefined || Number.isNaN(defaultValue); + const invalidDefault = + defaultValue === null || + defaultValue === undefined || + Number.isNaN(defaultValue); if (invalidDefault) continue; const value = modified[key]; @@ -626,7 +702,12 @@ export class Backend { modified[key] = this.validate_values(value, defaultValue); } else { // Check if we need to replace the value - const isValidValue = !(value === undefined || value === null || value === "" || Number.isNaN(value)); + const isValidValue = !( + value === undefined || + value === null || + value === "" || + Number.isNaN(value) + ); if (isValidValue) continue; // Otherwise set the default value diff --git a/src/components/language_select.vue b/src/components/language_select.vue index e962b9bc..0749e347 100644 --- a/src/components/language_select.vue +++ b/src/components/language_select.vue @@ -1,6 +1,8 @@