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",
|
||||
|
|
|
@ -6,7 +6,7 @@ module.exports = function (ctx) {
|
|||
plugins: [
|
||||
"i18n",
|
||||
"axios",
|
||||
"vuelidate",
|
||||
"vuelidate",
|
||||
"gateway",
|
||||
"timeago"
|
||||
],
|
||||
|
@ -28,7 +28,7 @@ module.exports = function (ctx) {
|
|||
// gzip: true,
|
||||
// analyze: true,
|
||||
// extractCSS: false,
|
||||
extendWebpack(cfg) {
|
||||
extendWebpack (cfg) {
|
||||
/*
|
||||
cfg.module.rules.push({
|
||||
enforce: "pre",
|
||||
|
@ -59,6 +59,7 @@ module.exports = function (ctx) {
|
|||
"QField",
|
||||
"QInput",
|
||||
"QRadio",
|
||||
"QOptionGroup",
|
||||
"QBtn",
|
||||
"QBtnToggle",
|
||||
"QIcon",
|
||||
|
@ -118,30 +119,30 @@ module.exports = function (ctx) {
|
|||
background_color: "#ffffff",
|
||||
theme_color: "#027be3",
|
||||
icons: [{
|
||||
"src": "statics/icons/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
"src": "statics/icons/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -150,7 +151,7 @@ module.exports = function (ctx) {
|
|||
},
|
||||
electron: {
|
||||
bundler: "builder", // or "packager"
|
||||
extendWebpack(cfg) {
|
||||
extendWebpack (cfg) {
|
||||
// do something with Electron process Webpack cfg
|
||||
},
|
||||
packager: {
|
||||
|
@ -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: {
|
||||
|
|
|
@ -19,23 +19,23 @@ let mainWindow, backend
|
|||
let showConfirmClose = true
|
||||
let forceQuit = false
|
||||
|
||||
const portInUse = function(port, callback) {
|
||||
var server = net.createServer(function(socket) {
|
||||
socket.write("Echo server\r\n");
|
||||
socket.pipe(socket);
|
||||
});
|
||||
const portInUse = function (port, callback) {
|
||||
var server = net.createServer(function (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() {
|
||||
function createWindow () {
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
|
@ -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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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,15 +1,15 @@
|
|||
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) {
|
||||
constructor (mainWindow) {
|
||||
this.mainWindow = mainWindow
|
||||
this.daemon = null
|
||||
this.walletd = null
|
||||
|
@ -21,59 +21,102 @@ export class Backend {
|
|||
this.scee = new SCEE()
|
||||
}
|
||||
|
||||
init(config) {
|
||||
|
||||
if(os.platform() == "win32") {
|
||||
this.config_dir = "C:\\ProgramData\\ryo";
|
||||
//this.config_dir = path.join(os.homedir(), "ryo");
|
||||
init (config) {
|
||||
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"));
|
||||
if (!fs.existsSync(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,228 +125,216 @@ 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={}) {
|
||||
send (event, data = {}) {
|
||||
let message = {
|
||||
event,
|
||||
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) {
|
||||
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));
|
||||
receive (data) {
|
||||
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;
|
||||
case "daemon":
|
||||
if (this.daemon) {
|
||||
this.daemon.handle(decrypted_data);
|
||||
}
|
||||
break;
|
||||
case "wallet":
|
||||
if (this.walletd) {
|
||||
this.walletd.handle(decrypted_data);
|
||||
}
|
||||
break;
|
||||
case "core":
|
||||
this.handle(decrypted_data)
|
||||
break
|
||||
case "daemon":
|
||||
if (this.daemon) {
|
||||
this.daemon.handle(decrypted_data)
|
||||
}
|
||||
break
|
||||
case "wallet":
|
||||
if (this.walletd) {
|
||||
this.walletd.handle(decrypted_data)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
handle(data) {
|
||||
|
||||
handle (data) {
|
||||
let params = data.data
|
||||
|
||||
switch (data.method) {
|
||||
case "quick_save_config":
|
||||
// save only partial config settings
|
||||
Object.keys(params).map(key => {
|
||||
this.config_data[key] = Object.assign(this.config_data[key], params[key])
|
||||
case "quick_save_config":
|
||||
// save only partial config settings
|
||||
Object.keys(params).map(key => {
|
||||
this.config_data[key] = Object.assign(this.config_data[key], params[key])
|
||||
})
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => {
|
||||
this.send("set_app_data", {
|
||||
config: params,
|
||||
pending_config: params
|
||||
})
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), 'utf8', () => {
|
||||
})
|
||||
break
|
||||
|
||||
case "save_config":
|
||||
// check if config has changed
|
||||
let config_changed = false
|
||||
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 }
|
||||
})
|
||||
})
|
||||
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", () => {
|
||||
if (data.method == "save_config_init") {
|
||||
this.startup()
|
||||
} else {
|
||||
this.send("set_app_data", {
|
||||
config: params,
|
||||
pending_config: params
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data
|
||||
})
|
||||
})
|
||||
break
|
||||
|
||||
case "save_config":
|
||||
// check if config has changed
|
||||
let config_changed = false
|
||||
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
|
||||
})
|
||||
})
|
||||
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', () => {
|
||||
|
||||
if(data.method == "save_config_init") {
|
||||
this.startup();
|
||||
} else {
|
||||
this.send("set_app_data", {
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data,
|
||||
})
|
||||
if(config_changed) {
|
||||
this.send("settings_changed_reboot")
|
||||
}
|
||||
if (config_changed) {
|
||||
this.send("settings_changed_reboot")
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "init":
|
||||
this.startup();
|
||||
break;
|
||||
|
||||
case "open_explorer":
|
||||
if(params.type == "tx") {
|
||||
require("electron").shell.openExternal("https://explorer.ryo-currency.com/tx/"+params.id)
|
||||
}
|
||||
break;
|
||||
})
|
||||
break
|
||||
case "init":
|
||||
this.startup()
|
||||
break
|
||||
|
||||
case "open_url":
|
||||
require("electron").shell.openExternal(params.url)
|
||||
break;
|
||||
case "open_explorer":
|
||||
if (params.type == "tx") {
|
||||
require("electron").shell.openExternal("https://lokiblocks.com/tx/" + params.id)
|
||||
}
|
||||
break
|
||||
|
||||
case "save_png":
|
||||
let filename = dialog.showSaveDialog(this.mainWindow, {
|
||||
title: "Save "+params.type,
|
||||
filters: [{name: "PNG", extensions:["png"]}],
|
||||
defaultPath: os.homedir()
|
||||
case "open_url":
|
||||
require("electron").shell.openExternal(params.url)
|
||||
break
|
||||
|
||||
case "save_png":
|
||||
let filename = dialog.showSaveDialog(this.mainWindow, {
|
||||
title: "Save " + params.type,
|
||||
filters: [{name: "PNG", extensions: ["png"]}],
|
||||
defaultPath: os.homedir()
|
||||
})
|
||||
if (filename) {
|
||||
let base64Data = params.img.replace(/^data:image\/png;base64,/, "")
|
||||
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(filename) {
|
||||
let base64Data = params.img.replace(/^data:image\/png;base64,/,"")
|
||||
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})
|
||||
})
|
||||
}
|
||||
break;
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
startup() {
|
||||
fs.readFile(this.config_file, "utf8", (err,data) => {
|
||||
startup () {
|
||||
this.send("set_app_data", {
|
||||
remotes: this.remotes
|
||||
})
|
||||
|
||||
fs.readFile(this.config_file, "utf8", (err, data) => {
|
||||
if (err) {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
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) {
|
||||
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() {
|
||||
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,65 +1,59 @@
|
|||
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) {
|
||||
constructor (backend) {
|
||||
this.backend = backend
|
||||
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() {
|
||||
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)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
start(options) {
|
||||
|
||||
if(options.daemon.type === "remote") {
|
||||
|
||||
start (options) {
|
||||
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) => {
|
||||
if(!data.hasOwnProperty("error")) {
|
||||
if (!data.hasOwnProperty("error")) {
|
||||
this.startHeartbeat()
|
||||
resolve()
|
||||
} else {
|
||||
|
@ -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}`))
|
||||
|
@ -125,17 +127,17 @@ export class Daemon {
|
|||
// To let caller know when the daemon is ready
|
||||
let intrvl = setInterval(() => {
|
||||
this.sendRPC("get_info").then((data) => {
|
||||
if(!data.hasOwnProperty("error")) {
|
||||
if (!data.hasOwnProperty("error")) {
|
||||
this.startHeartbeat()
|
||||
clearInterval(intrvl);
|
||||
resolve();
|
||||
clearInterval(intrvl)
|
||||
resolve()
|
||||
} else {
|
||||
if(data.error.cause &&
|
||||
if (data.error.cause &&
|
||||
data.error.cause.code === "ECONNREFUSED") {
|
||||
// Ignore
|
||||
} else {
|
||||
clearInterval(intrvl);
|
||||
reject(error);
|
||||
clearInterval(intrvl)
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -143,26 +145,20 @@ export class Daemon {
|
|||
})
|
||||
}
|
||||
|
||||
handle(data) {
|
||||
|
||||
handle (data) {
|
||||
let params = data.data
|
||||
|
||||
switch (data.method) {
|
||||
case "ban_peer":
|
||||
this.banPeer(params.host, params.seconds)
|
||||
break
|
||||
|
||||
case "ban_peer":
|
||||
this.banPeer(params.host, params.seconds)
|
||||
break
|
||||
|
||||
default:
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
banPeer(host, seconds=3600) {
|
||||
|
||||
if(!seconds)
|
||||
seconds=3600
|
||||
banPeer (host, seconds = 3600) {
|
||||
if (!seconds) { seconds = 3600 }
|
||||
|
||||
let params = {
|
||||
bans: [{
|
||||
|
@ -173,51 +169,45 @@ export class Daemon {
|
|||
}
|
||||
|
||||
this.sendRPC("set_bans", params).then((data) => {
|
||||
if(data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
this.sendGateway("show_notification", {type: "negative", message: "Error banning peer", timeout: 2000})
|
||||
return
|
||||
}
|
||||
|
||||
let end_time = new Date(Date.now() + seconds * 1000).toLocaleString()
|
||||
this.sendGateway("show_notification", {message: "Banned "+host+" until "+end_time, timeout: 2000})
|
||||
this.sendGateway("show_notification", {message: "Banned " + host + " until " + end_time, timeout: 2000})
|
||||
|
||||
// Send updated peer and ban list
|
||||
this.heartbeatSlowAction()
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
timestampToHeight(timestamp, pivot=null, recursion_limit=null) {
|
||||
|
||||
timestampToHeight (timestamp, pivot = null, recursion_limit = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if(timestamp > 999999999999) {
|
||||
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
|
||||
|
||||
if(estimated_height <= 0) {
|
||||
if (estimated_height <= 0) {
|
||||
return resolve(0)
|
||||
}
|
||||
|
||||
if(recursion_limit > 10) {
|
||||
if (recursion_limit > 10) {
|
||||
return resolve(pivot[0])
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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")) {
|
||||
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
return reject()
|
||||
}
|
||||
|
||||
|
@ -226,7 +216,7 @@ export class Daemon {
|
|||
// If we are within an hour that is good enough
|
||||
// If for some reason there is a > 1h gap between blocks
|
||||
// the recursion limit will take care of infinite loop
|
||||
if(Math.abs(timestamp - new_pivot[1]) < 3600) {
|
||||
if (Math.abs(timestamp - new_pivot[1]) < 3600) {
|
||||
return resolve(new_pivot[0])
|
||||
}
|
||||
|
||||
|
@ -244,26 +234,23 @@ export class Daemon {
|
|||
// If we are within an hour that is good enough
|
||||
// If for some reason there is a > 1h gap between blocks
|
||||
// the recursion limit will take care of infinite loop
|
||||
if(Math.abs(timestamp - new_pivot[1]) < 3600) {
|
||||
if (Math.abs(timestamp - new_pivot[1]) < 3600) {
|
||||
return resolve(new_pivot[0])
|
||||
}
|
||||
|
||||
// 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
|
||||
})
|
||||
}
|
||||
|
||||
startHeartbeat() {
|
||||
startHeartbeat () {
|
||||
clearInterval(this.heartbeat)
|
||||
this.heartbeat = setInterval(() => {
|
||||
this.heartbeatAction()
|
||||
|
@ -275,14 +262,13 @@ export class Daemon {
|
|||
this.heartbeatSlowAction()
|
||||
}, 30 * 1000) // 30 seconds
|
||||
this.heartbeatSlowAction()
|
||||
|
||||
}
|
||||
|
||||
heartbeatAction() {
|
||||
heartbeatAction () {
|
||||
let actions = []
|
||||
|
||||
// No difference between local and remote heartbeat action for now
|
||||
if(this.local) {
|
||||
if (this.local) {
|
||||
actions = [
|
||||
this.getRPC("info")
|
||||
]
|
||||
|
@ -296,9 +282,8 @@ export class Daemon {
|
|||
let daemon_info = {
|
||||
}
|
||||
for (let n of data) {
|
||||
if(n == undefined || !n.hasOwnProperty("result") || n.result == undefined)
|
||||
continue
|
||||
if(n.method == "get_info") {
|
||||
if (n == undefined || !n.hasOwnProperty("result") || n.result == undefined) { continue }
|
||||
if (n.method == "get_info") {
|
||||
daemon_info.info = n.result
|
||||
}
|
||||
}
|
||||
|
@ -306,28 +291,27 @@ export class Daemon {
|
|||
})
|
||||
}
|
||||
|
||||
heartbeatSlowAction() {
|
||||
heartbeatSlowAction () {
|
||||
let actions = []
|
||||
if(this.local) {
|
||||
if (this.local) {
|
||||
actions = [
|
||||
this.getRPC("connections"),
|
||||
this.getRPC("bans"),
|
||||
//this.getRPC("txpool_backlog"),
|
||||
this.getRPC("bans")
|
||||
// this.getRPC("txpool_backlog"),
|
||||
]
|
||||
} else {
|
||||
actions = [
|
||||
//this.getRPC("txpool_backlog"),
|
||||
// this.getRPC("txpool_backlog"),
|
||||
]
|
||||
}
|
||||
|
||||
if(actions.length === 0) return
|
||||
if (actions.length === 0) return
|
||||
|
||||
Promise.all(actions).then((data) => {
|
||||
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")) {
|
||||
|
@ -340,11 +324,11 @@ export class Daemon {
|
|||
})
|
||||
}
|
||||
|
||||
sendGateway(method, data) {
|
||||
sendGateway (method, data) {
|
||||
this.backend.send(method, data)
|
||||
}
|
||||
|
||||
sendRPC(method, params={}) {
|
||||
sendRPC (method, params = {}) {
|
||||
let id = this.id++
|
||||
let options = {
|
||||
uri: `${this.protocol}${this.hostname}:${this.port}/json_rpc`,
|
||||
|
@ -355,15 +339,15 @@ export class Daemon {
|
|||
method: method
|
||||
},
|
||||
agent: this.agent
|
||||
};
|
||||
if(Object.keys(params).length !== 0) {
|
||||
options.json.params = params;
|
||||
}
|
||||
if (Object.keys(params).length !== 0) {
|
||||
options.json.params = params
|
||||
}
|
||||
|
||||
return this.queue.add(() => {
|
||||
return request(options)
|
||||
.then((response) => {
|
||||
if(response.hasOwnProperty("error")) {
|
||||
if (response.hasOwnProperty("error")) {
|
||||
return {
|
||||
method: method,
|
||||
params: params,
|
||||
|
@ -392,12 +376,12 @@ export class Daemon {
|
|||
/**
|
||||
* Call one of the get_* RPC calls
|
||||
*/
|
||||
getRPC(parameter, args) {
|
||||
return this.sendRPC(`get_${parameter}`, args);
|
||||
getRPC (parameter, args) {
|
||||
return this.sendRPC(`get_${parameter}`, args)
|
||||
}
|
||||
|
||||
quit() {
|
||||
clearInterval(this.heartbeat);
|
||||
quit () {
|
||||
clearInterval(this.heartbeat)
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.daemonProcess) {
|
||||
this.daemonProcess.on("close", code => {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
|
||||
constructor (app, router) {
|
||||
this.app = app
|
||||
this.router = router
|
||||
this.token = null
|
||||
|
@ -19,8 +16,8 @@ export class Gateway {
|
|||
theme
|
||||
}
|
||||
}
|
||||
});
|
||||
this.app.store.watch( state => state.gateway.app.config.appearance.theme, (theme) => {
|
||||
})
|
||||
this.app.store.watch(state => state.gateway.app.config.appearance.theme, (theme) => {
|
||||
LocalStorage.set("theme", theme)
|
||||
})
|
||||
|
||||
|
@ -30,34 +27,33 @@ 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() {
|
||||
open () {
|
||||
this.app.store.commit("gateway/set_app_data", {
|
||||
status: {
|
||||
code: 2 // Loading config
|
||||
}
|
||||
});
|
||||
this.send("core", "init");
|
||||
})
|
||||
this.send("core", "init")
|
||||
}
|
||||
|
||||
confirmClose(msg) {
|
||||
if(this.closeDialog) {
|
||||
confirmClose (msg) {
|
||||
if (this.closeDialog) {
|
||||
return
|
||||
}
|
||||
this.closeDialog = true
|
||||
|
@ -70,7 +66,7 @@ export class Gateway {
|
|||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.app.store.state.gateway.app.config.appearance.theme=="dark"?"white":"dark"
|
||||
color: this.app.store.state.gateway.app.config.appearance.theme == "dark" ? "white" : "dark"
|
||||
}
|
||||
}).then(() => {
|
||||
this.closeDialog = false
|
||||
|
@ -80,76 +76,70 @@ export class Gateway {
|
|||
}).catch(() => {
|
||||
this.closeDialog = false
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
send(module, method, data={}) {
|
||||
send (module, method, data = {}) {
|
||||
let message = {
|
||||
module,
|
||||
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) {
|
||||
|
||||
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
|
||||
|
||||
case "set_app_data":
|
||||
this.app.store.commit("gateway/set_app_data", decrypted_data.data)
|
||||
break
|
||||
case "set_daemon_data":
|
||||
this.app.store.commit("gateway/set_daemon_data", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "set_daemon_data":
|
||||
this.app.store.commit("gateway/set_daemon_data", decrypted_data.data)
|
||||
break
|
||||
case "set_wallet_data":
|
||||
case "set_wallet_error":
|
||||
this.app.store.commit("gateway/set_wallet_data", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "set_wallet_data":
|
||||
case "set_wallet_error":
|
||||
this.app.store.commit("gateway/set_wallet_data", decrypted_data.data)
|
||||
break
|
||||
case "set_tx_status":
|
||||
this.app.store.commit("gateway/set_tx_status", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "set_tx_status":
|
||||
this.app.store.commit("gateway/set_tx_status", decrypted_data.data)
|
||||
break
|
||||
case "wallet_list":
|
||||
this.app.store.commit("gateway/set_wallet_list", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "wallet_list":
|
||||
this.app.store.commit("gateway/set_wallet_list", decrypted_data.data)
|
||||
break
|
||||
case "settings_changed_reboot":
|
||||
this.confirmClose("Changes require restart. Would you like to exit now?")
|
||||
break
|
||||
|
||||
case "settings_changed_reboot":
|
||||
this.confirmClose("Changes require restart. Would you like to exit now?")
|
||||
break
|
||||
|
||||
case "show_notification":
|
||||
let notification = {
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: ""
|
||||
}
|
||||
Notify.create(Object.assign(notification, decrypted_data.data))
|
||||
break
|
||||
|
||||
case "return_to_wallet_select":
|
||||
this.router.replace({ path: "/wallet-select" })
|
||||
setTimeout(() => {
|
||||
// short delay to prevent wallet data reaching the
|
||||
// websocket moments after we close and reset data
|
||||
this.app.store.dispatch("gateway/resetWalletData")
|
||||
}, 250);
|
||||
break
|
||||
case "show_notification":
|
||||
let notification = {
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: ""
|
||||
}
|
||||
Notify.create(Object.assign(notification, decrypted_data.data))
|
||||
break
|
||||
|
||||
case "return_to_wallet_select":
|
||||
this.router.replace({ path: "/wallet-select" })
|
||||
setTimeout(() => {
|
||||
// short delay to prevent wallet data reaching the
|
||||
// websocket moments after we close and reset data
|
||||
this.app.store.dispatch("gateway/resetWalletData")
|
||||
}, 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,28 +74,13 @@
|
|||
/>
|
||||
</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"
|
||||
float-label="Priority"
|
||||
:options="priorityOptions"
|
||||
/>
|
||||
</q-field>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<q-field>
|
||||
<q-select :dark="theme=='dark'"
|
||||
v-model="newTx.priority"
|
||||
float-label="Priority"
|
||||
:options="priorityOptions"
|
||||
/>
|
||||
</q-field>
|
||||
|
||||
|
||||
<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)
|
||||
|
|
|
@ -2,115 +2,120 @@ export default [
|
|||
{
|
||||
path: "/",
|
||||
component: () =>
|
||||
import ("layouts/init/loading"),
|
||||
import("layouts/init/loading"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () =>
|
||||
import ("pages/init/index")
|
||||
import("pages/init/index")
|
||||
},
|
||||
{
|
||||
path: "/quit",
|
||||
component: () =>
|
||||
import ("pages/init/quit")
|
||||
},
|
||||
import("pages/init/quit")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/welcome",
|
||||
component: () =>
|
||||
import ("layouts/init/welcome"),
|
||||
import("layouts/init/welcome"),
|
||||
children: [{
|
||||
path: "",
|
||||
component: () =>
|
||||
import ("pages/init/welcome")
|
||||
import("pages/init/welcome")
|
||||
}]
|
||||
},
|
||||
{
|
||||
path: "/wallet-select",
|
||||
component: () =>
|
||||
import ("layouts/wallet-select/main"),
|
||||
import("layouts/wallet-select/main"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "wallet-select",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/index")
|
||||
import("pages/wallet-select/index")
|
||||
},
|
||||
{
|
||||
path: "create",
|
||||
name: "wallet-create",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/create")
|
||||
import("pages/wallet-select/create")
|
||||
},
|
||||
{
|
||||
path: "restore",
|
||||
name: "wallet-restore",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/restore")
|
||||
import("pages/wallet-select/restore")
|
||||
},
|
||||
{
|
||||
path: "import-view-only",
|
||||
name: "wallet-import-view-only",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/import-view-only")
|
||||
import("pages/wallet-select/import-view-only")
|
||||
},
|
||||
{
|
||||
path: "import",
|
||||
name: "wallet-import",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/import")
|
||||
import("pages/wallet-select/import")
|
||||
},
|
||||
{
|
||||
path: "import-legacy",
|
||||
name: "wallet-import-legacy",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/import-legacy")
|
||||
import("pages/wallet-select/import-legacy")
|
||||
},
|
||||
{
|
||||
path: "created",
|
||||
name: "wallet-created",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/created")
|
||||
import("pages/wallet-select/created")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/wallet",
|
||||
component: () =>
|
||||
import ("layouts/wallet/main"),
|
||||
import("layouts/wallet/main"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () =>
|
||||
import ("pages/wallet/wallet")
|
||||
import("pages/wallet/wallet")
|
||||
},
|
||||
{
|
||||
path: "receive",
|
||||
component: () =>
|
||||
import ("pages/wallet/receive")
|
||||
import("pages/wallet/receive")
|
||||
},
|
||||
{
|
||||
path: "send",
|
||||
component: () =>
|
||||
import ("pages/wallet/send")
|
||||
import("pages/wallet/send")
|
||||
},
|
||||
{
|
||||
path: "addressbook",
|
||||
component: () =>
|
||||
import ("pages/wallet/addressbook")
|
||||
import("pages/wallet/addressbook")
|
||||
},
|
||||
{
|
||||
path: "txhistory",
|
||||
component: () =>
|
||||
import ("pages/wallet/txhistory")
|
||||
import("pages/wallet/txhistory")
|
||||
},
|
||||
{
|
||||
path: "servicenode",
|
||||
component: () =>
|
||||
import("pages/wallet/service-node")
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{ // Always leave this as last one
|
||||
path: "*",
|
||||
component: () =>
|
||||
import ("pages/404")
|
||||
import("pages/404")
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -4,9 +4,9 @@ import * as mutations from "./mutations"
|
|||
import * as actions from "./actions"
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
actions
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
actions
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//import { validateAddress } from "./address_tools"
|
||||
// import { validateAddress } from "./address_tools"
|
||||
|
||||
export const payment_id = (input) => {
|
||||
return input.length === 0 || (/^[0-9A-Fa-f]+$/.test(input) && (input.length == 16 || input.length == 64))
|
||||
|
@ -8,36 +8,39 @@ export const privkey = (input) => {
|
|||
return input.length === 0 || (/^[0-9A-Fa-f]+$/.test(input) && input.length == 64)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if(!(/^[0-9A-Za-z]+$/.test(input))) return false
|
||||
switch (input.substring(0, 4)) {
|
||||
case "Sumo":
|
||||
case "RYoL":
|
||||
case "Suto":
|
||||
case "RYoT":
|
||||
return input.length === 99
|
||||
|
||||
switch (input.substring(0,4)) {
|
||||
case "Sumo":
|
||||
case "RYoL":
|
||||
case "Suto":
|
||||
case "RYoT":
|
||||
return input.length === 99
|
||||
case "Subo":
|
||||
case "Suso":
|
||||
return input.length == 98
|
||||
|
||||
case "Subo":
|
||||
case "Suso":
|
||||
return input.length == 98
|
||||
case "RYoS":
|
||||
case "RYoU":
|
||||
return input.length == 99
|
||||
|
||||
case "RYoS":
|
||||
case "RYoU":
|
||||
return input.length == 99
|
||||
case "Sumi":
|
||||
case "RYoN":
|
||||
case "Suti":
|
||||
case "RYoE":
|
||||
return input.length === 110
|
||||
|
||||
case "Sumi":
|
||||
case "RYoN":
|
||||
case "Suti":
|
||||
case "RYoE":
|
||||
return input.length === 110
|
||||
case "RYoK":
|
||||
case "RYoH":
|
||||
return input.length === 55
|
||||
|
||||
case "RYoK":
|
||||
case "RYoH":
|
||||
return input.length === 55
|
||||
|
||||
default:
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue