More features and fixes
Linting Increased default window size. Fixed transaction text overlapping on a small screen More changes. Replaced icons. Added loki icon to the top menu bar. Added small margin to copy button in show private key modal. Hid back and next button in welcome screen. Users must now click the language to go to the next screen. Randomise remote nodes when configuring from the welcome screen. Added viewing QR code from receive Added QR copying Made mac icon a bit smaller Updated links and added loki project to copyright check data dir exists Updated service node page. Moved staking into its own component. Check remote node before booting up daemon. Update restoration start date. Made network types in GUI match those returned from lokid (main -> mainnet, staging -> stagenet, test -> testnet). Removed unaccessible remote nodes. Separate data and wallet directories. Updated created page layout Don't ask user for a password if it's not set. Show a dialog to the user if they don't set a password. Show staking transaction. Added option to import file from old gui. Added saving transaction notes straight from send. Fix updated tx notes not showing in transactions Clean up transfer code Minor fixes Show user error if a wallet failed to import. bug fixes
|
@ -22,6 +22,7 @@ quasar build -m electron -t mat
|
|||
|
||||
### LICENSE
|
||||
|
||||
Copyright (c) 2018-2019, Loki Project
|
||||
Copyright (c) 2018, Ryo Currency Project
|
||||
|
||||
Portions of this software are available under BSD-3 license. Please see ORIGINAL-LICENSE for details
|
||||
|
|
28
package-lock.json
generated
|
@ -7003,7 +7003,6 @@
|
|||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
|
@ -12610,12 +12609,28 @@
|
|||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
|
||||
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
|
||||
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.0.5"
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
|
||||
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ripemd160": {
|
||||
|
@ -14432,8 +14447,7 @@
|
|||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"electron-window-state": "^5.0.2",
|
||||
"fs-extra": "^7.0.1",
|
||||
"object-assign-deep": "^0.4.0",
|
||||
"portscanner": "^2.2.0",
|
||||
"promise-queue": "^2.2.5",
|
||||
|
|
|
@ -176,7 +176,7 @@ module.exports = function (ctx) {
|
|||
|
||||
appId: "com.lokinetwork.wallet",
|
||||
productName: "Loki Wallet Atom",
|
||||
copyright: "Copyright © 2018 Ryo Currency Project",
|
||||
copyright: "Copyright © 2018-2019 Loki Project, 2018 Ryo Currency Project",
|
||||
|
||||
// directories: {
|
||||
// buildResources: "src-electron/build"
|
||||
|
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 51 KiB |
|
@ -41,8 +41,8 @@ function createWindow () {
|
|||
*/
|
||||
|
||||
let mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 800,
|
||||
defaultHeight: 650
|
||||
defaultWidth: 900,
|
||||
defaultHeight: 700
|
||||
})
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
|
|
|
@ -5,7 +5,7 @@ import { dialog } from "electron"
|
|||
|
||||
const WebSocket = require("ws")
|
||||
const os = require("os")
|
||||
const fs = require("fs")
|
||||
const fs = require("fs-extra")
|
||||
const path = require("path")
|
||||
const objectAssignDeep = require("object-assign-deep")
|
||||
|
||||
|
@ -17,6 +17,7 @@ export class Backend {
|
|||
this.wss = null
|
||||
this.token = null
|
||||
this.config_dir = null
|
||||
this.wallet_dir = null
|
||||
this.config_file = null
|
||||
this.config_data = {}
|
||||
this.scee = new SCEE()
|
||||
|
@ -24,16 +25,19 @@ export class Backend {
|
|||
|
||||
init (config) {
|
||||
if (os.platform() === "win32") {
|
||||
this.config_dir = "C:\\ProgramData\\loki-wallet"
|
||||
this.config_dir = "C:\\ProgramData\\loki"
|
||||
this.wallet_dir = `${os.homedir()}\\Documents\\Loki`
|
||||
} else {
|
||||
this.config_dir = path.join(os.homedir(), ".loki-wallet")
|
||||
this.config_dir = path.join(os.homedir(), ".loki")
|
||||
this.wallet_dir = path.join(os.homedir(), "Loki")
|
||||
}
|
||||
|
||||
if (!fs.existsSync(this.config_dir)) {
|
||||
fs.mkdirSync(this.config_dir)
|
||||
fs.mkdirpSync(this.config_dir)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(path.join(this.config_dir, "gui"))) {
|
||||
fs.mkdirSync(path.join(this.config_dir, "gui"))
|
||||
fs.mkdirpSync(path.join(this.config_dir, "gui"))
|
||||
}
|
||||
|
||||
this.config_file = path.join(this.config_dir, "gui", "config.json")
|
||||
|
@ -54,19 +58,19 @@ export class Backend {
|
|||
}
|
||||
|
||||
const daemons = {
|
||||
main: {
|
||||
mainnet: {
|
||||
...daemon,
|
||||
remote_host: "doopool.xyz",
|
||||
remote_port: 22020
|
||||
},
|
||||
staging: {
|
||||
stagenet: {
|
||||
...daemon,
|
||||
type: "local",
|
||||
p2p_bind_port: 38153,
|
||||
rpc_bind_port: 38154,
|
||||
zmq_rpc_bind_port: 38155
|
||||
},
|
||||
test: {
|
||||
testnet: {
|
||||
...daemon,
|
||||
type: "local",
|
||||
p2p_bind_port: 38156,
|
||||
|
@ -80,8 +84,9 @@ export class Backend {
|
|||
daemons: objectAssignDeep({}, daemons),
|
||||
app: {
|
||||
data_dir: this.config_dir,
|
||||
wallet_data_dir: this.wallet_dir,
|
||||
ws_bind_port: 12213,
|
||||
net_type: "main"
|
||||
net_type: "mainnet"
|
||||
},
|
||||
wallet: {
|
||||
rpc_bind_port: 18082,
|
||||
|
@ -102,10 +107,6 @@ export class Backend {
|
|||
host: "doopool.xyz",
|
||||
port: "22020"
|
||||
},
|
||||
{
|
||||
host: "rpc.nodes.rentals",
|
||||
port: "22023"
|
||||
},
|
||||
{
|
||||
host: "daemons.cryptopool.space",
|
||||
port: "22023"
|
||||
|
@ -114,10 +115,6 @@ export class Backend {
|
|||
host: "node.loki-pool.com",
|
||||
port: "18081"
|
||||
},
|
||||
{
|
||||
host: "uk.loki.cash",
|
||||
port: "22020"
|
||||
},
|
||||
{
|
||||
host: "imaginary.stream",
|
||||
port: "22023"
|
||||
|
@ -214,7 +211,7 @@ export class Backend {
|
|||
// Validate deamon data
|
||||
this.config_data = {
|
||||
...this.config_data,
|
||||
...validated,
|
||||
...validated
|
||||
}
|
||||
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => {
|
||||
|
@ -307,27 +304,60 @@ export class Backend {
|
|||
}
|
||||
|
||||
// save config file back to file, so updated options are stored on disk
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8")
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => {})
|
||||
|
||||
this.send("set_app_data", {
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data
|
||||
})
|
||||
|
||||
// Make the wallet dir
|
||||
const { wallet_data_dir, data_dir } = this.config_data.app
|
||||
if (!fs.existsSync(wallet_data_dir)) { fs.mkdirpSync(wallet_data_dir) }
|
||||
|
||||
// Check to see if data and wallet directories exist
|
||||
const dirs_to_check = [{
|
||||
path: data_dir,
|
||||
error: "Data storge path not found"
|
||||
},
|
||||
{
|
||||
path: wallet_data_dir,
|
||||
error: "Wallet data storge path not found"
|
||||
}]
|
||||
|
||||
for (const dir of dirs_to_check) {
|
||||
// Check to see if dir exists
|
||||
if (!fs.existsSync(dir.path)) {
|
||||
this.send("show_notification", {
|
||||
type: "negative",
|
||||
message: `Error: ${dir.error}`,
|
||||
timeout: 2000
|
||||
})
|
||||
|
||||
// Go back to config
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
"mainnet": this.config_data.app.data_dir,
|
||||
"stagenet": path.join(this.config_data.app.data_dir, "stagenet"),
|
||||
"testnet": path.join(this.config_data.app.data_dir, "testnet")
|
||||
}
|
||||
|
||||
// Make sure we have the directories we need
|
||||
const net_dir = dirs[net_type]
|
||||
if (!fs.existsSync(net_dir)) { fs.mkdirSync(net_dir) }
|
||||
if (!fs.existsSync(net_dir)) { fs.mkdirpSync(net_dir) }
|
||||
|
||||
const log_dir = path.join(net_dir, "logs")
|
||||
if (!fs.existsSync(log_dir)) { fs.mkdirSync(log_dir) }
|
||||
if (!fs.existsSync(log_dir)) { fs.mkdirpSync(log_dir) }
|
||||
|
||||
this.daemon = new Daemon(this)
|
||||
this.walletd = new WalletRPC(this)
|
||||
|
@ -338,72 +368,127 @@ export class Backend {
|
|||
}
|
||||
})
|
||||
|
||||
this.daemon.checkVersion().then((version) => {
|
||||
if (version) {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 4,
|
||||
message: version
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// daemon not found, probably removed by AV, set to remote node
|
||||
this.config_data.daemons[net_type].type = "remote"
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 5
|
||||
},
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data
|
||||
})
|
||||
// Make sure the remote node provided is accessible
|
||||
const config_daemon = this.config_data.daemons[net_type]
|
||||
this.daemon.checkRemote(config_daemon).then(data => {
|
||||
if (data.error) {
|
||||
// If we can default to local then we do so, otherwise we tell the user to re-set the node
|
||||
if (config_daemon.type === "local_remote") {
|
||||
this.config_data.daemons[net_type].type = "local"
|
||||
this.send("set_app_data", {
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data
|
||||
})
|
||||
this.send("show_notification", {
|
||||
type: "warning",
|
||||
textColor: "black",
|
||||
message: "Warning: Could not access remote node, switching to local only",
|
||||
timeout: 2000
|
||||
})
|
||||
} else {
|
||||
this.send("show_notification", {
|
||||
type: "negative",
|
||||
message: "Error: Could not access remote node, please try another remote node",
|
||||
timeout: 2000
|
||||
})
|
||||
|
||||
// Go back to config
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.daemon.start(this.config_data).then(() => {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 6 // Starting wallet
|
||||
}
|
||||
// If we got a net type back then check if ours match
|
||||
if (data.net_type && data.net_type !== net_type) {
|
||||
this.send("show_notification", {
|
||||
type: "negative",
|
||||
message: "Error: Remote node is using a different nettype",
|
||||
timeout: 2000
|
||||
})
|
||||
|
||||
this.walletd.start(this.config_data).then(() => {
|
||||
// Go back to config
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.daemon.checkVersion().then((version) => {
|
||||
if (version) {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 7 // Reading wallet list
|
||||
code: 4,
|
||||
message: version
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// daemon not found, probably removed by AV, set to remote node
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
this.daemon.start(this.config_data).then(() => {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 6 // Starting wallet
|
||||
}
|
||||
})
|
||||
|
||||
this.walletd.listWallets(true)
|
||||
this.walletd.start(this.config_data).then(() => {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 7 // Reading wallet list
|
||||
}
|
||||
})
|
||||
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 0 // Ready
|
||||
}
|
||||
this.walletd.listWallets(true)
|
||||
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 0 // Ready
|
||||
}
|
||||
})
|
||||
// eslint-disable-next-line
|
||||
}).catch(error => {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
})
|
||||
})
|
||||
// eslint-disable-next-line
|
||||
}).catch(error => {
|
||||
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})
|
||||
}
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
})
|
||||
})
|
||||
// eslint-disable-next-line
|
||||
}).catch(error => {
|
||||
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})
|
||||
}
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
})
|
||||
})
|
||||
}).catch(error => {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -423,7 +508,7 @@ export class Backend {
|
|||
|
||||
// Replace any invalid value with default values
|
||||
validate_values (values, defaults) {
|
||||
const isDictionary = (v) => typeof v === "object" && v !== null && !(v instanceof Array) && !(v instanceof Date);
|
||||
const isDictionary = (v) => typeof v === "object" && v !== null && !(v instanceof Array) && !(v instanceof Date)
|
||||
const modified = { ...values }
|
||||
|
||||
// Make sure we have valid defaults
|
||||
|
|
|
@ -11,7 +11,7 @@ export class Daemon {
|
|||
this.heartbeat = null
|
||||
this.heartbeat_slow = null
|
||||
this.id = 0
|
||||
this.net_type = "main"
|
||||
this.net_type = "mainnet"
|
||||
this.local = false // do we have a local daemon ?
|
||||
|
||||
this.agent = new http.Agent({keepAlive: true, maxSockets: 1})
|
||||
|
@ -40,6 +40,23 @@ export class Daemon {
|
|||
})
|
||||
}
|
||||
|
||||
checkRemote (daemon) {
|
||||
if (daemon.type === "local") {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
return this.sendRPC("get_info", {}, {
|
||||
protocol: "http://",
|
||||
hostname: daemon.remote_host,
|
||||
port: daemon.remote_port
|
||||
}).then(data => {
|
||||
if (data.error) return { error: data.error }
|
||||
return {
|
||||
net_type: data.result.nettype
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
start (options) {
|
||||
const { net_type } = options.app
|
||||
const daemon = options.daemons[net_type]
|
||||
|
@ -81,17 +98,17 @@ export class Daemon {
|
|||
]
|
||||
|
||||
const dirs = {
|
||||
"main": options.app.data_dir,
|
||||
"staging": path.join(options.app.data_dir, "staging"),
|
||||
"test": path.join(options.app.data_dir, "testnet")
|
||||
"mainnet": options.app.data_dir,
|
||||
"stagenet": path.join(options.app.data_dir, "stagenet"),
|
||||
"testnet": path.join(options.app.data_dir, "testnet")
|
||||
}
|
||||
|
||||
const { net_type } = options.app
|
||||
this.net_type = net_type
|
||||
|
||||
if (net_type === "test") {
|
||||
if (net_type === "testnet") {
|
||||
args.push("--testnet")
|
||||
} else if (net_type === "staging") {
|
||||
} else if (net_type === "stagenet") {
|
||||
args.push("--stagenet")
|
||||
}
|
||||
|
||||
|
@ -100,7 +117,7 @@ export class Daemon {
|
|||
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") {
|
||||
if (daemon.type === "local_remote" && net_type === "mainnet") {
|
||||
args.push(
|
||||
"--bootstrap-daemon-address",
|
||||
`${daemon.remote_host}:${daemon.remote_port}`
|
||||
|
@ -328,10 +345,15 @@ export class Daemon {
|
|||
this.backend.send(method, data)
|
||||
}
|
||||
|
||||
sendRPC (method, params = {}) {
|
||||
sendRPC (method, params = {}, options = {}) {
|
||||
let id = this.id++
|
||||
let options = {
|
||||
uri: `${this.protocol}${this.hostname}:${this.port}/json_rpc`,
|
||||
|
||||
const protocol = options.protocol || this.protocol
|
||||
const hostname = options.hostname || this.hostname
|
||||
const port = options.port || this.port
|
||||
|
||||
let requestOptions = {
|
||||
uri: `${protocol}${hostname}:${port}/json_rpc`,
|
||||
method: "POST",
|
||||
json: {
|
||||
jsonrpc: "2.0",
|
||||
|
@ -341,11 +363,11 @@ export class Daemon {
|
|||
agent: this.agent
|
||||
}
|
||||
if (Object.keys(params).length !== 0) {
|
||||
options.json.params = params
|
||||
requestOptions.json.params = params
|
||||
}
|
||||
|
||||
return this.queue.add(() => {
|
||||
return request(options)
|
||||
return request(requestOptions)
|
||||
.then((response) => {
|
||||
if (response.hasOwnProperty("error")) {
|
||||
return {
|
||||
|
|
|
@ -3,7 +3,7 @@ const request = require("request-promise")
|
|||
const queue = require("promise-queue")
|
||||
const http = require("http")
|
||||
const os = require("os")
|
||||
const fs = require("fs")
|
||||
const fs = require("fs-extra")
|
||||
const path = require("path")
|
||||
const crypto = require("crypto")
|
||||
|
||||
|
@ -14,7 +14,7 @@ export class WalletRPC {
|
|||
this.wallet_dir = null
|
||||
this.auth = []
|
||||
this.id = 0
|
||||
this.net_type = "main"
|
||||
this.net_type = "mainnet"
|
||||
this.heartbeat = null
|
||||
this.wallet_state = {
|
||||
open: false,
|
||||
|
@ -23,7 +23,7 @@ export class WalletRPC {
|
|||
balance: null,
|
||||
unlocked_balance: null
|
||||
}
|
||||
|
||||
this.dirs = null
|
||||
this.last_height_send_time = Date.now()
|
||||
|
||||
this.height_regex1 = /Processed block: <([a-f0-9]+)>, height (\d+)/
|
||||
|
@ -63,31 +63,32 @@ export class WalletRPC {
|
|||
"--log-level", "*:WARNING,net*:FATAL,net.http:DEBUG,global:INFO,verify:FATAL,stacktrace:INFO"
|
||||
]
|
||||
|
||||
const { net_type } = options.app
|
||||
const { net_type, wallet_data_dir, data_dir } = options.app
|
||||
this.net_type = net_type
|
||||
this.data_dir = options.app.data_dir
|
||||
this.data_dir = data_dir
|
||||
this.wallet_data_dir = wallet_data_dir
|
||||
|
||||
const dirs = {
|
||||
"main": this.data_dir,
|
||||
"staging": path.join(this.data_dir, "staging"),
|
||||
"test": path.join(this.data_dir, "testnet")
|
||||
this.dirs = {
|
||||
"mainnet": this.wallet_data_dir,
|
||||
"stagenet": path.join(this.wallet_data_dir, "stagenet"),
|
||||
"testnet": path.join(this.wallet_data_dir, "testnet")
|
||||
}
|
||||
|
||||
this.wallet_dir = path.join(dirs[net_type], "wallets")
|
||||
this.wallet_dir = path.join(this.dirs[net_type], "wallets")
|
||||
args.push("--wallet-dir", this.wallet_dir)
|
||||
|
||||
const log_file = path.join(dirs[net_type], "logs", "wallet-rpc.log")
|
||||
const log_file = path.join(this.dirs[net_type], "logs", "wallet-rpc.log")
|
||||
args.push("--log-file", log_file)
|
||||
|
||||
if (net_type === "test") {
|
||||
if (net_type === "testnet") {
|
||||
args.push("--testnet")
|
||||
} else if (net_type === "staging") {
|
||||
} else if (net_type === "stagenet") {
|
||||
args.push("--stagenet")
|
||||
}
|
||||
|
||||
if (fs.existsSync(log_file)) { fs.truncateSync(log_file, 0) }
|
||||
|
||||
if (!fs.existsSync(this.wallet_dir)) { fs.mkdirSync(this.wallet_dir) }
|
||||
if (!fs.existsSync(this.wallet_dir)) { fs.mkdirpSync(this.wallet_dir) }
|
||||
|
||||
if (process.platform === "win32") {
|
||||
this.walletRPCProcess = child_process.spawn(path.join(__ryo_bin, "loki-wallet-rpc.exe"), args)
|
||||
|
@ -160,10 +161,18 @@ export class WalletRPC {
|
|||
let params = data.data
|
||||
|
||||
switch (data.method) {
|
||||
case "has_password":
|
||||
this.hasPassword()
|
||||
break
|
||||
|
||||
case "validate_address":
|
||||
this.validateAddress(params.address)
|
||||
break
|
||||
|
||||
case "copy_old_gui_wallets":
|
||||
this.copyOldGuiWallets(params.wallets || [])
|
||||
break
|
||||
|
||||
case "list_wallets":
|
||||
this.listWallets()
|
||||
break
|
||||
|
@ -199,8 +208,16 @@ export class WalletRPC {
|
|||
this.stake(params.password, params.amount, params.key, params.destination)
|
||||
break
|
||||
|
||||
case "register_service_node":
|
||||
this.registerSnode(params.password, params.string)
|
||||
break
|
||||
|
||||
case "unlock_stake":
|
||||
this.unlockStake(params.password, params.service_node_key, params.confirmed || false)
|
||||
break
|
||||
|
||||
case "transfer":
|
||||
this.transfer(params.password, params.amount, params.address, params.payment_id, params.priority, params.address_book)
|
||||
this.transfer(params.password, params.amount, params.address, params.payment_id, params.priority, params.note || "", params.address_book)
|
||||
break
|
||||
|
||||
case "add_address_book":
|
||||
|
@ -246,6 +263,18 @@ export class WalletRPC {
|
|||
}
|
||||
}
|
||||
|
||||
hasPassword () {
|
||||
crypto.pbkdf2("", this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
|
||||
if (err) {
|
||||
this.sendGateway("set_has_password", false)
|
||||
return
|
||||
}
|
||||
|
||||
// If the pass hash doesn't match empty string then we don't have a password
|
||||
this.sendGateway("set_has_password", this.wallet_state.password_hash !== password_hash.toString("hex"))
|
||||
})
|
||||
}
|
||||
|
||||
validateAddress (address) {
|
||||
this.sendRPC("validate_address", {
|
||||
address
|
||||
|
@ -260,12 +289,7 @@ export class WalletRPC {
|
|||
|
||||
const { valid, nettype } = data.result
|
||||
|
||||
// Check if the net types match
|
||||
let ourNetType = "mainnet"
|
||||
if (this.net_type === "test") ourNetType = "testnet"
|
||||
if (this.net_type === "staging") ourNetType = "stagenet"
|
||||
|
||||
const netMatches = ourNetType === nettype
|
||||
const netMatches = this.net_type === nettype
|
||||
const isValid = valid && netMatches
|
||||
|
||||
this.sendGateway("set_valid_address", {
|
||||
|
@ -620,18 +644,22 @@ export class WalletRPC {
|
|||
stake (password, amount, service_node_key, destination) {
|
||||
crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
|
||||
if (err) {
|
||||
this.sendGateway("set_stake_status", {
|
||||
code: -1,
|
||||
message: "Internal error",
|
||||
sending: false
|
||||
this.sendGateway("set_snode_status", {
|
||||
stake: {
|
||||
code: -1,
|
||||
message: "Internal error",
|
||||
sending: false
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
if (this.wallet_state.password_hash !== password_hash.toString("hex")) {
|
||||
this.sendGateway("set_stake_status", {
|
||||
code: -1,
|
||||
message: "Invalid password",
|
||||
sending: false
|
||||
this.sendGateway("set_snode_status", {
|
||||
stake: {
|
||||
code: -1,
|
||||
message: "Invalid password",
|
||||
sending: false
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -645,24 +673,150 @@ export class WalletRPC {
|
|||
}).then((data) => {
|
||||
if (data.hasOwnProperty("error")) {
|
||||
let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1)
|
||||
this.sendGateway("set_stake_status", {
|
||||
code: -1,
|
||||
message: error,
|
||||
sending: false
|
||||
this.sendGateway("set_snode_status", {
|
||||
stake: {
|
||||
code: -1,
|
||||
message: error,
|
||||
sending: false
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.sendGateway("set_stake_status", {
|
||||
code: 0,
|
||||
message: "Successfully staked",
|
||||
sending: false
|
||||
this.sendGateway("set_snode_status", {
|
||||
stake: {
|
||||
code: 0,
|
||||
message: "Successfully staked",
|
||||
sending: false
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
transfer (password, amount, address, payment_id, priority, address_book = {}) {
|
||||
registerSnode (password, register_service_node_str) {
|
||||
crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
|
||||
if (err) {
|
||||
this.sendGateway("set_snode_status", {
|
||||
registration: {
|
||||
code: -1,
|
||||
message: "Internal error",
|
||||
sending: false
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.wallet_state.password_hash !== password_hash.toString("hex")) {
|
||||
this.sendGateway("set_snode_status", {
|
||||
registration: {
|
||||
code: -1,
|
||||
message: "Invalid password",
|
||||
sending: false
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.sendRPC("register_service_node", {
|
||||
register_service_node_str
|
||||
}).then((data) => {
|
||||
if (data.hasOwnProperty("error")) {
|
||||
const error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1)
|
||||
this.sendGateway("set_snode_status", {
|
||||
registration: {
|
||||
code: -1,
|
||||
message: error,
|
||||
sending: false
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.sendGateway("set_snode_status", {
|
||||
registration: {
|
||||
code: 0,
|
||||
message: "Successfully registered service node",
|
||||
sending: false
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
unlockStake (password, service_node_key, confirmed = false) {
|
||||
// Unlock code 0 means success, 1 means can unlock, -1 means error
|
||||
crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
|
||||
if (err) {
|
||||
this.sendGateway("set_snode_status", {
|
||||
unlock: {
|
||||
code: -1,
|
||||
message: "Internal error",
|
||||
sending: false
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.wallet_state.password_hash !== password_hash.toString("hex")) {
|
||||
this.sendGateway("set_snode_status", {
|
||||
unlock: {
|
||||
code: -1,
|
||||
message: "Invalid password",
|
||||
sending: false
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const sendRPC = (path) => {
|
||||
return this.sendRPC(path, {
|
||||
service_node_key
|
||||
}).then(data => {
|
||||
if (data.hasOwnProperty("error")) {
|
||||
const error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1)
|
||||
this.sendGateway("set_snode_status", {
|
||||
unlock: {
|
||||
code: -1,
|
||||
message: error,
|
||||
sending: false
|
||||
}
|
||||
})
|
||||
return null
|
||||
}
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
if (confirmed) {
|
||||
sendRPC("request_stake_unlock").then((data) => {
|
||||
if (!data) return
|
||||
|
||||
const unlock = {
|
||||
code: data.unlocked ? 0 : -1,
|
||||
message: data.msg,
|
||||
sending: false
|
||||
}
|
||||
|
||||
this.sendGateway("set_snode_status", { unlock })
|
||||
})
|
||||
} else {
|
||||
sendRPC("can_request_stake_unlock").then((data) => {
|
||||
if (!data) return
|
||||
|
||||
const unlock = {
|
||||
code: data.can_unlock ? 1 : -1,
|
||||
message: data.msg,
|
||||
sending: false
|
||||
}
|
||||
|
||||
this.sendGateway("set_snode_status", { unlock })
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
transfer (password, amount, address, payment_id, priority, note, address_book = {}) {
|
||||
crypto.pbkdf2(password, this.auth[2], 1000, 64, "sha512", (err, password_hash) => {
|
||||
if (err) {
|
||||
this.sendGateway("set_tx_status", {
|
||||
|
@ -685,65 +839,48 @@ export class WalletRPC {
|
|||
|
||||
let sweep_all = amount == this.wallet_state.unlocked_balance
|
||||
|
||||
if (sweep_all) {
|
||||
let params = {
|
||||
"address": address,
|
||||
"account_index": 0,
|
||||
"priority": priority,
|
||||
"mixin": 9 // Always force a ring size of 10 (ringsize = mixin + 1)
|
||||
}
|
||||
|
||||
if (payment_id) {
|
||||
params.payment_id = payment_id
|
||||
}
|
||||
|
||||
this.sendRPC("sweep_all", params).then((data) => {
|
||||
if (data.hasOwnProperty("error")) {
|
||||
let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1)
|
||||
this.sendGateway("set_tx_status", {
|
||||
code: -1,
|
||||
message: error,
|
||||
sending: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.sendGateway("set_tx_status", {
|
||||
code: 0,
|
||||
message: "Transaction successfully sent",
|
||||
sending: false
|
||||
})
|
||||
})
|
||||
} else {
|
||||
let params = {
|
||||
"destinations": [{"amount": amount, "address": address}],
|
||||
"priority": priority,
|
||||
"mixin": 9
|
||||
}
|
||||
|
||||
if (payment_id) {
|
||||
params.payment_id = payment_id
|
||||
}
|
||||
|
||||
this.sendRPC("transfer_split", params).then((data) => {
|
||||
if (data.hasOwnProperty("error")) {
|
||||
let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1)
|
||||
this.sendGateway("set_tx_status", {
|
||||
code: -1,
|
||||
message: error,
|
||||
sending: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.sendGateway("set_tx_status", {
|
||||
code: 0,
|
||||
message: "Transaction successfully sent",
|
||||
sending: false
|
||||
})
|
||||
})
|
||||
const rpc_endpoint = sweep_all ? "sweep_all" : "transfer_split"
|
||||
const params = sweep_all ? {
|
||||
"address": address,
|
||||
"account_index": 0,
|
||||
"priority": priority,
|
||||
"mixin": 9 // Always force a ring size of 10 (ringsize = mixin + 1)
|
||||
} : {
|
||||
"destinations": [{"amount": amount, "address": address}],
|
||||
"priority": priority,
|
||||
"mixin": 9
|
||||
}
|
||||
|
||||
if (payment_id) {
|
||||
params.payment_id = payment_id
|
||||
}
|
||||
|
||||
this.sendRPC(rpc_endpoint, params).then((data) => {
|
||||
if (data.hasOwnProperty("error")) {
|
||||
let error = data.error.message.charAt(0).toUpperCase() + data.error.message.slice(1)
|
||||
this.sendGateway("set_tx_status", {
|
||||
code: -1,
|
||||
message: error,
|
||||
sending: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.sendGateway("set_tx_status", {
|
||||
code: 0,
|
||||
message: "Transaction successfully sent",
|
||||
sending: false
|
||||
})
|
||||
|
||||
if (data.result) {
|
||||
const hash_list = data.result.tx_hash_list || []
|
||||
// Save notes
|
||||
if (note && note !== "") {
|
||||
hash_list.forEach(txid => this.saveTxNotes(txid, note))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (address_book.hasOwnProperty("save") && address_book.save) { this.addAddressBook(address, payment_id, address_book.description, address_book.name) }
|
||||
})
|
||||
}
|
||||
|
@ -891,10 +1028,10 @@ export class WalletRPC {
|
|||
}
|
||||
}
|
||||
|
||||
const types = ["in", "out", "pending", "failed", "pool", "miner", "snode", "gov"]
|
||||
const types = ["in", "out", "pending", "failed", "pool", "miner", "snode", "gov", "stake"]
|
||||
types.forEach(type => {
|
||||
if (data.result.hasOwnProperty(type)) {
|
||||
wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result[type]);
|
||||
wallet.transactions.tx_list = wallet.transactions.tx_list.concat(data.result[type])
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1063,9 +1200,71 @@ export class WalletRPC {
|
|||
})
|
||||
}
|
||||
|
||||
copyOldGuiWallets (wallets) {
|
||||
this.sendGateway("set_old_gui_import_status", { code: 1, failed_wallets: [] })
|
||||
|
||||
const failed_wallets = []
|
||||
|
||||
for (const wallet of wallets) {
|
||||
const { type, directory } = wallet
|
||||
|
||||
const old_gui_path = path.join(this.wallet_dir, "old-gui")
|
||||
const dir_path = path.join(this.wallet_dir, directory)
|
||||
const stat = fs.statSync(dir_path)
|
||||
if (!stat.isDirectory()) continue
|
||||
|
||||
// Make sure the directory has the regular and keys file
|
||||
const wallet_file = path.join(dir_path, directory)
|
||||
const key_file = wallet_file + ".keys"
|
||||
|
||||
// If we don't have them then don't bother copying
|
||||
if (!(fs.existsSync(wallet_file) && fs.existsSync(key_file))) {
|
||||
failed_wallets.push(directory)
|
||||
continue
|
||||
}
|
||||
|
||||
// Copy out the file into the relevant directory
|
||||
const destination = path.join(this.dirs[type], "wallets")
|
||||
if (!fs.existsSync(destination)) fs.mkdirpSync(destination)
|
||||
|
||||
const new_path = path.join(destination, directory)
|
||||
|
||||
try {
|
||||
// Copy into temp file
|
||||
if (fs.existsSync(new_path + ".atom") || fs.existsSync(new_path + ".atom.keys")) {
|
||||
failed_wallets.push(directory)
|
||||
continue
|
||||
}
|
||||
|
||||
fs.copyFileSync(wallet_file, new_path + ".atom", fs.constants.COPYFILE_EXCL)
|
||||
fs.copyFileSync(key_file, new_path + ".atom.keys", fs.constants.COPYFILE_EXCL)
|
||||
|
||||
// Move the folder into a subfolder
|
||||
if (!fs.existsSync(old_gui_path)) fs.mkdirpSync(old_gui_path)
|
||||
fs.moveSync(dir_path, path.join(old_gui_path, directory), { overwrite: true })
|
||||
} catch (e) {
|
||||
// Cleanup the copied files if an error
|
||||
if (fs.existsSync(new_path + ".atom")) fs.unlinkSync(new_path + ".atom")
|
||||
if (fs.existsSync(new_path + ".atom.keys")) fs.unlinkSync(new_path + ".atom.keys")
|
||||
failed_wallets.push(directory)
|
||||
continue
|
||||
}
|
||||
|
||||
// Rename the imported wallets if we can
|
||||
if (!fs.existsSync(new_path) && !fs.existsSync(new_path + ".keys")) {
|
||||
fs.renameSync(new_path + ".atom", new_path)
|
||||
fs.renameSync(new_path + ".atom.keys", new_path + ".keys")
|
||||
}
|
||||
}
|
||||
|
||||
this.sendGateway("set_old_gui_import_status", { code: 0, failed_wallets })
|
||||
this.listWallets()
|
||||
}
|
||||
|
||||
listWallets (legacy = false) {
|
||||
let wallets = {
|
||||
list: []
|
||||
list: [],
|
||||
directories: []
|
||||
}
|
||||
|
||||
fs.readdirSync(this.wallet_dir).forEach(filename => {
|
||||
|
@ -1083,6 +1282,22 @@ export class WalletRPC {
|
|||
case ".Trashes":
|
||||
case "ehthumbs.db":
|
||||
case "Thumbs.db":
|
||||
case "old-gui":
|
||||
return
|
||||
}
|
||||
|
||||
// If it's a directory then check if it's an old gui wallet
|
||||
const name = path.join(this.wallet_dir, filename)
|
||||
const stat = fs.statSync(name)
|
||||
if (stat.isDirectory()) {
|
||||
// Make sure the directory has the regular and keys file
|
||||
const wallet_file = path.join(name, filename)
|
||||
const key_file = wallet_file + ".keys"
|
||||
|
||||
// If we have them then it is an old gui wallet
|
||||
if (fs.existsSync(wallet_file) && fs.existsSync(key_file)) {
|
||||
wallets.directories.push(filename)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -107,6 +107,9 @@
|
|||
</qrcode-vue>
|
||||
<q-context-menu>
|
||||
<q-list link separator style="min-width: 150px; max-height: 300px;">
|
||||
<q-item v-close-overlay @click.native="copyQR()">
|
||||
<q-item-main label="Copy QR code" />
|
||||
</q-item>
|
||||
<q-item v-close-overlay @click.native="saveQR()">
|
||||
<q-item-main label="Save QR code to file" />
|
||||
</q-item>
|
||||
|
@ -127,7 +130,7 @@
|
|||
|
||||
<script>
|
||||
import { mapState } from "vuex"
|
||||
const {clipboard} = require("electron")
|
||||
const { clipboard, nativeImage } = require("electron")
|
||||
import AddressHeader from "components/address_header"
|
||||
import FormatLoki from "components/format_loki"
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
|
@ -144,6 +147,16 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
copyQR () {
|
||||
const data = this.$refs.qr.$el.childNodes[0].toDataURL()
|
||||
const img = nativeImage.createFromDataURL(data)
|
||||
clipboard.writeImage(img)
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: "Copied QR code to clipboard"
|
||||
})
|
||||
},
|
||||
saveQR() {
|
||||
let img = this.$refs.qr.$el.childNodes[0].toDataURL()
|
||||
this.$gateway.send("core", "save_png", {img, type: "QR Code"})
|
||||
|
|
|
@ -77,7 +77,7 @@ export default {
|
|||
} else {
|
||||
if(this.wallet.info.height < this.target_height - 1 && this.wallet.info.height != 0) {
|
||||
return "scanning"
|
||||
} else if(this.daemon.info.height_without_bootstrap < this.target_height) {
|
||||
} else if(this.config_daemon.type === "local_remote" && this.daemon.info.height_without_bootstrap < this.target_height) {
|
||||
return "syncing"
|
||||
} else {
|
||||
return "ready"
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
<img class="q-mb-md" src="statics/loki.svg" height="42" />
|
||||
|
||||
<p class="q-my-sm">Version: ATOM v{{version}}-v{{daemonVersion}}</p>
|
||||
<p class="q-my-sm">Copyright (c) 2018-2019, Loki Project</p>
|
||||
<p class="q-my-sm">Copyright (c) 2018, Ryo Currency Project</p>
|
||||
<p class="q-my-sm">All rights reserved.</p>
|
||||
|
||||
|
@ -45,7 +46,7 @@
|
|||
<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> -
|
||||
<a @click="openExternal('https://github.com')" href="#">Github</a>
|
||||
<a @click="openExternal('https://github.com/loki-project/loki-electron-wallet')" href="#">Github</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -103,7 +104,7 @@ export default {
|
|||
title: "Switch wallet",
|
||||
message: "Are you sure you want to close the current wallet?",
|
||||
ok: {
|
||||
label: "CLOSE"
|
||||
label: "OK"
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
|
|
120
src/components/service_node_registration.vue
Normal file
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<div class="service-node-registration">
|
||||
<div class="q-pa-md">
|
||||
<div class="description q-mb-lg">
|
||||
Enter the <b>register_service_node</b> command produced by the daemon that is registering to become a Service Node using the "<b>prepare_registration</b>" command
|
||||
</div>
|
||||
<LokiField label="Service Node Command" :error="$v.registration_string.$error" :disabled="registration_status.sending">
|
||||
<q-input
|
||||
v-model="registration_string"
|
||||
type="textarea"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.registration_string.$touch"
|
||||
placeholder="register_service_node ..."
|
||||
:disabled="registration_status.sending"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
<q-field class="q-pt-sm">
|
||||
<q-btn color="primary" @click="register()" label="Register service node" :disabled="registration_status.sending"/>
|
||||
</q-field>
|
||||
</div>
|
||||
|
||||
<q-inner-loading :visible="registration_status.sending" :dark="theme=='dark'">
|
||||
<q-spinner color="primary" :size="30" />
|
||||
</q-inner-loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const objectAssignDeep = require("object-assign-deep");
|
||||
import { mapState } from "vuex"
|
||||
import { required } from "vuelidate/lib/validators"
|
||||
import LokiField from "components/loki_field"
|
||||
import WalletPassword from "src/mixins/wallet_password"
|
||||
|
||||
export default {
|
||||
name: "ServiceNodeRegistration",
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
registration_status: state => state.gateway.service_node_status.registration,
|
||||
}),
|
||||
data () {
|
||||
return {
|
||||
registration_string: "",
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
registration_string: { required }
|
||||
},
|
||||
watch: {
|
||||
registration_status: {
|
||||
handler(val, old){
|
||||
if(val.code == old.code) return
|
||||
switch(this.registration_status.code) {
|
||||
case 0:
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: this.registration_status.message
|
||||
})
|
||||
this.$v.$reset();
|
||||
this.registration_string = ""
|
||||
break;
|
||||
case -1:
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 3000,
|
||||
message: this.registration_status.message
|
||||
})
|
||||
break;
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
register: function() {
|
||||
this.$v.registration_string.$touch()
|
||||
|
||||
if (this.$v.registration_string.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Please enter the service node registration command"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.showPasswordConfirmation({
|
||||
title: "Register service node",
|
||||
noPasswordMessage: "Do you want to register the service node?",
|
||||
ok: {
|
||||
label: "REGISTER"
|
||||
},
|
||||
}).then(password => {
|
||||
this.$store.commit("gateway/set_snode_status", {
|
||||
registration: {
|
||||
code: 1,
|
||||
message: "Registering...",
|
||||
sending: true
|
||||
}
|
||||
})
|
||||
this.$gateway.send("wallet", "register_service_node", {
|
||||
password,
|
||||
string: this.registration_string
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
mixins: [WalletPassword],
|
||||
components: {
|
||||
LokiField
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
276
src/components/service_node_staking.vue
Normal file
|
@ -0,0 +1,276 @@
|
|||
<template>
|
||||
<div class="service-node-staking">
|
||||
<div class="q-pa-md">
|
||||
<LokiField label="Service Node Key" :error="$v.service_node.key.$error">
|
||||
<q-input v-model="service_node.key"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.service_node.key.$touch"
|
||||
placeholder="64 hexadecimal characters"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
|
||||
<div class="q-mt-md col">
|
||||
<LokiField label="Award Recepient's Address" :error="$v.service_node.award_address.$error">
|
||||
<q-input v-model="service_node.award_address"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.service_node.award_address.$touch"
|
||||
placeholder="64 hexadecimal characters"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
<div class="address-type" :class="[addressType]">( {{ addressType | addressTypeString }} )</div>
|
||||
</div>
|
||||
|
||||
<LokiField label="Amount" class="q-mt-md" :error="$v.service_node.amount.$error">
|
||||
<q-input v-model="service_node.amount"
|
||||
:dark="theme=='dark'"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="unlocked_balance / 1e9"
|
||||
placeholder="0"
|
||||
@blur="$v.service_node.amount.$touch"
|
||||
hide-underline
|
||||
/>
|
||||
<q-btn color="secondary" @click="service_node.amount = unlocked_balance / 1e9" :text-color="theme=='dark'?'white':'dark'">All</q-btn>
|
||||
</LokiField>
|
||||
|
||||
|
||||
|
||||
<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="stake_status.sending" :dark="theme=='dark'">
|
||||
<q-spinner color="primary" :size="30" />
|
||||
</q-inner-loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const { clipboard } = require("electron")
|
||||
const objectAssignDeep = require("object-assign-deep");
|
||||
import { mapState } from "vuex"
|
||||
import { required, decimal } from "vuelidate/lib/validators"
|
||||
import { payment_id, service_node_key, greater_than_zero, address } from "src/validators/common"
|
||||
import LokiField from "components/loki_field"
|
||||
import WalletPassword from "src/mixins/wallet_password"
|
||||
|
||||
export default {
|
||||
name: "ServiceNodeStaking",
|
||||
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,
|
||||
address_list: state => state.gateway.wallet.address_list,
|
||||
stake_status: state => state.gateway.service_node_status.stake,
|
||||
is_ready (state) {
|
||||
return this.$store.getters["gateway/isReady"]
|
||||
},
|
||||
is_able_to_send (state) {
|
||||
return this.$store.getters["gateway/isAbleToSend"]
|
||||
},
|
||||
|
||||
addressType (state) {
|
||||
const address = this.service_node.award_address;
|
||||
const inArray = (array) => array.map(o => o.address).includes(address);
|
||||
|
||||
const { primary, used, unused } = this.address_list
|
||||
if (inArray(primary)) {
|
||||
return "primary"
|
||||
} else if (inArray(used)) {
|
||||
return "used"
|
||||
} else if (inArray(unused)) {
|
||||
return "unsued"
|
||||
} else {
|
||||
return "not-ours"
|
||||
}
|
||||
}
|
||||
}),
|
||||
data () {
|
||||
return {
|
||||
service_node: {
|
||||
key: "",
|
||||
amount: 0,
|
||||
award_address: "",
|
||||
},
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
addressTypeString: function (value) {
|
||||
switch (value) {
|
||||
case "primary":
|
||||
return "Your primary address"
|
||||
case "used":
|
||||
return "Your used address"
|
||||
case "ununsed":
|
||||
return "Your unused address"
|
||||
default:
|
||||
return "Not your address!"
|
||||
}
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
service_node: {
|
||||
key: { required, service_node_key },
|
||||
amount: {
|
||||
required,
|
||||
decimal,
|
||||
greater_than_zero,
|
||||
},
|
||||
award_address: {
|
||||
required,
|
||||
isAddress(value) {
|
||||
if (value === '') return true
|
||||
|
||||
return new Promise(resolve => {
|
||||
address(value, this.$gateway)
|
||||
.then(() => resolve(true))
|
||||
.catch(e => resolve(false))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
stake_status: {
|
||||
handler(val, old){
|
||||
if(val.code == old.code) return
|
||||
switch(this.stake_status.code) {
|
||||
case 0:
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: this.stake_status.message
|
||||
})
|
||||
this.$v.$reset();
|
||||
this.service_node = {
|
||||
key: "",
|
||||
amount: 0,
|
||||
award_address: "",
|
||||
}
|
||||
break;
|
||||
case -1:
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: this.stake_status.message
|
||||
})
|
||||
break;
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
created () {
|
||||
const { address } = this.info;
|
||||
if (!this.service_node.award_address || this.service_node.award_address === "") {
|
||||
this.service_node.award_address = address || ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isOurAddress (address) {
|
||||
const { primary, used, unused } = this.address_list
|
||||
const addresses = [...primary, ...used, ...unused].map(o => o.address);
|
||||
console.log(addresses);
|
||||
return addresses.includes(address);
|
||||
},
|
||||
stake: function () {
|
||||
|
||||
this.$v.service_node.$touch()
|
||||
|
||||
if (this.$v.service_node.key.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Service node key not valid"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.$v.service_node.award_address.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Address not valid"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if(this.service_node.amount < 0) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Amount cannot be negative"
|
||||
})
|
||||
return
|
||||
} else if(this.service_node.amount == 0) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Amount must be greater than zero"
|
||||
})
|
||||
return
|
||||
} else if(this.service_node.amount > this.unlocked_balance / 1e9) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Not enough unlocked balance"
|
||||
})
|
||||
return
|
||||
} else if (this.$v.service_node.amount.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Amount not valid"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.showPasswordConfirmation({
|
||||
title: "Stake",
|
||||
noPasswordMessage: "Do you want to stake?",
|
||||
ok: {
|
||||
label: "STAKE"
|
||||
},
|
||||
}).then(password => {
|
||||
this.$store.commit("gateway/set_snode_status", {
|
||||
stake: {
|
||||
code: 1,
|
||||
message: "Staking...",
|
||||
sending: true
|
||||
}
|
||||
})
|
||||
const service_node = objectAssignDeep.noMutate(this.service_node, {password})
|
||||
this.$gateway.send("wallet", "stake", {
|
||||
...service_node,
|
||||
destination: service_node.award_address,
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
},
|
||||
mixins: [WalletPassword],
|
||||
components: {
|
||||
LokiField
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.service-node-staking {
|
||||
.address-type {
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
text-align: right;
|
||||
&.not-ours {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
147
src/components/service_node_unlock.vue
Normal file
|
@ -0,0 +1,147 @@
|
|||
<template>
|
||||
<div class="service-node-unlock">
|
||||
<div class="q-pa-md">
|
||||
<LokiField label="Service Node Key" :error="$v.node_key.$error" :disabled="unlock_status.sending">
|
||||
<q-input
|
||||
v-model="node_key"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.node_key.$touch"
|
||||
placeholder="64 hexadecimal characters"
|
||||
:disabled="unlock_status.sending"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
<q-field class="q-pt-sm">
|
||||
<q-btn color="primary" @click="unlock()" label="Unlock service node" :disabled="unlock_status.sending"/>
|
||||
</q-field>
|
||||
</div>
|
||||
|
||||
<q-inner-loading :visible="unlock_status.sending" :dark="theme=='dark'">
|
||||
<q-spinner color="primary" :size="30" />
|
||||
</q-inner-loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const objectAssignDeep = require("object-assign-deep");
|
||||
import { mapState } from "vuex"
|
||||
import { required } from "vuelidate/lib/validators"
|
||||
import { service_node_key } from "src/validators/common"
|
||||
import LokiField from "components/loki_field"
|
||||
import WalletPassword from "src/mixins/wallet_password"
|
||||
|
||||
export default {
|
||||
name: "ServiceNodeUnlock",
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
unlock_status: state => state.gateway.service_node_status.unlock,
|
||||
}),
|
||||
data () {
|
||||
return {
|
||||
node_key: "",
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
node_key: { required, service_node_key }
|
||||
},
|
||||
watch: {
|
||||
unlock_status: {
|
||||
handler(val, old){
|
||||
if(val.code == old.code) return
|
||||
switch(this.unlock_status.code) {
|
||||
case 0:
|
||||
this.key = null
|
||||
this.password = null
|
||||
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: this.unlock_status.message
|
||||
})
|
||||
this.$v.$reset();
|
||||
this.node_key = ""
|
||||
break;
|
||||
case 1:
|
||||
// Tell the user to confirm
|
||||
this.$q.dialog({
|
||||
title: "Confirm",
|
||||
message: this.unlock_status.message,
|
||||
ok: {
|
||||
label: "UNLOCK"
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme=="dark"?"white":"dark"
|
||||
}
|
||||
}).then(() => {
|
||||
this.gatewayUnlock(this.password, this.key, true);
|
||||
}).catch(() => {})
|
||||
break;
|
||||
case -1:
|
||||
this.key = null
|
||||
this.password = null
|
||||
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: this.unlock_status.message
|
||||
})
|
||||
break;
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
unlock: function () {
|
||||
this.$v.node_key.$touch()
|
||||
if (this.$v.node_key.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Service node key not valid"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// We store this as it could change between the 2 step process
|
||||
this.key = this.node_key
|
||||
|
||||
this.showPasswordConfirmation({
|
||||
title: "Unlock service node",
|
||||
noPasswordMessage: "Do you want to unlock the service node?",
|
||||
ok: {
|
||||
label: "UNLOCK"
|
||||
},
|
||||
}).then(password => {
|
||||
this.password = password
|
||||
this.gatewayUnlock(this.password, this.key, false);
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
gatewayUnlock: function (password, key, confirmed = false) {
|
||||
this.$store.commit("gateway/set_snode_status", {
|
||||
unlock: {
|
||||
code: 2, // Code 1 is reserved for can_unlock
|
||||
message: "Unlocking...",
|
||||
sending: true
|
||||
}
|
||||
})
|
||||
this.$gateway.send("wallet", "unlock_stake", {
|
||||
password,
|
||||
service_node_key: key,
|
||||
confirmed
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mixins: [WalletPassword],
|
||||
components: {
|
||||
LokiField
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
|
@ -53,7 +53,7 @@
|
|||
hide-underline
|
||||
/>
|
||||
<!-- Remote node presets -->
|
||||
<q-btn-dropdown class="remote-dropdown" v-if="config.app.net_type === 'main'" flat>
|
||||
<q-btn-dropdown class="remote-dropdown" v-if="config.app.net_type === 'mainnet'" flat>
|
||||
<q-list link dark no-border>
|
||||
<q-item v-for="option in remotes" :key="option.host" @click.native="setPreset(option)" v-close-overlay>
|
||||
<q-item-main>
|
||||
|
@ -80,12 +80,17 @@
|
|||
|
||||
</template>
|
||||
|
||||
<div class="col q-mt-md">
|
||||
<div class="col q-mt-md pt-sm">
|
||||
<LokiField label="Data Storage Path" disable-hover>
|
||||
<q-input v-model="config.app.data_dir" disable :dark="theme=='dark'" hide-underline/>
|
||||
<input type="file" webkitdirectory directory id="dataPath" v-on:change="setDataPath" ref="fileInput" hidden />
|
||||
<q-btn color="secondary" v-on:click="selectPath" :text-color="theme=='dark'?'white':'dark'">Select Location</q-btn>
|
||||
</LokiField>
|
||||
<LokiField label="Wallet Storage Path" disable-hover>
|
||||
<q-input v-model="config.app.wallet_data_dir" disable :dark="theme=='dark'" hide-underline/>
|
||||
<input type="file" webkitdirectory directory id="dataPath" v-on:change="setWalletDataPath" ref="fileInput" hidden />
|
||||
<q-btn color="secondary" v-on:click="selectPath" :text-color="theme=='dark'?'white':'dark'">Select Location</q-btn>
|
||||
</LokiField>
|
||||
</div>
|
||||
|
||||
<q-collapsible label="Advanced Options" header-class="q-mt-sm non-selectable row reverse advanced-options-label">
|
||||
|
@ -142,9 +147,9 @@
|
|||
type="radio"
|
||||
v-model="config.app.net_type"
|
||||
:options="[
|
||||
{ label: 'Main Net', value: 'main' },
|
||||
{ label: 'Stage Net', value: 'staging' },
|
||||
{ label: 'Test Net', value: 'test' }
|
||||
{ label: 'Main Net', value: 'mainnet' },
|
||||
{ label: 'Stage Net', value: 'stagenet' },
|
||||
{ label: 'Test Net', value: 'testnet' }
|
||||
]"
|
||||
/>
|
||||
</q-field>
|
||||
|
@ -158,6 +163,13 @@ import { mapState } from "vuex"
|
|||
import LokiField from "components/loki_field"
|
||||
export default {
|
||||
name: "SettingsGeneral",
|
||||
props: {
|
||||
randomise_remote: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
remotes: state => state.gateway.app.remotes,
|
||||
|
@ -173,6 +185,12 @@ export default {
|
|||
return this.defaults.daemons[this.config.app.net_type]
|
||||
}
|
||||
}),
|
||||
mounted () {
|
||||
if(this.randomise_remote && this.remotes.length > 0 && this.config.app.net_type === "mainnet") {
|
||||
const index = Math.floor(Math.random() * Math.floor(this.remotes.length));
|
||||
this.setPreset(this.remotes[index]);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectPath () {
|
||||
this.$refs.fileInput.click()
|
||||
|
@ -180,6 +198,9 @@ export default {
|
|||
setDataPath (file) {
|
||||
this.config.app.data_dir = file.target.files[0].path
|
||||
},
|
||||
setWalletDataPath (file) {
|
||||
this.config.app.wallet_data_dir = file.target.files[0].path
|
||||
},
|
||||
setPreset (option) {
|
||||
if (!option) return;
|
||||
|
||||
|
@ -227,6 +248,12 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.col.pt-sm {
|
||||
> * + * {
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.remote-dropdown {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
|
|
@ -198,7 +198,11 @@ export default {
|
|||
for(j=0; j < address_book.length; j++) {
|
||||
console.log(destination.address, address_book[j].address)
|
||||
if(destination.address == address_book[j].address) {
|
||||
destination.name = address_book[j].description
|
||||
let name = address_book[j].description
|
||||
if (name === "") {
|
||||
name = address_book[j].name
|
||||
}
|
||||
destination.name = name
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ import Identicon from "components/identicon"
|
|||
import TxTypeIcon from "components/tx_type_icon"
|
||||
import TxDetails from "components/tx_details"
|
||||
import FormatLoki from "components/format_loki"
|
||||
|
||||
export default {
|
||||
name: "TxList",
|
||||
props: {
|
||||
|
@ -119,8 +120,12 @@ export default {
|
|||
}
|
||||
},
|
||||
tx_list: {
|
||||
handler(val, old){
|
||||
if(val.length == old.length) return
|
||||
handler(val, old ) {
|
||||
// Check if anything changed in the tx list
|
||||
if(val.length == old.length) {
|
||||
const changed = val.filter((v, i) => v.note !== old[i].note)
|
||||
if (changed.length === 0) return
|
||||
}
|
||||
this.filterTxList()
|
||||
this.pageTxList()
|
||||
}
|
||||
|
@ -170,6 +175,8 @@ export default {
|
|||
return "Service Node"
|
||||
case "gov":
|
||||
return "Governance"
|
||||
case "stake":
|
||||
return "Stake"
|
||||
default:
|
||||
return "-"
|
||||
}
|
||||
|
@ -177,9 +184,9 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
filterTxList () {
|
||||
const all_in = ['in', 'pool', "miner", "snode", "gov"]
|
||||
const all_out = ['out', 'pending']
|
||||
const all_pending = ['pending', 'pool']
|
||||
const all_in = ["in", "pool", "miner", "snode", "gov"]
|
||||
const all_out = ["out", "pending", "stake"]
|
||||
const all_pending = ["pending", "pool"]
|
||||
this.tx_list_filtered = this.tx_list.filter((tx) => {
|
||||
let valid = true
|
||||
|
||||
|
@ -306,6 +313,10 @@ export default {
|
|||
.main {
|
||||
margin: 0;
|
||||
padding: 8px 10px;
|
||||
div {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.type {
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
</q-btn>
|
||||
|
||||
<!-- Modals -->
|
||||
<q-modal minimized v-model="modals.private_keys.visible" @hide="closePrivateKeys()">
|
||||
<q-modal minimized class="private-key-modal" v-model="modals.private_keys.visible" @hide="closePrivateKeys()">
|
||||
<div class="modal-header">Show private keys</div>
|
||||
<div class="q-ma-lg">
|
||||
|
||||
|
@ -50,6 +50,7 @@
|
|||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
class="copy-btn"
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('mnemonic', $event)">
|
||||
|
@ -69,6 +70,7 @@
|
|||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
class="copy-btn"
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('view_key', $event)">
|
||||
|
@ -88,6 +90,7 @@
|
|||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
class="copy-btn"
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('spend_key', $event)">
|
||||
|
@ -226,6 +229,7 @@
|
|||
<script>
|
||||
const { clipboard } = require("electron")
|
||||
import { mapState } from "vuex"
|
||||
import WalletPassword from "src/mixins/wallet_password"
|
||||
|
||||
export default {
|
||||
name: "WalletSettings",
|
||||
|
@ -348,21 +352,12 @@ export default {
|
|||
},
|
||||
getPrivateKeys () {
|
||||
if(!this.is_ready) return
|
||||
this.$q.dialog({
|
||||
this.showPasswordConfirmation({
|
||||
title: "Show private keys",
|
||||
message: "Enter wallet password to continue.",
|
||||
prompt: {
|
||||
model: "",
|
||||
type: "password"
|
||||
},
|
||||
noPasswordMessage: "Do you want to view your private keys?",
|
||||
ok: {
|
||||
label: "SHOW"
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme=="dark"?"white":"dark"
|
||||
}
|
||||
}).then(password => {
|
||||
this.$gateway.send("wallet", "get_private_keys", {password})
|
||||
}).catch(() => {
|
||||
|
@ -417,21 +412,12 @@ export default {
|
|||
doKeyImages () {
|
||||
this.hideModal("key_image")
|
||||
|
||||
this.$q.dialog({
|
||||
this.showPasswordConfirmation({
|
||||
title: this.modals.key_image.type + " key images",
|
||||
message: "Enter wallet password to continue.",
|
||||
prompt: {
|
||||
model: "",
|
||||
type: "password"
|
||||
},
|
||||
noPasswordMessage: `Do you want to ${this.modals.key_image.type.toLowerCase()} key images?`,
|
||||
ok: {
|
||||
label: this.modals.key_image.type
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme=="dark"?"white":"dark"
|
||||
}
|
||||
}).then(password => {
|
||||
if(this.modals.key_image.type == "Export")
|
||||
this.$gateway.send("wallet", "export_key_images", {password: password, path: this.modals.key_image.export_path})
|
||||
|
@ -484,7 +470,10 @@ export default {
|
|||
color: this.theme=="dark"?"white":"dark"
|
||||
}
|
||||
}).then(() => {
|
||||
this.$q.dialog({
|
||||
return this.hasPassword()
|
||||
}).then(hasPassword => {
|
||||
if (!hasPassword) return ""
|
||||
return this.$q.dialog({
|
||||
title: "Delete wallet",
|
||||
message: "Enter wallet password to continue.",
|
||||
prompt: {
|
||||
|
@ -500,17 +489,22 @@ export default {
|
|||
label: "CANCEL",
|
||||
color: this.theme=="dark"?"white":"dark"
|
||||
}
|
||||
}).then(password => {
|
||||
this.$gateway.send("wallet", "delete_wallet", {password})
|
||||
}).catch(() => {
|
||||
})
|
||||
}).then(password => {
|
||||
this.$gateway.send("wallet", "delete_wallet", {password})
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
},
|
||||
mixins: [WalletPassword],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.private-key-modal {
|
||||
.copy-btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ footer,
|
|||
}
|
||||
|
||||
.q-item-sublabel {
|
||||
colr: #cecece;
|
||||
color: #cecece;
|
||||
}
|
||||
|
||||
.advanced-options-label .q-item-side-right {
|
||||
|
@ -307,6 +307,12 @@ footer,
|
|||
}
|
||||
}
|
||||
|
||||
.q-item.tx-stake {
|
||||
.amount span {
|
||||
color: goldenrod;
|
||||
}
|
||||
}
|
||||
|
||||
.q-item.tx-out,
|
||||
.q-item.tx-pending {
|
||||
.amount span {
|
||||
|
@ -431,7 +437,7 @@ footer,
|
|||
}
|
||||
}
|
||||
|
||||
.q-item:hover {
|
||||
.q-item:hover, .q-item.selected {
|
||||
background: $primary !important;
|
||||
|
||||
.wallet-icon {
|
||||
|
@ -441,6 +447,10 @@ footer,
|
|||
}
|
||||
}
|
||||
|
||||
.q-icon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.q-item-sublabel {
|
||||
color: white
|
||||
}
|
||||
|
@ -550,7 +560,7 @@ footer,
|
|||
}
|
||||
}
|
||||
|
||||
.service-node-page {
|
||||
.service-node-staking {
|
||||
.address-type {
|
||||
color: $loki-green-solid;
|
||||
&.not-ours {
|
||||
|
@ -558,3 +568,20 @@ footer,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.service-node-registration {
|
||||
.description{
|
||||
color: #b7b7b7;
|
||||
font-style: normal;
|
||||
b {
|
||||
color: white;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcome {
|
||||
.q-layout-footer {
|
||||
background: $secondary
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,6 +100,9 @@ export class Gateway extends EventEmitter {
|
|||
!decrypted_data.hasOwnProperty("data")) { return }
|
||||
|
||||
switch (decrypted_data.event) {
|
||||
case "set_has_password":
|
||||
this.emit("has_password", decrypted_data.data)
|
||||
break
|
||||
case "set_valid_address":
|
||||
this.emit("validate_address", decrypted_data.data)
|
||||
break
|
||||
|
@ -124,8 +127,12 @@ export class Gateway extends EventEmitter {
|
|||
this.app.store.commit("gateway/set_tx_status", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "set_stake_status":
|
||||
this.app.store.commit("gateway/set_stake_status", decrypted_data.data)
|
||||
case "set_snode_status":
|
||||
this.app.store.commit("gateway/set_snode_status", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "set_old_gui_import_status":
|
||||
this.app.store.commit("gateway/set_old_gui_import_status", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "wallet_list":
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
</template>
|
||||
|
||||
<q-toolbar-title v-if="page_title=='Loki'">
|
||||
<div style="margin-top:7px">
|
||||
<div class="flex items-center justify-center" style="margin-top:7px">
|
||||
<img src="statics/loki.svg" height="32">
|
||||
</div>
|
||||
</q-toolbar-title>
|
||||
|
@ -59,6 +59,8 @@ export default {
|
|||
return "Restore view-only wallet"
|
||||
case "wallet-import-legacy":
|
||||
return "Import wallet from legacy gui"
|
||||
case "wallet-import-old-gui":
|
||||
return "Import wallets from old GUI"
|
||||
case "wallet-created":
|
||||
return "Wallet created/restored"
|
||||
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
<q-layout view="hHh Lpr lFf">
|
||||
<q-layout-header class="shift-title">
|
||||
<main-menu />
|
||||
|
||||
<q-toolbar-title>
|
||||
<div class="flex items-center justify-center" style="margin-top:7px">
|
||||
<img src="statics/loki.svg" height="32">
|
||||
</div>
|
||||
</q-toolbar-title>
|
||||
</q-layout-header>
|
||||
|
||||
<q-page-container>
|
||||
|
@ -96,9 +100,10 @@ export default {
|
|||
|
||||
<style lang="scss">
|
||||
.navigation {
|
||||
padding: 12px;
|
||||
padding: 8px 12px;
|
||||
|
||||
> * {
|
||||
margin: 2px 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
|
|
38
src/mixins/wallet_password.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { mapState } from "vuex"
|
||||
|
||||
export default {
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme
|
||||
}),
|
||||
methods: {
|
||||
hasPassword () {
|
||||
// Validate the address
|
||||
return new Promise((resolve) => {
|
||||
this.$gateway.once("has_password", (data) => {
|
||||
resolve(!!data)
|
||||
})
|
||||
this.$gateway.send("wallet", "has_password")
|
||||
})
|
||||
},
|
||||
|
||||
showPasswordConfirmation (options) {
|
||||
const { noPasswordMessage, ...other } = options
|
||||
|
||||
return this.hasPassword().then(hasPassword => {
|
||||
return this.$q.dialog({
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme === "dark" ? "white" : "dark"
|
||||
},
|
||||
...other,
|
||||
message: hasPassword ? "Enter wallet password to continue." : noPasswordMessage,
|
||||
prompt: hasPassword ? {
|
||||
model: "",
|
||||
type: "password"
|
||||
} : null
|
||||
})
|
||||
}).then(password => password || "")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<q-page class="welcome">
|
||||
|
||||
<q-stepper class="no-shadow" ref="stepper" :color="theme == 'dark' ? 'light' : 'dark'" dark>
|
||||
<q-stepper class="no-shadow" ref="stepper" :color="theme == 'dark' ? 'light' : 'dark'" dark @step="onStep">
|
||||
|
||||
<q-step default title="Welcome" class="first-step">
|
||||
|
||||
|
@ -11,14 +11,13 @@
|
|||
|
||||
<h6 class="q-mb-md" style="font-weight: 300">Select language:</h6>
|
||||
|
||||
<q-btn-toggle
|
||||
v-model="choose_lang"
|
||||
toggle-color="primary"
|
||||
<q-btn
|
||||
color="primary"
|
||||
size="md"
|
||||
:options="[
|
||||
{label: 'English', value: 'EN', icon: 'language'},
|
||||
]"
|
||||
/>
|
||||
icon="language"
|
||||
label="English"
|
||||
@click="clickNext()"
|
||||
/>
|
||||
|
||||
<p class="q-mt-md">More languages coming soon</p>
|
||||
</div>
|
||||
|
@ -26,31 +25,28 @@
|
|||
</q-step>
|
||||
|
||||
<q-step title="Configure">
|
||||
|
||||
<SettingsGeneral ref="settingsGeneral"></SettingsGeneral>
|
||||
|
||||
<SettingsGeneral randomise_remote ref="settingsGeneral" />
|
||||
</q-step>
|
||||
</q-stepper>
|
||||
|
||||
<q-layout-footer class="no-shadow q-pa-sm">
|
||||
<q-layout-footer v-if="!is_first_page" class="no-shadow q-pa-sm">
|
||||
<div class="row justify-end">
|
||||
<div>
|
||||
<q-btn
|
||||
flat
|
||||
@click="clickPrev()"
|
||||
label="Back"
|
||||
/>
|
||||
<q-btn
|
||||
flat
|
||||
@click="clickPrev()"
|
||||
label="Back"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<q-btn
|
||||
<q-btn
|
||||
class="q-ml-sm"
|
||||
color="primary"
|
||||
@click="clickNext()"
|
||||
label="Next"
|
||||
/>
|
||||
@click="clickNext()"
|
||||
label="Next"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</q-layout-footer>
|
||||
|
||||
</q-page>
|
||||
|
@ -70,6 +66,7 @@ export default {
|
|||
}),
|
||||
data() {
|
||||
return {
|
||||
is_first_page: true,
|
||||
choose_lang: "EN",
|
||||
version: "",
|
||||
daemonVersion: ""
|
||||
|
@ -88,6 +85,9 @@ export default {
|
|||
});
|
||||
},
|
||||
methods: {
|
||||
onStep () {
|
||||
this.is_first_page = this.$refs.stepper.steps[0].active
|
||||
},
|
||||
clickNext () {
|
||||
if(this.$refs.stepper.steps[this.$refs.stepper.length-1].active) {
|
||||
this.$gateway.send("core", "save_config_init", this.pending_config);
|
||||
|
|
|
@ -133,11 +133,31 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
this.$q.loading.show({
|
||||
delay: 0
|
||||
})
|
||||
// Warn user if no password is set
|
||||
let passwordPromise = Promise.resolve();
|
||||
if (!this.wallet.password) {
|
||||
passwordPromise = this.$q.dialog({
|
||||
title: "No password set",
|
||||
message: "Are you sure you want to create a wallet with no password?",
|
||||
ok: {
|
||||
label: "YES",
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme === "dark" ? "white" : "dark"
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
this.$gateway.send("wallet", "create_wallet", this.wallet);
|
||||
passwordPromise
|
||||
.then(() => {
|
||||
this.$q.loading.show({
|
||||
delay: 0
|
||||
})
|
||||
this.$gateway.send("wallet", "create_wallet", this.wallet)
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
cancel() {
|
||||
this.$router.replace({ path: "/wallet-select" });
|
||||
|
|
|
@ -1,32 +1,10 @@
|
|||
<template>
|
||||
<q-page padding>
|
||||
|
||||
<AddressHeader :address="info.address" :title="walletName"/>
|
||||
|
||||
<template v-if="secret.mnemonic">
|
||||
<h6 class="q-mb-xs q-mt-lg">Seed words</h6>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{{ secret.mnemonic }}
|
||||
</div>
|
||||
<div class="q-item-side">
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('mnemonic', $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy seed words
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="secret.view_key != secret.spend_key">
|
||||
<h6 class="q-mb-xs">View key</h6>
|
||||
<div class="row">
|
||||
<div class="col" style="word-break:break-all;">
|
||||
{{ secret.view_key }}
|
||||
<q-page padding class="created">
|
||||
<div class="col wallet q-mb-lg">
|
||||
<h6>{{walletName}}</h6>
|
||||
<div class="row items-center">
|
||||
<div class="col address">
|
||||
{{ info.address }}
|
||||
</div>
|
||||
<div class="q-item-side">
|
||||
<q-btn
|
||||
|
@ -39,30 +17,70 @@
|
|||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template v-if="!/^0*$/.test(secret.spend_key)">
|
||||
<h6 class="q-mb-xs">Spend key</h6>
|
||||
<div class="row">
|
||||
<div class="col" style="word-break:break-all;">
|
||||
{{ secret.spend_key }}
|
||||
<template v-if="secret.mnemonic">
|
||||
<div class="seed-box col">
|
||||
<h6 class="q-mb-xs q-mt-lg">Seed words</h6>
|
||||
<div class="seed q-my-lg">
|
||||
{{ secret.mnemonic }}
|
||||
</div>
|
||||
<div class="q-item-side">
|
||||
<div class="q-my-md warning">
|
||||
Please copy and save these in a secure location!
|
||||
</div>
|
||||
<div>
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('spend_key', $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy spend key
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
color="primary"
|
||||
size="md"
|
||||
icon="file_copy"
|
||||
label="Copy seed words"
|
||||
@click="copyPrivateKey('mnemonic', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<q-field>
|
||||
<q-btn class="q-mt-lg" color="primary" @click="open" label="Open wallet" />
|
||||
</q-field>
|
||||
<q-collapsible label="Advanced" header-class="q-mt-sm non-selectable row reverse advanced-options-label">
|
||||
<template v-if="secret.view_key != secret.spend_key">
|
||||
<h6 class="q-mb-xs title">View key</h6>
|
||||
<div class="row">
|
||||
<div class="col" style="word-break:break-all;">
|
||||
{{ secret.view_key }}
|
||||
</div>
|
||||
<div class="q-item-side">
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('view_key', $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy view key
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="!/^0*$/.test(secret.spend_key)">
|
||||
<h6 class="q-mb-xs title">Spend key</h6>
|
||||
<div class="row">
|
||||
<div class="col" style="word-break:break-all;">
|
||||
{{ secret.spend_key }}
|
||||
</div>
|
||||
<div class="q-item-side">
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('spend_key', $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy spend key
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</q-collapsible>
|
||||
|
||||
<q-btn class="q-mt-lg" color="primary" @click="open" label="Open wallet" />
|
||||
|
||||
</q-page>
|
||||
</template>
|
||||
|
@ -76,7 +94,7 @@ export default {
|
|||
info: state => state.gateway.wallet.info,
|
||||
secret: state => state.gateway.wallet.secret,
|
||||
walletName (state) {
|
||||
return `Your Wallet (${this.info.name})`
|
||||
return `Wallet: ${this.info.name}`
|
||||
}
|
||||
}),
|
||||
methods: {
|
||||
|
@ -141,5 +159,45 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.created {
|
||||
.wallet h6 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.address {
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.seed-box {
|
||||
border: 1px solid white;
|
||||
border-radius: 3px;
|
||||
margin: 16px;
|
||||
padding: 16px;
|
||||
|
||||
div, h6 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.seed {
|
||||
font-size: 24px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: goldenrod;
|
||||
}
|
||||
}
|
||||
h6 {
|
||||
font-size: 18px;
|
||||
margin: 8px 0;
|
||||
font-weight: 450;
|
||||
}
|
||||
.advanced-options-label {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
139
src/pages/wallet-select/import-old-gui.vue
Normal file
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<div class="q-mx-md import-old-gui">
|
||||
<q-list link dark no-border class="wallet-list">
|
||||
<q-item v-for="state in directory_state" :key="state.directory" :class="{selected : state.selected}">
|
||||
<q-item-side>
|
||||
<q-checkbox v-model="state.selected" />
|
||||
</q-item-side>
|
||||
<q-item-main @click.native="state.selected = !state.selected">
|
||||
<q-item-tile label>{{ state.directory }}</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-select hide-underline dark class="q-ma-none full-width" v-model="state.type" :options="selectOptions" />
|
||||
</q-item-side>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
<q-field>
|
||||
<q-btn color="primary" @click="import_wallets" label="Import wallets" :disable="selectedWallets.length === 0"/>
|
||||
</q-field>
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex"
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
directory_state: [],
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
directories: state => state.gateway.wallets.directories,
|
||||
old_gui_import_status: state => state.gateway.old_gui_import_status,
|
||||
selectOptions: state => [
|
||||
{
|
||||
label: 'Main',
|
||||
value: 'mainnet'
|
||||
},
|
||||
{
|
||||
label: 'Staging',
|
||||
value: 'stagenet'
|
||||
},
|
||||
{
|
||||
label: 'Test',
|
||||
value: 'testnet'
|
||||
},
|
||||
],
|
||||
selectedWallets () {
|
||||
return this.directory_state.filter(s => s.selected)
|
||||
}
|
||||
}),
|
||||
watch: {
|
||||
directories: {
|
||||
handler(val, old) {
|
||||
this.populate_state()
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
old_gui_import_status: {
|
||||
handler(val, old) {
|
||||
if(val.code == old.code) return
|
||||
|
||||
const { code, failed_wallets } = this.old_gui_import_status
|
||||
|
||||
// Imported
|
||||
if (code === 0) {
|
||||
this.$q.loading.hide()
|
||||
if (failed_wallets.length === 0) {
|
||||
this.$router.replace({ path: "/wallet-select" });
|
||||
} else {
|
||||
failed_wallets.forEach(wallet => {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 3000,
|
||||
message: `Failed to import wallet: ${wallet}`
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$gateway.send("wallet", "list_wallets")
|
||||
this.populate_state()
|
||||
},
|
||||
methods: {
|
||||
populate_state () {
|
||||
// Keep any directories that intersect
|
||||
const new_state = this.directory_state.filter(state => this.directories.includes(state.directory))
|
||||
|
||||
// Add in new directories
|
||||
this.directories
|
||||
.filter(dir => !new_state.find(state => state.directory === dir))
|
||||
.forEach(directory => {
|
||||
new_state.push({
|
||||
directory,
|
||||
selected: false,
|
||||
type: "mainnet"
|
||||
})
|
||||
});
|
||||
|
||||
// Sort them
|
||||
this.directory_state = new_state.sort(function(a, b){
|
||||
return a.directory.localeCompare(b.directory);
|
||||
})
|
||||
},
|
||||
import_wallets() {
|
||||
this.$q.loading.show({
|
||||
delay: 0
|
||||
})
|
||||
this.$gateway.send("wallet", "copy_old_gui_wallets", {
|
||||
wallets: this.selectedWallets
|
||||
})
|
||||
},
|
||||
cancel() {
|
||||
this.$router.replace({ path: "/wallet-select" });
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.import-old-gui {
|
||||
.wallet-list {
|
||||
.q-item {
|
||||
margin: 10px 0px;
|
||||
margin-bottom: 0px;
|
||||
padding: 14px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -16,7 +16,7 @@
|
|||
</q-btn>
|
||||
</div>
|
||||
<div class="hr-separator" />
|
||||
<q-item v-for="wallet in wallets.list" @click.native="openWallet(wallet)" :key="wallet.address">
|
||||
<q-item v-for="wallet in wallets.list" @click.native="openWallet(wallet)" :key="`${wallet.address}-${wallet.name}`">
|
||||
<q-item-side>
|
||||
<div class="wallet-icon">
|
||||
<svg width="48" viewBox="0 0 17 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="si-glyph si-glyph-wallet"><title>969</title><defs class="si-glyph-fill"></defs><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g transform="translate(1.000000, 0.000000)" fill="#434343"><path d="M7.988,10.635 L7.988,8.327 C7.988,7.578 8.561,6.969 9.267,6.969 L13.964,6.969 L13.964,5.531 C13.964,4.849 13.56,4.279 13.007,4.093 L13.007,4.094 L11.356,4.08 L11.336,4.022 L3.925,4.022 L3.784,4.07 L1.17,4.068 L1.165,4.047 C0.529,4.167 0.017,4.743 0.017,5.484 L0.017,13.437 C0.017,14.269 0.665,14.992 1.408,14.992 L12.622,14.992 C13.365,14.992 13.965,14.316 13.965,13.484 L13.965,12.031 L9.268,12.031 C8.562,12.031 7.988,11.384 7.988,10.635 L7.988,10.635 Z" class="si-glyph-fill"></path><path d="M14.996,8.061 L14.947,8.061 L9.989,8.061 C9.46,8.061 9.031,8.529 9.031,9.106 L9.031,9.922 C9.031,10.498 9.46,10.966 9.989,10.966 L14.947,10.966 L14.996,10.966 C15.525,10.966 15.955,10.498 15.955,9.922 L15.955,9.106 C15.955,8.528 15.525,8.061 14.996,8.061 L14.996,8.061 Z M12.031,10.016 L9.969,10.016 L9.969,9 L12.031,9 L12.031,10.016 L12.031,10.016 Z" class="si-glyph-fill"></path><path d="M3.926,4.022 L10.557,1.753 L11.337,4.022 L12.622,4.022 C12.757,4.022 12.885,4.051 13.008,4.092 L11.619,0.051 L1.049,3.572 L1.166,4.048 C1.245,4.033 1.326,4.023 1.408,4.023 L3.926,4.023 L3.926,4.022 Z" class="si-glyph-fill"></path></g></g></svg>
|
||||
|
@ -58,6 +58,7 @@
|
|||
const { clipboard } = require("electron")
|
||||
import { mapState } from "vuex"
|
||||
import Identicon from "components/identicon"
|
||||
|
||||
export default {
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
|
@ -68,7 +69,7 @@ export default {
|
|||
// <q-item @click.native="restoreViewWallet()">
|
||||
// <q-item-main label="Restore view-only wallet" />
|
||||
// </q-item>
|
||||
return [
|
||||
const actions = [
|
||||
{
|
||||
name: "Create new wallet",
|
||||
handler: this.createNewWallet,
|
||||
|
@ -82,8 +83,20 @@ export default {
|
|||
handler: this.importWallet,
|
||||
}
|
||||
];
|
||||
|
||||
if (this.wallets.directories.length > 0) {
|
||||
actions.push( {
|
||||
name: "Import wallets from old GUI",
|
||||
handler: this.importOldGuiWallets,
|
||||
})
|
||||
}
|
||||
|
||||
return actions
|
||||
}
|
||||
}),
|
||||
created () {
|
||||
this.$gateway.send("wallet", "list_wallets")
|
||||
},
|
||||
methods: {
|
||||
openWallet(wallet) {
|
||||
if(wallet.password_protected !== false) {
|
||||
|
@ -129,6 +142,9 @@ export default {
|
|||
importWallet() {
|
||||
this.$router.replace({ path: "wallet-select/import" });
|
||||
},
|
||||
importOldGuiWallets() {
|
||||
this.$router.replace({ path: "wallet-select/import-old-gui" });
|
||||
},
|
||||
importLegacyWallet() {
|
||||
this.$router.replace({ path: "wallet-select/import-legacy" });
|
||||
},
|
||||
|
@ -183,6 +199,10 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.header-popover.q-popover {
|
||||
max-width: 250px !important;
|
||||
}
|
||||
|
||||
.wallet-list {
|
||||
.header {
|
||||
margin: 0 16px;
|
||||
|
|
|
@ -99,7 +99,7 @@ export default {
|
|||
seed: "",
|
||||
refresh_type: "date",
|
||||
refresh_start_height: 0,
|
||||
refresh_start_date: 1492486495000, // timestamp of block 1
|
||||
refresh_start_date: 1525305600000, // timestamp of block 1
|
||||
password: "",
|
||||
password_confirm: ""
|
||||
},
|
||||
|
@ -161,7 +161,11 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
let seed = this.wallet.seed.trim().replace(/\s{2,}/g, " ").split(" ")
|
||||
let seed = this.wallet.seed.trim()
|
||||
.replace(/\n/g, " ")
|
||||
.replace(/\t/g, " ")
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.split(" ")
|
||||
if(seed.length !== 14 && seed.length !== 24 && seed.length !== 25 && seed.length !== 26) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
|
|
|
@ -10,12 +10,24 @@
|
|||
<q-item-tile sublabel>Primary address</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
flat
|
||||
style="width:25px;"
|
||||
size="md"
|
||||
@click="showQR(address.address, $event)"
|
||||
>
|
||||
<img src="statics/qr-code.svg" height="20" />
|
||||
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
|
||||
Show QR code
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
style="width:25px;"
|
||||
size="md" icon="file_copy"
|
||||
@click="copyAddress(address.address, $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
@click="copyAddress(address.address, $event)"
|
||||
>
|
||||
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
|
||||
Copy address
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
|
@ -62,12 +74,23 @@
|
|||
<q-item-tile sublabel>Sub-address (Index {{ address.address_index }})</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
flat
|
||||
style="width:25px;"
|
||||
size="md"
|
||||
@click="showQR(address.address, $event)"
|
||||
>
|
||||
<img src="statics/qr-code-grey.svg" height="20" />
|
||||
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
|
||||
Show QR code
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
style="width:25px;"
|
||||
size="md" icon="file_copy"
|
||||
@click="copyAddress(address.address, $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
<q-tooltip anchor="bottom right" self="top right" :offset="[5, 10]">
|
||||
Copy address
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
|
@ -117,12 +140,23 @@
|
|||
<q-item-tile sublabel>Sub-address (Index {{ address.address_index }})</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
flat
|
||||
style="width:25px;"
|
||||
size="md"
|
||||
@click="showQR(address.address, $event)"
|
||||
>
|
||||
<img src="statics/qr-code-grey.svg" height="20" />
|
||||
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
|
||||
Show QR code
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
style="width:25px;"
|
||||
size="md" icon="file_copy"
|
||||
@click="copyAddress(address.address, $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
<q-tooltip anchor="bottom right" self="top right" :offset="[5, 10]">
|
||||
Copy address
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
|
@ -148,15 +182,44 @@
|
|||
|
||||
</q-list>
|
||||
<AddressDetails ref="addressDetails" />
|
||||
|
||||
<!-- QR Code -->
|
||||
<template v-if="QR.address != null">
|
||||
<q-modal v-model="QR.visible" minimized :content-css="{padding: '25px'}">
|
||||
|
||||
<div class="text-center q-mb-sm q-pa-md" style="background: white;">
|
||||
<qrcode-vue :value="QR.address" size="240" ref="qr">
|
||||
</qrcode-vue>
|
||||
<q-context-menu>
|
||||
<q-list link separator style="min-width: 150px; max-height: 300px;">
|
||||
<q-item v-close-overlay @click.native="copyQR()">
|
||||
<q-item-main label="Copy QR code" />
|
||||
</q-item>
|
||||
<q-item v-close-overlay @click.native="saveQR()">
|
||||
<q-item-main label="Save QR code to file" />
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-context-menu>
|
||||
</div>
|
||||
|
||||
<q-btn
|
||||
color="primary"
|
||||
@click="QR.visible = false"
|
||||
label="Close"
|
||||
/>
|
||||
</q-modal>
|
||||
</template>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
const { clipboard } = require("electron")
|
||||
const { clipboard, nativeImage } = require("electron")
|
||||
import { mapState } from "vuex"
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
import Identicon from "components/identicon"
|
||||
import AddressDetails from "components/address_details"
|
||||
|
||||
export default {
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
|
@ -174,11 +237,38 @@ export default {
|
|||
return amount.toLocaleString()
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
QR: {
|
||||
visible: false,
|
||||
address: null,
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
details (address) {
|
||||
this.$refs.addressDetails.address = address;
|
||||
this.$refs.addressDetails.isVisible = true;
|
||||
},
|
||||
showQR (address, event) {
|
||||
event.stopPropagation()
|
||||
this.QR.visible = true
|
||||
this.QR.address = address
|
||||
},
|
||||
copyQR () {
|
||||
const data = this.$refs.qr.$el.childNodes[0].toDataURL()
|
||||
const img = nativeImage.createFromDataURL(data)
|
||||
clipboard.writeImage(img)
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: "Copied QR code to clipboard"
|
||||
})
|
||||
},
|
||||
saveQR () {
|
||||
let img = this.$refs.qr.$el.childNodes[0].toDataURL()
|
||||
this.$gateway.send("core", "save_png", {img, type: "QR Code"})
|
||||
},
|
||||
copyAddress (address, event) {
|
||||
event.stopPropagation()
|
||||
for(let i = 0; i < event.path.length; i++) {
|
||||
|
@ -198,6 +288,7 @@ export default {
|
|||
components: {
|
||||
Identicon,
|
||||
AddressDetails,
|
||||
QrcodeVue
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -65,6 +65,18 @@
|
|||
</LokiField>
|
||||
</div>
|
||||
|
||||
<!-- Notes -->
|
||||
<div class="col q-mt-sm">
|
||||
<LokiField label="Notes" optional>
|
||||
<q-input v-model="newTx.note"
|
||||
type="textarea"
|
||||
:dark="theme=='dark'"
|
||||
placeholder="Additional notes to attach to the transaction"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
</div>
|
||||
|
||||
<!-- Save to address book -->
|
||||
<q-field>
|
||||
<q-checkbox v-model="newTx.address_book.save" label="Save to address book" :dark="theme=='dark'" />
|
||||
|
@ -113,7 +125,9 @@ import { required, decimal } from "vuelidate/lib/validators"
|
|||
import { payment_id, address, greater_than_zero } from "src/validators/common"
|
||||
import Identicon from "components/identicon"
|
||||
import LokiField from "components/loki_field"
|
||||
import WalletPassword from "src/mixins/wallet_password"
|
||||
const objectAssignDeep = require("object-assign-deep");
|
||||
|
||||
export default {
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
|
@ -147,10 +161,11 @@ export default {
|
|||
}
|
||||
},
|
||||
priorityOptions: [
|
||||
{label: "Normal (x1 fee)", value: 0},
|
||||
{label: "Slow (x0.25 fee)", value: 1},
|
||||
{label: "Fast (x5 fee)", value: 2},
|
||||
{label: "Fastest (x41.5 fee)", value: 3},
|
||||
{label: "Automatic", value: 0},
|
||||
{label: "Slow (x0.2 fee)", value: 1},
|
||||
{label: "Normal (x1 fee)", value: 2},
|
||||
{label: "Fast (x5 fee)", value: 3},
|
||||
{label: "Fastest (x200 fee)", value: 4},
|
||||
],
|
||||
}
|
||||
},
|
||||
|
@ -197,7 +212,8 @@ export default {
|
|||
save: false,
|
||||
name: "",
|
||||
description: ""
|
||||
}
|
||||
},
|
||||
note: ""
|
||||
}
|
||||
break;
|
||||
case -1:
|
||||
|
@ -230,7 +246,6 @@ export default {
|
|||
},
|
||||
|
||||
send: function () {
|
||||
|
||||
this.$v.newTx.$touch()
|
||||
|
||||
if(this.newTx.amount < 0) {
|
||||
|
@ -282,21 +297,12 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
this.$q.dialog({
|
||||
this.showPasswordConfirmation({
|
||||
title: "Transfer",
|
||||
message: "Enter wallet password to continue.",
|
||||
prompt: {
|
||||
model: "",
|
||||
type: "password"
|
||||
},
|
||||
noPasswordMessage: "Do you want to send the transaction?",
|
||||
ok: {
|
||||
label: "SEND"
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme=="dark"?"white":"dark"
|
||||
}
|
||||
}).then(password => {
|
||||
this.$store.commit("gateway/set_tx_status", {
|
||||
code: 1,
|
||||
|
@ -309,6 +315,7 @@ export default {
|
|||
})
|
||||
}
|
||||
},
|
||||
mixins: [WalletPassword],
|
||||
components: {
|
||||
Identicon,
|
||||
LokiField
|
||||
|
|
|
@ -1,286 +1,40 @@
|
|||
<template>
|
||||
<q-page class="service-node-page">
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
|
||||
<LokiField label="Service Node Key" :error="$v.service_node.key.$error">
|
||||
<q-input v-model="service_node.key"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.service_node.key.$touch"
|
||||
placeholder="64 hexadecimal characters"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
|
||||
<div class="q-mt-md col">
|
||||
<LokiField label="Award Recepient's Address" :error="$v.service_node.award_address.$error">
|
||||
<q-input v-model="service_node.award_address"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.service_node.award_address.$touch"
|
||||
placeholder="64 hexadecimal characters"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
<div class="address-type" :class="[addressType]">( {{ addressType | addressTypeString }} )</div>
|
||||
</div>
|
||||
|
||||
<LokiField label="Amount" class="q-mt-md" :error="$v.service_node.amount.$error">
|
||||
<q-input v-model="service_node.amount"
|
||||
:dark="theme=='dark'"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="unlocked_balance / 1e9"
|
||||
placeholder="0"
|
||||
@blur="$v.service_node.amount.$touch"
|
||||
hide-underline
|
||||
/>
|
||||
<q-btn color="secondary" @click="service_node.amount = unlocked_balance / 1e9" :text-color="theme=='dark'?'white':'dark'">All</q-btn>
|
||||
</LokiField>
|
||||
|
||||
|
||||
|
||||
<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="stake_status.sending" :dark="theme=='dark'">
|
||||
<q-spinner color="primary" :size="30" />
|
||||
</q-inner-loading>
|
||||
|
||||
</template>
|
||||
|
||||
<div class="header row items-center justify-center q-mt-md">
|
||||
<q-btn-toggle
|
||||
v-model="screen"
|
||||
toggle-color="primary"
|
||||
color="secondary"
|
||||
:options="[
|
||||
{label: 'Staking', value: 'staking'},
|
||||
{label: 'Registration', value: 'registration'},
|
||||
{label: 'Unlock', value: 'unlock'}
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<ServiceNodeStaking v-if="screen === 'staking'"/>
|
||||
<ServiceNodeRegistration v-if="screen === 'registration'" />
|
||||
<ServiceNodeUnlock v-if="screen === 'unlock'" />
|
||||
</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, greater_than_zero, address } from "src/validators/common"
|
||||
import Identicon from "components/identicon"
|
||||
import LokiField from "components/loki_field"
|
||||
const objectAssignDeep = require("object-assign-deep");
|
||||
import ServiceNodeStaking from "components/service_node_staking"
|
||||
import ServiceNodeRegistration from "components/service_node_registration"
|
||||
import ServiceNodeUnlock from "components/service_node_unlock"
|
||||
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,
|
||||
address_list: state => state.gateway.wallet.address_list,
|
||||
stake_status: state => state.gateway.stake_status,
|
||||
is_ready (state) {
|
||||
return this.$store.getters["gateway/isReady"]
|
||||
},
|
||||
is_able_to_send (state) {
|
||||
return this.$store.getters["gateway/isAbleToSend"]
|
||||
},
|
||||
|
||||
addressType (state) {
|
||||
const address = this.service_node.award_address;
|
||||
const inArray = (array) => array.map(o => o.address).includes(address);
|
||||
|
||||
const { primary, used, unused } = this.address_list
|
||||
if (inArray(primary)) {
|
||||
return "primary"
|
||||
} else if (inArray(used)) {
|
||||
return "used"
|
||||
} else if (inArray(unused)) {
|
||||
return "unsued"
|
||||
} else {
|
||||
return "not-ours"
|
||||
}
|
||||
}
|
||||
}),
|
||||
data () {
|
||||
return {
|
||||
service_node: {
|
||||
key: "",
|
||||
amount: 0,
|
||||
award_address: "",
|
||||
},
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
addressTypeString: function (value) {
|
||||
switch (value) {
|
||||
case "primary":
|
||||
return "Your primary address"
|
||||
case "used":
|
||||
return "Your used address"
|
||||
case "ununsed":
|
||||
return "Your unused address"
|
||||
default:
|
||||
return "Not your address!"
|
||||
}
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
service_node: {
|
||||
key: { required, service_node_key },
|
||||
amount: {
|
||||
required,
|
||||
decimal,
|
||||
greater_than_zero,
|
||||
},
|
||||
award_address: {
|
||||
required,
|
||||
isAddress(value) {
|
||||
if (value === '') return true
|
||||
|
||||
return new Promise(resolve => {
|
||||
address(value, this.$gateway)
|
||||
.then(() => resolve(true))
|
||||
.catch(e => resolve(false))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
stake_status: {
|
||||
handler(val, old){
|
||||
if(val.code == old.code) return
|
||||
switch(this.stake_status.code) {
|
||||
case 0:
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: this.stake_status.message
|
||||
})
|
||||
this.$v.$reset();
|
||||
this.service_node = {
|
||||
key: "",
|
||||
amount: 0,
|
||||
award_address: "",
|
||||
}
|
||||
break;
|
||||
case -1:
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: this.stake_status.message
|
||||
})
|
||||
break;
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
created () {
|
||||
const { address } = this.info;
|
||||
if (!this.service_node.award_address || this.service_node.award_address === "") {
|
||||
this.service_node.award_address = address || ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isOurAddress (address) {
|
||||
const { primary, used, unused } = this.address_list
|
||||
const addresses = [...primary, ...used, ...unused].map(o => o.address);
|
||||
console.log(addresses);
|
||||
return addresses.includes(address);
|
||||
},
|
||||
stake: function () {
|
||||
|
||||
this.$v.service_node.$touch()
|
||||
|
||||
if (this.$v.service_node.key.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Service node key not valid"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.$v.service_node.award_address.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Address not valid"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if(this.service_node.amount < 0) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Amount cannot be negative"
|
||||
})
|
||||
return
|
||||
} else if(this.service_node.amount == 0) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Amount must be greater than zero"
|
||||
})
|
||||
return
|
||||
} else if(this.service_node.amount > this.unlocked_balance / 1e9) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Not enough unlocked balance"
|
||||
})
|
||||
return
|
||||
} else if (this.$v.service_node.amount.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Amount 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_stake_status", {
|
||||
code: 1,
|
||||
message: "Staking...",
|
||||
sending: true
|
||||
})
|
||||
const service_node = objectAssignDeep.noMutate(this.service_node, {password})
|
||||
this.$gateway.send("wallet", "stake", {
|
||||
...service_node,
|
||||
destination: service_node.award_address,
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
screen: "staking",
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Identicon,
|
||||
LokiField
|
||||
ServiceNodeStaking,
|
||||
ServiceNodeRegistration,
|
||||
ServiceNodeUnlock
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.service-node-page {
|
||||
.address-type {
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
text-align: right;
|
||||
&.not-ours {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -45,6 +45,7 @@ export default {
|
|||
{label: "Miner", value: "miner"},
|
||||
{label: "Service Node", value: "snode"},
|
||||
{label: "Governance", value: "gov"},
|
||||
{label: "Stake", value: "stake"},
|
||||
{label: "Failed", value: "failed"},
|
||||
]
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
This is an unused class in LOKI
|
||||
*/
|
||||
|
||||
<template>
|
||||
<q-page padding>
|
||||
|
||||
|
|
|
@ -72,6 +72,12 @@ export default [
|
|||
name: "wallet-created",
|
||||
component: () =>
|
||||
import("pages/wallet-select/created")
|
||||
},
|
||||
{
|
||||
path: "import-old-gui",
|
||||
name: "wallet-import-old-gui",
|
||||
component: () =>
|
||||
import("pages/wallet-select/import-old-gui")
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 51 KiB |
3
src/statics/qr-code-grey.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 401 401" width="401" height="401"><defs><path d="M0 401L0 218.72L182.27 218.72L182.27 401L0 401ZM36.45 364.26L145.82 364.26L145.82 255.18L36.45 255.18L36.45 364.26Z" id="cP4vhvhrf"></path><path d="M109.36 291.63C109.36 291.63 109.36 291.63 109.36 291.63C109.36 313.51 109.36 325.66 109.36 328.09C109.36 328.09 109.36 328.09 109.36 328.09C87.49 328.09 75.34 328.09 72.91 328.09C72.91 328.09 72.91 328.09 72.91 328.09C72.91 306.22 72.91 294.06 72.91 291.63C72.91 291.63 72.91 291.63 72.91 291.63C94.78 291.63 106.93 291.63 109.36 291.63Z" id="b3K4Wy5wBr"></path><path d="M328.09 364.55C328.09 364.55 328.09 364.55 328.09 364.55C328.09 386.42 328.09 398.57 328.09 401C328.09 401 328.09 401 328.09 401C306.21 401 294.06 401 291.63 401C291.63 401 291.63 401 291.63 401C291.63 379.13 291.63 366.98 291.63 364.55C291.63 364.55 291.63 364.55 291.63 364.55C313.51 364.55 325.66 364.55 328.09 364.55Z" id="ajtxNY2Qi"></path><path d="M401 364.55C401 364.55 401 364.55 401 364.55C401 386.42 401 398.57 401 401C401 401 401 401 401 401C379.12 401 366.97 401 364.54 401C364.54 401 364.54 401 364.54 401C364.54 379.13 364.54 366.98 364.54 364.55C364.54 364.55 364.54 364.55 364.54 364.55C386.42 364.55 398.57 364.55 401 364.55Z" id="b12z2Qj5i"></path><path d="M328.09 255.18L328.09 218.72L218.72 218.72L218.72 401L255.18 401L255.18 291.63L291.63 291.63L291.63 328.09L401 328.09L401 218.72L401 218.72L364.54 218.72L364.54 255.18L328.09 255.18Z" id="aDSxNIZuL"></path><path d="M0 182.28L0 0L182.27 0L182.27 182.28L0 182.28ZM36.45 145.82L145.82 145.82L145.82 36.45L36.45 36.45L36.45 145.82Z" id="bpcQxL4BR"></path><path d="M109.36 72.91C109.36 72.91 109.36 72.91 109.36 72.91C109.36 94.78 109.36 106.93 109.36 109.36C109.36 109.36 109.36 109.36 109.36 109.36C87.49 109.36 75.34 109.36 72.91 109.36C72.91 109.36 72.91 109.36 72.91 109.36C72.91 87.49 72.91 75.34 72.91 72.91C72.91 72.91 72.91 72.91 72.91 72.91C94.78 72.91 106.93 72.91 109.36 72.91Z" id="b2tkw7De1"></path><path d="M218.72 182.28L218.72 0L401 0L401 182.28L218.72 182.28ZM255.18 145.82L364.54 145.82L364.54 36.45L255.18 36.45L255.18 145.82Z" id="b3NlRRtUXl"></path><path d="M328.09 72.91C328.09 72.91 328.09 72.91 328.09 72.91C328.09 94.78 328.09 106.93 328.09 109.36C328.09 109.36 328.09 109.36 328.09 109.36C306.21 109.36 294.06 109.36 291.63 109.36C291.63 109.36 291.63 109.36 291.63 109.36C291.63 87.49 291.63 75.34 291.63 72.91C291.63 72.91 291.63 72.91 291.63 72.91C313.51 72.91 325.66 72.91 328.09 72.91Z" id="aiJPdIloT"></path></defs><g><g><g><use xlink:href="#cP4vhvhrf" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#cP4vhvhrf" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b3K4Wy5wBr" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#b3K4Wy5wBr" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#ajtxNY2Qi" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#ajtxNY2Qi" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b12z2Qj5i" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#b12z2Qj5i" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#aDSxNIZuL" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#aDSxNIZuL" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#bpcQxL4BR" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#bpcQxL4BR" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b2tkw7De1" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#b2tkw7De1" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b3NlRRtUXl" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#b3NlRRtUXl" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#aiJPdIloT" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#aiJPdIloT" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></g></svg>
|
After Width: | Height: | Size: 4.6 KiB |
3
src/statics/qr-code.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 401 401" width="401" height="401"><defs><path d="M0 401L0 218.72L182.27 218.72L182.27 401L0 401ZM36.45 364.26L145.82 364.26L145.82 255.18L36.45 255.18L36.45 364.26Z" id="c4EjTiqRPu"></path><path d="M109.36 291.63C109.36 291.63 109.36 291.63 109.36 291.63C109.36 313.51 109.36 325.66 109.36 328.09C109.36 328.09 109.36 328.09 109.36 328.09C87.49 328.09 75.34 328.09 72.91 328.09C72.91 328.09 72.91 328.09 72.91 328.09C72.91 306.22 72.91 294.06 72.91 291.63C72.91 291.63 72.91 291.63 72.91 291.63C94.78 291.63 106.93 291.63 109.36 291.63Z" id="e1x2wlSIyH"></path><path d="M328.09 364.55C328.09 364.55 328.09 364.55 328.09 364.55C328.09 386.42 328.09 398.57 328.09 401C328.09 401 328.09 401 328.09 401C306.21 401 294.06 401 291.63 401C291.63 401 291.63 401 291.63 401C291.63 379.13 291.63 366.98 291.63 364.55C291.63 364.55 291.63 364.55 291.63 364.55C313.51 364.55 325.66 364.55 328.09 364.55Z" id="b2SEDxmpGS"></path><path d="M401 364.55C401 364.55 401 364.55 401 364.55C401 386.42 401 398.57 401 401C401 401 401 401 401 401C379.12 401 366.97 401 364.54 401C364.54 401 364.54 401 364.54 401C364.54 379.13 364.54 366.98 364.54 364.55C364.54 364.55 364.54 364.55 364.54 364.55C386.42 364.55 398.57 364.55 401 364.55Z" id="b1t3ctO9Ib"></path><path d="M328.09 255.18L328.09 218.72L218.72 218.72L218.72 401L255.18 401L255.18 291.63L291.63 291.63L291.63 328.09L401 328.09L401 218.72L401 218.72L364.54 218.72L364.54 255.18L328.09 255.18Z" id="f2X4EE7RDM"></path><path d="M0 182.28L0 0L182.27 0L182.27 182.28L0 182.28ZM36.45 145.82L145.82 145.82L145.82 36.45L36.45 36.45L36.45 145.82Z" id="c222nLedUh"></path><path d="M109.36 72.91C109.36 72.91 109.36 72.91 109.36 72.91C109.36 94.78 109.36 106.93 109.36 109.36C109.36 109.36 109.36 109.36 109.36 109.36C87.49 109.36 75.34 109.36 72.91 109.36C72.91 109.36 72.91 109.36 72.91 109.36C72.91 87.49 72.91 75.34 72.91 72.91C72.91 72.91 72.91 72.91 72.91 72.91C94.78 72.91 106.93 72.91 109.36 72.91Z" id="bfDGWJSeW"></path><path d="M218.72 182.28L218.72 0L401 0L401 182.28L218.72 182.28ZM255.18 145.82L364.54 145.82L364.54 36.45L255.18 36.45L255.18 145.82Z" id="a8V10SdpO"></path><path d="M328.09 72.91C328.09 72.91 328.09 72.91 328.09 72.91C328.09 94.78 328.09 106.93 328.09 109.36C328.09 109.36 328.09 109.36 328.09 109.36C306.21 109.36 294.06 109.36 291.63 109.36C291.63 109.36 291.63 109.36 291.63 109.36C291.63 87.49 291.63 75.34 291.63 72.91C291.63 72.91 291.63 72.91 291.63 72.91C313.51 72.91 325.66 72.91 328.09 72.91Z" id="eWcmpmYP8"></path></defs><g><g><g><use xlink:href="#c4EjTiqRPu" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#c4EjTiqRPu" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#e1x2wlSIyH" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#e1x2wlSIyH" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b2SEDxmpGS" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#b2SEDxmpGS" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b1t3ctO9Ib" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#b1t3ctO9Ib" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#f2X4EE7RDM" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#f2X4EE7RDM" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#c222nLedUh" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#c222nLedUh" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#bfDGWJSeW" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#bfDGWJSeW" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a8V10SdpO" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#a8V10SdpO" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#eWcmpmYP8" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#eWcmpmYP8" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></g></svg>
|
After Width: | Height: | Size: 4.6 KiB |
|
@ -12,9 +12,12 @@ export const set_wallet_data = (state, data) => {
|
|||
export const set_wallet_list = (state, data) => {
|
||||
state.wallets = objectAssignDeep.noMutate(state.wallets, data)
|
||||
}
|
||||
export const set_old_gui_import_status = (state, data) => {
|
||||
state.old_gui_import_status = data
|
||||
}
|
||||
export const set_tx_status = (state, data) => {
|
||||
state.tx_status = data
|
||||
}
|
||||
export const set_stake_status = (state, data) => {
|
||||
state.stake_status = data
|
||||
export const set_snode_status = (state, data) => {
|
||||
state.service_node_status = objectAssignDeep.noMutate(state.service_node_status, data)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,14 @@ export default {
|
|||
},
|
||||
wallets: {
|
||||
list: [],
|
||||
legacy: []
|
||||
legacy: [],
|
||||
|
||||
// List of wallets that are in a sub folder (format of the old GUI)
|
||||
directories: []
|
||||
},
|
||||
old_gui_import_status: {
|
||||
code: 0, // Success
|
||||
failed_wallets: []
|
||||
},
|
||||
wallet: {
|
||||
status: {
|
||||
|
@ -48,10 +55,22 @@ export default {
|
|||
code: 0,
|
||||
message: ""
|
||||
},
|
||||
stake_status: {
|
||||
code: 0,
|
||||
message: "",
|
||||
sending: false
|
||||
service_node_status: {
|
||||
stake: {
|
||||
code: 0,
|
||||
message: "",
|
||||
sending: false
|
||||
},
|
||||
registration: {
|
||||
code: 0,
|
||||
message: "",
|
||||
sending: false
|
||||
},
|
||||
unlock: {
|
||||
code: 0,
|
||||
message: "",
|
||||
sending: false
|
||||
}
|
||||
},
|
||||
daemon: {
|
||||
info: {
|
||||
|
|