feat 051125/recover_wallet
This commit is contained in:
parent
f23a20df10
commit
e48669d972
2
.gitignore
vendored
2
.gitignore
vendored
@ -13,7 +13,7 @@ dist
|
|||||||
dist-ssr
|
dist-ssr
|
||||||
coverage
|
coverage
|
||||||
*.local
|
*.local
|
||||||
.vite/*/**
|
.vite/
|
||||||
wallets/*
|
wallets/*
|
||||||
|
|
||||||
/cypress/videos/
|
/cypress/videos/
|
||||||
|
|||||||
@ -1,502 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
const require$$3$1 = require("electron");
|
|
||||||
const path$1 = require("node:path");
|
|
||||||
const require$$0$1 = require("path");
|
|
||||||
const require$$1$1 = require("child_process");
|
|
||||||
const require$$0 = require("tty");
|
|
||||||
const require$$1 = require("util");
|
|
||||||
const require$$3 = require("fs");
|
|
||||||
const require$$4 = require("net");
|
|
||||||
function getDefaultExportFromCjs(x) {
|
|
||||||
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
||||||
}
|
|
||||||
var src = { exports: {} };
|
|
||||||
var browser = { exports: {} };
|
|
||||||
var debug$1 = { exports: {} };
|
|
||||||
var ms;
|
|
||||||
var hasRequiredMs;
|
|
||||||
function requireMs() {
|
|
||||||
if (hasRequiredMs) return ms;
|
|
||||||
hasRequiredMs = 1;
|
|
||||||
var s = 1e3;
|
|
||||||
var m = s * 60;
|
|
||||||
var h = m * 60;
|
|
||||||
var d = h * 24;
|
|
||||||
var y = d * 365.25;
|
|
||||||
ms = function(val, options) {
|
|
||||||
options = options || {};
|
|
||||||
var type = typeof val;
|
|
||||||
if (type === "string" && val.length > 0) {
|
|
||||||
return parse(val);
|
|
||||||
} else if (type === "number" && isNaN(val) === false) {
|
|
||||||
return options.long ? fmtLong(val) : fmtShort(val);
|
|
||||||
}
|
|
||||||
throw new Error(
|
|
||||||
"val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
function parse(str) {
|
|
||||||
str = String(str);
|
|
||||||
if (str.length > 100) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(
|
|
||||||
str
|
|
||||||
);
|
|
||||||
if (!match) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var n = parseFloat(match[1]);
|
|
||||||
var type = (match[2] || "ms").toLowerCase();
|
|
||||||
switch (type) {
|
|
||||||
case "years":
|
|
||||||
case "year":
|
|
||||||
case "yrs":
|
|
||||||
case "yr":
|
|
||||||
case "y":
|
|
||||||
return n * y;
|
|
||||||
case "days":
|
|
||||||
case "day":
|
|
||||||
case "d":
|
|
||||||
return n * d;
|
|
||||||
case "hours":
|
|
||||||
case "hour":
|
|
||||||
case "hrs":
|
|
||||||
case "hr":
|
|
||||||
case "h":
|
|
||||||
return n * h;
|
|
||||||
case "minutes":
|
|
||||||
case "minute":
|
|
||||||
case "mins":
|
|
||||||
case "min":
|
|
||||||
case "m":
|
|
||||||
return n * m;
|
|
||||||
case "seconds":
|
|
||||||
case "second":
|
|
||||||
case "secs":
|
|
||||||
case "sec":
|
|
||||||
case "s":
|
|
||||||
return n * s;
|
|
||||||
case "milliseconds":
|
|
||||||
case "millisecond":
|
|
||||||
case "msecs":
|
|
||||||
case "msec":
|
|
||||||
case "ms":
|
|
||||||
return n;
|
|
||||||
default:
|
|
||||||
return void 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function fmtShort(ms2) {
|
|
||||||
if (ms2 >= d) {
|
|
||||||
return Math.round(ms2 / d) + "d";
|
|
||||||
}
|
|
||||||
if (ms2 >= h) {
|
|
||||||
return Math.round(ms2 / h) + "h";
|
|
||||||
}
|
|
||||||
if (ms2 >= m) {
|
|
||||||
return Math.round(ms2 / m) + "m";
|
|
||||||
}
|
|
||||||
if (ms2 >= s) {
|
|
||||||
return Math.round(ms2 / s) + "s";
|
|
||||||
}
|
|
||||||
return ms2 + "ms";
|
|
||||||
}
|
|
||||||
function fmtLong(ms2) {
|
|
||||||
return plural(ms2, d, "day") || plural(ms2, h, "hour") || plural(ms2, m, "minute") || plural(ms2, s, "second") || ms2 + " ms";
|
|
||||||
}
|
|
||||||
function plural(ms2, n, name) {
|
|
||||||
if (ms2 < n) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ms2 < n * 1.5) {
|
|
||||||
return Math.floor(ms2 / n) + " " + name;
|
|
||||||
}
|
|
||||||
return Math.ceil(ms2 / n) + " " + name + "s";
|
|
||||||
}
|
|
||||||
return ms;
|
|
||||||
}
|
|
||||||
var hasRequiredDebug;
|
|
||||||
function requireDebug() {
|
|
||||||
if (hasRequiredDebug) return debug$1.exports;
|
|
||||||
hasRequiredDebug = 1;
|
|
||||||
(function(module2, exports2) {
|
|
||||||
exports2 = module2.exports = createDebug.debug = createDebug["default"] = createDebug;
|
|
||||||
exports2.coerce = coerce;
|
|
||||||
exports2.disable = disable;
|
|
||||||
exports2.enable = enable;
|
|
||||||
exports2.enabled = enabled;
|
|
||||||
exports2.humanize = requireMs();
|
|
||||||
exports2.names = [];
|
|
||||||
exports2.skips = [];
|
|
||||||
exports2.formatters = {};
|
|
||||||
var prevTime;
|
|
||||||
function selectColor(namespace) {
|
|
||||||
var hash = 0, i;
|
|
||||||
for (i in namespace) {
|
|
||||||
hash = (hash << 5) - hash + namespace.charCodeAt(i);
|
|
||||||
hash |= 0;
|
|
||||||
}
|
|
||||||
return exports2.colors[Math.abs(hash) % exports2.colors.length];
|
|
||||||
}
|
|
||||||
function createDebug(namespace) {
|
|
||||||
function debug2() {
|
|
||||||
if (!debug2.enabled) return;
|
|
||||||
var self = debug2;
|
|
||||||
var curr = +/* @__PURE__ */ new Date();
|
|
||||||
var ms2 = curr - (prevTime || curr);
|
|
||||||
self.diff = ms2;
|
|
||||||
self.prev = prevTime;
|
|
||||||
self.curr = curr;
|
|
||||||
prevTime = curr;
|
|
||||||
var args = new Array(arguments.length);
|
|
||||||
for (var i = 0; i < args.length; i++) {
|
|
||||||
args[i] = arguments[i];
|
|
||||||
}
|
|
||||||
args[0] = exports2.coerce(args[0]);
|
|
||||||
if ("string" !== typeof args[0]) {
|
|
||||||
args.unshift("%O");
|
|
||||||
}
|
|
||||||
var index = 0;
|
|
||||||
args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) {
|
|
||||||
if (match === "%%") return match;
|
|
||||||
index++;
|
|
||||||
var formatter = exports2.formatters[format];
|
|
||||||
if ("function" === typeof formatter) {
|
|
||||||
var val = args[index];
|
|
||||||
match = formatter.call(self, val);
|
|
||||||
args.splice(index, 1);
|
|
||||||
index--;
|
|
||||||
}
|
|
||||||
return match;
|
|
||||||
});
|
|
||||||
exports2.formatArgs.call(self, args);
|
|
||||||
var logFn = debug2.log || exports2.log || console.log.bind(console);
|
|
||||||
logFn.apply(self, args);
|
|
||||||
}
|
|
||||||
debug2.namespace = namespace;
|
|
||||||
debug2.enabled = exports2.enabled(namespace);
|
|
||||||
debug2.useColors = exports2.useColors();
|
|
||||||
debug2.color = selectColor(namespace);
|
|
||||||
if ("function" === typeof exports2.init) {
|
|
||||||
exports2.init(debug2);
|
|
||||||
}
|
|
||||||
return debug2;
|
|
||||||
}
|
|
||||||
function enable(namespaces) {
|
|
||||||
exports2.save(namespaces);
|
|
||||||
exports2.names = [];
|
|
||||||
exports2.skips = [];
|
|
||||||
var split = (typeof namespaces === "string" ? namespaces : "").split(/[\s,]+/);
|
|
||||||
var len = split.length;
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
if (!split[i]) continue;
|
|
||||||
namespaces = split[i].replace(/\*/g, ".*?");
|
|
||||||
if (namespaces[0] === "-") {
|
|
||||||
exports2.skips.push(new RegExp("^" + namespaces.substr(1) + "$"));
|
|
||||||
} else {
|
|
||||||
exports2.names.push(new RegExp("^" + namespaces + "$"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function disable() {
|
|
||||||
exports2.enable("");
|
|
||||||
}
|
|
||||||
function enabled(name) {
|
|
||||||
var i, len;
|
|
||||||
for (i = 0, len = exports2.skips.length; i < len; i++) {
|
|
||||||
if (exports2.skips[i].test(name)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (i = 0, len = exports2.names.length; i < len; i++) {
|
|
||||||
if (exports2.names[i].test(name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
function coerce(val) {
|
|
||||||
if (val instanceof Error) return val.stack || val.message;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
})(debug$1, debug$1.exports);
|
|
||||||
return debug$1.exports;
|
|
||||||
}
|
|
||||||
var hasRequiredBrowser;
|
|
||||||
function requireBrowser() {
|
|
||||||
if (hasRequiredBrowser) return browser.exports;
|
|
||||||
hasRequiredBrowser = 1;
|
|
||||||
(function(module2, exports2) {
|
|
||||||
exports2 = module2.exports = requireDebug();
|
|
||||||
exports2.log = log;
|
|
||||||
exports2.formatArgs = formatArgs;
|
|
||||||
exports2.save = save;
|
|
||||||
exports2.load = load;
|
|
||||||
exports2.useColors = useColors;
|
|
||||||
exports2.storage = "undefined" != typeof chrome && "undefined" != typeof chrome.storage ? chrome.storage.local : localstorage();
|
|
||||||
exports2.colors = [
|
|
||||||
"lightseagreen",
|
|
||||||
"forestgreen",
|
|
||||||
"goldenrod",
|
|
||||||
"dodgerblue",
|
|
||||||
"darkorchid",
|
|
||||||
"crimson"
|
|
||||||
];
|
|
||||||
function useColors() {
|
|
||||||
if (typeof window !== "undefined" && window.process && window.process.type === "renderer") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // is firebug? http://stackoverflow.com/a/398120/376773
|
|
||||||
typeof window !== "undefined" && window.console && (window.console.firebug || window.console.exception && window.console.table) || // is firefox >= v31?
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
|
|
||||||
typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || // double check webkit in userAgent just in case we are in a worker
|
|
||||||
typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
|
|
||||||
}
|
|
||||||
exports2.formatters.j = function(v) {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(v);
|
|
||||||
} catch (err) {
|
|
||||||
return "[UnexpectedJSONParseError]: " + err.message;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function formatArgs(args) {
|
|
||||||
var useColors2 = this.useColors;
|
|
||||||
args[0] = (useColors2 ? "%c" : "") + this.namespace + (useColors2 ? " %c" : " ") + args[0] + (useColors2 ? "%c " : " ") + "+" + exports2.humanize(this.diff);
|
|
||||||
if (!useColors2) return;
|
|
||||||
var c = "color: " + this.color;
|
|
||||||
args.splice(1, 0, c, "color: inherit");
|
|
||||||
var index = 0;
|
|
||||||
var lastC = 0;
|
|
||||||
args[0].replace(/%[a-zA-Z%]/g, function(match) {
|
|
||||||
if ("%%" === match) return;
|
|
||||||
index++;
|
|
||||||
if ("%c" === match) {
|
|
||||||
lastC = index;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
args.splice(lastC, 0, c);
|
|
||||||
}
|
|
||||||
function log() {
|
|
||||||
return "object" === typeof console && console.log && Function.prototype.apply.call(console.log, console, arguments);
|
|
||||||
}
|
|
||||||
function save(namespaces) {
|
|
||||||
try {
|
|
||||||
if (null == namespaces) {
|
|
||||||
exports2.storage.removeItem("debug");
|
|
||||||
} else {
|
|
||||||
exports2.storage.debug = namespaces;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function load() {
|
|
||||||
var r;
|
|
||||||
try {
|
|
||||||
r = exports2.storage.debug;
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
if (!r && typeof process !== "undefined" && "env" in process) {
|
|
||||||
r = process.env.DEBUG;
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
exports2.enable(load());
|
|
||||||
function localstorage() {
|
|
||||||
try {
|
|
||||||
return window.localStorage;
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})(browser, browser.exports);
|
|
||||||
return browser.exports;
|
|
||||||
}
|
|
||||||
var node = { exports: {} };
|
|
||||||
var hasRequiredNode;
|
|
||||||
function requireNode() {
|
|
||||||
if (hasRequiredNode) return node.exports;
|
|
||||||
hasRequiredNode = 1;
|
|
||||||
(function(module2, exports2) {
|
|
||||||
var tty = require$$0;
|
|
||||||
var util = require$$1;
|
|
||||||
exports2 = module2.exports = requireDebug();
|
|
||||||
exports2.init = init;
|
|
||||||
exports2.log = log;
|
|
||||||
exports2.formatArgs = formatArgs;
|
|
||||||
exports2.save = save;
|
|
||||||
exports2.load = load;
|
|
||||||
exports2.useColors = useColors;
|
|
||||||
exports2.colors = [6, 2, 3, 4, 5, 1];
|
|
||||||
exports2.inspectOpts = Object.keys(process.env).filter(function(key) {
|
|
||||||
return /^debug_/i.test(key);
|
|
||||||
}).reduce(function(obj, key) {
|
|
||||||
var prop = key.substring(6).toLowerCase().replace(/_([a-z])/g, function(_, k) {
|
|
||||||
return k.toUpperCase();
|
|
||||||
});
|
|
||||||
var val = process.env[key];
|
|
||||||
if (/^(yes|on|true|enabled)$/i.test(val)) val = true;
|
|
||||||
else if (/^(no|off|false|disabled)$/i.test(val)) val = false;
|
|
||||||
else if (val === "null") val = null;
|
|
||||||
else val = Number(val);
|
|
||||||
obj[prop] = val;
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
var fd = parseInt(process.env.DEBUG_FD, 10) || 2;
|
|
||||||
if (1 !== fd && 2 !== fd) {
|
|
||||||
util.deprecate(function() {
|
|
||||||
}, "except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)")();
|
|
||||||
}
|
|
||||||
var stream = 1 === fd ? process.stdout : 2 === fd ? process.stderr : createWritableStdioStream(fd);
|
|
||||||
function useColors() {
|
|
||||||
return "colors" in exports2.inspectOpts ? Boolean(exports2.inspectOpts.colors) : tty.isatty(fd);
|
|
||||||
}
|
|
||||||
exports2.formatters.o = function(v) {
|
|
||||||
this.inspectOpts.colors = this.useColors;
|
|
||||||
return util.inspect(v, this.inspectOpts).split("\n").map(function(str) {
|
|
||||||
return str.trim();
|
|
||||||
}).join(" ");
|
|
||||||
};
|
|
||||||
exports2.formatters.O = function(v) {
|
|
||||||
this.inspectOpts.colors = this.useColors;
|
|
||||||
return util.inspect(v, this.inspectOpts);
|
|
||||||
};
|
|
||||||
function formatArgs(args) {
|
|
||||||
var name = this.namespace;
|
|
||||||
var useColors2 = this.useColors;
|
|
||||||
if (useColors2) {
|
|
||||||
var c = this.color;
|
|
||||||
var prefix = " \x1B[3" + c + ";1m" + name + " \x1B[0m";
|
|
||||||
args[0] = prefix + args[0].split("\n").join("\n" + prefix);
|
|
||||||
args.push("\x1B[3" + c + "m+" + exports2.humanize(this.diff) + "\x1B[0m");
|
|
||||||
} else {
|
|
||||||
args[0] = (/* @__PURE__ */ new Date()).toUTCString() + " " + name + " " + args[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function log() {
|
|
||||||
return stream.write(util.format.apply(util, arguments) + "\n");
|
|
||||||
}
|
|
||||||
function save(namespaces) {
|
|
||||||
if (null == namespaces) {
|
|
||||||
delete process.env.DEBUG;
|
|
||||||
} else {
|
|
||||||
process.env.DEBUG = namespaces;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function load() {
|
|
||||||
return process.env.DEBUG;
|
|
||||||
}
|
|
||||||
function createWritableStdioStream(fd2) {
|
|
||||||
var stream2;
|
|
||||||
var tty_wrap = process.binding("tty_wrap");
|
|
||||||
switch (tty_wrap.guessHandleType(fd2)) {
|
|
||||||
case "TTY":
|
|
||||||
stream2 = new tty.WriteStream(fd2);
|
|
||||||
stream2._type = "tty";
|
|
||||||
if (stream2._handle && stream2._handle.unref) {
|
|
||||||
stream2._handle.unref();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "FILE":
|
|
||||||
var fs = require$$3;
|
|
||||||
stream2 = new fs.SyncWriteStream(fd2, { autoClose: false });
|
|
||||||
stream2._type = "fs";
|
|
||||||
break;
|
|
||||||
case "PIPE":
|
|
||||||
case "TCP":
|
|
||||||
var net = require$$4;
|
|
||||||
stream2 = new net.Socket({
|
|
||||||
fd: fd2,
|
|
||||||
readable: false,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
stream2.readable = false;
|
|
||||||
stream2.read = null;
|
|
||||||
stream2._type = "pipe";
|
|
||||||
if (stream2._handle && stream2._handle.unref) {
|
|
||||||
stream2._handle.unref();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("Implement me. Unknown stream file type!");
|
|
||||||
}
|
|
||||||
stream2.fd = fd2;
|
|
||||||
stream2._isStdio = true;
|
|
||||||
return stream2;
|
|
||||||
}
|
|
||||||
function init(debug2) {
|
|
||||||
debug2.inspectOpts = {};
|
|
||||||
var keys = Object.keys(exports2.inspectOpts);
|
|
||||||
for (var i = 0; i < keys.length; i++) {
|
|
||||||
debug2.inspectOpts[keys[i]] = exports2.inspectOpts[keys[i]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports2.enable(load());
|
|
||||||
})(node, node.exports);
|
|
||||||
return node.exports;
|
|
||||||
}
|
|
||||||
if (typeof process !== "undefined" && process.type === "renderer") {
|
|
||||||
src.exports = requireBrowser();
|
|
||||||
} else {
|
|
||||||
src.exports = requireNode();
|
|
||||||
}
|
|
||||||
var srcExports = src.exports;
|
|
||||||
var path = require$$0$1;
|
|
||||||
var spawn = require$$1$1.spawn;
|
|
||||||
var debug = srcExports("electron-squirrel-startup");
|
|
||||||
var app = require$$3$1.app;
|
|
||||||
var run = function(args, done) {
|
|
||||||
var updateExe = path.resolve(path.dirname(process.execPath), "..", "Update.exe");
|
|
||||||
debug("Spawning `%s` with args `%s`", updateExe, args);
|
|
||||||
spawn(updateExe, args, {
|
|
||||||
detached: true
|
|
||||||
}).on("close", done);
|
|
||||||
};
|
|
||||||
var check = function() {
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
var cmd = process.argv[1];
|
|
||||||
debug("processing squirrel command `%s`", cmd);
|
|
||||||
var target = path.basename(process.execPath);
|
|
||||||
if (cmd === "--squirrel-install" || cmd === "--squirrel-updated") {
|
|
||||||
run(["--createShortcut=" + target], app.quit);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (cmd === "--squirrel-uninstall") {
|
|
||||||
run(["--removeShortcut=" + target], app.quit);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (cmd === "--squirrel-obsolete") {
|
|
||||||
app.quit();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
var electronSquirrelStartup = check();
|
|
||||||
const started = /* @__PURE__ */ getDefaultExportFromCjs(electronSquirrelStartup);
|
|
||||||
if (started) {
|
|
||||||
require$$3$1.app.quit();
|
|
||||||
}
|
|
||||||
const createWindow = () => {
|
|
||||||
const mainWindow = new require$$3$1.BrowserWindow({
|
|
||||||
width: 800,
|
|
||||||
height: 600,
|
|
||||||
webPreferences: {
|
|
||||||
preload: path$1.join(__dirname, "preload.js")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
{
|
|
||||||
mainWindow.loadURL("http://localhost:3008");
|
|
||||||
}
|
|
||||||
mainWindow.webContents.openDevTools();
|
|
||||||
};
|
|
||||||
require$$3$1.app.on("ready", createWindow);
|
|
||||||
require$$3$1.app.on("window-all-closed", () => {
|
|
||||||
if (process.platform !== "darwin") {
|
|
||||||
require$$3$1.app.quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
require$$3$1.app.on("activate", () => {
|
|
||||||
if (require$$3$1.BrowserWindow.getAllWindows().length === 0) {
|
|
||||||
createWindow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
@ -1,54 +1,77 @@
|
|||||||
import { ipcMain } from 'electron'
|
import { ipcMain, dialog, app } from 'electron'
|
||||||
import { Wallet } from 'ethers'
|
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { encrypt, fromEncryptedJson } from './utils/keystore'
|
||||||
|
|
||||||
|
// Create keystore into default wallets directory
|
||||||
ipcMain.handle('wallet:createKeystore', async (_event, seed, password) => {
|
ipcMain.handle('wallet:createKeystore', async (_event, seed, password) => {
|
||||||
try {
|
try {
|
||||||
const wallet = Wallet.fromPhrase(seed)
|
const keystore = await encrypt(seed, password)
|
||||||
const keystore = await wallet.encrypt(password)
|
|
||||||
|
|
||||||
const savePath = path.join(process.cwd(), 'wallets')
|
const savePath = path.join(process.cwd(), 'wallets')
|
||||||
fs.mkdirSync(savePath, { recursive: true })
|
fs.mkdirSync(savePath, { recursive: true })
|
||||||
|
|
||||||
const filePath = path.join(savePath, `${wallet.address}.json`)
|
// Use timestamp for filename
|
||||||
|
const timestamp = Date.now()
|
||||||
|
const fileName = `neptune-wallet-${timestamp}.json`
|
||||||
|
const filePath = path.join(savePath, fileName)
|
||||||
fs.writeFileSync(filePath, keystore)
|
fs.writeFileSync(filePath, keystore)
|
||||||
|
|
||||||
return { address: wallet.address, filePath, error: null }
|
return { filePath }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating keystore:', error)
|
console.error('Error creating keystore:', error)
|
||||||
return { address: null, filePath: null, error: String(error) }
|
throw error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// New handler: let user choose folder and filename to save keystore
|
||||||
|
ipcMain.handle('wallet:saveKeystoreAs', async (_event, seed: string, password: string) => {
|
||||||
|
try {
|
||||||
|
const keystore = await encrypt(seed, password)
|
||||||
|
|
||||||
|
// Use timestamp for default filename
|
||||||
|
const timestamp = Date.now()
|
||||||
|
const defaultName = `neptune-wallet-${timestamp}.json`
|
||||||
|
const { canceled, filePath } = await dialog.showSaveDialog({
|
||||||
|
title: 'Save Keystore File',
|
||||||
|
defaultPath: path.join(app.getPath('documents'), defaultName),
|
||||||
|
filters: [{ name: 'JSON', extensions: ['json'] }],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (canceled || !filePath) return { filePath: null }
|
||||||
|
|
||||||
|
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
||||||
|
fs.writeFileSync(filePath, keystore)
|
||||||
|
|
||||||
|
return { filePath }
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving keystore (Save As):', error)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('wallet:decryptKeystore', async (_event, filePath, password) => {
|
ipcMain.handle('wallet:decryptKeystore', async (_event, filePath, password) => {
|
||||||
try {
|
try {
|
||||||
const json = fs.readFileSync(filePath, 'utf-8')
|
const json = fs.readFileSync(filePath, 'utf-8')
|
||||||
const wallet = await Wallet.fromEncryptedJson(json, password)
|
const phrase = await fromEncryptedJson(json, password)
|
||||||
|
|
||||||
let phrase: string | undefined
|
return { phrase }
|
||||||
if ('mnemonic' in wallet && wallet.mnemonic) {
|
|
||||||
phrase = wallet.mnemonic.phrase
|
|
||||||
}
|
|
||||||
|
|
||||||
return { address: wallet.address, phrase, error: null }
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error decrypting keystore ipc:', error)
|
console.error('Error decrypting keystore ipc:', error)
|
||||||
return { address: null, phrase: null, error: String(error) }
|
throw error
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('wallet:checkKeystore', async () => {
|
ipcMain.handle('wallet:checkKeystore', async () => {
|
||||||
try {
|
try {
|
||||||
const walletDir = path.join(process.cwd(), 'wallets')
|
const walletDir = path.join(process.cwd(), 'wallets')
|
||||||
if (!fs.existsSync(walletDir))
|
if (!fs.existsSync(walletDir)) return { exists: false, filePath: null }
|
||||||
return { exists: false, filePath: null, error: 'Wallet directory not found' }
|
|
||||||
|
|
||||||
const file = fs.readdirSync(walletDir).find((f) => f.endsWith('.json'))
|
const file = fs.readdirSync(walletDir).find((f) => f.endsWith('.json'))
|
||||||
if (!file) return { exists: false, filePath: null, error: 'Keystore file not found' }
|
if (!file) return { exists: false, filePath: null }
|
||||||
|
|
||||||
const filePath = path.join(walletDir, file)
|
const filePath = path.join(walletDir, file)
|
||||||
return { exists: true, filePath, error: null }
|
return { exists: true, filePath}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking keystore:', error)
|
console.error('Error checking keystore:', error)
|
||||||
return { exists: false, filePath: null, error: String(error) }
|
return { exists: false, filePath: null, error: String(error) }
|
||||||
|
|||||||
@ -9,7 +9,7 @@ if (started) {
|
|||||||
|
|
||||||
const createWindow = () => {
|
const createWindow = () => {
|
||||||
const mainWindow = new BrowserWindow({
|
const mainWindow = new BrowserWindow({
|
||||||
width: 1000,
|
width: 800,
|
||||||
height: 800,
|
height: 800,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { contextBridge, ipcRenderer } from 'electron'
|
|||||||
contextBridge.exposeInMainWorld('walletApi', {
|
contextBridge.exposeInMainWorld('walletApi', {
|
||||||
createKeystore: (seed: string, password: string) =>
|
createKeystore: (seed: string, password: string) =>
|
||||||
ipcRenderer.invoke('wallet:createKeystore', seed, password),
|
ipcRenderer.invoke('wallet:createKeystore', seed, password),
|
||||||
|
saveKeystoreAs: (seed: string, password: string) =>
|
||||||
|
ipcRenderer.invoke('wallet:saveKeystoreAs', seed, password),
|
||||||
decryptKeystore: (filePath: string, password: string) =>
|
decryptKeystore: (filePath: string, password: string) =>
|
||||||
ipcRenderer.invoke('wallet:decryptKeystore', filePath, password),
|
ipcRenderer.invoke('wallet:decryptKeystore', filePath, password),
|
||||||
checkKeystore: () => ipcRenderer.invoke('wallet:checkKeystore'),
|
checkKeystore: () => ipcRenderer.invoke('wallet:checkKeystore'),
|
||||||
|
|||||||
1
electron/utils/index.ts
Normal file
1
electron/utils/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './keystore'
|
||||||
54
electron/utils/keystore.ts
Normal file
54
electron/utils/keystore.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import crypto from 'crypto'
|
||||||
|
|
||||||
|
export async function encrypt(seed: string, password: string) {
|
||||||
|
const salt = crypto.randomBytes(16)
|
||||||
|
const iv = crypto.randomBytes(12)
|
||||||
|
|
||||||
|
// derive 32-byte key từ password
|
||||||
|
const key = await new Promise<Buffer>((resolve, reject) => {
|
||||||
|
crypto.scrypt(password, salt, 32, { N: 16384, r: 8, p: 1 }, (err, derivedKey) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
else resolve(derivedKey as Buffer)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv)
|
||||||
|
const ciphertext = Buffer.concat([cipher.update(seed, 'utf8'), cipher.final()])
|
||||||
|
const authTag = cipher.getAuthTag()
|
||||||
|
|
||||||
|
const encryptedMnemonic = Buffer.concat([salt, iv, authTag, ciphertext]).toString('hex')
|
||||||
|
|
||||||
|
const keystore = {
|
||||||
|
type: 'neptune-wallet',
|
||||||
|
encryption: 'aes-256-gcm',
|
||||||
|
version: 1,
|
||||||
|
wallet: {
|
||||||
|
mnemonic: encryptedMnemonic,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(keystore, null, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fromEncryptedJson(json: string, password: string) {
|
||||||
|
const data = JSON.parse(json)
|
||||||
|
const encrypted = Buffer.from(data.wallet.mnemonic, 'hex')
|
||||||
|
|
||||||
|
const salt = encrypted.subarray(0, 16)
|
||||||
|
const iv = encrypted.subarray(16, 28)
|
||||||
|
const authTag = encrypted.subarray(28, 44)
|
||||||
|
const ciphertext = encrypted.subarray(44)
|
||||||
|
|
||||||
|
const key = await new Promise<Buffer>((resolve, reject) => {
|
||||||
|
crypto.scrypt(password, salt, 32, { N: 16384, r: 8, p: 1 }, (err, derivedKey) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
else resolve(derivedKey as Buffer)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)
|
||||||
|
decipher.setAuthTag(authTag)
|
||||||
|
|
||||||
|
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()])
|
||||||
|
return decrypted.toString('utf8')
|
||||||
|
}
|
||||||
116
package-lock.json
generated
116
package-lock.json
generated
@ -14,9 +14,8 @@
|
|||||||
"@neptune/wasm": "file:./packages/neptune-wasm",
|
"@neptune/wasm": "file:./packages/neptune-wasm",
|
||||||
"ant-design-vue": "^4.2.6",
|
"ant-design-vue": "^4.2.6",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"dayjs": "^1.11.10",
|
"crypto": "^1.0.1",
|
||||||
"electron-squirrel-startup": "^1.0.1",
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
"ethers": "^6.15.0",
|
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-router": "^4.3.0",
|
"vue-router": "^4.3.0",
|
||||||
@ -53,12 +52,6 @@
|
|||||||
"vue-tsc": "^2.0.11"
|
"vue-tsc": "^2.0.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@adraffy/ens-normalize": {
|
|
||||||
"version": "1.10.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz",
|
|
||||||
"integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@ant-design/colors": {
|
"node_modules/@ant-design/colors": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz",
|
||||||
@ -2533,30 +2526,6 @@
|
|||||||
"resolved": "packages/neptune-wasm",
|
"resolved": "packages/neptune-wasm",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@noble/curves": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@noble/hashes": "1.3.2"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@noble/hashes": {
|
|
||||||
"version": "1.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
|
|
||||||
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 16"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -4338,12 +4307,6 @@
|
|||||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aes-js": {
|
|
||||||
"version": "4.0.0-beta.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz",
|
|
||||||
"integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
@ -5338,6 +5301,13 @@
|
|||||||
"node": ">=12.10"
|
"node": ">=12.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crypto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
|
||||||
|
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@ -6734,49 +6704,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ethers": {
|
|
||||||
"version": "6.15.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.15.0.tgz",
|
|
||||||
"integrity": "sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://github.com/sponsors/ethers-io/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://www.buymeacoffee.com/ricmoo"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@adraffy/ens-normalize": "1.10.1",
|
|
||||||
"@noble/curves": "1.2.0",
|
|
||||||
"@noble/hashes": "1.3.2",
|
|
||||||
"@types/node": "22.7.5",
|
|
||||||
"aes-js": "4.0.0-beta.5",
|
|
||||||
"tslib": "2.7.0",
|
|
||||||
"ws": "8.17.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ethers/node_modules/@types/node": {
|
|
||||||
"version": "22.7.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
|
|
||||||
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~6.19.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ethers/node_modules/undici-types": {
|
|
||||||
"version": "6.19.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
|
||||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/eventemitter3": {
|
"node_modules/eventemitter3": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
@ -11351,12 +11278,6 @@
|
|||||||
"typescript": ">=4.2.0"
|
"typescript": ">=4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tslib": {
|
|
||||||
"version": "2.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
|
||||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
|
||||||
"license": "0BSD"
|
|
||||||
},
|
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
@ -12181,27 +12102,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
|
||||||
"version": "8.17.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
|
||||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/wsl-utils": {
|
"node_modules/wsl-utils": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
|
||||||
|
|||||||
@ -24,9 +24,8 @@
|
|||||||
"@neptune/wasm": "file:./packages/neptune-wasm",
|
"@neptune/wasm": "file:./packages/neptune-wasm",
|
||||||
"ant-design-vue": "^4.2.6",
|
"ant-design-vue": "^4.2.6",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"dayjs": "^1.11.10",
|
"crypto": "^1.0.1",
|
||||||
"electron-squirrel-startup": "^1.0.1",
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
"ethers": "^6.15.0",
|
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-router": "^4.3.0",
|
"vue-router": "^4.3.0",
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { STATUS_CODE_SUCCESS } from '@/utils/constants/code'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
axios.defaults.withCredentials = false
|
axios.defaults.withCredentials = false
|
||||||
@ -10,6 +11,28 @@ const instance = axios.create({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
function (response) {
|
||||||
|
if (response?.status !== STATUS_CODE_SUCCESS) return Promise.reject(response?.data)
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
if (error?.response?.data) {
|
||||||
|
return Promise.reject(error?.response?.data)
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export const setLocaleApi = (locale: string) => {
|
export const setLocaleApi = (locale: string) => {
|
||||||
instance.defaults.headers.common['lang'] = locale
|
instance.defaults.headers.common['lang'] = locale
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
import { callJsonRpc } from '@/api/request'
|
import { callJsonRpc } from '@/api/request'
|
||||||
|
|
||||||
export const getUtxosFromViewKey = (
|
export const getUtxosFromViewKey = async (
|
||||||
viewKey: string,
|
viewKey: string,
|
||||||
startBlock: number = 0,
|
startBlock: number = 0,
|
||||||
endBlock: number | null = null,
|
endBlock: number | null = null,
|
||||||
maxSearchDepth: number = 1000
|
maxSearchDepth: number = 1000
|
||||||
) => {
|
): Promise<any> => {
|
||||||
const params = {
|
const params = {
|
||||||
viewKey,
|
viewKey,
|
||||||
startBlock,
|
startBlock,
|
||||||
endBlock,
|
endBlock,
|
||||||
maxSearchDepth,
|
maxSearchDepth,
|
||||||
}
|
}
|
||||||
return callJsonRpc('wallet_getUtxosFromViewKey', params)
|
return await callJsonRpc('wallet_getUtxosFromViewKey', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBalance = async (): Promise<any> => {
|
export const getBalance = async (): Promise<any> => {
|
||||||
|
|||||||
@ -1,8 +1,17 @@
|
|||||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100;200;300;400;500;600;700;800;900&display=swap');
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-family: 'Noto Sans JP';
|
font-family: var(--font-primary);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
max-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
@ -31,3 +40,40 @@ h2 {
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== CUSTOM SCROLLBAR ====================
|
||||||
|
|
||||||
|
// Webkit browsers (Chrome, Safari, Edge)
|
||||||
|
* {
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 127, 207, 0.4);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background-clip: padding-box;
|
||||||
|
box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: background var(--transition-fast), box-shadow var(--transition-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 127, 207, 0.6);
|
||||||
|
box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: var(--primary-color);
|
||||||
|
box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-corner {
|
||||||
|
background: var(--bg-light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -74,9 +74,8 @@
|
|||||||
// ==================== TYPOGRAPHY ====================
|
// ==================== TYPOGRAPHY ====================
|
||||||
|
|
||||||
// Font Families
|
// Font Families
|
||||||
--font-primary: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
--font-primary: --apple-system, BlinkMacSystemFont, 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
--font-mono: 'Courier New', monospace;
|
--font-mono: 'Courier New', monospace;
|
||||||
--font-noto: 'Noto Sans JP';
|
|
||||||
|
|
||||||
// Font Sizes
|
// Font Sizes
|
||||||
--font-xs: 0.75rem; // 12px
|
--font-xs: 0.75rem; // 12px
|
||||||
@ -87,7 +86,7 @@
|
|||||||
--font-xl: 1.1rem; // 17.6px
|
--font-xl: 1.1rem; // 17.6px
|
||||||
--font-2xl: 1.2rem; // 19.2px
|
--font-2xl: 1.2rem; // 19.2px
|
||||||
--font-3xl: 1.5rem; // 24px
|
--font-3xl: 1.5rem; // 24px
|
||||||
--font-4xl: 3rem; // 48px
|
--font-4xl: 2rem; // 32px
|
||||||
|
|
||||||
// Font Weights
|
// Font Weights
|
||||||
--font-normal: 400;
|
--font-normal: 400;
|
||||||
@ -106,26 +105,6 @@
|
|||||||
--tracking-wide: 0.5px;
|
--tracking-wide: 0.5px;
|
||||||
--tracking-wider: 1px;
|
--tracking-wider: 1px;
|
||||||
|
|
||||||
// ==================== Z-INDEX ====================
|
|
||||||
|
|
||||||
--z-base: 1;
|
|
||||||
--z-dropdown: 10;
|
|
||||||
--z-sticky: 20;
|
|
||||||
--z-fixed: 30;
|
|
||||||
--z-modal-backdrop: 40;
|
|
||||||
--z-modal: 50;
|
|
||||||
--z-popover: 60;
|
|
||||||
--z-tooltip: 70;
|
|
||||||
|
|
||||||
// ==================== BREAKPOINTS ====================
|
|
||||||
|
|
||||||
--breakpoint-xs: 480px;
|
|
||||||
--breakpoint-sm: 640px;
|
|
||||||
--breakpoint-md: 768px;
|
|
||||||
--breakpoint-lg: 1024px;
|
|
||||||
--breakpoint-xl: 1280px;
|
|
||||||
--breakpoint-2xl: 1536px;
|
|
||||||
|
|
||||||
// ==================== COMPONENTS SPECIFIC ====================
|
// ==================== COMPONENTS SPECIFIC ====================
|
||||||
|
|
||||||
// Card
|
// Card
|
||||||
@ -139,13 +118,6 @@
|
|||||||
--btn-padding-y: 0.75rem;
|
--btn-padding-y: 0.75rem;
|
||||||
--btn-padding-x: 1rem;
|
--btn-padding-x: 1rem;
|
||||||
--btn-radius: var(--radius-md);
|
--btn-radius: var(--radius-md);
|
||||||
--btn-transition: var(--transition-all);
|
|
||||||
|
|
||||||
// QR Code
|
|
||||||
--qr-size: 200px;
|
|
||||||
--qr-border: 3px solid var(--border-light);
|
|
||||||
--qr-radius: var(--radius-lg);
|
|
||||||
--qr-shadow: var(--shadow-sm);
|
|
||||||
|
|
||||||
// Tabs
|
// Tabs
|
||||||
--tabs-height: 3px;
|
--tabs-height: 3px;
|
||||||
|
|||||||
@ -1,333 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, onMounted } from 'vue'
|
|
||||||
import { formatNumberToLocaleString } from '@/utils'
|
|
||||||
import { useNeptuneStore } from '@/stores/neptuneStore'
|
|
||||||
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
|
||||||
import { message } from 'ant-design-vue'
|
|
||||||
import { ButtonCommon, SpinnerCommon } from '@/components'
|
|
||||||
|
|
||||||
const neptuneStore = useNeptuneStore()
|
|
||||||
const { getBalance, getBlockHeight } = useNeptuneWallet()
|
|
||||||
|
|
||||||
const availableBalance = ref(0)
|
|
||||||
const pendingBalance = ref(0)
|
|
||||||
const currentDaaScore = ref(0)
|
|
||||||
const isLoadingData = ref(false)
|
|
||||||
|
|
||||||
const receiveAddress = computed(() => neptuneStore.getWallet?.address || '')
|
|
||||||
const isAddressExpanded = ref(false)
|
|
||||||
|
|
||||||
const walletStatus = computed(() => {
|
|
||||||
if (neptuneStore.getLoading) return 'Loading...'
|
|
||||||
if (neptuneStore.getError) return 'Error'
|
|
||||||
if (neptuneStore.getWallet?.address) return 'Online'
|
|
||||||
return 'Offline'
|
|
||||||
})
|
|
||||||
|
|
||||||
const toggleAddressExpanded = () => {
|
|
||||||
isAddressExpanded.value = !isAddressExpanded.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const copyAddress = async () => {
|
|
||||||
if (!receiveAddress.value) {
|
|
||||||
message.error('No address available')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(receiveAddress.value)
|
|
||||||
message.success('Address copied to clipboard!')
|
|
||||||
} catch (err) {
|
|
||||||
message.error('Failed to copy address')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSend = () => {
|
|
||||||
// TODO: Implement send transaction functionality
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadWalletData = async () => {
|
|
||||||
if (!receiveAddress.value) return
|
|
||||||
|
|
||||||
isLoadingData.value = true
|
|
||||||
try {
|
|
||||||
const [balanceResult, blockHeightResult] = await Promise.all([
|
|
||||||
getBalance(),
|
|
||||||
getBlockHeight(),
|
|
||||||
])
|
|
||||||
|
|
||||||
if (balanceResult) {
|
|
||||||
if (typeof balanceResult === 'number') {
|
|
||||||
availableBalance.value = balanceResult
|
|
||||||
} else if (balanceResult.confirmed !== undefined) {
|
|
||||||
availableBalance.value = balanceResult.confirmed || 0
|
|
||||||
pendingBalance.value = balanceResult.unconfirmed || 0
|
|
||||||
} else if (balanceResult.balance !== undefined) {
|
|
||||||
availableBalance.value = parseFloat(balanceResult.balance) || 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blockHeightResult) {
|
|
||||||
currentDaaScore.value =
|
|
||||||
typeof blockHeightResult === 'number'
|
|
||||||
? blockHeightResult
|
|
||||||
: blockHeightResult.height || 0
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
message.error('Failed to load wallet data')
|
|
||||||
} finally {
|
|
||||||
isLoadingData.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadWalletData()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="wallet-info-container">
|
|
||||||
<div v-if="isLoadingData && !receiveAddress" class="loading-state">
|
|
||||||
<SpinnerCommon size="medium" />
|
|
||||||
<p>Loading wallet data...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="!receiveAddress" class="empty-state">
|
|
||||||
<p>No wallet found. Please create or import a wallet.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<!-- Balance Section -->
|
|
||||||
<div class="balance-section">
|
|
||||||
<div class="balance-label">Available</div>
|
|
||||||
<div class="balance-amount">
|
|
||||||
<span v-if="isLoadingData">Loading...</span>
|
|
||||||
<span v-else>{{ formatNumberToLocaleString(availableBalance) }} NEPT</span>
|
|
||||||
</div>
|
|
||||||
<div class="pending-section">
|
|
||||||
<span class="pending-label">Pending</span>
|
|
||||||
<span class="pending-amount">
|
|
||||||
{{ isLoadingData ? '...' : formatNumberToLocaleString(pendingBalance) }}
|
|
||||||
NEPT
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Receive Address Section -->
|
|
||||||
<div class="receive-section">
|
|
||||||
<div class="address-label">Receive Address:</div>
|
|
||||||
<div
|
|
||||||
class="address-value"
|
|
||||||
:class="{ expanded: isAddressExpanded, collapsed: !isAddressExpanded }"
|
|
||||||
@click="copyAddress"
|
|
||||||
>
|
|
||||||
{{ receiveAddress || 'No address available' }}
|
|
||||||
<svg
|
|
||||||
class="copy-icon"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
>
|
|
||||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
||||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
v-if="receiveAddress && receiveAddress.length > 80"
|
|
||||||
class="toggle-address-btn"
|
|
||||||
@click.stop="toggleAddressExpanded"
|
|
||||||
>
|
|
||||||
{{ isAddressExpanded ? 'Show less' : 'Show more' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<!-- Action Buttons -->
|
|
||||||
<div class="action-buttons">
|
|
||||||
<ButtonCommon
|
|
||||||
type="primary"
|
|
||||||
size="large"
|
|
||||||
block
|
|
||||||
@click="handleSend"
|
|
||||||
class="btn-send"
|
|
||||||
>
|
|
||||||
SEND
|
|
||||||
</ButtonCommon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Wallet Status -->
|
|
||||||
<div class="wallet-status">
|
|
||||||
<span
|
|
||||||
>Wallet Status: <strong>{{ walletStatus }}</strong></span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
>DAA Score:
|
|
||||||
<strong>{{
|
|
||||||
isLoadingData ? '...' : formatNumberToLocaleString(currentDaaScore)
|
|
||||||
}}</strong></span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.wallet-info-container {
|
|
||||||
@include card-base;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-state,
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: var(--spacing-3xl);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: var(--spacing-lg) 0 0;
|
|
||||||
font-size: var(--font-base);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.balance-section {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: var(--spacing-3xl);
|
|
||||||
padding-bottom: var(--spacing-2xl);
|
|
||||||
border-bottom: 2px solid var(--border-color);
|
|
||||||
|
|
||||||
.balance-label {
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: var(--font-base);
|
|
||||||
margin-bottom: var(--spacing-sm);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: var(--tracking-wider);
|
|
||||||
}
|
|
||||||
|
|
||||||
.balance-amount {
|
|
||||||
font-size: var(--font-4xl);
|
|
||||||
font-weight: var(--font-bold);
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
letter-spacing: var(--tracking-tight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pending-section {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: var(--font-md);
|
|
||||||
|
|
||||||
.pending-label {
|
|
||||||
font-weight: var(--font-medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pending-amount {
|
|
||||||
font-weight: var(--font-semibold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.receive-section {
|
|
||||||
margin-bottom: var(--spacing-2xl);
|
|
||||||
|
|
||||||
.address-label {
|
|
||||||
font-size: var(--font-base);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin-bottom: var(--spacing-md);
|
|
||||||
font-weight: var(--font-semibold);
|
|
||||||
}
|
|
||||||
|
|
||||||
.address-value {
|
|
||||||
background: var(--bg-light);
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
word-break: break-all;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: var(--font-sm);
|
|
||||||
color: var(--primary-color);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: var(--transition-all);
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
border: 2px solid transparent;
|
|
||||||
line-height: 1.5;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.collapsed {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 3;
|
|
||||||
line-clamp: 3;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.expanded {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--bg-hover);
|
|
||||||
border-color: var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-icon {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: var(--primary-color);
|
|
||||||
margin-top: 2px;
|
|
||||||
align-self: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-address-btn {
|
|
||||||
margin-top: var(--spacing-md);
|
|
||||||
padding: var(--spacing-xs) var(--spacing-md);
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--primary-color);
|
|
||||||
font-size: var(--font-sm);
|
|
||||||
font-weight: var(--font-medium);
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
|
||||||
transition: color 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--primary-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.action-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-lg);
|
|
||||||
margin-bottom: var(--spacing-2xl);
|
|
||||||
|
|
||||||
:deep(.btn-send) {
|
|
||||||
letter-spacing: var(--tracking-wide);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-status {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
padding: 1.25rem;
|
|
||||||
background: var(--bg-light);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
font-size: var(--font-base);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
|
|
||||||
strong {
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-weight: var(--font-semibold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
14
src/components/common/CardBase.vue
Normal file
14
src/components/common/CardBase.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="card-base">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card-base {
|
||||||
|
@include card-base;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
20
src/components/common/CardBaseScrollable.vue
Normal file
20
src/components/common/CardBaseScrollable.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="card-base scrollable">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card-base {
|
||||||
|
@include card-base;
|
||||||
|
|
||||||
|
&.scrollable {
|
||||||
|
height: 100%;
|
||||||
|
max-height: calc(100vh - 100px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,12 +1,32 @@
|
|||||||
<script lang="ts" setup></script>
|
<script lang="ts" setup></script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a-layout>
|
<div class="app-layout">
|
||||||
<a-layout class="ant-layout-body">
|
<div class="app-layout-body">
|
||||||
<!-- <AppHeaderVue /> -->
|
<div class="app-content">
|
||||||
<a-layout-content>
|
|
||||||
<slot />
|
<slot />
|
||||||
</a-layout-content>
|
</div>
|
||||||
</a-layout>
|
</div>
|
||||||
</a-layout>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-layout {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-layout-body {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { ButtonCommon, FormCommon } from '@/components'
|
import { ButtonCommon, CardBase, FormCommon } from '@/components'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string
|
title?: string
|
||||||
@ -52,13 +52,14 @@ const handleSubmit = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
emit('back')
|
|
||||||
password.value = ''
|
password.value = ''
|
||||||
passwordError.value = ''
|
passwordError.value = ''
|
||||||
|
emit('back')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<CardBase>
|
||||||
<div class="auth-card-content">
|
<div class="auth-card-content">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<FormCommon
|
<FormCommon
|
||||||
@ -99,6 +100,7 @@ const handleBack = () => {
|
|||||||
</ButtonCommon>
|
</ButtonCommon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</CardBase>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import ButtonCommon from './common/ButtonCommon.vue'
|
|||||||
import FormCommon from './common/FormCommon.vue'
|
import FormCommon from './common/FormCommon.vue'
|
||||||
import PasswordForm from './common/PasswordForm.vue'
|
import PasswordForm from './common/PasswordForm.vue'
|
||||||
import SpinnerCommon from './common/SpinnerCommon.vue'
|
import SpinnerCommon from './common/SpinnerCommon.vue'
|
||||||
|
import CardBase from './common/CardBase.vue'
|
||||||
|
import CardBaseScrollable from './common/CardBaseScrollable.vue'
|
||||||
import { IconCommon } from './icon'
|
import { IconCommon } from './icon'
|
||||||
|
|
||||||
export { LayoutVue, ButtonCommon, FormCommon, PasswordForm, SpinnerCommon, IconCommon }
|
export { LayoutVue, ButtonCommon, FormCommon, PasswordForm, SpinnerCommon, CardBase, CardBaseScrollable, IconCommon }
|
||||||
|
|||||||
@ -26,20 +26,14 @@ export function useNeptuneWallet() {
|
|||||||
|
|
||||||
initPromise = (async () => {
|
initPromise = (async () => {
|
||||||
try {
|
try {
|
||||||
store.setLoading(true)
|
|
||||||
store.setError(null)
|
|
||||||
|
|
||||||
await initWasm()
|
await initWasm()
|
||||||
|
|
||||||
wasmInitialized = true
|
wasmInitialized = true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
wasmInitialized = false
|
wasmInitialized = false
|
||||||
const errorMsg = 'Failed to initialize Neptune WASM'
|
const errorMsg = 'Failed to initialize Neptune WASM'
|
||||||
store.setError(errorMsg)
|
|
||||||
console.error('WASM init error:', err)
|
console.error('WASM init error:', err)
|
||||||
throw new Error(errorMsg)
|
throw new Error(errorMsg)
|
||||||
} finally {
|
|
||||||
store.setLoading(false)
|
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
@ -48,9 +42,6 @@ export function useNeptuneWallet() {
|
|||||||
|
|
||||||
const generateWallet = async (): Promise<GenerateSeedResult> => {
|
const generateWallet = async (): Promise<GenerateSeedResult> => {
|
||||||
try {
|
try {
|
||||||
store.setLoading(true)
|
|
||||||
store.setError(null)
|
|
||||||
|
|
||||||
await ensureWasmInitialized()
|
await ensureWasmInitialized()
|
||||||
|
|
||||||
const resultJson = generate_seed()
|
const resultJson = generate_seed()
|
||||||
@ -65,30 +56,27 @@ export function useNeptuneWallet() {
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = err instanceof Error ? err.message : 'Failed to generate wallet'
|
console.error('Error generating wallet:', err)
|
||||||
store.setError(errorMsg)
|
|
||||||
throw err
|
throw err
|
||||||
} finally {
|
|
||||||
store.setLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getViewKeyFromSeed = async (seedPhrase: string[]): Promise<ViewKeyResult> => {
|
const getViewKeyFromSeed = async (seedPhrase: string[]): Promise<ViewKeyResult> => {
|
||||||
|
try {
|
||||||
await ensureWasmInitialized()
|
await ensureWasmInitialized()
|
||||||
const seedPhraseJson = JSON.stringify(seedPhrase)
|
const seedPhraseJson = JSON.stringify(seedPhrase)
|
||||||
const resultJson = get_viewkey(seedPhraseJson, store.getNetwork)
|
const resultJson = get_viewkey(seedPhraseJson, store.getNetwork)
|
||||||
return JSON.parse(resultJson)
|
return JSON.parse(resultJson)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error getting view key from seed:', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const importWallet = async (seedPhrase: string[]): Promise<ViewKeyResult> => {
|
const recoverWalletFromSeed = async (seedPhrase: string[]): Promise<ViewKeyResult> => {
|
||||||
try {
|
try {
|
||||||
store.setLoading(true)
|
const isValid = validate_seed_phrase(JSON.stringify(seedPhrase))
|
||||||
store.setError(null)
|
if (!isValid) throw new Error('Invalid seed phrase')
|
||||||
|
|
||||||
const isValid = await validateSeedPhrase(seedPhrase)
|
|
||||||
if (!isValid) {
|
|
||||||
throw new Error('Invalid seed phrase')
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await getViewKeyFromSeed(seedPhrase)
|
const result = await getViewKeyFromSeed(seedPhrase)
|
||||||
|
|
||||||
@ -99,51 +87,84 @@ export function useNeptuneWallet() {
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = err instanceof Error ? err.message : 'Failed to import wallet'
|
console.error('Error recovering wallet from seed:', err)
|
||||||
store.setError(errorMsg)
|
|
||||||
throw err
|
throw err
|
||||||
} finally {
|
|
||||||
store.setLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAddressFromSeed = async (seedPhrase: string[]): Promise<string> => {
|
const getAddressFromSeed = async (seedPhrase: string[]): Promise<string> => {
|
||||||
await ensureWasmInitialized()
|
|
||||||
const seedPhraseJson = JSON.stringify(seedPhrase)
|
|
||||||
return address_from_seed(seedPhraseJson, store.getNetwork)
|
|
||||||
}
|
|
||||||
|
|
||||||
const validateSeedPhrase = async (seedPhrase: string[]): Promise<boolean> => {
|
|
||||||
try {
|
try {
|
||||||
await ensureWasmInitialized()
|
await ensureWasmInitialized()
|
||||||
const seedPhraseJson = JSON.stringify(seedPhrase)
|
const seedPhraseJson = JSON.stringify(seedPhrase)
|
||||||
return validate_seed_phrase(seedPhraseJson)
|
return address_from_seed(seedPhraseJson, store.getNetwork)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Validation error:', err)
|
console.error('Error getting address from seed:', err)
|
||||||
return false
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptKeystore = async (password: string): Promise<void> => {
|
const decryptKeystore = async (password: string): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
const keystorePath = store.getKeystorePath
|
||||||
|
if (!keystorePath) await checkKeystore()
|
||||||
|
|
||||||
const result = await (window as any).walletApi.decryptKeystore(
|
const result = await (window as any).walletApi.decryptKeystore(
|
||||||
store.getKeystorePath || '',
|
store.getKeystorePath,
|
||||||
password
|
password
|
||||||
)
|
)
|
||||||
if (result.error) {
|
|
||||||
console.error('Error decrypting keystore composable:', result.error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const seedPhrase = result.phrase.trim().split(/\s+/)
|
const seedPhrase = result.phrase.trim().split(/\s+/)
|
||||||
const viewKeyResult = await getViewKeyFromSeed(seedPhrase)
|
const viewKeyResult = await getViewKeyFromSeed(seedPhrase)
|
||||||
|
|
||||||
|
store.setPassword(password)
|
||||||
store.setSeedPhrase(seedPhrase)
|
store.setSeedPhrase(seedPhrase)
|
||||||
store.setAddress(viewKeyResult.address)
|
store.setAddress(viewKeyResult.address)
|
||||||
store.setViewKey(viewKeyResult.view_key)
|
store.setViewKey(viewKeyResult.view_key)
|
||||||
store.setReceiverId(viewKeyResult.receiver_identifier)
|
store.setReceiverId(viewKeyResult.receiver_identifier)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error decrypting keystore composable:', err)
|
if (
|
||||||
|
err instanceof Error &&
|
||||||
|
(err.message.includes('Unsupported state') ||
|
||||||
|
err.message.includes('unable to authenticate'))
|
||||||
|
) {
|
||||||
|
console.error('Invalid password')
|
||||||
|
} else console.error('Error decrypting keystore:', err)
|
||||||
|
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createKeystore = async (seed: string, password: string): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const result = await (window as any).walletApi.createKeystore(seed, password)
|
||||||
|
return result.filePath
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error creating keystore:', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveKeystoreAs = async (seed: string, password: string): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const result = await (window as any).walletApi.saveKeystoreAs(seed, password)
|
||||||
|
if (!result.filePath) throw new Error('User canceled')
|
||||||
|
return result.filePath
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error saving keystore:', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkKeystore = async (): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const keystoreFile = await (window as any).walletApi.checkKeystore()
|
||||||
|
if (!keystoreFile.exists) return false
|
||||||
|
|
||||||
|
store.setKeystorePath(keystoreFile.filePath)
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error checking keystore:', err)
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,9 +180,6 @@ export function useNeptuneWallet() {
|
|||||||
throw new Error('No view key available. Please import or generate a wallet first.')
|
throw new Error('No view key available. Please import or generate a wallet first.')
|
||||||
}
|
}
|
||||||
|
|
||||||
store.setLoading(true)
|
|
||||||
store.setError(null)
|
|
||||||
|
|
||||||
const response = await API.getUtxosFromViewKey(
|
const response = await API.getUtxosFromViewKey(
|
||||||
store.getViewKey,
|
store.getViewKey,
|
||||||
startBlock,
|
startBlock,
|
||||||
@ -169,68 +187,49 @@ export function useNeptuneWallet() {
|
|||||||
maxSearchDepth
|
maxSearchDepth
|
||||||
)
|
)
|
||||||
|
|
||||||
const result = response.data?.result || response.data
|
const result = response?.result || response
|
||||||
store.setUtxos(result.utxos || result || [])
|
store.setUtxos(result.utxos || result || [])
|
||||||
return result
|
return result
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = err instanceof Error ? err.message : 'Failed to get UTXOs'
|
console.error('Error getting UTXOs:', err)
|
||||||
store.setError(errorMsg)
|
|
||||||
throw err
|
throw err
|
||||||
} finally {
|
|
||||||
store.setLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBalance = async (): Promise<any> => {
|
const getBalance = async (): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
store.setLoading(true)
|
|
||||||
store.setError(null)
|
|
||||||
|
|
||||||
const response = await API.getBalance()
|
const response = await API.getBalance()
|
||||||
const result = response.data?.result || response.data
|
const result = response?.result || response
|
||||||
store.setBalance(result.balance || result)
|
store.setBalance(result.balance || result)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = err instanceof Error ? err.message : 'Failed to get balance'
|
console.error('Error getting balance:', err)
|
||||||
store.setError(errorMsg)
|
|
||||||
throw err
|
throw err
|
||||||
} finally {
|
|
||||||
store.setLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBlockHeight = async (): Promise<any> => {
|
const getBlockHeight = async (): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
store.setLoading(true)
|
|
||||||
store.setError(null)
|
|
||||||
|
|
||||||
const response = await API.getBlockHeight()
|
const response = await API.getBlockHeight()
|
||||||
const result = response.data?.result || response.data
|
const result = response?.result || response
|
||||||
return result.height || result
|
return result?.height || result
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = err instanceof Error ? err.message : 'Failed to get block height'
|
console.error('Error getting block height:', err)
|
||||||
store.setError(errorMsg)
|
|
||||||
throw err
|
throw err
|
||||||
} finally {
|
|
||||||
store.setLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNetworkInfo = async (): Promise<any> => {
|
const getNetworkInfo = async (): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
store.setLoading(true)
|
|
||||||
store.setError(null)
|
|
||||||
|
|
||||||
const response = await API.getNetworkInfo()
|
const response = await API.getNetworkInfo()
|
||||||
const result = response.data?.result || response.data
|
const result = response?.result || response
|
||||||
|
store.setNetwork((result.network + 'net') as 'mainnet' | 'testnet')
|
||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = err instanceof Error ? err.message : 'Failed to get network info'
|
console.error('Error getting network info:', err)
|
||||||
store.setError(errorMsg)
|
|
||||||
throw err
|
throw err
|
||||||
} finally {
|
|
||||||
store.setLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,22 +239,17 @@ export function useNeptuneWallet() {
|
|||||||
fee: string
|
fee: string
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
store.setLoading(true)
|
|
||||||
store.setError(null)
|
|
||||||
|
|
||||||
const response = await API.sendTransaction(toAddress, amount, fee)
|
const response = await API.sendTransaction(toAddress, amount, fee)
|
||||||
const result = response.data?.result || response.data
|
const result = response?.result || response
|
||||||
return result
|
return result
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = err instanceof Error ? err.message : 'Failed to send transaction'
|
console.error('Error sending transaction:', err)
|
||||||
store.setError(errorMsg)
|
|
||||||
throw err
|
throw err
|
||||||
} finally {
|
|
||||||
store.setLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setNetwork = async (network: 'mainnet' | 'testnet') => {
|
const setNetwork = async (network: 'mainnet' | 'testnet') => {
|
||||||
|
try {
|
||||||
store.setNetwork(network)
|
store.setNetwork(network)
|
||||||
|
|
||||||
if (store.getSeedPhrase) {
|
if (store.getSeedPhrase) {
|
||||||
@ -263,6 +257,10 @@ export function useNeptuneWallet() {
|
|||||||
store.setAddress(viewKeyResult.address)
|
store.setAddress(viewKeyResult.address)
|
||||||
store.setViewKey(viewKeyResult.view_key)
|
store.setViewKey(viewKeyResult.view_key)
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error setting network:', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== UTILITY METHODS =====
|
// ===== UTILITY METHODS =====
|
||||||
@ -273,10 +271,9 @@ export function useNeptuneWallet() {
|
|||||||
return {
|
return {
|
||||||
initWasm: ensureWasmInitialized,
|
initWasm: ensureWasmInitialized,
|
||||||
generateWallet,
|
generateWallet,
|
||||||
importWallet,
|
recoverWalletFromSeed,
|
||||||
getViewKeyFromSeed,
|
getViewKeyFromSeed,
|
||||||
getAddressFromSeed,
|
getAddressFromSeed,
|
||||||
validateSeedPhrase,
|
|
||||||
|
|
||||||
getUtxos,
|
getUtxos,
|
||||||
getBalance,
|
getBalance,
|
||||||
@ -284,6 +281,9 @@ export function useNeptuneWallet() {
|
|||||||
getNetworkInfo,
|
getNetworkInfo,
|
||||||
sendTransaction,
|
sendTransaction,
|
||||||
decryptKeystore,
|
decryptKeystore,
|
||||||
|
createKeystore,
|
||||||
|
saveKeystoreAs,
|
||||||
|
checkKeystore,
|
||||||
|
|
||||||
clearWallet,
|
clearWallet,
|
||||||
setNetwork,
|
setNetwork,
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import * as Page from '@/views'
|
import * as Page from '@/views'
|
||||||
import { useNeptuneStore } from '@/stores/neptuneStore'
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
import { useAuthStore } from '@/stores/authStore'
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
|
import { useAuthStore } from '@/stores'
|
||||||
|
|
||||||
const ifAuthenticated = (to: any, from: any, next: any) => {
|
const ifAuthenticated = (to: any, from: any, next: any) => {
|
||||||
const neptuneStore = useNeptuneStore()
|
const neptuneStore = useNeptuneStore()
|
||||||
|
|
||||||
@ -12,16 +14,12 @@ const ifAuthenticated = (to: any, from: any, next: any) => {
|
|||||||
next('/auth')
|
next('/auth')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const ifNotAuthenticated = async (to: any, from: any, next: any) => {
|
const ifNotAuthenticated = async (to: any, from: any, next: any) => {
|
||||||
const neptuneStore = useNeptuneStore()
|
const neptuneWallet = useNeptuneWallet()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
const keystoreFile = await (window as any).walletApi.checkKeystore()
|
const exists = await neptuneWallet.checkKeystore()
|
||||||
if (keystoreFile.exists) {
|
if (exists) authStore.goToLogin()
|
||||||
neptuneStore.setKeystorePath(keystoreFile.filePath)
|
|
||||||
authStore.setState('login')
|
|
||||||
}
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,11 +5,14 @@ export type AuthState = 'onboarding' | 'login' | 'create' | 'recovery' | 'confir
|
|||||||
|
|
||||||
// Auth store to manage the flow
|
// Auth store to manage the flow
|
||||||
const currentState = ref<AuthState>('onboarding')
|
const currentState = ref<AuthState>('onboarding')
|
||||||
|
const previousState = ref<AuthState | null>(null)
|
||||||
|
|
||||||
export const useAuthStore = () => {
|
export const useAuthStore = () => {
|
||||||
const getCurrentState = () => currentState.value
|
const getCurrentState = () => currentState.value
|
||||||
|
const getPreviousState = () => previousState.value
|
||||||
|
|
||||||
const setState = (state: AuthState) => {
|
const setState = (state: AuthState) => {
|
||||||
|
previousState.value = currentState.value
|
||||||
currentState.value = state
|
currentState.value = state
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,17 +28,18 @@ export const useAuthStore = () => {
|
|||||||
setState('recovery')
|
setState('recovery')
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetFlow = () => {
|
const goBack = () => {
|
||||||
setState('onboarding')
|
previousState.value ? setState(previousState.value) : setState('onboarding')
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentState: currentState.value,
|
currentState: currentState.value,
|
||||||
getCurrentState,
|
getCurrentState,
|
||||||
|
getPreviousState,
|
||||||
setState,
|
setState,
|
||||||
goToCreate,
|
goToCreate,
|
||||||
goToLogin,
|
goToLogin,
|
||||||
goToRecover,
|
goToRecover,
|
||||||
resetFlow,
|
goBack,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,2 @@
|
|||||||
export * from './seedStore'
|
|
||||||
export * from './authStore'
|
export * from './authStore'
|
||||||
export * from './neptuneStore'
|
export * from './neptuneStore'
|
||||||
|
|||||||
@ -19,9 +19,6 @@ export const useNeptuneStore = defineStore('neptune', () => {
|
|||||||
|
|
||||||
const keystorePath = ref<null | string>(null)
|
const keystorePath = ref<null | string>(null)
|
||||||
|
|
||||||
const isLoading = ref(false)
|
|
||||||
const error = ref<string | null>(null)
|
|
||||||
|
|
||||||
// ===== SETTERS =====
|
// ===== SETTERS =====
|
||||||
|
|
||||||
const setSeedPhrase = (seedPhrase: string[] | null) => {
|
const setSeedPhrase = (seedPhrase: string[] | null) => {
|
||||||
@ -56,14 +53,6 @@ export const useNeptuneStore = defineStore('neptune', () => {
|
|||||||
wallet.value.utxos = utxos
|
wallet.value.utxos = utxos
|
||||||
}
|
}
|
||||||
|
|
||||||
const setLoading = (loading: boolean) => {
|
|
||||||
isLoading.value = loading
|
|
||||||
}
|
|
||||||
|
|
||||||
const setError = (err: string | null) => {
|
|
||||||
error.value = err
|
|
||||||
}
|
|
||||||
|
|
||||||
const setWallet = (walletData: Partial<WalletState>) => {
|
const setWallet = (walletData: Partial<WalletState>) => {
|
||||||
wallet.value = { ...wallet.value, ...walletData }
|
wallet.value = { ...wallet.value, ...walletData }
|
||||||
}
|
}
|
||||||
@ -83,7 +72,6 @@ export const useNeptuneStore = defineStore('neptune', () => {
|
|||||||
balance: null,
|
balance: null,
|
||||||
utxos: [],
|
utxos: [],
|
||||||
}
|
}
|
||||||
error.value = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== GETTERS =====
|
// ===== GETTERS =====
|
||||||
@ -98,8 +86,6 @@ export const useNeptuneStore = defineStore('neptune', () => {
|
|||||||
const getBalance = computed(() => wallet.value.balance)
|
const getBalance = computed(() => wallet.value.balance)
|
||||||
const getUtxos = computed(() => wallet.value.utxos)
|
const getUtxos = computed(() => wallet.value.utxos)
|
||||||
const hasWallet = computed(() => wallet.value.address !== null)
|
const hasWallet = computed(() => wallet.value.address !== null)
|
||||||
const getLoading = computed(() => isLoading.value)
|
|
||||||
const getError = computed(() => error.value)
|
|
||||||
const getKeystorePath = computed(() => keystorePath.value)
|
const getKeystorePath = computed(() => keystorePath.value)
|
||||||
return {
|
return {
|
||||||
getWallet,
|
getWallet,
|
||||||
@ -113,8 +99,6 @@ export const useNeptuneStore = defineStore('neptune', () => {
|
|||||||
getBalance,
|
getBalance,
|
||||||
getUtxos,
|
getUtxos,
|
||||||
hasWallet,
|
hasWallet,
|
||||||
getLoading,
|
|
||||||
getError,
|
|
||||||
getKeystorePath,
|
getKeystorePath,
|
||||||
setSeedPhrase,
|
setSeedPhrase,
|
||||||
setPassword,
|
setPassword,
|
||||||
@ -124,8 +108,6 @@ export const useNeptuneStore = defineStore('neptune', () => {
|
|||||||
setNetwork,
|
setNetwork,
|
||||||
setBalance,
|
setBalance,
|
||||||
setUtxos,
|
setUtxos,
|
||||||
setLoading,
|
|
||||||
setError,
|
|
||||||
setWallet,
|
setWallet,
|
||||||
setKeystorePath,
|
setKeystorePath,
|
||||||
clearWallet,
|
clearWallet,
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
const seedWords = ref<string[]>([])
|
|
||||||
const isSeedGenerated = ref(false)
|
|
||||||
|
|
||||||
export const useSeedStore = () => {
|
|
||||||
const setSeedWords = (words: string[]) => {
|
|
||||||
seedWords.value = words
|
|
||||||
isSeedGenerated.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSeedWords = () => {
|
|
||||||
return seedWords.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearSeedWords = () => {
|
|
||||||
seedWords.value = []
|
|
||||||
isSeedGenerated.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasSeedWords = () => {
|
|
||||||
return isSeedGenerated.value && seedWords.value.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
seedWords: seedWords.value,
|
|
||||||
isSeedGenerated: isSeedGenerated.value,
|
|
||||||
setSeedWords,
|
|
||||||
getSeedWords,
|
|
||||||
clearSeedWords,
|
|
||||||
hasSeedWords,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +1,2 @@
|
|||||||
import dayjs from 'dayjs'
|
|
||||||
|
|
||||||
export const PAGE_FIRST = 1
|
export const PAGE_FIRST = 1
|
||||||
export const PER_PAGE = 40
|
export const PER_PAGE = 40
|
||||||
export const MAX_STRING = 255
|
|
||||||
|
|
||||||
export const CURRENT_DAY = dayjs(new Date()).format('DD')
|
|
||||||
export const CURRENT_MONTH = dayjs(new Date()).format('MM')
|
|
||||||
export const CURRENT_YEAR = dayjs(new Date()).format('YYYY')
|
|
||||||
export const MONTHS = Array.from({ length: 12 }, (item, i) => {
|
|
||||||
return dayjs(new Date(0, i)).format('MM')
|
|
||||||
})
|
|
||||||
|
|||||||
@ -1,9 +1,3 @@
|
|||||||
import dayjs from 'dayjs'
|
|
||||||
|
|
||||||
export const formatNumberToLocaleString = (num: number): string => {
|
export const formatNumberToLocaleString = (num: number): string => {
|
||||||
return num.toLocaleString('en-US')
|
return num.toLocaleString('en-US')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatDate = (day: any, format = 'YYYY-MM-DD') => {
|
|
||||||
return dayjs(new Date(day)).format(format)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
export const ACCESS_TOKEN = 'access_token'
|
|
||||||
|
|
||||||
export const USER = 'user'
|
|
||||||
|
|
||||||
export const getToken = () => localStorage.getItem(ACCESS_TOKEN)
|
|
||||||
|
|
||||||
export const getAdmin = () => localStorage.getItem(USER)
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
export * from './constants/code'
|
export * from './constants/code'
|
||||||
export * from './constants/constants'
|
export * from './constants/constants'
|
||||||
export * from './helpers/format'
|
export * from './helpers/format'
|
||||||
export * from './helpers/localStorage'
|
|
||||||
export * from './helpers/seedPhrase'
|
export * from './helpers/seedPhrase'
|
||||||
|
|||||||
@ -34,7 +34,6 @@ const handleGoToRecover = () => {
|
|||||||
.auth-container {
|
.auth-container {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-light);
|
background: var(--bg-light);
|
||||||
font-family: var(--font-primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.complete-state {
|
.complete-state {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { PasswordForm } from '@/components'
|
|||||||
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/authStore'
|
import { useAuthStore } from '@/stores/authStore'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -15,13 +16,12 @@ const error = ref(false)
|
|||||||
const handlePasswordSubmit = async (password: string) => {
|
const handlePasswordSubmit = async (password: string) => {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
error.value = false
|
||||||
await neptuneWallet.decryptKeystore(password)
|
await neptuneWallet.decryptKeystore(password)
|
||||||
router.push({ name: 'home' })
|
router.push({ name: 'home' })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = true
|
error.value = true
|
||||||
console.error('Password Error')
|
} finally {
|
||||||
}
|
|
||||||
finally {
|
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RecoverWalletComponent } from '.'
|
import { RecoverWalletComponent } from '.'
|
||||||
|
import { useAuthStore } from '@/stores'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const handleImported = (payload: { type: 'seed' | 'keystore'; value: string | string[] }) => {
|
const handleCancel = () => {
|
||||||
if (payload.type === 'keystore') {
|
authStore.goBack()
|
||||||
localStorage.setItem('temp_keystore', JSON.stringify(payload.value))
|
|
||||||
return router.push({ name: 'password' })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleAccessWallet = () => {
|
||||||
|
router.push({ name: 'home' })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="recover-seed-tab">
|
<div class="recover-seed-tab">
|
||||||
<RecoverWalletComponent @import-success="handleImported" />
|
<RecoverWalletComponent @cancel="handleCancel" @access-wallet="handleAccessWallet" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,20 @@ import { ButtonCommon } from '@/components'
|
|||||||
import { useNeptuneStore } from '@/stores/neptuneStore'
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
backButton?: boolean
|
||||||
|
nextButton?: boolean
|
||||||
|
backButtonText?: string
|
||||||
|
nextButtonText?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
backButton: true,
|
||||||
|
nextButton: true,
|
||||||
|
backButtonText: 'BACK',
|
||||||
|
nextButtonText: 'NEXT',
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
next: []
|
next: []
|
||||||
back: []
|
back: []
|
||||||
@ -50,10 +64,6 @@ const handleCopySeed = async () => {
|
|||||||
|
|
||||||
<div class="recovery-content">
|
<div class="recovery-content">
|
||||||
<div class="instruction-text">
|
<div class="instruction-text">
|
||||||
<p>
|
|
||||||
Your wallet is accessible by a seed phrase. The seed phrase is an ordered
|
|
||||||
18-word secret phrase.
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
Make sure no one is looking, as anyone with your seed phrase can access your
|
Make sure no one is looking, as anyone with your seed phrase can access your
|
||||||
wallet and funds. Write it down and keep it safe.
|
wallet and funds. Write it down and keep it safe.
|
||||||
@ -91,24 +101,23 @@ const handleCopySeed = async () => {
|
|||||||
<p>⚠️ No seed phrase found. Please go back and generate a wallet first.</p>
|
<p>⚠️ No seed phrase found. Please go back and generate a wallet first.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cool-fact">
|
|
||||||
<p>
|
|
||||||
Cool fact: there are more 18-word phrase combinations than atoms in the
|
|
||||||
observable universe!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="recovery-actions">
|
<div class="recovery-actions">
|
||||||
<ButtonCommon type="default" size="large" @click="handleBack">
|
<ButtonCommon
|
||||||
BACK
|
v-if="props.backButton"
|
||||||
|
type="default"
|
||||||
|
size="large"
|
||||||
|
@click="handleBack"
|
||||||
|
>
|
||||||
|
{{ backButtonText }}
|
||||||
</ButtonCommon>
|
</ButtonCommon>
|
||||||
<ButtonCommon
|
<ButtonCommon
|
||||||
|
v-if="props.nextButton"
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
@click="handleNext"
|
@click="handleNext"
|
||||||
:disabled="!seedWords || seedWords.length === 0"
|
:disabled="!seedWords || seedWords.length === 0"
|
||||||
>
|
>
|
||||||
NEXT
|
{{ nextButtonText }}
|
||||||
</ButtonCommon>
|
</ButtonCommon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -121,16 +130,15 @@ const handleCopySeed = async () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: var(--spacing-xl);
|
|
||||||
background: var(--bg-light);
|
background: var(--bg-light);
|
||||||
min-height: 100vh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.recovery-card {
|
.recovery-card {
|
||||||
@include card-base;
|
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
|
padding: var(--spacing-xl);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 2px solid var(--primary-color);
|
border: 2px solid var(--primary-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.recovery-header {
|
.recovery-header {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, defineEmits, onMounted, computed } from 'vue'
|
import { ref, defineEmits, onMounted, computed } from 'vue'
|
||||||
import { ButtonCommon } from '@/components'
|
import { ButtonCommon, CardBase } from '@/components'
|
||||||
import { useNeptuneStore } from '@/stores/neptuneStore'
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
@ -82,7 +82,6 @@ const handleAnswerSelect = (answer: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
emit('next')
|
|
||||||
if (isCorrect.value) {
|
if (isCorrect.value) {
|
||||||
correctCount.value++
|
correctCount.value++
|
||||||
askedPositions.value.add(quizData.value!.position)
|
askedPositions.value.add(quizData.value!.position)
|
||||||
@ -208,16 +207,13 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
NEXT QUESTION
|
NEXT QUESTION
|
||||||
</ButtonCommon>
|
</ButtonCommon>
|
||||||
<!-- <ButtonCommon
|
<ButtonCommon
|
||||||
v-if="showResult && isCorrect && correctCount + 1 >= totalQuestions"
|
v-if="showResult && isCorrect && correctCount + 1 >= totalQuestions"
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
@click="handleNext"
|
@click="handleNext"
|
||||||
>
|
>
|
||||||
CONTINUE
|
CONTINUE
|
||||||
</ButtonCommon> -->
|
|
||||||
<ButtonCommon type="primary" size="large" @click="handleNext">
|
|
||||||
CONTINUE
|
|
||||||
</ButtonCommon>
|
</ButtonCommon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -230,16 +226,15 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: var(--spacing-xl);
|
|
||||||
background: var(--bg-light);
|
background: var(--bg-light);
|
||||||
min-height: 100vh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-card {
|
.confirm-card {
|
||||||
@include card-base;
|
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding: var(--spacing-xl);
|
||||||
border: 2px solid var(--primary-color);
|
border: 2px solid var(--primary-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-header {
|
.confirm-header {
|
||||||
|
|||||||
@ -163,6 +163,7 @@ const handleIHaveWallet = () => {
|
|||||||
required
|
required
|
||||||
:error="confirmPasswordError"
|
:error="confirmPasswordError"
|
||||||
@input="confirmPasswordError = ''"
|
@input="confirmPasswordError = ''"
|
||||||
|
@keyup.enter="handleNext"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="confirmPassword"
|
v-if="confirmPassword"
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { SeedPhraseDisplayComponent, ConfirmSeedComponent } from '..'
|
|||||||
import { CreatePasswordStep, WalletCreatedStep } from '.'
|
import { CreatePasswordStep, WalletCreatedStep } from '.'
|
||||||
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
|
import { CardBaseScrollable } from '@/components'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -36,9 +37,20 @@ const handleNextToWalletCreated = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleNextFromPassword = async () => {
|
const handleNextFromPassword = async () => {
|
||||||
const result = await generateWallet()
|
try {
|
||||||
if (result) step.value = 2
|
await generateWallet()
|
||||||
else message.error('Failed to generate wallet')
|
step.value = 2
|
||||||
|
} catch (err) {
|
||||||
|
message.error('Failed to generate wallet')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBackToCreatePassword = () => {
|
||||||
|
step.value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBackToSeedPhrase = () => {
|
||||||
|
step.value = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAccessWallet = () => {
|
const handleAccessWallet = () => {
|
||||||
@ -52,6 +64,7 @@ function resetAll() {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="auth-container">
|
<div class="auth-container">
|
||||||
|
<CardBaseScrollable>
|
||||||
<div class="auth-card">
|
<div class="auth-card">
|
||||||
<!-- Step 1: Create Password -->
|
<!-- Step 1: Create Password -->
|
||||||
<CreatePasswordStep
|
<CreatePasswordStep
|
||||||
@ -61,10 +74,18 @@ function resetAll() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Step 2: Recovery Seed -->
|
<!-- Step 2: Recovery Seed -->
|
||||||
<SeedPhraseDisplayComponent v-else-if="step === 2" @next="handleNextToConfirmSeed" />
|
<SeedPhraseDisplayComponent
|
||||||
|
v-else-if="step === 2"
|
||||||
|
@back="handleBackToCreatePassword"
|
||||||
|
@next="handleNextToConfirmSeed"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Step 3: Confirm Seed -->
|
<!-- Step 3: Confirm Seed -->
|
||||||
<ConfirmSeedComponent v-else-if="step === 3" @next="handleNextToWalletCreated" />
|
<ConfirmSeedComponent
|
||||||
|
v-else-if="step === 3"
|
||||||
|
@back="handleBackToSeedPhrase"
|
||||||
|
@next="handleNextToWalletCreated"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Step 4: Success -->
|
<!-- Step 4: Success -->
|
||||||
<WalletCreatedStep
|
<WalletCreatedStep
|
||||||
@ -72,12 +93,8 @@ function resetAll() {
|
|||||||
@access-wallet="handleAccessWallet"
|
@access-wallet="handleAccessWallet"
|
||||||
@create-another="resetAll"
|
@create-another="resetAll"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Fallback slot -->
|
|
||||||
<template v-else>
|
|
||||||
<slot></slot>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
|
</CardBaseScrollable>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -91,7 +108,6 @@ function resetAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.auth-card {
|
.auth-card {
|
||||||
@include card-base;
|
|
||||||
max-width: 720px;
|
max-width: 720px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonCommon } from '@/components'
|
import { ButtonCommon } from '@/components'
|
||||||
import { useNeptuneStore } from '@/stores/neptuneStore'
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
const neptuneStore = useNeptuneStore()
|
const neptuneStore = useNeptuneStore()
|
||||||
|
const { createKeystore } = useNeptuneWallet()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
accessWallet: []
|
accessWallet: []
|
||||||
@ -10,10 +13,20 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const handleAccessWallet = async () => {
|
const handleAccessWallet = async () => {
|
||||||
|
try {
|
||||||
const seedPhrase = neptuneStore.getSeedPhraseString
|
const seedPhrase = neptuneStore.getSeedPhraseString
|
||||||
const password = neptuneStore.getPassword!
|
const password = neptuneStore.getPassword!
|
||||||
const encrypted = (window as any).walletApi.createKeystore(seedPhrase, password)
|
|
||||||
|
if (!seedPhrase || !password) {
|
||||||
|
message.error('Missing seed or password')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await createKeystore(seedPhrase, password)
|
||||||
emit('accessWallet')
|
emit('accessWallet')
|
||||||
|
} catch (err) {
|
||||||
|
message.error('Failed to create keystore')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCreateAnother = () => {
|
const handleCreateAnother = () => {
|
||||||
|
|||||||
@ -1,34 +1,37 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { validateSeedPhrase18 } from '@/utils'
|
import { ref } from 'vue'
|
||||||
import { computed, ref, watch } from 'vue'
|
|
||||||
|
interface Props {
|
||||||
|
valid?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
valid: true,
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:valid', valid: boolean): void
|
|
||||||
(e: 'submit', words: string[]): void
|
(e: 'submit', words: string[]): void
|
||||||
|
(e: 'update:words', words: string[]): void
|
||||||
|
(e: 'update:valid', valid: boolean): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const seedWords = ref<string[]>(Array.from({ length: 18 }, () => ''))
|
const seedWords = ref<string[]>(Array.from({ length: 18 }, () => ''))
|
||||||
const seedError = ref('')
|
|
||||||
|
|
||||||
const isValid = computed(() => {
|
|
||||||
const words = seedWords.value.filter((w) => w.trim())
|
|
||||||
return validateSeedPhrase18(words) && !seedError.value
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(isValid, (newVal) => {
|
|
||||||
emit('update:valid', newVal)
|
|
||||||
})
|
|
||||||
|
|
||||||
const inputBoxFocus = (idx: number) => {
|
const inputBoxFocus = (idx: number) => {
|
||||||
|
if (idx < 18) {
|
||||||
document.getElementById('input-' + idx)?.focus()
|
document.getElementById('input-' + idx)?.focus()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleGridInput = (index: number, value: string) => {
|
const handleGridInput = (index: number, value: string) => {
|
||||||
|
emit('update:valid', true)
|
||||||
seedWords.value[index] = value
|
seedWords.value[index] = value
|
||||||
|
emit('update:words', seedWords.value.filter((w) => w.trim()))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePaste = (event: ClipboardEvent) => {
|
const handlePaste = (event: ClipboardEvent) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
emit('update:valid', true)
|
||||||
const pastedData = event.clipboardData?.getData('text') || ''
|
const pastedData = event.clipboardData?.getData('text') || ''
|
||||||
const words = pastedData
|
const words = pastedData
|
||||||
.trim()
|
.trim()
|
||||||
@ -37,13 +40,13 @@ const handlePaste = (event: ClipboardEvent) => {
|
|||||||
|
|
||||||
if (words.length === 0) return
|
if (words.length === 0) return
|
||||||
|
|
||||||
seedWords.value = words
|
const filledWords = Array.from({ length: 18 }, (_, i) => words[i] || '')
|
||||||
seedError.value = ''
|
seedWords.value = filledWords
|
||||||
|
emit('update:words', words.filter((w) => w.trim()))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
const words = seedWords.value.filter((w) => w.trim())
|
const words = seedWords.value.filter((w) => w.trim())
|
||||||
if (validateSeedPhrase18(words)) seedError.value = ''
|
|
||||||
emit('submit', words)
|
emit('submit', words)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,15 +76,13 @@ defineExpose({
|
|||||||
:placeholder="i + 1 + '.'"
|
:placeholder="i + 1 + '.'"
|
||||||
maxlength="24"
|
maxlength="24"
|
||||||
@keydown.enter="inputBoxFocus(i + 1)"
|
@keydown.enter="inputBoxFocus(i + 1)"
|
||||||
:class="{ error: seedError && !word.trim() }"
|
|
||||||
@focus="seedError = ''"
|
|
||||||
@input="handleGridInput(i, ($event.target as HTMLInputElement).value)"
|
@input="handleGridInput(i, ($event.target as HTMLInputElement).value)"
|
||||||
@paste="handlePaste($event)"
|
@paste="handlePaste($event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="seedError" class="error-text">{{ seedError }}</div>
|
<div v-if="!props.valid" class="error-text">Invalid seed phrase</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -1,88 +1,109 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { Modal } from 'ant-design-vue'
|
|
||||||
import { ButtonCommon, PasswordForm } from '@/components'
|
import { ButtonCommon, PasswordForm } from '@/components'
|
||||||
import { RecoverSeedComponent } from '..'
|
import { RecoverSeedComponent } from '..'
|
||||||
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'import-success', data: { type: 'seed'; value: string[]; password: string }): void
|
(e: 'cancel'): void
|
||||||
|
(e: 'accessWallet'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const { recoverWalletFromSeed, createKeystore } = useNeptuneWallet()
|
||||||
|
|
||||||
const recoverSeedComponentRef = ref<InstanceType<typeof RecoverSeedComponent>>()
|
const recoverSeedComponentRef = ref<InstanceType<typeof RecoverSeedComponent>>()
|
||||||
const isSeedPhraseValid = ref(false)
|
const showPasswordForm = ref(false)
|
||||||
const showPasswordModal = ref(false)
|
|
||||||
const seedPhrase = ref<string[]>([])
|
const seedPhrase = ref<string[]>([])
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const passwordError = ref(false)
|
const isSeedPhraseValid = ref(true)
|
||||||
|
|
||||||
const handleSeedPhraseSubmit = (words: string[]) => {
|
const handleSeedWordsUpdate = (words: string[]) => {
|
||||||
seedPhrase.value = words
|
seedPhrase.value = words
|
||||||
showPasswordModal.value = true
|
}
|
||||||
|
|
||||||
|
const handleSeedPhraseSubmit = async (words: string[]) => {
|
||||||
|
seedPhrase.value = words
|
||||||
|
showPasswordForm.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleContinue = () => {
|
const handleContinue = () => {
|
||||||
recoverSeedComponentRef.value?.handleSubmit?.()
|
recoverSeedComponentRef.value?.handleSubmit?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePasswordSubmit = (password: string) => {
|
const handlePasswordSubmit = async (password: string) => {
|
||||||
showPasswordModal.value = false
|
try {
|
||||||
emit('import-success', {
|
isLoading.value = true
|
||||||
type: 'seed',
|
const result = await recoverWalletFromSeed(seedPhrase.value)
|
||||||
value: seedPhrase.value,
|
if (result.address) {
|
||||||
password,
|
await createKeystore(seedPhrase.value.join(' '), password)
|
||||||
})
|
emit('accessWallet')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error && err.message.includes('Invalid seed phrase')) {
|
||||||
|
isSeedPhraseValid.value = false
|
||||||
|
} else {
|
||||||
|
message.error('Failed to recover wallet from seed')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
showPasswordForm.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePasswordBack = () => {
|
const handlePasswordBack = () => {
|
||||||
showPasswordModal.value = false
|
showPasswordForm.value = false
|
||||||
passwordError.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleModalCancel = () => {
|
const handleCancel = () => {
|
||||||
showPasswordModal.value = false
|
emit('cancel')
|
||||||
passwordError.value = false
|
showPasswordForm.value = false
|
||||||
|
seedPhrase.value = []
|
||||||
|
isSeedPhraseValid.value = true
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="import-wallet dark-card">
|
<div class="import-wallet dark-card">
|
||||||
<h2 class="title">Recover Wallet</h2>
|
<h2 class="title">Recover Wallet</h2>
|
||||||
|
|
||||||
|
<!-- Seed Phrase Step -->
|
||||||
|
<div v-if="!showPasswordForm">
|
||||||
<div class="desc">Enter your recovery seed phrase</div>
|
<div class="desc">Enter your recovery seed phrase</div>
|
||||||
<RecoverSeedComponent
|
<RecoverSeedComponent
|
||||||
ref="recoverSeedComponentRef"
|
ref="recoverSeedComponentRef"
|
||||||
@update:valid="isSeedPhraseValid = $event"
|
:valid="isSeedPhraseValid"
|
||||||
@submit="handleSeedPhraseSubmit"
|
@submit="handleSeedPhraseSubmit"
|
||||||
|
@update:words="handleSeedWordsUpdate"
|
||||||
|
@update:valid="isSeedPhraseValid = $event"
|
||||||
/>
|
/>
|
||||||
<ButtonCommon
|
<ButtonCommon
|
||||||
class="mt-lg"
|
class="mt-20"
|
||||||
type="primary"
|
type="primary"
|
||||||
block
|
block
|
||||||
size="large"
|
size="large"
|
||||||
:disabled="!isSeedPhraseValid"
|
:disabled="isLoading"
|
||||||
@click="handleContinue"
|
@click="handleContinue"
|
||||||
>Continue</ButtonCommon
|
|
||||||
>
|
>
|
||||||
|
Continue
|
||||||
|
</ButtonCommon>
|
||||||
|
<ButtonCommon class="mt-10" type="default" block size="large" @click="handleCancel">
|
||||||
|
Cancel
|
||||||
|
</ButtonCommon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal
|
<!-- Password Step -->
|
||||||
v-model:open="showPasswordModal"
|
<div v-else>
|
||||||
title="Enter Password"
|
<div class="desc">Enter password to encrypt seed phrase</div>
|
||||||
:footer="null"
|
|
||||||
:width="480"
|
|
||||||
:mask-closable="false"
|
|
||||||
:keyboard="false"
|
|
||||||
@cancel="handleModalCancel"
|
|
||||||
>
|
|
||||||
<PasswordForm
|
<PasswordForm
|
||||||
button-text="Continue"
|
button-text="Submit"
|
||||||
placeholder="Enter password to encrypt seed phrase"
|
placeholder="Enter password to encrypt seed phrase"
|
||||||
label="Password"
|
label="Password"
|
||||||
:loading="isLoading"
|
:loading="isLoading"
|
||||||
:error="passwordError"
|
|
||||||
:error-message="'Invalid password'"
|
|
||||||
@submit="handlePasswordSubmit"
|
@submit="handlePasswordSubmit"
|
||||||
@back="handlePasswordBack"
|
@back="handlePasswordBack"
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.import-wallet {
|
.import-wallet {
|
||||||
@ -107,9 +128,6 @@ const handleModalCancel = () => {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
.mt-lg {
|
|
||||||
margin-top: 32px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@include screen(mobile) {
|
@include screen(mobile) {
|
||||||
.import-wallet {
|
.import-wallet {
|
||||||
|
|||||||
@ -1,23 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { Tabs, Row, Col } from 'ant-design-vue'
|
import { Tabs } from 'ant-design-vue'
|
||||||
import WalletInfo from '@/components/WalletInfo.vue'
|
|
||||||
import { WalletTab, NetworkTab, UTXOTab } from './components'
|
import { WalletTab, NetworkTab, UTXOTab } from './components'
|
||||||
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
|
|
||||||
|
const neptuneWallet = useNeptuneWallet()
|
||||||
|
|
||||||
const activeTab = ref('UTXOs')
|
const activeTab = ref('UTXOs')
|
||||||
const network = ref('neptune-mainnet')
|
const network = ref('neptune-mainnet')
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await neptuneWallet.getNetworkInfo()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="home-container">
|
<div class="home-container">
|
||||||
<Row :gutter="[24, 24]">
|
|
||||||
<!-- Left Column --->
|
|
||||||
<Col :xs="24" :lg="10">
|
|
||||||
<WalletInfo />
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<!-- Right Column - Tabs Content -->
|
|
||||||
<Col :xs="24" :lg="12">
|
|
||||||
<Tabs v-model:activeKey="activeTab" size="large" class="main-tabs">
|
<Tabs v-model:activeKey="activeTab" size="large" class="main-tabs">
|
||||||
<!-- DEBUG TAB -->
|
<!-- DEBUG TAB -->
|
||||||
<Tabs.TabPane key="UTXOs" tab="UTXOs">
|
<Tabs.TabPane key="UTXOs" tab="UTXOs">
|
||||||
@ -34,30 +32,33 @@ const network = ref('neptune-mainnet')
|
|||||||
<NetworkTab />
|
<NetworkTab />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.home-container {
|
.home-container {
|
||||||
min-height: 100vh;
|
height: 100%;
|
||||||
background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%);
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
padding: var(--spacing-lg);
|
padding: var(--spacing-lg);
|
||||||
font-family: var(--font-primary);
|
|
||||||
|
|
||||||
@include screen(tablet) {
|
@include screen(tablet) {
|
||||||
padding: var(--spacing-2xl);
|
padding: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@include screen(desktop) {
|
@include screen(desktop) {
|
||||||
padding: var(--spacing-2xl);
|
padding: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.main-tabs) {
|
:deep(.main-tabs) {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.ant-tabs-nav {
|
.ant-tabs-nav {
|
||||||
margin-bottom: var(--spacing-lg);
|
margin-bottom: var(--spacing-md);
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-tab {
|
.ant-tabs-tab {
|
||||||
@ -82,7 +83,15 @@ const network = ref('neptune-mainnet')
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-tabs-content {
|
.ant-tabs-content {
|
||||||
padding-top: var(--spacing-lg);
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.ant-tabs-tabpane {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,46 +1,32 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { formatNumberToLocaleString } from '@/utils'
|
import { formatNumberToLocaleString } from '@/utils'
|
||||||
import { SpinnerCommon } from '@/components'
|
import { CardBase, SpinnerCommon } from '@/components'
|
||||||
import { useNeptuneStore } from '@/stores/neptuneStore'
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { get_network_info } from '@neptune/wasm'
|
|
||||||
|
|
||||||
const neptuneStore = useNeptuneStore()
|
const neptuneStore = useNeptuneStore()
|
||||||
const { getBlockHeight, getNetworkInfo } = useNeptuneWallet()
|
const { getBlockHeight } = useNeptuneWallet()
|
||||||
|
|
||||||
const blockHeight = ref(0)
|
const blockHeight = ref(0)
|
||||||
const networkInfo = ref<any>(null)
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
const lastUpdate = ref<Date | null>(null)
|
const lastUpdate = ref<Date | null>(null)
|
||||||
|
|
||||||
// let pollingInterval: number | null = null
|
let pollingInterval: number | null = null
|
||||||
|
|
||||||
const network = computed(() => neptuneStore.getNetwork)
|
const network = computed(() => neptuneStore.getNetwork)
|
||||||
|
|
||||||
const loadNetworkData = async () => {
|
const loadNetworkData = async () => {
|
||||||
try {
|
try {
|
||||||
|
loading.value = true
|
||||||
error.value = ''
|
error.value = ''
|
||||||
const [heightResult, infoResult] = await Promise.all([getBlockHeight(), getNetworkInfo()])
|
const result = await getBlockHeight()
|
||||||
|
if (result.height || result) blockHeight.value = Number(result.height || result)
|
||||||
if (heightResult !== undefined) {
|
|
||||||
blockHeight.value =
|
|
||||||
typeof heightResult === 'number'
|
|
||||||
? heightResult
|
|
||||||
: heightResult.height || heightResult || 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (infoResult) {
|
|
||||||
networkInfo.value = infoResult
|
|
||||||
}
|
|
||||||
|
|
||||||
lastUpdate.value = new Date()
|
lastUpdate.value = new Date()
|
||||||
|
|
||||||
if (loading.value) {
|
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = err instanceof Error ? err.message : 'Failed to load network data'
|
const errorMsg = err instanceof Error ? err.message : 'Failed to load network data'
|
||||||
error.value = errorMsg
|
error.value = errorMsg
|
||||||
@ -55,33 +41,31 @@ const retryConnection = async () => {
|
|||||||
await loadNetworkData()
|
await loadNetworkData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// const startPolling = () => {
|
const startPolling = () => {
|
||||||
// pollingInterval = window.setInterval(() => {
|
pollingInterval = window.setInterval(async () => {
|
||||||
// if (!loading.value) {
|
if (!loading.value) await loadNetworkData()
|
||||||
// loadNetworkData()
|
}, 10000)
|
||||||
// }
|
}
|
||||||
// }, 10000)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const stopPolling = () => {
|
const stopPolling = () => {
|
||||||
// if (pollingInterval) {
|
if (pollingInterval) {
|
||||||
// clearInterval(pollingInterval)
|
clearInterval(pollingInterval)
|
||||||
// pollingInterval = null
|
pollingInterval = null
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadNetworkData()
|
await loadNetworkData()
|
||||||
// startPolling()
|
startPolling()
|
||||||
})
|
})
|
||||||
|
|
||||||
// onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// stopPolling()
|
stopPolling()
|
||||||
// })
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="content-card">
|
<CardBase class="content-card">
|
||||||
<div class="network-status-container">
|
<div class="network-status-container">
|
||||||
<h2 class="section-title">NETWORK STATUS</h2>
|
<h2 class="section-title">NETWORK STATUS</h2>
|
||||||
|
|
||||||
@ -105,39 +89,20 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status-item">
|
<div class="status-item">
|
||||||
<span class="status-label">Block Height</span>
|
<span class="status-label">DAA Score</span>
|
||||||
<span class="status-value">{{ formatNumberToLocaleString(blockHeight) }}</span>
|
<span class="status-value">{{ formatNumberToLocaleString(blockHeight) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="networkInfo?.version" class="status-item">
|
|
||||||
<span class="status-label">Version</span>
|
|
||||||
<span class="status-value">{{ networkInfo.version }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="networkInfo?.peer_count !== undefined" class="status-item">
|
|
||||||
<span class="status-label">Peers</span>
|
|
||||||
<span class="status-value">{{ networkInfo.peer_count }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="networkInfo?.chain_id" class="status-item">
|
|
||||||
<span class="status-label">Chain ID</span>
|
|
||||||
<span class="status-value">{{ networkInfo.chain_id }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="lastUpdate" class="status-item">
|
<div v-if="lastUpdate" class="status-item">
|
||||||
<span class="status-label">Last Updated</span>
|
<span class="status-label">Last Updated</span>
|
||||||
<span class="status-value">{{ lastUpdate.toLocaleTimeString() }}</span>
|
<span class="status-value">{{ lastUpdate.toLocaleTimeString() }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CardBase>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.content-card {
|
|
||||||
@include card-base;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-status-container {
|
.network-status-container {
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: var(--font-xl);
|
font-size: var(--font-xl);
|
||||||
@ -181,7 +146,6 @@ onMounted(async () => {
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: var(--font-lg);
|
font-size: var(--font-lg);
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-family: var(--font-mono);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { EditOutlined } from '@ant-design/icons-vue'
|
import { EditOutlined } from '@ant-design/icons-vue'
|
||||||
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
|
import { CardBaseScrollable } from '@/components'
|
||||||
|
|
||||||
const { getUtxos } = useNeptuneWallet()
|
const { getUtxos } = useNeptuneWallet()
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ const inUseUtxosAmount = ref(0)
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="content-card debug-card">
|
<CardBaseScrollable class="content-card debug-card">
|
||||||
<div class="debug-header">
|
<div class="debug-header">
|
||||||
<h3 class="debug-title">
|
<h3 class="debug-title">
|
||||||
IN USE UTXOS
|
IN USE UTXOS
|
||||||
@ -18,19 +19,15 @@ const inUseUtxosAmount = ref(0)
|
|||||||
</h3>
|
</h3>
|
||||||
<div class="debug-info">
|
<div class="debug-info">
|
||||||
<p><strong>COUNT</strong> {{ inUseUtxosCount }}</p>
|
<p><strong>COUNT</strong> {{ inUseUtxosCount }}</p>
|
||||||
<p><strong>AMOUNT</strong> {{ inUseUtxosAmount }} KAS</p>
|
<p><strong>AMOUNT</strong> {{ inUseUtxosAmount }} NPT</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="list-pagination"></div>
|
<div class="list-pagination"></div>
|
||||||
</div>
|
</CardBaseScrollable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.content-card {
|
|
||||||
@include card-base;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-card {
|
.debug-card {
|
||||||
.debug-header {
|
.debug-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@ -1,140 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { Divider, Modal } from 'ant-design-vue'
|
|
||||||
import ButtonCommon from '@/components/common/ButtonCommon.vue'
|
|
||||||
import SeedPhraseDisplayComponent from '@/views/Auth/components/SeedPhraseDisplayComponent.vue'
|
|
||||||
|
|
||||||
const showSeedModal = ref(false)
|
|
||||||
|
|
||||||
const handleBackupFile = () => {
|
|
||||||
// TODO: Implement backup file functionality
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBackupSeed = () => {
|
|
||||||
showSeedModal.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
|
||||||
showSeedModal.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleModalNext = () => {
|
|
||||||
// SeedPhraseDisplayComponent emits 'next' but in modal context, we just close
|
|
||||||
handleCloseModal()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleModalBack = () => {
|
|
||||||
// SeedPhraseDisplayComponent emits 'back' but in modal context, we just close
|
|
||||||
handleCloseModal()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="content-card wallet-info-card">
|
|
||||||
<div class="wallet-header">
|
|
||||||
<h2 class="wallet-title">NEPTUNE WALLET</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="wallet-actions">
|
|
||||||
<ButtonCommon type="primary" size="large" block @click="handleBackupFile">
|
|
||||||
Backup File
|
|
||||||
</ButtonCommon>
|
|
||||||
<ButtonCommon type="primary" size="large" block @click="handleBackupSeed">
|
|
||||||
Backup Seed
|
|
||||||
</ButtonCommon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Modal
|
|
||||||
v-model:open="showSeedModal"
|
|
||||||
title="Backup Seed Phrase"
|
|
||||||
:footer="null"
|
|
||||||
:width="600"
|
|
||||||
:mask-closable="false"
|
|
||||||
:keyboard="false"
|
|
||||||
@cancel="handleCloseModal"
|
|
||||||
>
|
|
||||||
<div class="seed-modal-content">
|
|
||||||
<SeedPhraseDisplayComponent @next="handleModalNext" @back="handleModalBack" />
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.content-card {
|
|
||||||
@include card-base;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-info-card {
|
|
||||||
.wallet-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: var(--spacing-2xl);
|
|
||||||
padding-bottom: var(--spacing-xl);
|
|
||||||
border-bottom: 2px solid var(--border-color);
|
|
||||||
|
|
||||||
.wallet-title {
|
|
||||||
font-size: var(--font-3xl);
|
|
||||||
font-weight: var(--font-bold);
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin-bottom: var(--spacing-sm);
|
|
||||||
letter-spacing: var(--tracking-wider);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-version {
|
|
||||||
font-size: var(--font-lg);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin-bottom: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-status-text,
|
|
||||||
.wallet-network {
|
|
||||||
font-size: var(--font-md);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin-bottom: var(--spacing-xs);
|
|
||||||
|
|
||||||
strong {
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.donations-section,
|
|
||||||
.developer-section {
|
|
||||||
.section-subtitle {
|
|
||||||
font-size: var(--font-lg);
|
|
||||||
font-weight: var(--font-semibold);
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: var(--transition-normal);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.seed-modal-content {
|
|
||||||
:deep(.recovery-container) {
|
|
||||||
padding: 0;
|
|
||||||
min-height: auto;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.recovery-card) {
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,3 +1,3 @@
|
|||||||
export { default as WalletTab } from './WalletTab.vue'
|
|
||||||
export { default as NetworkTab } from './NetworkTab.vue'
|
export { default as NetworkTab } from './NetworkTab.vue'
|
||||||
export { default as UTXOTab } from './UTXOTab.vue'
|
export { default as UTXOTab } from './UTXOTab.vue'
|
||||||
|
export { WalletInfo, WalletBalanceAndAddress, WalletTab } from './wallet-tab'
|
||||||
|
|||||||
239
src/views/Home/components/wallet-tab/WalletBalanceAndAddress.vue
Normal file
239
src/views/Home/components/wallet-tab/WalletBalanceAndAddress.vue
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import { formatNumberToLocaleString } from '@/utils'
|
||||||
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
|
import { SpinnerCommon } from '@/components'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isLoadingData: boolean
|
||||||
|
availableBalance: number
|
||||||
|
pendingBalance: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const neptuneStore = useNeptuneStore()
|
||||||
|
|
||||||
|
const receiveAddress = computed(() => neptuneStore.getWallet?.address || '')
|
||||||
|
const isAddressExpanded = ref(false)
|
||||||
|
|
||||||
|
const toggleAddressExpanded = () => {
|
||||||
|
isAddressExpanded.value = !isAddressExpanded.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyAddress = async () => {
|
||||||
|
if (!receiveAddress.value) {
|
||||||
|
message.error('No address available')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(receiveAddress.value)
|
||||||
|
message.success('Address copied to clipboard!')
|
||||||
|
} catch (err) {
|
||||||
|
message.error('Failed to copy address')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="props.isLoadingData && !receiveAddress" class="loading-state">
|
||||||
|
<SpinnerCommon size="medium" />
|
||||||
|
<p>Loading wallet data...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="!receiveAddress" class="empty-state">
|
||||||
|
<p>No wallet found. Please create or import a wallet.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<!-- Balance Section -->
|
||||||
|
<div class="balance-section">
|
||||||
|
<div class="balance-label">Available</div>
|
||||||
|
<div class="balance-amount">
|
||||||
|
<span v-if="props.isLoadingData">Loading...</span>
|
||||||
|
<span v-else>{{ formatNumberToLocaleString(props.availableBalance) }} NPT</span>
|
||||||
|
</div>
|
||||||
|
<div class="pending-section">
|
||||||
|
<span class="pending-label">Pending</span>
|
||||||
|
<span class="pending-amount">
|
||||||
|
{{ props.isLoadingData ? '...' : formatNumberToLocaleString(props.pendingBalance) }}
|
||||||
|
NPT
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Receive Address Section -->
|
||||||
|
<div class="receive-section">
|
||||||
|
<div class="address-label">Receive Address:</div>
|
||||||
|
<div
|
||||||
|
class="address-value"
|
||||||
|
:class="{ expanded: isAddressExpanded, collapsed: !isAddressExpanded }"
|
||||||
|
@click="copyAddress"
|
||||||
|
>
|
||||||
|
<span class="address-text">
|
||||||
|
{{ receiveAddress || 'No address available' }}
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
class="copy-icon"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||||
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="receiveAddress && receiveAddress.length > 80"
|
||||||
|
class="toggle-address-btn"
|
||||||
|
@click.stop="toggleAddressExpanded"
|
||||||
|
>
|
||||||
|
{{ isAddressExpanded ? 'Show less' : 'Show more' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.loading-state,
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--spacing-3xl);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: var(--spacing-lg) 0 0;
|
||||||
|
font-size: var(--font-base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-section {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
|
padding-bottom: var(--spacing-lg);
|
||||||
|
border-bottom: 2px solid var(--border-color);
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.balance-label {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: var(--font-base);
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: var(--tracking-wider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-amount {
|
||||||
|
font-size: var(--font-4xl);
|
||||||
|
font-weight: var(--font-bold);
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
letter-spacing: var(--tracking-tight);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: var(--font-md);
|
||||||
|
|
||||||
|
.pending-label {
|
||||||
|
font-weight: var(--font-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-amount {
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.receive-section {
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.address-label {
|
||||||
|
font-size: var(--font-base);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-value {
|
||||||
|
background: var(--bg-light);
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
word-break: break-all;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
color: var(--primary-color);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-all);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
line-height: 1.5;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.address-text {
|
||||||
|
flex: 1;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapsed .address-text {
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.expanded .address-text {
|
||||||
|
display: block;
|
||||||
|
overflow: visible;
|
||||||
|
text-overflow: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
border-color: var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-top: 2px;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-address-btn {
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
padding: var(--spacing-xs) var(--spacing-md);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
font-weight: var(--font-medium);
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
229
src/views/Home/components/wallet-tab/WalletInfo.vue
Normal file
229
src/views/Home/components/wallet-tab/WalletInfo.vue
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { Modal } from 'ant-design-vue'
|
||||||
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
||||||
|
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import { ButtonCommon, CardBaseScrollable } from '@/components'
|
||||||
|
import SeedPhraseDisplayComponent from '@/views/Auth/components/SeedPhraseDisplayComponent.vue'
|
||||||
|
import WalletBalanceAndAddress from './WalletBalanceAndAddress.vue'
|
||||||
|
|
||||||
|
const neptuneStore = useNeptuneStore()
|
||||||
|
const { getBalance, saveKeystoreAs } = useNeptuneWallet()
|
||||||
|
|
||||||
|
const availableBalance = ref(0)
|
||||||
|
const pendingBalance = ref(0)
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
const showSeedModal = ref(false)
|
||||||
|
|
||||||
|
const getModalContainer = (): HTMLElement => {
|
||||||
|
const homeContainer = document.querySelector('.home-container') as HTMLElement
|
||||||
|
return homeContainer || document.body
|
||||||
|
}
|
||||||
|
|
||||||
|
const receiveAddress = computed(() => neptuneStore.getWallet?.address || '')
|
||||||
|
|
||||||
|
const walletStatus = computed(() => {
|
||||||
|
if (loading.value) return 'Loading...'
|
||||||
|
if (error.value) return 'Error'
|
||||||
|
if (neptuneStore.getWallet?.address) return 'Online'
|
||||||
|
return 'Offline'
|
||||||
|
})
|
||||||
|
|
||||||
|
const windowWidth = ref(window.innerWidth)
|
||||||
|
const modalWidth = computed(() => {
|
||||||
|
if (windowWidth.value <= 767) {
|
||||||
|
return '90%'
|
||||||
|
}
|
||||||
|
return '60%'
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
windowWidth.value = window.innerWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClickSendButton = () => {
|
||||||
|
// TODO: Implement send transaction functionality
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBackupFile = async () => {
|
||||||
|
try {
|
||||||
|
const seed = neptuneStore.getSeedPhraseString
|
||||||
|
const password = neptuneStore.getPassword
|
||||||
|
|
||||||
|
if (!seed || !password) {
|
||||||
|
message.error('Missing seed or password')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await saveKeystoreAs(seed, password)
|
||||||
|
message.success('Keystore saved successfully')
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error && err.message.includes('User canceled')) return
|
||||||
|
message.error('Failed to save keystore')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBackupSeed = () => {
|
||||||
|
showSeedModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
showSeedModal.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleModalNext = () => {
|
||||||
|
handleCloseModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadWalletData = async () => {
|
||||||
|
const receiveAddress = neptuneStore.getWallet?.address || ''
|
||||||
|
if (!receiveAddress) return
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
try {
|
||||||
|
const result = await getBalance()
|
||||||
|
|
||||||
|
availableBalance.value = +result.confirmedAvailable || 0
|
||||||
|
pendingBalance.value = +result.unconfirmedAvailable || 0
|
||||||
|
} catch (error) {
|
||||||
|
message.error('Failed to load wallet data')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadWalletData()
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CardBaseScrollable class="wallet-info-container">
|
||||||
|
<div class="wallet-content">
|
||||||
|
<WalletBalanceAndAddress
|
||||||
|
:is-loading-data="loading"
|
||||||
|
:available-balance="availableBalance"
|
||||||
|
:pending-balance="pendingBalance"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div v-if="receiveAddress" class="action-buttons">
|
||||||
|
<ButtonCommon
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
block
|
||||||
|
@click="handleClickSendButton"
|
||||||
|
class="btn-send"
|
||||||
|
>
|
||||||
|
SEND
|
||||||
|
</ButtonCommon>
|
||||||
|
<ButtonCommon type="primary" size="large" block @click="handleBackupFile">
|
||||||
|
Backup File
|
||||||
|
</ButtonCommon>
|
||||||
|
<ButtonCommon type="primary" size="large" block @click="handleBackupSeed">
|
||||||
|
Backup Seed
|
||||||
|
</ButtonCommon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Wallet Status -->
|
||||||
|
<div v-if="receiveAddress" class="wallet-status">
|
||||||
|
<span
|
||||||
|
>Wallet Status: <strong>{{ walletStatus }}</strong></span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardBaseScrollable>
|
||||||
|
<Modal
|
||||||
|
v-model:open="showSeedModal"
|
||||||
|
title="Backup Seed Phrase"
|
||||||
|
:footer="null"
|
||||||
|
:width="modalWidth"
|
||||||
|
:mask-closable="false"
|
||||||
|
:keyboard="false"
|
||||||
|
:get-container="getModalContainer"
|
||||||
|
@cancel="handleCloseModal"
|
||||||
|
>
|
||||||
|
<div class="seed-modal-content">
|
||||||
|
<SeedPhraseDisplayComponent
|
||||||
|
:back-button="false"
|
||||||
|
:next-button-text="'DONE'"
|
||||||
|
@next="handleModalNext"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.wallet-info-container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-content {
|
||||||
|
background: inherit;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
@include center_flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
:deep(.btn-send) {
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-status {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--bg-light);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: var(--font-base);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.seed-modal-content {
|
||||||
|
:deep(.recovery-container) {
|
||||||
|
padding: 0;
|
||||||
|
min-height: auto;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.recovery-card) {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modal responsive width
|
||||||
|
:deep(.ant-modal) {
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
width: 90% !important;
|
||||||
|
max-width: 90% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
width: 60% !important;
|
||||||
|
max-width: 60% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
src/views/Home/components/wallet-tab/WalletTab.vue
Normal file
15
src/views/Home/components/wallet-tab/WalletTab.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { WalletInfo } from '@/views/Home/components'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="wallet-tab-container">
|
||||||
|
<WalletInfo />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.wallet-tab-container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
3
src/views/Home/components/wallet-tab/index.ts
Normal file
3
src/views/Home/components/wallet-tab/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as WalletInfo } from './WalletInfo.vue'
|
||||||
|
export { default as WalletBalanceAndAddress } from './WalletBalanceAndAddress.vue'
|
||||||
|
export { default as WalletTab } from './WalletTab.vue'
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "electron/*"],
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "electron/*", "electron/utils/keystore.ts"],
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"exclude": ["src/**/__tests__/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user