Loki changes
Update package json Linting. Fixed websocket not working. Move to lokid and rpc. Re-branding Fixed rpc call for wallet restoration. Force ring size of 10 Other fix. Added service node ui. Added different daemons Added remote preset loading. Fixed priority options. Fixed links.
This commit is contained in:
parent
9ce31d6ea3
commit
7b761877d1
|
@ -1 +1,2 @@
|
|||
/dist
|
||||
/src/validators/address_tools.js
|
||||
|
|
|
@ -25,6 +25,9 @@ module.exports = {
|
|||
},
|
||||
// add your custom rules here
|
||||
"rules": {
|
||||
"indent": ["error", 4],
|
||||
"quotes": ["error", "double"],
|
||||
|
||||
// allow async-await
|
||||
"generator-star-spacing": "off",
|
||||
|
||||
|
@ -32,6 +35,8 @@ module.exports = {
|
|||
"arrow-parens": 0,
|
||||
"one-var": 0,
|
||||
|
||||
"camelcase": 0,
|
||||
|
||||
"import/first": 0,
|
||||
"import/named": 2,
|
||||
"import/namespace": 2,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
|
@ -1,14 +1,17 @@
|
|||
{
|
||||
"name": "ryo-wallet-atom",
|
||||
"name": "loki-wallet-atom",
|
||||
"version": "1.1.0",
|
||||
"daemonVersion": "0.3.2.0",
|
||||
"description": "Modern GUI interface for Ryo Currency",
|
||||
"productName": "Ryo Wallet Atom",
|
||||
"description": "Modern GUI interface for Loki Currency",
|
||||
"productName": "Loki Wallet Atom",
|
||||
"cordovaId": "com.ryo-currency.ryo-gui-wallet",
|
||||
"author": "Ryo-currency <contact@ryo-currency.com>",
|
||||
"author": "Loki",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "quasar dev -m electron -t mat",
|
||||
"build": "quasar build -m electron -t mat",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"lint-fix": "eslint --fix .",
|
||||
"test": "echo \"No test specified\" && exit 0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -42,8 +45,9 @@
|
|||
"eslint-plugin-standard": "^3.0.1",
|
||||
"eslint-plugin-vue": "^4.3.0",
|
||||
"node-sass": "^4.9.3",
|
||||
"quasar-cli": "^0.16.0",
|
||||
"sass-loader": "^7.1.0"
|
||||
"quasar-cli": "^0.17.23",
|
||||
"sass-loader": "^7.1.0",
|
||||
"strip-ansi": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.9.0",
|
||||
|
|
|
@ -59,6 +59,7 @@ module.exports = function (ctx) {
|
|||
"QField",
|
||||
"QInput",
|
||||
"QRadio",
|
||||
"QOptionGroup",
|
||||
"QBtn",
|
||||
"QBtnToggle",
|
||||
"QIcon",
|
||||
|
@ -166,14 +167,14 @@ module.exports = function (ctx) {
|
|||
// win32metadata: { ... }
|
||||
|
||||
extraResource: [
|
||||
"bin",
|
||||
"bin"
|
||||
]
|
||||
},
|
||||
builder: {
|
||||
// https://www.electron.build/configuration/configuration
|
||||
|
||||
appId: "com.ryo-currency.wallet",
|
||||
productName: "Ryo Wallet Atom",
|
||||
appId: "com.lokinetwork.wallet",
|
||||
productName: "Loki Wallet Atom",
|
||||
copyright: "Copyright © 2018 Ryo Currency Project",
|
||||
|
||||
// directories: {
|
||||
|
|
|
@ -21,19 +21,19 @@ let forceQuit = false
|
|||
|
||||
const portInUse = function (port, callback) {
|
||||
var server = net.createServer(function (socket) {
|
||||
socket.write("Echo server\r\n");
|
||||
socket.pipe(socket);
|
||||
});
|
||||
socket.write("Echo server\r\n")
|
||||
socket.pipe(socket)
|
||||
})
|
||||
|
||||
server.listen(port, "127.0.0.1");
|
||||
server.listen(port, "127.0.0.1")
|
||||
server.on("error", function (e) {
|
||||
callback(true);
|
||||
});
|
||||
callback(true)
|
||||
})
|
||||
server.on("listening", function (e) {
|
||||
server.close();
|
||||
callback(false);
|
||||
});
|
||||
};
|
||||
server.close()
|
||||
callback(false)
|
||||
})
|
||||
}
|
||||
|
||||
function createWindow () {
|
||||
/**
|
||||
|
@ -93,11 +93,9 @@ function createWindow() {
|
|||
})
|
||||
|
||||
mainWindow.webContents.on("did-finish-load", () => {
|
||||
|
||||
require("crypto").randomBytes(64, (err, buffer) => {
|
||||
|
||||
// if err, then we may have to use insecure token generation perhaps
|
||||
if (err) throw err;
|
||||
if (err) throw err
|
||||
|
||||
let config = {
|
||||
port: 12213,
|
||||
|
@ -112,20 +110,16 @@ function createWindow() {
|
|||
} else {
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
title: "Startup error",
|
||||
message: `Ryo Wallet is already open, or port ${config.port} is in use`,
|
||||
message: `Loki Wallet is already open, or port ${config.port} is in use`,
|
||||
type: "error",
|
||||
buttons: ["ok"]
|
||||
}, () => {
|
||||
showConfirmClose = false
|
||||
app.quit()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
mainWindow.loadURL(process.env.APP_URL)
|
||||
|
|
|
@ -35,7 +35,7 @@ let template = [
|
|||
submenu: [
|
||||
{
|
||||
label: "Learn More",
|
||||
click () { require("electron").shell.openExternal("https://ryo-currency.com/") }
|
||||
click () { require("electron").shell.openExternal("https://loki.network/") }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ let template = [
|
|||
|
||||
if (process.platform === "darwin") {
|
||||
template.unshift({
|
||||
label: "Ryo Wallet Atom",
|
||||
label: "Loki Wallet Atom",
|
||||
submenu: [
|
||||
{role: "about"},
|
||||
{type: "separator"},
|
||||
|
|
|
@ -22,70 +22,70 @@
|
|||
SOFTWARE.
|
||||
*/
|
||||
|
||||
const crypto = require("crypto");
|
||||
const crypto = require("crypto")
|
||||
|
||||
const ALGORITHM_NAME = "aes-128-gcm";
|
||||
const ALGORITHM_NONCE_SIZE = 12;
|
||||
const ALGORITHM_TAG_SIZE = 16;
|
||||
const ALGORITHM_KEY_SIZE = 16;
|
||||
const PBKDF2_NAME = "sha256";
|
||||
const PBKDF2_SALT_SIZE = 16;
|
||||
const PBKDF2_ITERATIONS = 32767;
|
||||
const ALGORITHM_NAME = "aes-128-gcm"
|
||||
const ALGORITHM_NONCE_SIZE = 12
|
||||
const ALGORITHM_TAG_SIZE = 16
|
||||
const ALGORITHM_KEY_SIZE = 16
|
||||
const PBKDF2_NAME = "sha256"
|
||||
const PBKDF2_SALT_SIZE = 16
|
||||
const PBKDF2_ITERATIONS = 32767
|
||||
|
||||
export class SCEE {
|
||||
encryptString (plaintext, password) {
|
||||
// Generate a 128-bit salt using a CSPRNG.
|
||||
let salt = crypto.randomBytes(PBKDF2_SALT_SIZE);
|
||||
let salt = crypto.randomBytes(PBKDF2_SALT_SIZE)
|
||||
|
||||
// Derive a key using PBKDF2.
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME);
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME)
|
||||
|
||||
// Encrypt and prepend salt.
|
||||
let ciphertextAndNonceAndSalt = Buffer.concat([ salt, this.encrypt(new Buffer(plaintext, "utf8"), key) ]);
|
||||
let ciphertextAndNonceAndSalt = Buffer.concat([ salt, this.encrypt(new Buffer(plaintext, "utf8"), key) ])
|
||||
|
||||
// Return as base64 string.
|
||||
return ciphertextAndNonceAndSalt.toString("base64");
|
||||
return ciphertextAndNonceAndSalt.toString("base64")
|
||||
}
|
||||
|
||||
decryptString (base64CiphertextAndNonceAndSalt, password) {
|
||||
// Decode the base64.
|
||||
let ciphertextAndNonceAndSalt = new Buffer(base64CiphertextAndNonceAndSalt, "base64");
|
||||
let ciphertextAndNonceAndSalt = new Buffer(base64CiphertextAndNonceAndSalt, "base64")
|
||||
|
||||
// Create buffers of salt and ciphertextAndNonce.
|
||||
let salt = ciphertextAndNonceAndSalt.slice(0, PBKDF2_SALT_SIZE);
|
||||
let ciphertextAndNonce = ciphertextAndNonceAndSalt.slice(PBKDF2_SALT_SIZE);
|
||||
let salt = ciphertextAndNonceAndSalt.slice(0, PBKDF2_SALT_SIZE)
|
||||
let ciphertextAndNonce = ciphertextAndNonceAndSalt.slice(PBKDF2_SALT_SIZE)
|
||||
|
||||
// Derive the key using PBKDF2.
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME);
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME)
|
||||
|
||||
// Decrypt and return result.
|
||||
return this.decrypt(ciphertextAndNonce, key).toString("utf8");
|
||||
return this.decrypt(ciphertextAndNonce, key).toString("utf8")
|
||||
}
|
||||
|
||||
encrypt (plaintext, key) {
|
||||
// Generate a 96-bit nonce using a CSPRNG.
|
||||
let nonce = crypto.randomBytes(ALGORITHM_NONCE_SIZE);
|
||||
let nonce = crypto.randomBytes(ALGORITHM_NONCE_SIZE)
|
||||
|
||||
// Create the cipher instance.
|
||||
let cipher = crypto.createCipheriv(ALGORITHM_NAME, key, nonce);
|
||||
let cipher = crypto.createCipheriv(ALGORITHM_NAME, key, nonce)
|
||||
|
||||
// Encrypt and prepend nonce.
|
||||
let ciphertext = Buffer.concat([ cipher.update(plaintext), cipher.final() ]);
|
||||
let ciphertext = Buffer.concat([ cipher.update(plaintext), cipher.final() ])
|
||||
|
||||
return Buffer.concat([ nonce, ciphertext, cipher.getAuthTag() ]);
|
||||
return Buffer.concat([ nonce, ciphertext, cipher.getAuthTag() ])
|
||||
}
|
||||
|
||||
decrypt (ciphertextAndNonce, key) {
|
||||
// Create buffers of nonce, ciphertext and tag.
|
||||
let nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE);
|
||||
let ciphertext = ciphertextAndNonce.slice(ALGORITHM_NONCE_SIZE, ciphertextAndNonce.length - ALGORITHM_TAG_SIZE);
|
||||
let tag = ciphertextAndNonce.slice(ciphertext.length + ALGORITHM_NONCE_SIZE);
|
||||
let nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE)
|
||||
let ciphertext = ciphertextAndNonce.slice(ALGORITHM_NONCE_SIZE, ciphertextAndNonce.length - ALGORITHM_TAG_SIZE)
|
||||
let tag = ciphertextAndNonce.slice(ciphertext.length + ALGORITHM_NONCE_SIZE)
|
||||
|
||||
// Create the cipher instance.
|
||||
let cipher = crypto.createDecipheriv(ALGORITHM_NAME, key, nonce);
|
||||
let cipher = crypto.createDecipheriv(ALGORITHM_NAME, key, nonce)
|
||||
|
||||
// Decrypt and return result.
|
||||
cipher.setAuthTag(tag);
|
||||
return Buffer.concat([ cipher.update(ciphertext), cipher.final() ]);
|
||||
cipher.setAuthTag(tag)
|
||||
return Buffer.concat([ cipher.update(ciphertext), cipher.final() ])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Daemon } from "./daemon";
|
||||
import { WalletRPC } from "./wallet-rpc";
|
||||
import { SCEE } from "./SCEE-Node";
|
||||
import { dialog } from "electron";
|
||||
import { Daemon } from "./daemon"
|
||||
import { WalletRPC } from "./wallet-rpc"
|
||||
import { SCEE } from "./SCEE-Node"
|
||||
import { dialog } from "electron"
|
||||
|
||||
const WebSocket = require("ws");
|
||||
const os = require("os");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const WebSocket = require("ws")
|
||||
const os = require("os")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
export class Backend {
|
||||
constructor (mainWindow) {
|
||||
|
@ -22,58 +22,101 @@ export class Backend {
|
|||
}
|
||||
|
||||
init (config) {
|
||||
|
||||
if(os.platform() == "win32") {
|
||||
this.config_dir = "C:\\ProgramData\\ryo";
|
||||
//this.config_dir = path.join(os.homedir(), "ryo");
|
||||
if (os.platform() === "win32") {
|
||||
this.config_dir = "C:\\ProgramData\\loki-wallet"
|
||||
} else {
|
||||
this.config_dir = path.join(os.homedir(), ".ryo");
|
||||
this.config_dir = path.join(os.homedir(), ".loki-wallet")
|
||||
}
|
||||
if (!fs.existsSync(this.config_dir)) {
|
||||
fs.mkdirSync(this.config_dir);
|
||||
fs.mkdirSync(this.config_dir)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(path.join(this.config_dir, "gui"))) {
|
||||
fs.mkdirSync(path.join(this.config_dir, "gui"));
|
||||
fs.mkdirSync(path.join(this.config_dir, "gui"))
|
||||
}
|
||||
|
||||
this.config_file = path.join(this.config_dir, "gui", "config.json");
|
||||
this.config_file = path.join(this.config_dir, "gui", "config.json")
|
||||
|
||||
const daemon = {
|
||||
type: "local_remote",
|
||||
p2p_bind_ip: "0.0.0.0",
|
||||
p2p_bind_port: 22022,
|
||||
rpc_bind_ip: "127.0.0.1",
|
||||
rpc_bind_port: 22023,
|
||||
zmq_rpc_bind_ip: "127.0.0.1",
|
||||
zmq_rpc_bind_port: 22024,
|
||||
out_peers: -1,
|
||||
in_peers: -1,
|
||||
limit_rate_up: -1,
|
||||
limit_rate_down: -1,
|
||||
log_level: 0
|
||||
}
|
||||
|
||||
this.config_data = {
|
||||
|
||||
app: {
|
||||
data_dir: this.config_dir,
|
||||
ws_bind_port: 12213,
|
||||
testnet: false
|
||||
net_type: "main"
|
||||
},
|
||||
|
||||
appearance: {
|
||||
theme: "light"
|
||||
},
|
||||
|
||||
daemon: {
|
||||
type: "local_remote",
|
||||
remote_host: "geo.ryoblocks.com",
|
||||
remote_port: 12211,
|
||||
p2p_bind_ip: "0.0.0.0",
|
||||
p2p_bind_port: 12210,
|
||||
rpc_bind_ip: "127.0.0.1",
|
||||
rpc_bind_port: 12211,
|
||||
zmq_rpc_bind_ip: "127.0.0.1",
|
||||
zmq_rpc_bind_port: 12212,
|
||||
out_peers: -1,
|
||||
in_peers: -1,
|
||||
limit_rate_up: -1,
|
||||
limit_rate_down: -1,
|
||||
log_level: 0
|
||||
daemons: {
|
||||
main: {
|
||||
...daemon,
|
||||
remote_host: "doopool.xyz",
|
||||
remote_port: 22020
|
||||
},
|
||||
staging: {
|
||||
...daemon,
|
||||
p2p_bind_port: 38153,
|
||||
rpc_bind_port: 38154,
|
||||
zmq_rpc_bind_port: 38155
|
||||
},
|
||||
test: {
|
||||
...daemon,
|
||||
p2p_bind_port: 38156,
|
||||
rpc_bind_port: 38157,
|
||||
zmq_rpc_bind_port: 38158
|
||||
}
|
||||
},
|
||||
|
||||
wallet: {
|
||||
rpc_bind_port: 12214,
|
||||
rpc_bind_port: 18082,
|
||||
log_level: 0
|
||||
}
|
||||
}
|
||||
|
||||
this.remotes = [
|
||||
{
|
||||
host: "doopool.xyz",
|
||||
port: "22020"
|
||||
},
|
||||
{
|
||||
host: "rpc.nodes.rentals",
|
||||
port: "22023"
|
||||
},
|
||||
{
|
||||
host: "daemons.cryptopool.space",
|
||||
port: "22023"
|
||||
},
|
||||
{
|
||||
host: "node.loki-pool.com",
|
||||
port: "18081"
|
||||
},
|
||||
{
|
||||
host: "uk.loki.cash",
|
||||
port: "22020"
|
||||
},
|
||||
{
|
||||
host: "imaginary.stream",
|
||||
port: "22023"
|
||||
}
|
||||
]
|
||||
|
||||
this.token = config.token
|
||||
|
||||
this.wss = new WebSocket.Server({
|
||||
|
@ -82,9 +125,8 @@ export class Backend {
|
|||
})
|
||||
|
||||
this.wss.on("connection", ws => {
|
||||
ws.on("message", data => this.receive(data));
|
||||
});
|
||||
|
||||
ws.on("message", data => this.receive(data))
|
||||
})
|
||||
}
|
||||
|
||||
send (event, data = {}) {
|
||||
|
@ -93,39 +135,37 @@ 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) {
|
||||
client.send(encrypted_data)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
receive (data) {
|
||||
|
||||
let decrypted_data = JSON.parse(this.scee.decryptString(data, this.token));
|
||||
let decrypted_data = JSON.parse(this.scee.decryptString(data, this.token))
|
||||
|
||||
// route incoming request to either the daemon, wallet, or here
|
||||
switch (decrypted_data.module) {
|
||||
case "core":
|
||||
this.handle(decrypted_data);
|
||||
break;
|
||||
this.handle(decrypted_data)
|
||||
break
|
||||
case "daemon":
|
||||
if (this.daemon) {
|
||||
this.daemon.handle(decrypted_data);
|
||||
this.daemon.handle(decrypted_data)
|
||||
}
|
||||
break;
|
||||
break
|
||||
case "wallet":
|
||||
if (this.walletd) {
|
||||
this.walletd.handle(decrypted_data);
|
||||
this.walletd.handle(decrypted_data)
|
||||
}
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
handle (data) {
|
||||
|
||||
let params = data.data
|
||||
|
||||
switch (data.method) {
|
||||
|
@ -134,7 +174,7 @@ export class Backend {
|
|||
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', () => {
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => {
|
||||
this.send("set_app_data", {
|
||||
config: params,
|
||||
pending_config: params
|
||||
|
@ -148,42 +188,40 @@ export class Backend {
|
|||
Object.keys(this.config_data).map(i => {
|
||||
if (i == "appearance") return
|
||||
Object.keys(this.config_data[i]).map(j => {
|
||||
if(this.config_data[i][j] !== params[i][j])
|
||||
config_changed = true
|
||||
if (this.config_data[i][j] !== params[i][j]) { config_changed = true }
|
||||
})
|
||||
})
|
||||
case "save_config_init":
|
||||
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', () => {
|
||||
|
||||
})
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => {
|
||||
if (data.method == "save_config_init") {
|
||||
this.startup();
|
||||
this.startup()
|
||||
} else {
|
||||
this.send("set_app_data", {
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data,
|
||||
pending_config: this.config_data
|
||||
})
|
||||
if (config_changed) {
|
||||
this.send("settings_changed_reboot")
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
})
|
||||
break
|
||||
case "init":
|
||||
this.startup();
|
||||
break;
|
||||
this.startup()
|
||||
break
|
||||
|
||||
case "open_explorer":
|
||||
if (params.type == "tx") {
|
||||
require("electron").shell.openExternal("https://explorer.ryo-currency.com/tx/"+params.id)
|
||||
require("electron").shell.openExternal("https://lokiblocks.com/tx/" + params.id)
|
||||
}
|
||||
break;
|
||||
break
|
||||
|
||||
case "open_url":
|
||||
require("electron").shell.openExternal(params.url)
|
||||
break;
|
||||
break
|
||||
|
||||
case "save_png":
|
||||
let filename = dialog.showSaveDialog(this.mainWindow, {
|
||||
|
@ -193,21 +231,22 @@ export class Backend {
|
|||
})
|
||||
if (filename) {
|
||||
let base64Data = params.img.replace(/^data:image\/png;base64,/, "")
|
||||
let binaryData = new Buffer(base64Data, 'base64').toString("binary")
|
||||
let binaryData = new Buffer(base64Data, "base64").toString("binary")
|
||||
fs.writeFile(filename, binaryData, "binary", (err) => {
|
||||
if(err)
|
||||
this.send("show_notification", {type: "negative", message: "Error saving "+params.type, timeout: 2000})
|
||||
else
|
||||
this.send("show_notification", {message: params.type+" saved to "+filename, timeout: 2000})
|
||||
if (err) { this.send("show_notification", {type: "negative", message: "Error saving " + params.type, timeout: 2000}) } else { this.send("show_notification", {message: params.type + " saved to " + filename, timeout: 2000}) }
|
||||
})
|
||||
}
|
||||
break;
|
||||
break
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
startup () {
|
||||
this.send("set_app_data", {
|
||||
remotes: this.remotes
|
||||
})
|
||||
|
||||
fs.readFile(this.config_file, "utf8", (err, data) => {
|
||||
if (err) {
|
||||
this.send("set_app_data", {
|
||||
|
@ -215,95 +254,87 @@ export class Backend {
|
|||
code: -1 // Config not found
|
||||
},
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data,
|
||||
});
|
||||
return;
|
||||
pending_config: this.config_data
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let disk_config_data = JSON.parse(data);
|
||||
let disk_config_data = JSON.parse(data)
|
||||
|
||||
// semi-shallow object merge
|
||||
Object.keys(disk_config_data).map(key => {
|
||||
if(!this.config_data.hasOwnProperty(key))
|
||||
this.config_data[key] = {}
|
||||
if (!this.config_data.hasOwnProperty(key)) { this.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
|
||||
|
||||
// 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,
|
||||
pending_config: this.config_data,
|
||||
});
|
||||
pending_config: this.config_data
|
||||
})
|
||||
|
||||
if(this.config_data.app.testnet) {
|
||||
|
||||
let testnet_dir = path.join(this.config_data.app.data_dir, "testnet")
|
||||
if (!fs.existsSync(testnet_dir))
|
||||
fs.mkdirSync(testnet_dir);
|
||||
|
||||
let log_dir = path.join(this.config_data.app.data_dir, "testnet", "logs")
|
||||
if (!fs.existsSync(log_dir))
|
||||
fs.mkdirSync(log_dir);
|
||||
|
||||
} else {
|
||||
|
||||
let log_dir = path.join(this.config_data.app.data_dir, "logs")
|
||||
if (!fs.existsSync(log_dir))
|
||||
fs.mkdirSync(log_dir);
|
||||
const { net_type } = this.config_data.app
|
||||
|
||||
const dirs = {
|
||||
"main": this.config_data.app.data_dir,
|
||||
"staging": path.join(this.config_data.app.data_dir, "staging"),
|
||||
"test": path.join(this.config_data.app.data_dir, "testnet")
|
||||
}
|
||||
|
||||
this.daemon = new Daemon(this);
|
||||
this.walletd = new WalletRPC(this);
|
||||
// Make sure we have the directories we need
|
||||
const net_dir = dirs[net_type]
|
||||
if (!fs.existsSync(net_dir)) { fs.mkdirSync(net_dir) }
|
||||
|
||||
const log_dir = path.join(net_dir, "logs")
|
||||
if (!fs.existsSync(log_dir)) { fs.mkdirSync(log_dir) }
|
||||
|
||||
this.daemon = new Daemon(this)
|
||||
this.walletd = new WalletRPC(this)
|
||||
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 3 // Starting daemon
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
this.daemon.checkVersion().then((version) => {
|
||||
|
||||
if (version) {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 4,
|
||||
message: version
|
||||
}
|
||||
});
|
||||
})
|
||||
} else {
|
||||
// daemon not found, probably removed by AV, set to remote node
|
||||
this.config_data.daemon.type = "remote"
|
||||
this.config_data.daemons[net_type].type = "remote"
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 5
|
||||
},
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data,
|
||||
});
|
||||
|
||||
pending_config: this.config_data
|
||||
})
|
||||
}
|
||||
|
||||
this.daemon.start(this.config_data).then(() => {
|
||||
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 6 // Starting wallet
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
this.walletd.start(this.config_data).then(() => {
|
||||
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 7 // Reading wallet list
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
this.walletd.listWallets(true)
|
||||
|
||||
|
@ -311,18 +342,16 @@ export class Backend {
|
|||
status: {
|
||||
code: 0 // Ready
|
||||
}
|
||||
});
|
||||
})
|
||||
}).catch(error => {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
})
|
||||
})
|
||||
}).catch(error => {
|
||||
if(this.config_data.daemon.type == "remote") {
|
||||
if (this.config_data.daemons[net_type].type == "remote") {
|
||||
this.send("show_notification", {type: "negative", message: "Remote daemon cannot be reached", timeout: 2000})
|
||||
} else {
|
||||
this.send("show_notification", {type: "negative", message: "Local daemon internal error", timeout: 2000})
|
||||
|
@ -331,31 +360,24 @@ export class Backend {
|
|||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
})
|
||||
})
|
||||
}).catch(error => {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
});
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
quit () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let process = []
|
||||
if(this.daemon)
|
||||
process.push(this.daemon.quit())
|
||||
if(this.walletd)
|
||||
process.push(this.walletd.quit())
|
||||
if(this.wss)
|
||||
this.wss.close();
|
||||
if (this.daemon) { process.push(this.daemon.quit()) }
|
||||
if (this.walletd) { process.push(this.walletd.quit()) }
|
||||
if (this.wss) { this.wss.close() }
|
||||
|
||||
Promise.all(process).then(() => {
|
||||
resolve()
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import child_process from "child_process";
|
||||
const request = require("request-promise");
|
||||
const queue = require("promise-queue");
|
||||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
import child_process from "child_process"
|
||||
const request = require("request-promise")
|
||||
const queue = require("promise-queue")
|
||||
const http = require("http")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
export class Daemon {
|
||||
constructor (backend) {
|
||||
|
@ -11,35 +11,29 @@ export class Daemon {
|
|||
this.heartbeat = null
|
||||
this.heartbeat_slow = null
|
||||
this.id = 0
|
||||
this.testnet = false
|
||||
this.net_type = "main"
|
||||
this.local = false // do we have a local daemon ?
|
||||
|
||||
this.agent = new http.Agent({keepAlive: true, maxSockets: 1})
|
||||
this.queue = new queue(1, Infinity)
|
||||
|
||||
}
|
||||
|
||||
|
||||
checkVersion () {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (process.platform === "win32") {
|
||||
let ryod_path = path.join(__ryo_bin, "ryod.exe")
|
||||
let ryod_version_cmd = `"${ryod_path}" --version`
|
||||
if (!fs.existsSync(ryod_path))
|
||||
resolve(false)
|
||||
child_process.exec(ryod_version_cmd, (error, stdout, stderr) => {
|
||||
if(error)
|
||||
resolve(false)
|
||||
let lokid_path = path.join(__ryo_bin, "lokid.exe")
|
||||
let lokid_version_cmd = `"${lokid_path}" --version`
|
||||
if (!fs.existsSync(lokid_path)) { resolve(false) }
|
||||
child_process.exec(lokid_version_cmd, (error, stdout, stderr) => {
|
||||
if (error) { resolve(false) }
|
||||
resolve(stdout)
|
||||
})
|
||||
} else {
|
||||
let ryod_path = path.join(__ryo_bin, "ryod")
|
||||
let ryod_version_cmd = `"${ryod_path}" --version`
|
||||
if (!fs.existsSync(ryod_path))
|
||||
resolve(false)
|
||||
child_process.exec(ryod_version_cmd, {detached: true}, (error, stdout, stderr) => {
|
||||
if(error)
|
||||
resolve(false)
|
||||
let lokid_path = path.join(__ryo_bin, "lokid")
|
||||
let lokid_version_cmd = `"${lokid_path}" --version`
|
||||
if (!fs.existsSync(lokid_path)) { resolve(false) }
|
||||
child_process.exec(lokid_version_cmd, {detached: true}, (error, stdout, stderr) => {
|
||||
if (error) { resolve(false) }
|
||||
resolve(stdout)
|
||||
})
|
||||
}
|
||||
|
@ -47,15 +41,15 @@ export class Daemon {
|
|||
}
|
||||
|
||||
start (options) {
|
||||
|
||||
if(options.daemon.type === "remote") {
|
||||
|
||||
const { net_type } = options.app
|
||||
const daemon = options.daemons[net_type]
|
||||
if (daemon.type === "remote") {
|
||||
this.local = false
|
||||
|
||||
// save this info for later RPC calls
|
||||
this.protocol = "http://"
|
||||
this.hostname = options.daemon.remote_host
|
||||
this.port = options.daemon.remote_port
|
||||
this.hostname = daemon.remote_host
|
||||
this.port = daemon.remote_port
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendRPC("get_info").then((data) => {
|
||||
|
@ -69,54 +63,62 @@ export class Daemon {
|
|||
})
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
this.local = true
|
||||
|
||||
const args = [
|
||||
"--data-dir", options.app.data_dir,
|
||||
"--p2p-bind-ip", options.daemon.p2p_bind_ip,
|
||||
"--p2p-bind-port", options.daemon.p2p_bind_port,
|
||||
"--rpc-bind-ip", options.daemon.rpc_bind_ip,
|
||||
"--rpc-bind-port", options.daemon.rpc_bind_port,
|
||||
"--zmq-rpc-bind-ip", options.daemon.zmq_rpc_bind_ip,
|
||||
"--zmq-rpc-bind-port", options.daemon.zmq_rpc_bind_port,
|
||||
"--out-peers", options.daemon.out_peers,
|
||||
"--in-peers", options.daemon.in_peers,
|
||||
"--limit-rate-up", options.daemon.limit_rate_up,
|
||||
"--limit-rate-down", options.daemon.limit_rate_down,
|
||||
"--log-level", options.daemon.log_level,
|
||||
];
|
||||
"--p2p-bind-ip", daemon.p2p_bind_ip,
|
||||
"--p2p-bind-port", daemon.p2p_bind_port,
|
||||
"--rpc-bind-ip", daemon.rpc_bind_ip,
|
||||
"--rpc-bind-port", daemon.rpc_bind_port,
|
||||
"--zmq-rpc-bind-ip", daemon.zmq_rpc_bind_ip,
|
||||
"--zmq-rpc-bind-port", daemon.zmq_rpc_bind_port,
|
||||
"--out-peers", daemon.out_peers,
|
||||
"--in-peers", daemon.in_peers,
|
||||
"--limit-rate-up", daemon.limit_rate_up,
|
||||
"--limit-rate-down", daemon.limit_rate_down,
|
||||
"--log-level", daemon.log_level
|
||||
]
|
||||
|
||||
if(options.app.testnet) {
|
||||
this.testnet = true
|
||||
args.push("--testnet")
|
||||
args.push("--log-file", path.join(options.app.data_dir, "testnet", "logs", "ryod.log"))
|
||||
} else {
|
||||
args.push("--log-file", path.join(options.app.data_dir, "logs", "ryod.log"))
|
||||
const dirs = {
|
||||
"main": options.app.data_dir,
|
||||
"staging": path.join(options.app.data_dir, "staging"),
|
||||
"test": path.join(options.app.data_dir, "testnet")
|
||||
}
|
||||
|
||||
if(options.daemon.rpc_bind_ip !== "127.0.0.1")
|
||||
args.push("--confirm-external-bind")
|
||||
const { net_type } = options.app
|
||||
this.net_type = net_type
|
||||
|
||||
if(options.daemon.type === "local_remote" && !options.app.testnet) {
|
||||
if (net_type === "test") {
|
||||
args.push("--testnet")
|
||||
} else if (net_type === "staging") {
|
||||
args.push("--stagenet")
|
||||
}
|
||||
|
||||
args.push("--log-file", path.join(dirs[net_type], "logs", "lokid.log"))
|
||||
|
||||
if (daemon.rpc_bind_ip !== "127.0.0.1") { args.push("--confirm-external-bind") }
|
||||
|
||||
// TODO: Check if we need to push this command for staging too
|
||||
if (daemon.type === "local_remote" && net_type === "main") {
|
||||
args.push(
|
||||
"--bootstrap-daemon-address",
|
||||
`${options.daemon.remote_host}:${options.daemon.remote_port}`
|
||||
`${daemon.remote_host}:${daemon.remote_port}`
|
||||
)
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
this.daemonProcess = child_process.spawn(path.join(__ryo_bin, "ryod.exe"), args)
|
||||
this.daemonProcess = child_process.spawn(path.join(__ryo_bin, "lokid.exe"), args)
|
||||
} else {
|
||||
this.daemonProcess = child_process.spawn(path.join(__ryo_bin, "ryod"), args, {
|
||||
this.daemonProcess = child_process.spawn(path.join(__ryo_bin, "lokid"), args, {
|
||||
detached: true
|
||||
})
|
||||
}
|
||||
|
||||
// save this info for later RPC calls
|
||||
this.protocol = "http://"
|
||||
this.hostname = options.daemon.rpc_bind_ip
|
||||
this.port = options.daemon.rpc_bind_port
|
||||
this.hostname = daemon.rpc_bind_ip
|
||||
this.port = daemon.rpc_bind_port
|
||||
|
||||
this.daemonProcess.stdout.on("data", data => process.stdout.write(`Daemon: ${data}`))
|
||||
this.daemonProcess.on("error", err => process.stderr.write(`Daemon: ${err}`))
|
||||
|
@ -127,15 +129,15 @@ export class Daemon {
|
|||
this.sendRPC("get_info").then((data) => {
|
||||
if (!data.hasOwnProperty("error")) {
|
||||
this.startHeartbeat()
|
||||
clearInterval(intrvl);
|
||||
resolve();
|
||||
clearInterval(intrvl)
|
||||
resolve()
|
||||
} else {
|
||||
if (data.error.cause &&
|
||||
data.error.cause.code === "ECONNREFUSED") {
|
||||
// Ignore
|
||||
} else {
|
||||
clearInterval(intrvl);
|
||||
reject(error);
|
||||
clearInterval(intrvl)
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -144,25 +146,19 @@ export class Daemon {
|
|||
}
|
||||
|
||||
handle (data) {
|
||||
|
||||
let params = data.data
|
||||
|
||||
switch (data.method) {
|
||||
|
||||
case "ban_peer":
|
||||
this.banPeer(params.host, params.seconds)
|
||||
break
|
||||
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
banPeer (host, seconds = 3600) {
|
||||
|
||||
if(!seconds)
|
||||
seconds=3600
|
||||
if (!seconds) { seconds = 3600 }
|
||||
|
||||
let params = {
|
||||
bans: [{
|
||||
|
@ -183,22 +179,18 @@ export class Daemon {
|
|||
|
||||
// Send updated peer and ban list
|
||||
this.heartbeatSlowAction()
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
timestampToHeight (timestamp, pivot = null, recursion_limit = null) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if (timestamp > 999999999999) {
|
||||
// We have got a JS ms timestamp, convert
|
||||
timestamp = Math.floor(timestamp / 1000)
|
||||
}
|
||||
|
||||
pivot = pivot || [137500, 1528073506]
|
||||
recursion_limit = recursion_limit || 0;
|
||||
recursion_limit = recursion_limit || 0
|
||||
|
||||
let diff = Math.floor((timestamp - pivot[1]) / 240)
|
||||
let estimated_height = pivot[0] + diff
|
||||
|
@ -212,10 +204,8 @@ export class Daemon {
|
|||
}
|
||||
|
||||
this.getRPC("block_header_by_height", {height: estimated_height}).then((data) => {
|
||||
|
||||
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
if (data.error.code == -2) { // Too big height
|
||||
|
||||
this.getRPC("last_block_header").then((data) => {
|
||||
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
return reject()
|
||||
|
@ -250,14 +240,11 @@ export class Daemon {
|
|||
|
||||
// Continue recursion with new pivot
|
||||
resolve(new_pivot)
|
||||
|
||||
})
|
||||
}).then((pivot_or_height) => {
|
||||
|
||||
return Array.isArray(pivot_or_height)
|
||||
? this.timestampToHeight(timestamp, pivot_or_height, recursion_limit + 1)
|
||||
: pivot_or_height
|
||||
|
||||
}).catch(error => {
|
||||
return false
|
||||
})
|
||||
|
@ -275,7 +262,6 @@ export class Daemon {
|
|||
this.heartbeatSlowAction()
|
||||
}, 30 * 1000) // 30 seconds
|
||||
this.heartbeatSlowAction()
|
||||
|
||||
}
|
||||
|
||||
heartbeatAction () {
|
||||
|
@ -296,8 +282,7 @@ export class Daemon {
|
|||
let daemon_info = {
|
||||
}
|
||||
for (let n of data) {
|
||||
if(n == undefined || !n.hasOwnProperty("result") || n.result == undefined)
|
||||
continue
|
||||
if (n == undefined || !n.hasOwnProperty("result") || n.result == undefined) { continue }
|
||||
if (n.method == "get_info") {
|
||||
daemon_info.info = n.result
|
||||
}
|
||||
|
@ -311,7 +296,7 @@ export class Daemon {
|
|||
if (this.local) {
|
||||
actions = [
|
||||
this.getRPC("connections"),
|
||||
this.getRPC("bans"),
|
||||
this.getRPC("bans")
|
||||
// this.getRPC("txpool_backlog"),
|
||||
]
|
||||
} else {
|
||||
|
@ -326,8 +311,7 @@ export class Daemon {
|
|||
let daemon_info = {
|
||||
}
|
||||
for (let n of data) {
|
||||
if(n == undefined || !n.hasOwnProperty("result") || n.result == undefined)
|
||||
continue
|
||||
if (n == undefined || !n.hasOwnProperty("result") || n.result == undefined) { continue }
|
||||
if (n.method == "get_connections" && n.result.hasOwnProperty("connections")) {
|
||||
daemon_info.connections = n.result.connections
|
||||
} else if (n.method == "get_bans" && n.result.hasOwnProperty("bans")) {
|
||||
|
@ -355,9 +339,9 @@ export class Daemon {
|
|||
method: method
|
||||
},
|
||||
agent: this.agent
|
||||
};
|
||||
}
|
||||
if (Object.keys(params).length !== 0) {
|
||||
options.json.params = params;
|
||||
options.json.params = params
|
||||
}
|
||||
|
||||
return this.queue.add(() => {
|
||||
|
@ -393,11 +377,11 @@ export class Daemon {
|
|||
* Call one of the get_* RPC calls
|
||||
*/
|
||||
getRPC (parameter, args) {
|
||||
return this.sendRPC(`get_${parameter}`, args);
|
||||
return this.sendRPC(`get_${parameter}`, args)
|
||||
}
|
||||
|
||||
quit () {
|
||||
clearInterval(this.heartbeat);
|
||||
clearInterval(this.heartbeat)
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.daemonProcess) {
|
||||
this.daemonProcess.on("close", code => {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import child_process from "child_process";
|
||||
const request = require("request-promise");
|
||||
const queue = require("promise-queue");
|
||||
const http = require("http");
|
||||
const os = require("os");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const crypto = require("crypto");
|
||||
import child_process from "child_process"
|
||||
const request = require("request-promise")
|
||||
const queue = require("promise-queue")
|
||||
const http = require("http")
|
||||
const os = require("os")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
const crypto = require("crypto")
|
||||
|
||||
export class WalletRPC {
|
||||
constructor (backend) {
|
||||
|
@ -14,7 +14,7 @@ export class WalletRPC {
|
|||
this.wallet_dir = null
|
||||
this.auth = []
|
||||
this.id = 0
|
||||
this.testnet = false
|
||||
this.net_type = "main"
|
||||
this.heartbeat = null
|
||||
this.wallet_state = {
|
||||
open: false,
|
||||
|
@ -32,16 +32,16 @@ export class WalletRPC {
|
|||
|
||||
this.agent = new http.Agent({keepAlive: true, maxSockets: 1})
|
||||
this.queue = new queue(1, Infinity)
|
||||
|
||||
}
|
||||
|
||||
// this function will take an options object for testnet, data-dir, etc
|
||||
start (options) {
|
||||
const { net_type } = options.app
|
||||
const daemon = options.daemons[net_type]
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let daemon_address = `${options.daemon.rpc_bind_ip}:${options.daemon.rpc_bind_port}`
|
||||
if(options.daemon.type == "remote") {
|
||||
daemon_address = `${options.daemon.remote_host}:${options.daemon.remote_port}`
|
||||
let daemon_address = `${daemon.rpc_bind_ip}:${daemon.rpc_bind_port}`
|
||||
if (daemon.type == "remote") {
|
||||
daemon_address = `${daemon.remote_host}:${daemon.remote_port}`
|
||||
}
|
||||
|
||||
crypto.randomBytes(64 + 64 + 32, (err, buffer) => {
|
||||
|
@ -52,7 +52,7 @@ export class WalletRPC {
|
|||
this.auth = [
|
||||
auth.substr(0, 64), // rpc username
|
||||
auth.substr(64, 64), // rpc password
|
||||
auth.substr(128,32), // password salt
|
||||
auth.substr(128, 32) // password salt
|
||||
]
|
||||
|
||||
const args = [
|
||||
|
@ -60,37 +60,39 @@ export class WalletRPC {
|
|||
"--rpc-bind-port", options.wallet.rpc_bind_port,
|
||||
"--daemon-address", daemon_address,
|
||||
// "--log-level", options.wallet.log_level,
|
||||
"--log-level", "*:WARNING,net*:FATAL,net.http:DEBUG,global:INFO,verify:FATAL,stacktrace:INFO",
|
||||
"--log-level", "*:WARNING,net*:FATAL,net.http:DEBUG,global:INFO,verify:FATAL,stacktrace:INFO"
|
||||
]
|
||||
|
||||
let log_file
|
||||
|
||||
const { net_type } = options.app
|
||||
this.net_type = net_type
|
||||
this.data_dir = options.app.data_dir
|
||||
|
||||
if(options.app.testnet) {
|
||||
this.testnet = true
|
||||
this.wallet_dir = path.join(options.app.data_dir, "testnet", "wallets")
|
||||
log_file = path.join(options.app.data_dir, "testnet", "logs", "wallet-rpc.log")
|
||||
args.push("--testnet")
|
||||
args.push("--log-file", log_file)
|
||||
args.push("--wallet-dir", this.wallet_dir)
|
||||
} else {
|
||||
this.wallet_dir = path.join(options.app.data_dir, "wallets")
|
||||
log_file = path.join(options.app.data_dir, "logs", "wallet-rpc.log")
|
||||
args.push("--log-file", log_file)
|
||||
args.push("--wallet-dir", this.wallet_dir)
|
||||
const dirs = {
|
||||
"main": this.data_dir,
|
||||
"staging": path.join(this.data_dir, "staging"),
|
||||
"test": path.join(this.data_dir, "testnet")
|
||||
}
|
||||
|
||||
if (fs.existsSync(log_file))
|
||||
fs.truncateSync(log_file, 0)
|
||||
this.wallet_dir = path.join(dirs[net_type], "wallets")
|
||||
args.push("--wallet-dir", this.wallet_dir)
|
||||
|
||||
if (!fs.existsSync(this.wallet_dir))
|
||||
fs.mkdirSync(this.wallet_dir)
|
||||
const log_file = path.join(dirs[net_type], "logs", "wallet-rpc.log")
|
||||
args.push("--log-file", log_file)
|
||||
|
||||
if (net_type === "test") {
|
||||
args.push("--testnet")
|
||||
} else if (net_type === "staging") {
|
||||
args.push("--stagenet")
|
||||
}
|
||||
|
||||
if (fs.existsSync(log_file)) { fs.truncateSync(log_file, 0) }
|
||||
|
||||
if (!fs.existsSync(this.wallet_dir)) { fs.mkdirSync(this.wallet_dir) }
|
||||
|
||||
if (process.platform === "win32") {
|
||||
this.walletRPCProcess = child_process.spawn(path.join(__ryo_bin, "ryo-wallet-rpc.exe"), args)
|
||||
this.walletRPCProcess = child_process.spawn(path.join(__ryo_bin, "loki-wallet-rpc.exe"), args)
|
||||
} else {
|
||||
this.walletRPCProcess = child_process.spawn(path.join(__ryo_bin, "ryo-wallet-rpc"), args, {
|
||||
this.walletRPCProcess = child_process.spawn(path.join(__ryo_bin, "loki-wallet-rpc"), args, {
|
||||
detached: true
|
||||
})
|
||||
}
|
||||
|
@ -101,10 +103,9 @@ export class WalletRPC {
|
|||
this.port = options.wallet.rpc_bind_port
|
||||
|
||||
this.walletRPCProcess.stdout.on("data", (data) => {
|
||||
|
||||
process.stdout.write(`Wallet: ${data}`)
|
||||
|
||||
let lines = data.toString().split("\n");
|
||||
let lines = data.toString().split("\n")
|
||||
let match, height = null
|
||||
lines.forEach((line) => {
|
||||
match = line.match(this.height_regex1)
|
||||
|
@ -156,17 +157,15 @@ export class WalletRPC {
|
|||
}
|
||||
|
||||
handle (data) {
|
||||
|
||||
let params = data.data
|
||||
|
||||
switch (data.method) {
|
||||
|
||||
case "list_wallets":
|
||||
this.listWallets()
|
||||
break
|
||||
|
||||
case "create_wallet":
|
||||
this.createWallet(params.name, params.password, params.language, params.type)
|
||||
this.createWallet(params.name, params.password, params.language)
|
||||
break
|
||||
|
||||
case "restore_wallet":
|
||||
|
@ -175,6 +174,7 @@ export class WalletRPC {
|
|||
break
|
||||
|
||||
case "restore_view_wallet":
|
||||
// TODO: Decide if we want this for loki
|
||||
this.restoreViewWallet(params.name, params.password, params.address, params.viewkey,
|
||||
params.refresh_type, params.refresh_type == "date" ? params.refresh_start_date : params.refresh_start_height)
|
||||
break
|
||||
|
@ -192,7 +192,7 @@ export class WalletRPC {
|
|||
break
|
||||
|
||||
case "transfer":
|
||||
this.transfer(params.password, params.amount, params.address, params.payment_id, params.mixin, params.priority, params.address_book)
|
||||
this.transfer(params.password, params.amount, params.address, params.payment_id, params.priority, params.address_book)
|
||||
break
|
||||
|
||||
case "add_address_book":
|
||||
|
@ -238,16 +238,11 @@ export class WalletRPC {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
createWallet(filename, password, language, type) {
|
||||
|
||||
let short_address = type == "kurz"
|
||||
|
||||
createWallet (filename, password, language) {
|
||||
this.sendRPC("create_wallet", {
|
||||
filename,
|
||||
password,
|
||||
language,
|
||||
short_address
|
||||
language
|
||||
}).then((data) => {
|
||||
if (data.hasOwnProperty("error")) {
|
||||
this.sendGateway("set_wallet_error", {status: data.error})
|
||||
|
@ -260,14 +255,10 @@ export class WalletRPC {
|
|||
this.wallet_state.open = true
|
||||
|
||||
this.finalizeNewWallet(filename)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
@ -275,26 +266,23 @@ export class WalletRPC {
|
|||
timestamp = timestamp - (timestamp % 86400000) - 86400000
|
||||
|
||||
this.backend.daemon.timestampToHeight(timestamp).then((height) => {
|
||||
if(height === false)
|
||||
this.sendGateway("set_wallet_error", {status:{code: -1, message: "Invalid restore date"}})
|
||||
else
|
||||
this.restoreWallet(filename, password, seed, "height", height)
|
||||
if (height === false) { this.sendGateway("set_wallet_error", {status: {code: -1, message: "Invalid restore date"}}) } else { this.restoreWallet(filename, password, seed, "height", height) }
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let refresh_start_height = refresh_start_timestamp_or_height
|
||||
let restore_height = refresh_start_timestamp_or_height
|
||||
|
||||
if(!Number.isInteger(refresh_start_height)) {
|
||||
refresh_start_height = 0
|
||||
if (!Number.isInteger(restore_height)) {
|
||||
restore_height = 0
|
||||
}
|
||||
seed = seed.trim().replace(/\s{2,}/g, " ")
|
||||
|
||||
this.sendRPC("restore_wallet", {
|
||||
this.sendRPC("restore_deterministic_wallet", {
|
||||
filename,
|
||||
password,
|
||||
seed,
|
||||
refresh_start_height
|
||||
restore_height
|
||||
}).then((data) => {
|
||||
if (data.hasOwnProperty("error")) {
|
||||
this.sendGateway("set_wallet_error", {status: data.error})
|
||||
|
@ -320,12 +308,10 @@ export class WalletRPC {
|
|||
this.finalizeNewWallet(filename)
|
||||
|
||||
// });
|
||||
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -333,10 +319,7 @@ export class WalletRPC {
|
|||
timestamp = timestamp - (timestamp % 86400000) - 86400000
|
||||
|
||||
this.backend.daemon.timestampToHeight(timestamp).then((height) => {
|
||||
if(height === false)
|
||||
this.sendGateway("set_wallet_error", {status:{code: -1, message: "Invalid restore date"}})
|
||||
else
|
||||
this.restoreViewWallet(filename, password, address, viewkey, "height", height)
|
||||
if (height === false) { this.sendGateway("set_wallet_error", {status: {code: -1, message: "Invalid restore date"}}) } else { this.restoreViewWallet(filename, password, address, viewkey, "height", height) }
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -365,12 +348,10 @@ export class WalletRPC {
|
|||
this.wallet_state.open = true
|
||||
|
||||
this.finalizeNewWallet(filename)
|
||||
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
importWallet (filename, password, import_path) {
|
||||
|
||||
// trim off suffix if exists
|
||||
if (import_path.endsWith(".keys")) {
|
||||
import_path = import_path.substring(0, import_path.length - ".keys".length)
|
||||
|
@ -380,9 +361,7 @@ export class WalletRPC {
|
|||
|
||||
if (!fs.existsSync(import_path)) {
|
||||
this.sendGateway("set_wallet_error", {status: {code: -1, message: "Invalid wallet path"}})
|
||||
return
|
||||
} else {
|
||||
|
||||
let destination = path.join(this.wallet_dir, filename)
|
||||
|
||||
if (fs.existsSync(destination) || fs.existsSync(destination + ".keys")) {
|
||||
|
@ -401,7 +380,6 @@ export class WalletRPC {
|
|||
password
|
||||
}).then((data) => {
|
||||
if (data.hasOwnProperty("error")) {
|
||||
|
||||
fs.unlinkSync(destination)
|
||||
fs.unlinkSync(destination + ".keys")
|
||||
|
||||
|
@ -415,15 +393,11 @@ export class WalletRPC {
|
|||
this.wallet_state.open = true
|
||||
|
||||
this.finalizeNewWallet(filename)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
finalizeNewWallet (filename) {
|
||||
|
||||
Promise.all([
|
||||
this.sendRPC("get_address"),
|
||||
this.sendRPC("getheight"),
|
||||
|
@ -482,13 +456,10 @@ export class WalletRPC {
|
|||
this.sendGateway("set_wallet_data", wallet)
|
||||
|
||||
this.startHeartbeat()
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
openWallet (filename, password) {
|
||||
|
||||
this.sendRPC("open_wallet", {
|
||||
filename,
|
||||
password
|
||||
|
@ -530,7 +501,6 @@ export class WalletRPC {
|
|||
})
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -556,19 +526,18 @@ export class WalletRPC {
|
|||
name: this.wallet_state.name
|
||||
},
|
||||
transactions: {
|
||||
tx_list: [],
|
||||
tx_list: []
|
||||
},
|
||||
address_list: {
|
||||
primary: [],
|
||||
used: [],
|
||||
unused: [],
|
||||
address_book: [],
|
||||
address_book_starred: [],
|
||||
address_book_starred: []
|
||||
}
|
||||
|
||||
}
|
||||
for (let n of data) {
|
||||
|
||||
if (n.hasOwnProperty("error") || !n.hasOwnProperty("result")) {
|
||||
continue
|
||||
}
|
||||
|
@ -580,7 +549,6 @@ export class WalletRPC {
|
|||
height: n.result.height
|
||||
}
|
||||
})
|
||||
|
||||
} else if (n.method == "getbalance") {
|
||||
if (this.wallet_state.balance == n.result.balance &&
|
||||
this.wallet_state.unlocked_balance == n.result.unlocked_balance) {
|
||||
|
@ -612,11 +580,9 @@ export class WalletRPC {
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
transfer (password, amount, address, payment_id, mixin, priority, address_book={}) {
|
||||
|
||||
transfer (password, amount, address, payment_id, priority, address_book = {}) {
|
||||
crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
|
||||
if (err) {
|
||||
this.sendGateway("set_tx_status", {
|
||||
|
@ -640,12 +606,11 @@ export class WalletRPC {
|
|||
let sweep_all = amount == this.wallet_state.unlocked_balance
|
||||
|
||||
if (sweep_all) {
|
||||
|
||||
let params = {
|
||||
"address": address,
|
||||
"account_index": 0,
|
||||
"priority": priority,
|
||||
"mixin": mixin
|
||||
"mixin": 9 // Always force a ring size of 10 (ringsize = mixin + 1)
|
||||
}
|
||||
|
||||
if (payment_id) {
|
||||
|
@ -654,7 +619,7 @@ export class WalletRPC {
|
|||
|
||||
this.sendRPC("sweep_all", 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_tx_status", {
|
||||
code: -1,
|
||||
message: error,
|
||||
|
@ -668,15 +633,12 @@ export class WalletRPC {
|
|||
message: "Transaction successfully sent",
|
||||
sending: false
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
let params = {
|
||||
"destinations": [{"amount": amount, "address": address}],
|
||||
"priority": priority,
|
||||
"mixin": mixin
|
||||
"mixin": 9
|
||||
}
|
||||
|
||||
if (payment_id) {
|
||||
|
@ -685,7 +647,7 @@ export class WalletRPC {
|
|||
|
||||
this.sendRPC("transfer_split", 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_tx_status", {
|
||||
code: -1,
|
||||
message: error,
|
||||
|
@ -699,16 +661,11 @@ export class WalletRPC {
|
|||
message: "Transaction successfully sent",
|
||||
sending: false
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
if(address_book.hasOwnProperty("save") && address_book.save)
|
||||
this.addAddressBook(address, payment_id, address_book.description, address_book.name)
|
||||
|
||||
if (address_book.hasOwnProperty("save") && address_book.save) { this.addAddressBook(address, payment_id, address_book.description, address_book.name) }
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
rescanBlockchain () {
|
||||
|
@ -720,7 +677,6 @@ export class WalletRPC {
|
|||
}
|
||||
|
||||
getPrivateKeys (password) {
|
||||
|
||||
crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
|
||||
if (err) {
|
||||
this.sendGateway("set_wallet_data", {
|
||||
|
@ -731,7 +687,6 @@ export class WalletRPC {
|
|||
}
|
||||
})
|
||||
return
|
||||
return
|
||||
}
|
||||
if (this.wallet_state.password_hash !== password_hash.toString("hex")) {
|
||||
this.sendGateway("set_wallet_data", {
|
||||
|
@ -763,22 +718,16 @@ export class WalletRPC {
|
|||
}
|
||||
|
||||
this.sendGateway("set_wallet_data", wallet)
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
getAddressList () {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
Promise.all([
|
||||
this.sendRPC("get_address", {account_index: 0}),
|
||||
this.sendRPC("getbalance", {account_index: 0})
|
||||
]).then((data) => {
|
||||
|
||||
for (let n of data) {
|
||||
if (n.hasOwnProperty("error") || !n.hasOwnProperty("result")) {
|
||||
resolve({})
|
||||
|
@ -792,7 +741,7 @@ export class WalletRPC {
|
|||
info: {
|
||||
address: data[0].result.address,
|
||||
balance: data[1].result.balance,
|
||||
unlocked_balance: data[1].result.unlocked_balance,
|
||||
unlocked_balance: data[1].result.unlocked_balance
|
||||
// num_unspent_outputs: data[1].result.num_unspent_outputs
|
||||
},
|
||||
address_list: {
|
||||
|
@ -803,7 +752,6 @@ export class WalletRPC {
|
|||
}
|
||||
|
||||
for (let address of data[0].result.addresses) {
|
||||
|
||||
address.balance = null
|
||||
address.unlocked_balance = null
|
||||
address.num_unspent_outputs = null
|
||||
|
@ -846,14 +794,10 @@ export class WalletRPC {
|
|||
} else {
|
||||
resolve(wallet)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
getTransactions () {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendRPC("get_transfers", {in: true, out: true, pending: true, failed: true, pool: true}).then((data) => {
|
||||
|
@ -863,20 +807,15 @@ export class WalletRPC {
|
|||
}
|
||||
let wallet = {
|
||||
transactions: {
|
||||
tx_list: [],
|
||||
tx_list: []
|
||||
}
|
||||
}
|
||||
|
||||
if(data.result.hasOwnProperty("in"))
|
||||
wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result.in)
|
||||
if(data.result.hasOwnProperty("out"))
|
||||
wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result.out)
|
||||
if(data.result.hasOwnProperty("pending"))
|
||||
wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result.pending)
|
||||
if(data.result.hasOwnProperty("failed"))
|
||||
wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result.failed)
|
||||
if(data.result.hasOwnProperty("pool"))
|
||||
wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result.pool)
|
||||
if (data.result.hasOwnProperty("in")) { wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result.in) }
|
||||
if (data.result.hasOwnProperty("out")) { wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result.out) }
|
||||
if (data.result.hasOwnProperty("pending")) { wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result.pending) }
|
||||
if (data.result.hasOwnProperty("failed")) { wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result.failed) }
|
||||
if (data.result.hasOwnProperty("pool")) { wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result.pool) }
|
||||
|
||||
for (let i = 0; i < wallet.transactions.tx_list.length; i++) {
|
||||
if (/^0*$/.test(wallet.transactions.tx_list[i].payment_id)) {
|
||||
|
@ -896,7 +835,6 @@ export class WalletRPC {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
getAddressBook () {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendRPC("get_address_book").then((data) => {
|
||||
|
@ -917,7 +855,7 @@ export class WalletRPC {
|
|||
let entry = data.result.entries[i]
|
||||
let desc = entry.description.split("::")
|
||||
if (desc.length == 3) {
|
||||
entry.starred = desc[0] == "starred" ? true : false
|
||||
entry.starred = desc[0] == "starred"
|
||||
entry.name = desc[1]
|
||||
entry.description = desc[2]
|
||||
} else if (desc.length == 2) {
|
||||
|
@ -936,15 +874,11 @@ export class WalletRPC {
|
|||
entry.payment_id = entry.payment_id.substring(0, 16)
|
||||
}
|
||||
|
||||
if(entry.starred)
|
||||
wallet.address_list.address_book_starred.push(entry)
|
||||
else
|
||||
wallet.address_list.address_book.push(entry)
|
||||
if (entry.starred) { wallet.address_list.address_book_starred.push(entry) } else { wallet.address_list.address_book.push(entry) }
|
||||
}
|
||||
}
|
||||
|
||||
resolve(wallet)
|
||||
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -961,9 +895,7 @@ export class WalletRPC {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
addAddressBook (address, payment_id = null, description = "", name = "", starred = false, index = false) {
|
||||
|
||||
if (index !== false) {
|
||||
this.sendRPC("delete_address_book", {index: index}).then((data) => {
|
||||
this.addAddressBook(address, payment_id, description, name, starred)
|
||||
|
@ -974,8 +906,7 @@ export class WalletRPC {
|
|||
let params = {
|
||||
address
|
||||
}
|
||||
if(payment_id != null)
|
||||
params.payment_id = payment_id
|
||||
if (payment_id != null) { params.payment_id = payment_id }
|
||||
|
||||
let desc = [
|
||||
]
|
||||
|
@ -1003,7 +934,6 @@ export class WalletRPC {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
exportKeyImages (password, filename = null) {
|
||||
crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
|
||||
if (err) {
|
||||
|
@ -1015,10 +945,7 @@ export class WalletRPC {
|
|||
return
|
||||
}
|
||||
|
||||
if(filename == null)
|
||||
filename = path.join(this.data_dir, "gui", "key_image_export")
|
||||
else
|
||||
filename = path.join(filename, "key_image_export")
|
||||
if (filename == null) { filename = path.join(this.data_dir, "gui", "key_image_export") } else { filename = path.join(filename, "key_image_export") }
|
||||
|
||||
this.sendRPC("export_key_images", {filename}).then((data) => {
|
||||
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
|
@ -1027,10 +954,8 @@ export class WalletRPC {
|
|||
}
|
||||
|
||||
this.sendGateway("show_notification", {message: "Key images exported to " + filename, timeout: 2000})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
importKeyImages (password, filename = null) {
|
||||
|
@ -1044,8 +969,7 @@ export class WalletRPC {
|
|||
return
|
||||
}
|
||||
|
||||
if(filename == null)
|
||||
filename = path.join(this.data_dir, "gui", "key_image_export")
|
||||
if (filename == null) { filename = path.join(this.data_dir, "gui", "key_image_export") }
|
||||
|
||||
this.sendRPC("import_key_images", {filename}).then((data) => {
|
||||
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
|
@ -1056,14 +980,11 @@ export class WalletRPC {
|
|||
this.sendGateway("show_notification", {message: "Key images imported", timeout: 2000})
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
listWallets (legacy = false) {
|
||||
|
||||
let wallets = {
|
||||
list: [],
|
||||
list: []
|
||||
}
|
||||
|
||||
fs.readdirSync(this.wallet_dir).forEach(filename => {
|
||||
|
@ -1071,8 +992,7 @@ export class WalletRPC {
|
|||
filename.endsWith(".meta.json") ||
|
||||
filename.endsWith(".address.txt") ||
|
||||
filename.endsWith(".bkp-old") ||
|
||||
filename.endsWith(".unportable"))
|
||||
return
|
||||
filename.endsWith(".unportable")) { return }
|
||||
|
||||
switch (filename) {
|
||||
case ".DS_Store":
|
||||
|
@ -1092,14 +1012,12 @@ export class WalletRPC {
|
|||
}
|
||||
|
||||
if (fs.existsSync(path.join(this.wallet_dir, filename + ".meta.json"))) {
|
||||
|
||||
let meta = fs.readFileSync(path.join(this.wallet_dir, filename + ".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, filename + ".address.txt"))) {
|
||||
let address = fs.readFileSync(path.join(this.wallet_dir, filename + ".address.txt"), "utf8")
|
||||
if (address) {
|
||||
|
@ -1108,7 +1026,6 @@ export class WalletRPC {
|
|||
}
|
||||
|
||||
wallets.list.push(wallet_data)
|
||||
|
||||
})
|
||||
|
||||
// Check for legacy wallet files
|
||||
|
@ -1116,31 +1033,28 @@ export class WalletRPC {
|
|||
wallets.legacy = []
|
||||
let legacy_paths = []
|
||||
if (os.platform() == "win32") {
|
||||
legacy_paths = ["C:\\ProgramData\\RyoGUIWallet", "C:\\ProgramData\\RyoLITEWallet"]
|
||||
legacy_paths = ["C:\\ProgramData\\Loki"]
|
||||
} else {
|
||||
legacy_paths = [path.join(os.homedir(), "RyoGUIWallet"), path.join(os.homedir(), "RyoLITEWallet")]
|
||||
legacy_paths = [path.join(os.homedir(), "Loki")]
|
||||
}
|
||||
for (var i = 0; i < legacy_paths.length; i++) {
|
||||
let legacy_config_path = path.join(legacy_paths[i], "config", "wallet_info.json")
|
||||
if(this.testnet)
|
||||
legacy_config_path = path.join(legacy_paths[i], "testnet", "config", "wallet_info.json")
|
||||
if(!fs.existsSync(legacy_config_path))
|
||||
continue
|
||||
if (this.net_type === "test") { 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_wallet_path = legacy_config.wallet_filepath
|
||||
if(!fs.existsSync(legacy_wallet_path))
|
||||
continue
|
||||
if (!fs.existsSync(legacy_wallet_path)) { continue }
|
||||
|
||||
let legacy_address = ""
|
||||
if (fs.existsSync(legacy_wallet_path + ".address.txt")) {
|
||||
legacy_address = fs.readFileSync(legacy_wallet_path + ".address.txt", "utf8")
|
||||
}
|
||||
wallets.legacy.push({path: legacy_wallet_path, address: legacy_address})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
this.sendGateway("wallet_list", wallets)
|
||||
|
||||
}
|
||||
|
||||
changeWalletPassword (old_password, new_password) {
|
||||
|
@ -1164,9 +1078,7 @@ export class WalletRPC {
|
|||
this.wallet_state.password_hash = crypto.pbkdf2Sync(new_password, this.auth[2], 1000, 64, "sha512").toString("hex")
|
||||
|
||||
this.sendGateway("show_notification", {message: "Password updated", timeout: 2000})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1223,8 +1135,7 @@ export class WalletRPC {
|
|||
// if wallet is closed, do not send any wallet data to gateway
|
||||
// this is for the case that we close the wallet at the same
|
||||
// after another action has started, but before it has finished
|
||||
if(!this.wallet_state.open && method == "set_wallet_data")
|
||||
return
|
||||
if (!this.wallet_state.open && method == "set_wallet_data") { return }
|
||||
this.backend.send(method, data)
|
||||
}
|
||||
|
||||
|
@ -1244,7 +1155,7 @@ export class WalletRPC {
|
|||
sendImmediately: false
|
||||
},
|
||||
agent: this.agent
|
||||
};
|
||||
}
|
||||
if (Object.keys(params).length !== 0) {
|
||||
options.json.params = params
|
||||
}
|
||||
|
|
|
@ -32,14 +32,14 @@
|
|||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text"><span>Balance</span></div>
|
||||
<div class="value"><span><FormatRyo :amount="address.balance" /></span></div>
|
||||
<div class="value"><span><FormatLoki :amount="address.balance" /></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text"><span>Unlocked balance</span></div>
|
||||
<div class="value"><span><FormatRyo :amount="address.unlocked_balance" /></span></div>
|
||||
<div class="value"><span><FormatLoki :amount="address.unlocked_balance" /></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -128,7 +128,7 @@
|
|||
import { mapState } from "vuex"
|
||||
const {clipboard} = require("electron")
|
||||
import AddressHeader from "components/address_header"
|
||||
import FormatRyo from "components/format_ryo"
|
||||
import FormatLoki from "components/format_loki"
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
import TxList from "components/tx_list"
|
||||
export default {
|
||||
|
@ -159,7 +159,7 @@ export default {
|
|||
components: {
|
||||
AddressHeader,
|
||||
TxList,
|
||||
FormatRyo,
|
||||
FormatLoki,
|
||||
QrcodeVue
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
<q-layout-footer class="status-footer">
|
||||
<div class="status-line">
|
||||
|
||||
<template v-if="config.daemon.type !== 'remote'">
|
||||
<template v-if="config_daemon.type !== 'remote'">
|
||||
<div>Daemon: {{ daemon.info.height_without_bootstrap }} / {{ target_height }} ({{ daemon_local_pct }}%)</div>
|
||||
</template>
|
||||
|
||||
<template v-if="config.daemon.type !== 'local'">
|
||||
<template v-if="config_daemon.type !== 'local'">
|
||||
<div>Remote: {{ daemon.info.height }}</div>
|
||||
</template>
|
||||
|
||||
|
@ -32,19 +32,22 @@ export default {
|
|||
daemon: state => state.gateway.daemon,
|
||||
wallet: state => state.gateway.wallet,
|
||||
|
||||
config_daemon (state) {
|
||||
return this.config.daemons[this.config.app.net_type]
|
||||
},
|
||||
target_height (state) {
|
||||
if(this.config.daemon.type === "local" && !this.daemon.info.is_ready)
|
||||
if(this.config_daemon.type === "local" && !this.daemon.info.is_ready)
|
||||
return Math.max(this.daemon.info.height, this.daemon.info.target_height)
|
||||
else
|
||||
return this.daemon.info.height
|
||||
},
|
||||
daemon_pct (state) {
|
||||
if(this.config.daemon.type === "local")
|
||||
if(this.config_daemon.type === "local")
|
||||
return this.daemon_local_pct
|
||||
return 0
|
||||
},
|
||||
daemon_local_pct (state) {
|
||||
if(this.config.daemon.type === "remote")
|
||||
if(this.config_daemon.type === "remote")
|
||||
return 0
|
||||
let pct = (100 * this.daemon.info.height_without_bootstrap / this.target_height).toFixed(1)
|
||||
if(pct == 100.0 && this.daemon.info.height_without_bootstrap < this.target_height)
|
||||
|
@ -60,7 +63,7 @@ export default {
|
|||
return pct
|
||||
},
|
||||
status(state) {
|
||||
if(this.config.daemon.type === "local") {
|
||||
if(this.config_daemon.type === "local") {
|
||||
if(this.daemon.info.height_without_bootstrap < this.target_height || !this.daemon.info.is_ready) {
|
||||
return "Syncing..."
|
||||
} else if(this.wallet.info.height < this.target_height - 1 && this.wallet.info.height != 0) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<span>
|
||||
{{ value }} Ryo
|
||||
{{ value }} LOKI
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FormatRyo",
|
||||
name: "FormatLoki",
|
||||
props: {
|
||||
amount: {
|
||||
required: true
|
|
@ -20,7 +20,7 @@
|
|||
</q-item>
|
||||
<q-item v-close-overlay @click.native="exit">
|
||||
<q-item-main>
|
||||
<q-item-tile label>Exit Ryo GUI Wallet</q-item-tile>
|
||||
<q-item-tile label>Exit Loki GUI Wallet</q-item-tile>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
@ -39,12 +39,12 @@
|
|||
|
||||
<div class="q-mt-md q-mb-lg external-links">
|
||||
<p>
|
||||
<a @click="openExternal('https://ryo-currency.com/')" href="#">https://ryo-currency.com/</a>
|
||||
<a @click="openExternal('https://loki.network/')" href="#">https://loki.network/</a>
|
||||
</p>
|
||||
<p>
|
||||
<a @click="openExternal('https://t.me/ryocurrency')" href="#">Telegram</a> -
|
||||
<a @click="openExternal('https://discord.gg/GFQmFtx')" href="#">Discord</a> -
|
||||
<a @click="openExternal('https://www.reddit.com/r/ryocurrency/')" href="#">Reddit</a>
|
||||
<a @click="openExternal('https://t.me/joinchat/DeNvR0JJ4JPn6TVSQjCsZQ')" href="#">Telegram</a> -
|
||||
<a @click="openExternal('https://discordapp.com/invite/67GXfD6')" href="#">Discord</a> -
|
||||
<a @click="openExternal('https://www.reddit.com/r/LokiProject/')" href="#">Reddit</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -83,11 +83,12 @@ export default {
|
|||
pending_config: state => state.gateway.app.pending_config,
|
||||
config: state => state.gateway.app.config,
|
||||
tabs: function(state) {
|
||||
const { app, daemons } = state.gateway.app.config;
|
||||
let tabs = [
|
||||
{label: 'General', value: 'general', icon: 'settings'},
|
||||
{label: 'Appearance', value: 'appearance', icon: 'visibility'},
|
||||
]
|
||||
if(state.gateway.app.config.daemon.type != 'remote') {
|
||||
if(daemons[app.net_type].type != 'remote') {
|
||||
tabs.push({label: 'Peers', value: 'peers', icon: 'cloud_queue'})
|
||||
}
|
||||
return tabs
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
<template>
|
||||
<div class="settings-general">
|
||||
<div class="row justify-between q-mb-md">
|
||||
<div><q-radio v-model="config.daemon.type" val="local_remote" label="Local + Remote Daemon" /></div>
|
||||
<div><q-radio v-model="config.daemon.type" val="local" label="Local Daemon Only" /></div>
|
||||
<div><q-radio v-model="config.daemon.type" val="remote" label="Remote Daemon Only" /></div>
|
||||
<div><q-radio v-model="config_daemon.type" val="local_remote" label="Local + Remote Daemon" /></div>
|
||||
<div><q-radio v-model="config_daemon.type" val="local" label="Local Daemon Only" /></div>
|
||||
<div><q-radio v-model="config_daemon.type" val="remote" label="Remote Daemon Only" /></div>
|
||||
</div>
|
||||
|
||||
<p v-if="config.daemon.type == 'local_remote'">
|
||||
<p v-if="config_daemon.type == 'local_remote'">
|
||||
Get started quickly with this default option. Wallet will download the full blockchain, but use a remote node while syncing.
|
||||
</p>
|
||||
<p v-if="config.daemon.type == 'local'">
|
||||
<p v-if="config_daemon.type == 'local'">
|
||||
Full security, wallet will download the full blockchain. You will not be able to transact until sync is completed.
|
||||
</p>
|
||||
<p v-if="config.daemon.type == 'remote'">
|
||||
<p v-if="config_daemon.type == 'remote'">
|
||||
Less security, wallet will connect to a remote node to make all transactions.
|
||||
</p>
|
||||
|
||||
<q-field v-if="config.daemon.type != 'remote'">
|
||||
<q-field v-if="config_daemon.type != 'remote'">
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-8">
|
||||
<q-input v-model="config.daemon.rpc_bind_ip" float-label="Local Daemon IP"
|
||||
<q-input v-model="config_daemon.rpc_bind_ip" float-label="Local Daemon IP"
|
||||
:dark="theme=='dark'" disable />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-input v-model="config.daemon.rpc_bind_port" float-label="Local Daemon Port (RPC)" type="number" :decimals="0" :step="1" min="1024" max="65535" :dark="theme=='dark'" />
|
||||
<q-input v-model="config_daemon.rpc_bind_port" float-label="Local Daemon Port (RPC)" type="number" :decimals="0" :step="1" min="1024" max="65535" :dark="theme=='dark'" />
|
||||
</div>
|
||||
</div>
|
||||
</q-field>
|
||||
|
||||
<q-field v-if="config.daemon.type != 'local'">
|
||||
<q-field v-if="config_daemon.type != 'local'">
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-8">
|
||||
<q-input v-model="config.daemon.remote_host" float-label="Remote Node Host" :dark="theme=='dark'" />
|
||||
<q-input v-model="config_daemon.remote_host" float-label="Remote Node Host" :dark="theme=='dark'" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-input v-model="config.daemon.remote_port" float-label="Remote Node Port" type="number" :decimals="0" :step="1" min="1024" max="65535" :dark="theme=='dark'" />
|
||||
<q-input v-model="config_daemon.remote_port" float-label="Remote Node Port" type="number" :decimals="0" :step="1" min="1024" max="65535" :dark="theme=='dark'" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -52,39 +52,58 @@
|
|||
</div>
|
||||
</q-field>
|
||||
|
||||
<div v-if="(config_daemon.type !== 'local') && (config.app.net_type === 'main')">
|
||||
<hr>
|
||||
<div class="presets">
|
||||
<div class="q-body-1">Remote Node Presets</div>
|
||||
<q-field>
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-8">
|
||||
<q-select
|
||||
v-model="select"
|
||||
:options="remoteOptions"
|
||||
:dark="theme=='dark'"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-btn v-on:click="loadPreset" :text-color="theme=='dark'?'white':'dark'">Load Preset</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-collapsible label="Advanced Options" header-class="non-selectable row reverse advanced-options-label">
|
||||
|
||||
<q-field>
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.log_level" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
<div class="col-6">
|
||||
<q-input v-model="config_daemon.log_level" :disable="config_daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Daemon Log Level" type="number" :decimals="0" :step="1" min="0" max="4" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div class="col-6">
|
||||
<q-input v-model="config.wallet.log_level" :dark="theme=='dark'"
|
||||
float-label="Wallet Log Level" type="number" :decimals="0" :step="1" min="0" max="4" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-checkbox v-model="config.app.testnet" label="Testnet" />
|
||||
</div>
|
||||
</div>
|
||||
</q-field>
|
||||
|
||||
<q-field>
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.in_peers" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
<q-input v-model="config_daemon.in_peers" :disable="config_daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Max Incoming Peers" type="number" :decimals="0" :step="1" min="-1" max="65535" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.out_peers" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
<q-input v-model="config_daemon.out_peers" :disable="config_daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Max Outgoing Peers" type="number" :decimals="0" :step="1" min="-1" max="65535" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.limit_rate_up" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
<q-input v-model="config_daemon.limit_rate_up" :disable="config_daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Limit Upload Rate" type="number" suffix="Kb/s" :decimals="0" :step="1" min="-1" max="65535" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.limit_rate_down" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
<q-input v-model="config_daemon.limit_rate_down" :disable="config_daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Limit Download Rate" type="number" suffix="Kb/s" :decimals="0" :step="1" min="-1" max="65535" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -92,11 +111,11 @@
|
|||
<q-field>
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.p2p_bind_port" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
<q-input v-model="config_daemon.p2p_bind_port" :disable="config_daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Daemon P2P Port" type="number" :decimals="0" :step="1" min="1024" max="65535" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.zmq_rpc_bind_port" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
<q-input v-model="config_daemon.zmq_rpc_bind_port" :disable="config_daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Daemon ZMQ Port" type="number" :decimals="0" :step="1" min="1024" max="65535" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
|
@ -104,11 +123,22 @@
|
|||
float-label="Internal Wallet Port" type="number" :decimals="0" :step="1" min="1024" max="65535" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.wallet.rpc_bind_port" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
<q-input v-model="config.wallet.rpc_bind_port" :disable="config_daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Wallet RPC Port" type="number" :decimals="0" :step="1" min="1024" max="65535" />
|
||||
</div>
|
||||
</div>
|
||||
</q-field>
|
||||
<q-field helper="Choose a network" label="Network" orientation="vertical">
|
||||
<q-option-group
|
||||
type="radio"
|
||||
v-model="config.app.net_type"
|
||||
:options="[
|
||||
{ label: 'Main Net', value: 'main' },
|
||||
{ label: 'Stage Net', value: 'staging' },
|
||||
{ label: 'Test Net', value: 'test' }
|
||||
]"
|
||||
/>
|
||||
</q-field>
|
||||
|
||||
</q-collapsible>
|
||||
</div>
|
||||
|
@ -120,7 +150,17 @@ export default {
|
|||
name: "SettingsGeneral",
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
remotes: state => state.gateway.app.remotes,
|
||||
remoteOptions (state) {
|
||||
return this.remotes.map((r, index) => ({
|
||||
label: `${r.host}:${r.port}`,
|
||||
value: index,
|
||||
}));
|
||||
},
|
||||
config: state => state.gateway.app.pending_config,
|
||||
config_daemon (state) {
|
||||
return this.config.daemons[this.config.app.net_type]
|
||||
},
|
||||
}),
|
||||
methods: {
|
||||
selectPath () {
|
||||
|
@ -128,8 +168,20 @@ export default {
|
|||
},
|
||||
setDataPath (file) {
|
||||
this.config.app.data_dir = file.target.files[0].path
|
||||
},
|
||||
loadPreset () {
|
||||
if (!this.remotes || this.remotes.length === 0) return;
|
||||
|
||||
const { host, port } = this.remotes[this.select];
|
||||
this.config_daemon.remote_host = host;
|
||||
this.config_daemon.remote_port = port;
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
select: 0,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -150,5 +202,9 @@ export default {
|
|||
.q-collapsible-sub-item {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.presets {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -46,14 +46,14 @@
|
|||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text"><span>Amount</span></div>
|
||||
<div class="value"><span><FormatRyo :amount="tx.amount" /></span></div>
|
||||
<div class="value"><span><FormatLoki :amount="tx.amount" /></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text"><span>Fee <template v-if="tx.type=='in'||tx.type=='pool'">(paid by sender)</template></span></div>
|
||||
<div class="value"><span><FormatRyo :amount="tx.fee" /></span></div>
|
||||
<div class="value"><span><FormatLoki :amount="tx.fee" /></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -122,7 +122,7 @@
|
|||
<q-item-main>
|
||||
<q-item-tile label>{{ destination.name }}</q-item-tile>
|
||||
<q-item-tile class="monospace ellipsis" sublabel>{{ destination.address }}</q-item-tile>
|
||||
<q-item-tile sublabel><FormatRyo :amount="destination.amount" /></q-item-tile>
|
||||
<q-item-tile sublabel><FormatLoki :amount="destination.amount" /></q-item-tile>
|
||||
</q-item-main>
|
||||
|
||||
<q-context-menu>
|
||||
|
@ -182,7 +182,7 @@ import { date } from "quasar"
|
|||
const { formatDate } = date
|
||||
import Identicon from "components/identicon"
|
||||
import TxTypeIcon from "components/tx_type_icon"
|
||||
import FormatRyo from "components/format_ryo"
|
||||
import FormatLoki from "components/format_loki"
|
||||
export default {
|
||||
name: "TxDetails",
|
||||
computed: mapState({
|
||||
|
@ -298,7 +298,7 @@ export default {
|
|||
components: {
|
||||
Identicon,
|
||||
TxTypeIcon,
|
||||
FormatRyo
|
||||
FormatLoki
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-item-tile label>
|
||||
<FormatRyo :amount="tx.amount" />
|
||||
<FormatLoki :amount="tx.amount" />
|
||||
</q-item-tile>
|
||||
<q-item-tile sublabel>
|
||||
<timeago :datetime="tx.timestamp*1000" :auto-update="60">
|
||||
|
@ -64,7 +64,7 @@ import { QSpinnerDots } from "quasar"
|
|||
import Identicon from "components/identicon"
|
||||
import TxTypeIcon from "components/tx_type_icon"
|
||||
import TxDetails from "components/tx_details"
|
||||
import FormatRyo from "components/format_ryo"
|
||||
import FormatLoki from "components/format_loki"
|
||||
export default {
|
||||
name: "TxList",
|
||||
props: {
|
||||
|
@ -236,7 +236,7 @@ export default {
|
|||
Identicon,
|
||||
TxTypeIcon,
|
||||
TxDetails,
|
||||
FormatRyo
|
||||
FormatLoki
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -22,70 +22,70 @@
|
|||
SOFTWARE.
|
||||
*/
|
||||
|
||||
const crypto = require("crypto");
|
||||
const crypto = require("crypto")
|
||||
|
||||
const ALGORITHM_NAME = "aes-128-gcm";
|
||||
const ALGORITHM_NONCE_SIZE = 12;
|
||||
const ALGORITHM_TAG_SIZE = 16;
|
||||
const ALGORITHM_KEY_SIZE = 16;
|
||||
const PBKDF2_NAME = "sha256";
|
||||
const PBKDF2_SALT_SIZE = 16;
|
||||
const PBKDF2_ITERATIONS = 32767;
|
||||
const ALGORITHM_NAME = "aes-128-gcm"
|
||||
const ALGORITHM_NONCE_SIZE = 12
|
||||
const ALGORITHM_TAG_SIZE = 16
|
||||
const ALGORITHM_KEY_SIZE = 16
|
||||
const PBKDF2_NAME = "sha256"
|
||||
const PBKDF2_SALT_SIZE = 16
|
||||
const PBKDF2_ITERATIONS = 32767
|
||||
|
||||
export class SCEE {
|
||||
encryptString (plaintext, password) {
|
||||
// Generate a 128-bit salt using a CSPRNG.
|
||||
let salt = crypto.randomBytes(PBKDF2_SALT_SIZE);
|
||||
let salt = crypto.randomBytes(PBKDF2_SALT_SIZE)
|
||||
|
||||
// Derive a key using PBKDF2.
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME);
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME)
|
||||
|
||||
// Encrypt and prepend salt.
|
||||
let ciphertextAndNonceAndSalt = Buffer.concat([ salt, this.encrypt(new Buffer(plaintext, "utf8"), key) ]);
|
||||
let ciphertextAndNonceAndSalt = Buffer.concat([ salt, this.encrypt(new Buffer(plaintext, "utf8"), key) ])
|
||||
|
||||
// Return as base64 string.
|
||||
return ciphertextAndNonceAndSalt.toString("base64");
|
||||
return ciphertextAndNonceAndSalt.toString("base64")
|
||||
}
|
||||
|
||||
decryptString (base64CiphertextAndNonceAndSalt, password) {
|
||||
// Decode the base64.
|
||||
let ciphertextAndNonceAndSalt = new Buffer(base64CiphertextAndNonceAndSalt, "base64");
|
||||
let ciphertextAndNonceAndSalt = new Buffer(base64CiphertextAndNonceAndSalt, "base64")
|
||||
|
||||
// Create buffers of salt and ciphertextAndNonce.
|
||||
let salt = ciphertextAndNonceAndSalt.slice(0, PBKDF2_SALT_SIZE);
|
||||
let ciphertextAndNonce = ciphertextAndNonceAndSalt.slice(PBKDF2_SALT_SIZE);
|
||||
let salt = ciphertextAndNonceAndSalt.slice(0, PBKDF2_SALT_SIZE)
|
||||
let ciphertextAndNonce = ciphertextAndNonceAndSalt.slice(PBKDF2_SALT_SIZE)
|
||||
|
||||
// Derive the key using PBKDF2.
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME);
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME)
|
||||
|
||||
// Decrypt and return result.
|
||||
return this.decrypt(ciphertextAndNonce, key).toString("utf8");
|
||||
return this.decrypt(ciphertextAndNonce, key).toString("utf8")
|
||||
}
|
||||
|
||||
encrypt (plaintext, key) {
|
||||
// Generate a 96-bit nonce using a CSPRNG.
|
||||
let nonce = crypto.randomBytes(ALGORITHM_NONCE_SIZE);
|
||||
let nonce = crypto.randomBytes(ALGORITHM_NONCE_SIZE)
|
||||
|
||||
// Create the cipher instance.
|
||||
let cipher = crypto.createCipheriv(ALGORITHM_NAME, key, nonce);
|
||||
let cipher = crypto.createCipheriv(ALGORITHM_NAME, key, nonce)
|
||||
|
||||
// Encrypt and prepend nonce.
|
||||
let ciphertext = Buffer.concat([ cipher.update(plaintext), cipher.final() ]);
|
||||
let ciphertext = Buffer.concat([ cipher.update(plaintext), cipher.final() ])
|
||||
|
||||
return Buffer.concat([ nonce, ciphertext, cipher.getAuthTag() ]);
|
||||
return Buffer.concat([ nonce, ciphertext, cipher.getAuthTag() ])
|
||||
}
|
||||
|
||||
decrypt (ciphertextAndNonce, key) {
|
||||
// Create buffers of nonce, ciphertext and tag.
|
||||
let nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE);
|
||||
let ciphertext = ciphertextAndNonce.slice(ALGORITHM_NONCE_SIZE, ciphertextAndNonce.length - ALGORITHM_TAG_SIZE);
|
||||
let tag = ciphertextAndNonce.slice(ciphertext.length + ALGORITHM_NONCE_SIZE);
|
||||
let nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE)
|
||||
let ciphertext = ciphertextAndNonce.slice(ALGORITHM_NONCE_SIZE, ciphertextAndNonce.length - ALGORITHM_TAG_SIZE)
|
||||
let tag = ciphertextAndNonce.slice(ciphertext.length + ALGORITHM_NONCE_SIZE)
|
||||
|
||||
// Create the cipher instance.
|
||||
let cipher = crypto.createDecipheriv(ALGORITHM_NAME, key, nonce);
|
||||
let cipher = crypto.createDecipheriv(ALGORITHM_NAME, key, nonce)
|
||||
|
||||
// Decrypt and return result.
|
||||
cipher.setAuthTag(tag);
|
||||
return Buffer.concat([ cipher.update(ciphertext), cipher.final() ]);
|
||||
cipher.setAuthTag(tag)
|
||||
return Buffer.concat([ cipher.update(ciphertext), cipher.final() ])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import { ipcRenderer } from "electron"
|
||||
import { Notify, Dialog, Loading, LocalStorage } from "quasar"
|
||||
import { SCEE } from "./SCEE-Node";
|
||||
import * as WebSocket from "ws"
|
||||
import { SCEE } from "./SCEE-Node"
|
||||
|
||||
export class Gateway {
|
||||
|
||||
constructor (app, router) {
|
||||
|
||||
this.app = app
|
||||
this.router = router
|
||||
this.token = null
|
||||
|
@ -19,7 +16,7 @@ export class Gateway {
|
|||
theme
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
this.app.store.watch(state => state.gateway.app.config.appearance.theme, (theme) => {
|
||||
LocalStorage.set("theme", theme)
|
||||
})
|
||||
|
@ -30,21 +27,20 @@ export class Gateway {
|
|||
status: {
|
||||
code: 1 // Connecting to backend
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
ipcRenderer.on("initialize", (event, data) => {
|
||||
this.token = data.token
|
||||
setTimeout(() => {
|
||||
this.ws = new WebSocket("ws://127.0.0.1:"+data.port);
|
||||
this.ws.on("open", () => {this.open()});
|
||||
this.ws.on("message", (message) => {this.receive(message)});
|
||||
}, 1000);
|
||||
});
|
||||
this.ws = new WebSocket("ws://127.0.0.1:" + data.port)
|
||||
this.ws.addEventListener("open", () => { this.open() })
|
||||
this.ws.addEventListener("message", (e) => { this.receive(e.data) })
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
ipcRenderer.on("confirmClose", () => {
|
||||
this.confirmClose("Are you sure you want to exit?")
|
||||
});
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
open () {
|
||||
|
@ -52,8 +48,8 @@ export class Gateway {
|
|||
status: {
|
||||
code: 2 // Loading config
|
||||
}
|
||||
});
|
||||
this.send("core", "init");
|
||||
})
|
||||
this.send("core", "init")
|
||||
}
|
||||
|
||||
confirmClose (msg) {
|
||||
|
@ -80,7 +76,6 @@ export class Gateway {
|
|||
}).catch(() => {
|
||||
this.closeDialog = false
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
send (module, method, data = {}) {
|
||||
|
@ -89,24 +84,20 @@ export class Gateway {
|
|||
method,
|
||||
data
|
||||
}
|
||||
let encrypted_data = this.scee.encryptString(JSON.stringify(message), this.token);
|
||||
this.ws.send(encrypted_data);
|
||||
|
||||
let encrypted_data = this.scee.encryptString(JSON.stringify(message), this.token)
|
||||
this.ws.send(encrypted_data)
|
||||
}
|
||||
|
||||
receive (message) {
|
||||
|
||||
// should wrap this in a try catch, and if fail redirect to error screen
|
||||
// shouldn't happen outside of dev environment
|
||||
let decrypted_data = JSON.parse(this.scee.decryptString(message, this.token));
|
||||
let decrypted_data = JSON.parse(this.scee.decryptString(message, this.token))
|
||||
|
||||
if (typeof decrypted_data !== "object" ||
|
||||
!decrypted_data.hasOwnProperty("event") ||
|
||||
!decrypted_data.hasOwnProperty("data"))
|
||||
return
|
||||
!decrypted_data.hasOwnProperty("data")) { return }
|
||||
|
||||
switch (decrypted_data.event) {
|
||||
|
||||
case "set_app_data":
|
||||
this.app.store.commit("gateway/set_app_data", decrypted_data.data)
|
||||
break
|
||||
|
@ -147,9 +138,8 @@ export class Gateway {
|
|||
// short delay to prevent wallet data reaching the
|
||||
// websocket moments after we close and reset data
|
||||
this.app.store.dispatch("gateway/resetWalletData")
|
||||
}, 250);
|
||||
}, 250)
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
@click="cancel()" />
|
||||
</template>
|
||||
|
||||
<q-toolbar-title v-if="page_title=='Ryo'">
|
||||
<q-toolbar-title v-if="page_title=='Loki'">
|
||||
<div style="margin-top:7px">
|
||||
<img src="statics/ryo-wallet.svg" height="32">
|
||||
</div>
|
||||
|
@ -64,7 +64,7 @@ export default {
|
|||
|
||||
default:
|
||||
case "wallet-select":
|
||||
return "Ryo"
|
||||
return "Loki"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
<q-route-tab to="/wallet/addressbook" slot="title">
|
||||
<span><q-icon name="person" /> Address Book</span>
|
||||
</q-route-tab>
|
||||
<q-route-tab to="/wallet/servicenode" slot="title">
|
||||
<span><q-icon name="router" /> Service Node</span>
|
||||
</q-route-tab>
|
||||
<q-route-tab to="/wallet/txhistory" slot="title">
|
||||
<span><q-icon name="history" /> TX History</span>
|
||||
</q-route-tab>
|
||||
|
|
|
@ -106,7 +106,7 @@ export default {
|
|||
this.$q.notify({
|
||||
type: "warning",
|
||||
timeout: 2000,
|
||||
message: "Warning: ryod not found, using remote node"
|
||||
message: "Warning: lokid not found, using remote node"
|
||||
})
|
||||
break;
|
||||
case 6:
|
||||
|
|
|
@ -67,18 +67,21 @@
|
|||
<h6 class="q-mb-md q-mt-md" style="font-weight: 300">Review settings:</h6>
|
||||
|
||||
<p>You are using a
|
||||
<template v-if="pending_config.daemon.type == 'local'">
|
||||
<template v-if="config_daemon.type == 'local'">
|
||||
<code>local node</code>
|
||||
</template>
|
||||
<template v-if="pending_config.daemon.type == 'local_remote'">
|
||||
<template v-if="config_daemon.type == 'local_remote'">
|
||||
<code>local + remote node</code>
|
||||
</template>
|
||||
<template v-if="pending_config.daemon.type == 'remote'">
|
||||
<template v-if="config_daemon.type == 'remote'">
|
||||
<code>remote node</code>
|
||||
</template>
|
||||
<template v-if="pending_config.app.testnet">
|
||||
<template v-if="pending_config.app.net_type == 'test'">
|
||||
<code>on testnet</code>
|
||||
</template>
|
||||
<template v-if="pending_config.app.net_type == 'staging'">
|
||||
<code>on staging</code>
|
||||
</template>
|
||||
and will store data in
|
||||
<code>{{ pending_config.app.data_dir }}</code>
|
||||
</p>
|
||||
|
@ -120,7 +123,10 @@ import SettingsGeneral from "components/settings_general"
|
|||
export default {
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
pending_config: state => state.gateway.app.pending_config
|
||||
pending_config: state => state.gateway.app.pending_config,
|
||||
config_daemon (state) {
|
||||
return this.pending_config.daemons[this.pending_config.app.net_type]
|
||||
},
|
||||
}),
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -20,20 +20,6 @@
|
|||
/>
|
||||
</q-field>
|
||||
|
||||
<q-field>
|
||||
<div class="row gutter-md">
|
||||
<div><q-radio v-model="wallet.type" val="long" label="Long address" /></div>
|
||||
<div><q-radio v-model="wallet.type" val="kurz" label="Short (kurz) address" /></div>
|
||||
</div>
|
||||
</q-field>
|
||||
|
||||
<p v-if="wallet.type == 'long'">
|
||||
Create both public/private view & spend keys. Allows creation of view-only wallets.
|
||||
</p>
|
||||
<p v-if="wallet.type == 'kurz'">
|
||||
Create shorter style address with only private view & spend keys. Does NOT support view-only wallets.
|
||||
</p>
|
||||
|
||||
<q-field>
|
||||
<q-input v-model="wallet.password" type="password" float-label="Password" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
|
@ -59,7 +45,6 @@ export default {
|
|||
wallet: {
|
||||
name: "",
|
||||
language: "English",
|
||||
type: "long",
|
||||
password: "",
|
||||
password_confirm: ""
|
||||
},
|
||||
|
|
|
@ -49,11 +49,12 @@
|
|||
<q-item-main label="Create new wallet" />
|
||||
</q-item>
|
||||
<q-item @click.native="restoreWallet()">
|
||||
<q-item-main label="Restore wallet from seed" />
|
||||
<q-item-main label="Restore wallet from seed (Currently not working)" />
|
||||
</q-item>
|
||||
<q-item @click.native="restoreViewWallet()">
|
||||
<!-- TODO: Re-enable this when LOKI has the functionality -->
|
||||
<!-- <q-item @click.native="restoreViewWallet()">
|
||||
<q-item-main label="Restore view-only wallet" />
|
||||
</q-item>
|
||||
</q-item> -->
|
||||
<q-item @click.native="importWallet()">
|
||||
<q-item-main label="Import wallet from file" />
|
||||
</q-item>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="row q-pt-sm q-mx-md q-mb-none items-center non-selectable" style="height: 44px;">
|
||||
|
||||
<div class="col-8">
|
||||
<q-icon name="call_received" size="24px" /> Receive Ryo
|
||||
<q-icon name="call_received" size="24px" /> Receive Loki
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div class="row q-pt-sm q-mx-md q-mb-none items-center non-selectable" style="height: 44px;">
|
||||
|
||||
<div class="col-8">
|
||||
<q-icon name="call_made" size="24px" /> Send Ryo
|
||||
<q-icon name="call_made" size="24px" /> Send Loki
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
|
@ -25,7 +25,7 @@
|
|||
<div class="row q-pt-sm q-mx-md q-mb-none items-center non-selectable" style="height: 44px;">
|
||||
|
||||
<div class="col-8">
|
||||
<q-icon name="call_made" size="24px" /> Send Ryo
|
||||
<q-icon name="call_made" size="24px" /> Send Loki
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
|
@ -74,18 +74,6 @@
|
|||
/>
|
||||
</q-field>
|
||||
|
||||
<div class="row gutter-md">
|
||||
|
||||
<div class="col-6">
|
||||
<q-field>
|
||||
<q-select :dark="theme=='dark'"
|
||||
v-model="newTx.mixin"
|
||||
float-label="Mixin"
|
||||
:options="mixinOptions"
|
||||
/>
|
||||
</q-field>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-field>
|
||||
<q-select :dark="theme=='dark'"
|
||||
v-model="newTx.priority"
|
||||
|
@ -93,9 +81,6 @@
|
|||
:options="priorityOptions"
|
||||
/>
|
||||
</q-field>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<q-field>
|
||||
|
@ -154,7 +139,6 @@ export default {
|
|||
amount: 0,
|
||||
address: "",
|
||||
payment_id: "",
|
||||
mixin: 12,
|
||||
priority: 0,
|
||||
address_book: {
|
||||
save: false,
|
||||
|
@ -162,17 +146,11 @@ export default {
|
|||
description: ""
|
||||
}
|
||||
},
|
||||
mixinOptions: [
|
||||
{label: "12 mixins (default)", value: 12},
|
||||
{label: "48 mixins (top secret)", value: 48},
|
||||
{label: "96 mixins (paranoid)", value: 60},
|
||||
],
|
||||
priorityOptions: [
|
||||
{label: "Normal (x1 fee)", value: 0},
|
||||
{label: "High (x2 fee)", value: 1},
|
||||
{label: "High (x4 fee)", value: 2},
|
||||
{label: "High (x20 fee)", value: 3},
|
||||
{label: "Highest (x144 fee)", value: 4},
|
||||
{label: "Slow (x0.25 fee)", value: 1},
|
||||
{label: "Fast (x5 fee)", value: 2},
|
||||
{label: "Fastest (x41.5 fee)", value: 3},
|
||||
],
|
||||
}
|
||||
},
|
||||
|
@ -202,7 +180,6 @@ export default {
|
|||
amount: 0,
|
||||
address: "",
|
||||
payment_id: "",
|
||||
mixin: 12,
|
||||
priority: 0,
|
||||
address_book: {
|
||||
save: false,
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
<template>
|
||||
<q-page class="service-node-page">
|
||||
<template>
|
||||
|
||||
<div class="row q-pt-sm q-mx-md q-mb-none items-center non-selectable" style="height: 44px;">
|
||||
|
||||
<div class="col-8">
|
||||
<q-icon name="router" size="24px" /> Service Nodes
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="q-pa-md">
|
||||
|
||||
<q-field>
|
||||
<q-input v-model="serviceNode.key" float-label="Service Node Key"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.serviceNode.key.$touch"
|
||||
:error="$v.serviceNode.key.$error"
|
||||
/>
|
||||
</q-field>
|
||||
|
||||
<q-item>
|
||||
<q-item-main>
|
||||
<q-item-tile label class="recepient-address">Award Recepient's Address (yours)</q-item-tile>
|
||||
<q-item-tile class="monospace break-all" label>{{ info.address }}</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyAddress(info.address, $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy address
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</q-item-side>
|
||||
</q-item>
|
||||
|
||||
<div class="row items-end gutter-md">
|
||||
<div class="col">
|
||||
<q-field class="q-ma-none">
|
||||
<q-input v-model="serviceNode.amount" float-label="Amount" :dark="theme=='dark'"
|
||||
type="number" min="0" :max="unlocked_balance / 1e9" />
|
||||
</q-field>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<q-btn @click="serviceNode.amount = unlocked_balance / 1e9" :text-color="theme=='dark'?'white':'dark'">All coins</q-btn>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<q-field class="q-pt-sm">
|
||||
<q-btn
|
||||
:disable="!is_able_to_send"
|
||||
color="primary" @click="stake()" label="Stake" />
|
||||
</q-field>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- <q-inner-loading :visible="tx_status.sending" :dark="theme=='dark'">
|
||||
<q-spinner color="primary" :size="30" />
|
||||
</q-inner-loading> -->
|
||||
|
||||
</template>
|
||||
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const { clipboard } = require("electron")
|
||||
import { mapState } from "vuex"
|
||||
import { required, decimal } from "vuelidate/lib/validators"
|
||||
import { payment_id, service_node_key } from "src/validators/common"
|
||||
import Identicon from "components/identicon"
|
||||
const objectAssignDeep = require("object-assign-deep");
|
||||
export default {
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
unlocked_balance: state => state.gateway.wallet.info.unlocked_balance,
|
||||
info: state => state.gateway.wallet.info,
|
||||
is_ready (state) {
|
||||
return this.$store.getters["gateway/isReady"]
|
||||
},
|
||||
is_able_to_send (state) {
|
||||
return this.$store.getters["gateway/isAbleToSend"]
|
||||
}
|
||||
}),
|
||||
data () {
|
||||
return {
|
||||
staking: false,
|
||||
serviceNode: {
|
||||
key: "",
|
||||
amount: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
serviceNode: {
|
||||
amount: {
|
||||
required,
|
||||
decimal
|
||||
},
|
||||
key: { required, service_node_key },
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
copyAddress (address, event) {
|
||||
event.stopPropagation()
|
||||
for(let i = 0; i < event.path.length; i++) {
|
||||
if(event.path[i].tagName == "BUTTON") {
|
||||
event.path[i].blur()
|
||||
break
|
||||
}
|
||||
}
|
||||
clipboard.writeText(address)
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: "Address copied to clipboard"
|
||||
})
|
||||
},
|
||||
stake: function () {
|
||||
|
||||
this.$v.serviceNode.$touch()
|
||||
|
||||
if(this.serviceNode.amount < 0) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Amount cannot be negative"
|
||||
})
|
||||
return
|
||||
} else if(this.serviceNode.amount == 0) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Amount must be greater than zero"
|
||||
})
|
||||
return
|
||||
} else if(this.serviceNode.amount > this.unlocked_balance / 1e9) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Not enough unlocked balance"
|
||||
})
|
||||
return
|
||||
} else if (this.$v.serviceNode.amount.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Amount not valid"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.$v.serviceNode.key.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Service node key not valid"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.$q.dialog({
|
||||
title: "Stake",
|
||||
message: "Enter wallet password to continue.",
|
||||
prompt: {
|
||||
model: "",
|
||||
type: "password"
|
||||
},
|
||||
ok: {
|
||||
label: "STAKE"
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme=="dark"?"white":"dark"
|
||||
}
|
||||
}).then(password => {
|
||||
// this.$store.commit("gateway/set_tx_status", {
|
||||
// code: 1,
|
||||
// message: "Sending transaction",
|
||||
// sending: true
|
||||
// })
|
||||
// let newTx = objectAssignDeep.noMutate(this.newTx, {password})
|
||||
// this.$gateway.send("wallet", "transfer", newTx)
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Identicon,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.service-node-page {
|
||||
|
||||
.q-item {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.recepient-address {
|
||||
margin-bottom: 8px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -9,7 +9,7 @@
|
|||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text"><span>Balance</span></div>
|
||||
<div class="value"><span><FormatRyo :amount="info.balance" /></span></div>
|
||||
<div class="value"><span><FormatLoki :amount="info.balance" /></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@
|
|||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text"><span>Unlocked balance</span></div>
|
||||
<div class="value"><span><FormatRyo :amount="info.unlocked_balance" /></span></div>
|
||||
<div class="value"><span><FormatLoki :amount="info.unlocked_balance" /></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -262,7 +262,7 @@
|
|||
const { clipboard } = require("electron")
|
||||
import { mapState } from "vuex"
|
||||
import AddressHeader from "components/address_header"
|
||||
import FormatRyo from "components/format_ryo"
|
||||
import FormatLoki from "components/format_loki"
|
||||
import TxList from "components/tx_list"
|
||||
export default {
|
||||
computed: mapState({
|
||||
|
@ -545,7 +545,7 @@ export default {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
FormatRyo,
|
||||
FormatLoki,
|
||||
AddressHeader,
|
||||
TxList
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import VueTimeago from 'vue-timeago'
|
||||
import VueTimeago from "vue-timeago"
|
||||
export default ({
|
||||
app,
|
||||
router,
|
||||
|
@ -6,7 +6,7 @@ export default ({
|
|||
Vue
|
||||
}) => {
|
||||
Vue.use(VueTimeago, {
|
||||
name: 'Timeago',
|
||||
locale: 'en'
|
||||
name: "Timeago",
|
||||
locale: "en"
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Vuelidate from 'vuelidate'
|
||||
import Vuelidate from "vuelidate"
|
||||
|
||||
export default ({ Vue }) => {
|
||||
Vue.use(Vuelidate)
|
||||
|
|
|
@ -13,7 +13,7 @@ export default [
|
|||
path: "/quit",
|
||||
component: () =>
|
||||
import("pages/init/quit")
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -105,6 +105,11 @@ export default [
|
|||
component: () =>
|
||||
import("pages/wallet/txhistory")
|
||||
},
|
||||
{
|
||||
path: "servicenode",
|
||||
component: () =>
|
||||
import("pages/wallet/service-node")
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
export const resetWalletData = (state) => {
|
||||
|
||||
state.commit("set_wallet_data", {
|
||||
|
||||
status: {
|
||||
|
@ -20,23 +19,20 @@ export const resetWalletData = (state) => {
|
|||
spend_key: ""
|
||||
},
|
||||
transactions: {
|
||||
tx_list: [],
|
||||
tx_list: []
|
||||
},
|
||||
address_list: {
|
||||
used: [],
|
||||
unused: [],
|
||||
address_book: [],
|
||||
address_book: []
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
export const resetPendingConfig = (state) => {
|
||||
|
||||
state.commit("set_app_data", {
|
||||
|
||||
pending_config: state.state.app.config
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
export const isReady = (state) => {
|
||||
const { daemons, app } = state.app.config
|
||||
const config_daemon = daemons[app.net_type]
|
||||
|
||||
let target_height
|
||||
if(state.app.config.daemon.type === "local" && !state.daemon.info.is_ready) {
|
||||
if (config_daemon.type === "local" && !state.daemon.info.is_ready) {
|
||||
target_height = Math.max(state.daemon.info.height, state.daemon.info.target_height)
|
||||
} else {
|
||||
target_height = state.daemon.info.height
|
||||
}
|
||||
|
||||
if(state.app.config.daemon.type === "local") {
|
||||
if (config_daemon.type === "local") {
|
||||
return state.daemon.info.is_ready && state.wallet.info.height >= target_height - 1
|
||||
} else {
|
||||
return state.wallet.info.height >= target_height - 1
|
||||
|
@ -15,16 +18,19 @@ export const isReady = (state) => {
|
|||
}
|
||||
|
||||
export const isAbleToSend = (state) => {
|
||||
const { daemons, app } = state.app.config
|
||||
const config_daemon = daemons[app.net_type]
|
||||
|
||||
let target_height
|
||||
if(state.app.config.daemon.type === "local" && !state.daemon.info.is_ready) {
|
||||
if (config_daemon.type === "local" && !state.daemon.info.is_ready) {
|
||||
target_height = Math.max(state.daemon.info.height, state.daemon.info.target_height)
|
||||
} else {
|
||||
target_height = state.daemon.info.height
|
||||
}
|
||||
|
||||
if(state.app.config.daemon.type === "local") {
|
||||
if (config_daemon.type === "local") {
|
||||
return state.daemon.info.is_ready && state.wallet.info.height >= target_height - 1
|
||||
} else if(state.app.config.daemon.type === "local_remote") {
|
||||
} else if (config_daemon.type === "local_remote") {
|
||||
return state.daemon.info.height_without_bootstrap >= target_height && state.wallet.info.height >= target_height - 1
|
||||
} else {
|
||||
return state.wallet.info.height >= target_height - 1
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const objectAssignDeep = require("object-assign-deep");
|
||||
const objectAssignDeep = require("object-assign-deep")
|
||||
|
||||
export const set_app_data = (state, data) => {
|
||||
state.app = objectAssignDeep.noMutate(state.app, data)
|
||||
|
|
|
@ -9,6 +9,8 @@ export default {
|
|||
}
|
||||
},
|
||||
pending_config: {
|
||||
},
|
||||
remotes: {
|
||||
}
|
||||
},
|
||||
wallets: {
|
||||
|
@ -34,12 +36,12 @@ export default {
|
|||
spend_key: ""
|
||||
},
|
||||
transactions: {
|
||||
tx_list: [],
|
||||
tx_list: []
|
||||
},
|
||||
address_list: {
|
||||
used: [],
|
||||
unused: [],
|
||||
address_book: [],
|
||||
address_book: []
|
||||
}
|
||||
},
|
||||
tx_status: {
|
||||
|
|
|
@ -1945,13 +1945,12 @@ return{_strlen:lb,_ge_mul8:Va,_keccak:db,_ge_scalarmult:Ta,_ge_fromfe_frombytes_
|
|||
|
||||
|
||||
|
||||
var ryoConfig = {
|
||||
coinUnitPlaces: 9,
|
||||
coinSymbol: 'RYO',
|
||||
coinName: 'Ryo',
|
||||
coinUriPrefix: 'ryo:',
|
||||
longAddrPrefix: 0x2ce192,
|
||||
kurzAddrPrefix: 0x2c6192
|
||||
var lokiConfig = {
|
||||
coinUnitPlaces: 12,
|
||||
coinSymbol: 'LOKI',
|
||||
coinName: 'Loki',
|
||||
coinUriPrefix: 'loki:',
|
||||
addressPrefix: 114,
|
||||
};
|
||||
|
||||
var cnUtilGen = function(initConfig) {
|
||||
|
@ -1962,7 +1961,7 @@ var cnUtilGen = function(initConfig) {
|
|||
var HASH_STATE_BYTES = 200;
|
||||
var HASH_SIZE = 32;
|
||||
var ADDRESS_CHECKSUM_SIZE = 4;
|
||||
var CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = config.longAddrPrefix;
|
||||
var CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = config.addressPrefix;
|
||||
var UINT64_MAX = new JSBigInt(2).pow(64);
|
||||
var CURRENT_TX_VERSION = 1;
|
||||
var TX_EXTRA_NONCE_MAX_COUNT = 255;
|
||||
|
@ -2092,13 +2091,13 @@ var cnUtilGen = function(initConfig) {
|
|||
|
||||
this.pubkeys_to_string = function(spend, view, use_kurz) {
|
||||
if(!use_kurz) {
|
||||
var prefix = this.encode_varint(config.longAddrPrefix);
|
||||
var prefix = this.encode_varint(config.addressPrefix);
|
||||
var data = prefix + spend + view;
|
||||
var checksum = this.cn_fast_hash(data);
|
||||
return cnBase58.encode(data + checksum.slice(0, ADDRESS_CHECKSUM_SIZE * 2));
|
||||
}
|
||||
else {
|
||||
var prefix = this.encode_varint(config.kurzAddrPrefix);
|
||||
var prefix = this.encode_varint(config.addressPrefix);
|
||||
var data = prefix + spend;
|
||||
var checksum = this.cn_fast_hash(data);
|
||||
return cnBase58.encode(data + checksum.slice(0, ADDRESS_CHECKSUM_SIZE * 2));
|
||||
|
@ -2390,7 +2389,7 @@ var cnUtilGen = function(initConfig) {
|
|||
|
||||
return this;
|
||||
};
|
||||
var cnUtil = cnUtilGen(ryoConfig);
|
||||
var cnUtil = cnUtilGen(lokiConfig);
|
||||
/*
|
||||
mnemonic.js : Converts between 4-byte aligned strings and a human-readable
|
||||
sequence of words. Uses 1626 common words taken from wikipedia article:
|
||||
|
|
|
@ -8,8 +8,11 @@ export const privkey = (input) => {
|
|||
return input.length === 0 || (/^[0-9A-Fa-f]+$/.test(input) && input.length == 64)
|
||||
}
|
||||
|
||||
export const address = (input) => {
|
||||
export const service_node_key = (input) => {
|
||||
return input.length === 64 && /^[0-9A-Za-z]+$/.test(input)
|
||||
}
|
||||
|
||||
export const address = (input) => {
|
||||
if (!(/^[0-9A-Za-z]+$/.test(input))) return false
|
||||
|
||||
switch (input.substring(0, 4)) {
|
||||
|
|
Loading…
Reference in New Issue