291025/implement_electron

This commit is contained in:
NguyenAnhQuan 2025-10-29 19:55:44 +07:00
parent 20b3e4ad07
commit a39f306f8d
30 changed files with 13385 additions and 3564 deletions

View File

@ -0,0 +1,502 @@
"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
.vite/build/preload.js Normal file
View File

@ -0,0 +1 @@
"use strict";

49
electron/main.ts Normal file
View File

@ -0,0 +1,49 @@
import { app, BrowserWindow } from 'electron'
import path from 'node:path'
import started from 'electron-squirrel-startup'
if (started) {
app.quit()
}
const createWindow = () => {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL)
} else {
mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`))
}
mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.

2
electron/preload.ts Normal file
View File

@ -0,0 +1,2 @@
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts

View File

@ -0,0 +1,13 @@
import { defineConfig } from 'vite'
export default defineConfig({
build: {
lib: {
entry: 'electron/main.ts',
formats: ['cjs'],
},
rollupOptions: {
external: ['electron'],
},
},
})

View File

@ -0,0 +1,3 @@
import { defineConfig } from 'vite'
export default defineConfig({})

3
env.d.ts vendored
View File

@ -1 +1,4 @@
/// <reference types="vite/client" />
declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string | undefined;
declare const MAIN_WINDOW_VITE_NAME: string | undefined;

59
forge.config.ts Normal file
View File

@ -0,0 +1,59 @@
import type { ForgeConfig } from '@electron-forge/shared-types';
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
import { MakerZIP } from '@electron-forge/maker-zip';
import { MakerDeb } from '@electron-forge/maker-deb';
import { MakerRpm } from '@electron-forge/maker-rpm';
import { VitePlugin } from '@electron-forge/plugin-vite';
import { FusesPlugin } from '@electron-forge/plugin-fuses';
import { FuseV1Options, FuseVersion } from '@electron/fuses';
const config: ForgeConfig = {
packagerConfig: {
asar: true,
},
rebuildConfig: {},
makers: [
new MakerSquirrel({}),
new MakerZIP({}, ['darwin']),
new MakerRpm({}),
new MakerDeb({}),
],
plugins: [
new VitePlugin({
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
// If you are familiar with Vite configuration, it will look really familiar.
build: [
{
// `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
entry: 'electron/main.ts',
config: 'electron/vite.main.config.ts',
target: 'main',
},
{
entry: 'electron/preload.ts',
config: 'electron/vite.preload.config.ts',
target: 'preload',
},
],
renderer: [
{
name: 'main_window',
config: 'vite.config.ts',
},
],
}),
// Fuses are used to enable/disable various Electron functionality
// at package time, before code signing the application
new FusesPlugin({
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false,
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
],
};
export default config;

View File

@ -2,8 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/favicon.png" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Neptune Web Wallet</title>
</head>

12268
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,13 @@
"workspaces": [
"packages/*"
],
"main": ".vite/build/neptune-web-wallet.cjs",
"scripts": {
"dev": "vite",
"start:electron": "electron-forge start",
"make:electron": "electron-forge make",
"package:electron": "electron-forge package",
"publish:electron": "electron-forge publish",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
@ -20,20 +25,32 @@
"ant-design-vue": "^4.2.6",
"axios": "^1.6.8",
"dayjs": "^1.11.10",
"electron-squirrel-startup": "^1.0.1",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0",
"vue3-i18n": "^1.1.5"
},
"devDependencies": {
"@electron-forge/cli": "^7.10.2",
"@electron-forge/maker-deb": "^7.10.2",
"@electron-forge/maker-rpm": "^7.10.2",
"@electron-forge/maker-squirrel": "^7.10.2",
"@electron-forge/maker-zip": "^7.10.2",
"@electron-forge/plugin-auto-unpack-natives": "^7.10.2",
"@electron-forge/plugin-fuses": "^7.10.2",
"@electron-forge/plugin-vite": "^7.10.2",
"@electron/fuses": "^1.8.0",
"@rushstack/eslint-patch": "^1.8.0",
"@tsconfig/node20": "^20.1.4",
"@types/electron-squirrel-startup": "^1.0.2",
"@types/node": "^20.12.5",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1",
"electron": "^39.0.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"npm-run-all2": "^6.1.2",

View File

@ -51,3 +51,11 @@ export const getTransaction = async (txHash: string): Promise<any> => {
export const getMempoolInfo = async (): Promise<any> => {
return await callJsonRpc('chain_mempool', [])
}
export const importKeystore = async (keystore: string, password: string): Promise<any> => {
const params = {
keystore,
password,
}
return await callJsonRpc('wallet_importKeystore', params)
}

View File

@ -57,6 +57,7 @@ export function useNeptuneWallet() {
const resultJson = generate_seed()
const result: GenerateSeedResult = JSON.parse(resultJson)
console.log('result :>> ', result);
store.setSeedPhrase(result.seed_phrase)
store.setReceiverId(result.receiver_identifier)
@ -165,6 +166,41 @@ export function useNeptuneWallet() {
}
}
const importFromKeystore = async (keystore: string, password: string): Promise<any> => {
try {
store.setLoading(true)
store.setError(null)
const response = await API.importKeystore(keystore, password)
const result = response.data?.result || response.data
// Set wallet data from keystore import result
if (result.seed_phrase) {
store.setSeedPhrase(result.seed_phrase)
}
if (result.view_key) {
store.setViewKey(result.view_key)
}
if (result.address) {
store.setAddress(result.address)
}
if (result.receiver_identifier) {
store.setReceiverId(result.receiver_identifier)
}
if (result.network) {
store.setNetwork(result.network)
}
return result
} catch (err) {
const errorMsg = err instanceof Error ? err.message : 'Failed to import from keystore'
store.setError(errorMsg)
throw err
} finally {
store.setLoading(false)
}
}
// ===== API METHODS =====
const getUtxos = async (
@ -298,6 +334,7 @@ export function useNeptuneWallet() {
generateWallet,
importWallet,
importFromViewKey,
importFromKeystore,
getViewKeyFromSeed,
getAddressFromSeed,
validateSeedPhrase,

View File

@ -22,6 +22,11 @@ export const routes: any = [
name: 'login',
component: Page.Auth,
},
{
path: '/password',
name: 'password',
component: Page.PasswordKeystore,
},
{
path: '/:pathMatch(.*)*',
component: Page.NotFound,

12
src/types/electron.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
// Global type definitions for Electron API
declare global {
interface Window {
electron: {
send: (channel: string, data: any) => void;
receive: (channel: string, func: (...args: any[]) => void) => void;
removeAllListeners: (channel: string) => void;
};
}
}
export {};

View File

@ -1,39 +0,0 @@
/**
* Type declarations for Neptune WASM module
*/
declare module '/wasm/neptune_wasm.js' {
export default function init(): Promise<void>
export function generate_seed(): string
export function get_viewkey(seedPhraseJson: string, network: string): string
export function address_from_seed(seedPhraseJson: string, network: string): string
export function validate_seed_phrase(seedPhraseJson: string): boolean
export function decode_viewkey(viewKeyHex: string): string
export function get_balance(rpcUrl: string): Promise<string>
export function get_block_height(rpcUrl: string): Promise<number>
export function get_network_info(rpcUrl: string): Promise<string>
export function get_wallet_address(rpcUrl: string): Promise<string>
export function get_utxos_from_viewkey(
rpcUrl: string,
viewKeyHex: string,
startBlock: number,
endBlock: number | null
): Promise<string>
export function build_and_sign_tx(requestJson: string): string
export function send_tx_jsonrpc(
rpcUrl: string,
toAddress: string,
amount: string,
fee: string
): Promise<string>
}
// Global type definitions
declare global {
interface Window {
neptuneWasm?: any
}
}
export {}

View File

@ -1,142 +1 @@
// BIP39 English wordlist (first 100 words for demo)
const BIP39_WORDS = [
'abandon',
'ability',
'able',
'about',
'above',
'absent',
'absorb',
'abstract',
'absurd',
'abuse',
'access',
'accident',
'account',
'accuse',
'achieve',
'acid',
'acoustic',
'acquire',
'across',
'act',
'action',
'actor',
'actress',
'actual',
'adapt',
'add',
'addict',
'address',
'adjust',
'admit',
'adult',
'advance',
'advice',
'aerobic',
'affair',
'afford',
'afraid',
'again',
'age',
'agent',
'agree',
'ahead',
'aim',
'air',
'airport',
'aisle',
'alarm',
'album',
'alcohol',
'alert',
'alien',
'all',
'alley',
'allow',
'almost',
'alone',
'alpha',
'already',
'also',
'alter',
'always',
'amateur',
'amazing',
'among',
'amount',
'amused',
'analyst',
'anchor',
'ancient',
'anger',
'angle',
'angry',
'animal',
'ankle',
'announce',
'annual',
'another',
'answer',
'antenna',
'antique',
'anxiety',
'any',
'apart',
'apology',
'appear',
'apple',
'approve',
'april',
'arch',
'arctic',
'area',
'arena',
'argue',
'arm',
'armed',
'armor',
'army',
'around',
'arrange',
'arrest',
]
/**
* Generate a random seed phrase with 12 words
* In a real application, you would use a proper BIP39 library
*/
export const generateSeedPhrase = (): string[] => {
const words: string[] = []
for (let i = 0; i < 12; i++) {
const randomIndex = Math.floor(Math.random() * BIP39_WORDS.length)
words.push(BIP39_WORDS[randomIndex])
}
return words
}
/**
* Validate if a seed phrase is valid (basic validation)
*/
export const validateSeedPhrase = (words: string[]): boolean => {
if (words.length !== 12) return false
// Check if all words are in the BIP39 wordlist
return words.every((word) => BIP39_WORDS.includes(word.toLowerCase()))
}
export const validateSeedPhrase18 = (words: string[]): boolean => {
if (words.length !== 18) return false
return words.every((word) => BIP39_WORDS.includes(word.toLowerCase()))
}
export const generateSeedPhrase18 = (): string[] => {
const words: string[] = []
for (let i = 0; i < 18; i++) {
const randomIndex = Math.floor(Math.random() * BIP39_WORDS.length)
words.push(BIP39_WORDS[randomIndex])
}
return words
}
export const validateSeedPhrase18 = (words: string[]): boolean => words.length !== 18

View File

@ -1,30 +1,19 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ImportWalletComponent, OpenWalletComponent } from '.'
import { ImportWalletComponent } from '.'
import { useRouter } from 'vue-router'
const emit = defineEmits<{ goToCreate: [] }>()
const stage = ref<'import' | 'password'>('import')
const importData = ref<any>(null)
const router = useRouter()
const handleImported = (payload: { type: 'seed' | 'keystore'; value: string | string[] }) => {
importData.value = payload
stage.value = 'password'
}
const handleNavigateToCreate = () => {
emit('goToCreate')
if (payload.type === 'keystore') {
localStorage.setItem('temp_keystore', JSON.stringify(payload.value))
return router.push({ name: 'password' })
}
}
</script>
<template>
<div class="login-tab">
<ImportWalletComponent v-if="stage === 'import'" @import-success="handleImported" />
<OpenWalletComponent
v-else
:import-data="importData"
@navigateToCreate="handleNavigateToCreate"
@back="stage = 'import'"
/>
<ImportWalletComponent @import-success="handleImported" />
</div>
</template>
<style lang="scss" scoped>

View File

@ -34,7 +34,7 @@ const handleGoToLogin = () => {
Open existing wallet
</ButtonCommon>
</div>
<div class="note">Thank you for being a part of the Kaspa community!</div>
<div class="note">Thank you for being a part of the Neptune community!</div>
</div>
</div>
</div>

View File

@ -1,424 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { message } from 'ant-design-vue'
import { ButtonCommon, FormCommon } from '@/components'
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
interface ImportData {
type: 'seed' | 'keystore'
value: string | string[]
}
const props = defineProps<{
importData?: ImportData | null
}>()
const emit = defineEmits<{
navigateToCreate: []
back: []
}>()
const router = useRouter()
const { importWallet, importFromViewKey } = useNeptuneWallet()
const password = ref('')
const passwordError = ref('')
const isLoading = ref(false)
const handleOpenWallet = async () => {
if (!password.value) {
passwordError.value = 'Please enter your password'
return
}
if (!props.importData) {
passwordError.value = 'No import data available'
return
}
isLoading.value = true
passwordError.value = ''
try {
// TODO: Use password to decrypt the wallet data
// For now, we'll just import directly
if (props.importData.type === 'seed') {
const seedPhrase = Array.isArray(props.importData.value)
? props.importData.value
: props.importData.value.split(' ')
await importWallet(seedPhrase, 'testnet')
message.success('Wallet imported successfully from seed phrase!')
} else if (props.importData.type === 'keystore') {
// Assume keystore contains the view key hex string
const viewKey = typeof props.importData.value === 'string'
? props.importData.value
: props.importData.value.join('')
await importFromViewKey(viewKey)
message.success('Wallet imported successfully from keystore!')
}
// Navigate to home after successful import
router.push('/')
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Failed to import wallet'
passwordError.value = errorMsg
message.error(errorMsg)
} finally {
isLoading.value = false
}
}
const navigateToNewWallet = () => {
emit('navigateToCreate')
}
const handleBack = () => {
emit('back')
}
</script>
<template>
<div class="auth-container">
<div class="auth-card">
<div class="auth-card-content">
<div class="wallet-icon">
<div class="logo-container">
<div class="logo-circle">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
class="neptune-logo"
>
<defs>
<linearGradient
id="neptuneGradient"
x1="0%"
y1="0%"
x2="100%"
y2="100%"
>
<stop
offset="0%"
style="stop-color: #007fcf; stop-opacity: 1"
/>
<stop
offset="100%"
style="stop-color: #0066a6; stop-opacity: 1"
/>
</linearGradient>
<linearGradient
id="ringGradient"
x1="0%"
y1="0%"
x2="100%"
y2="0%"
>
<stop
offset="0%"
style="stop-color: #007fcf; stop-opacity: 0.3"
/>
<stop
offset="50%"
style="stop-color: #007fcf; stop-opacity: 0.6"
/>
<stop
offset="100%"
style="stop-color: #007fcf; stop-opacity: 0.3"
/>
</linearGradient>
</defs>
<circle cx="50" cy="50" r="28" fill="url(#neptuneGradient)" />
<ellipse
cx="50"
cy="45"
rx="22"
ry="6"
fill="rgba(255, 255, 255, 0.1)"
/>
<ellipse cx="50" cy="55" rx="20" ry="5" fill="rgba(0, 0, 0, 0.1)" />
<ellipse
cx="50"
cy="50"
rx="42"
ry="12"
fill="none"
stroke="url(#ringGradient)"
stroke-width="4"
opacity="0.8"
/>
<circle cx="42" cy="42" r="6" fill="rgba(255, 255, 255, 0.4)" />
</svg>
</div>
<div class="logo-text">
<span class="coin-name">Neptune</span>
<span class="coin-symbol">NPTUN</span>
</div>
</div>
</div>
<div class="import-info" v-if="importData">
<div class="info-label">Importing from:</div>
<div class="info-value">{{ importData.type === 'seed' ? 'Seed Phrase' : 'Keystore' }}</div>
</div>
<div class="form-group">
<FormCommon
v-model="password"
type="password"
label="Create a password to encrypt your wallet"
placeholder="Enter password"
show-password-toggle
required
:error="passwordError"
:disabled="isLoading"
@input="passwordError = ''"
@keyup.enter="handleOpenWallet"
/>
</div>
<div class="security-notice">
<div class="notice-icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path
d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"
stroke="currentColor"
stroke-width="2"
/>
<path
d="M9 12l2 2 4-4"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
<span>Your password is encrypted and stored locally</span>
</div>
<div class="auth-button-group">
<ButtonCommon
type="primary"
size="large"
block
:disabled="!password || isLoading"
:loading="isLoading"
@click="handleOpenWallet"
>
{{ isLoading ? 'Importing...' : 'Import Wallet' }}
</ButtonCommon>
<div class="button-row">
<ButtonCommon type="default" size="large" @click="handleBack" :disabled="isLoading">
Back
</ButtonCommon>
<ButtonCommon type="default" size="large" @click="navigateToNewWallet" :disabled="isLoading">
New Wallet
</ButtonCommon>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.auth-container {
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing-xl);
background: var(--bg-light);
}
.auth-card {
@include card-base;
max-width: 420px;
width: 100%;
@include screen(mobile) {
max-width: 100%;
}
}
.auth-card-content {
.import-info {
text-align: center;
padding: var(--spacing-md);
background: var(--primary-light);
border-radius: var(--radius-md);
margin-bottom: var(--spacing-lg);
.info-label {
font-size: var(--font-sm);
color: var(--text-secondary);
margin-bottom: var(--spacing-xs);
}
.info-value {
font-size: var(--font-md);
font-weight: var(--font-semibold);
color: var(--primary-color);
}
}
.form-group {
margin-bottom: var(--spacing-xl);
}
}
.logo-container {
display: flex;
align-items: center;
justify-content: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
.logo-circle {
width: 48px;
height: 48px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary-light), var(--bg-white));
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing-sm);
box-shadow: 0 2px 8px rgba(0, 127, 207, 0.15);
.neptune-logo {
width: 100%;
height: 100%;
}
}
.logo-text {
display: flex;
flex-direction: column;
align-items: flex-start;
.coin-name {
font-size: var(--font-lg);
font-weight: var(--font-bold);
color: var(--text-primary);
line-height: 1;
margin-bottom: 2px;
}
.coin-symbol {
font-size: var(--font-xs);
font-weight: var(--font-medium);
color: var(--primary-color);
background: var(--primary-light);
padding: 2px 8px;
border-radius: var(--radius-sm);
}
}
}
.security-notice {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-md);
background: var(--bg-light);
border-radius: var(--radius-md);
border: 1px solid var(--border-light);
margin-bottom: var(--spacing-xl);
.notice-icon {
color: var(--success-color);
flex-shrink: 0;
}
span {
font-size: var(--font-xs);
color: var(--text-secondary);
line-height: var(--leading-normal);
}
}
.auth-button-group {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
margin-bottom: var(--spacing-xl);
.button-row {
display: flex;
gap: var(--spacing-md);
> * {
flex: 1;
}
}
}
// Help Links
.help-links {
display: flex;
justify-content: center;
align-items: center;
gap: var(--spacing-sm);
padding-top: var(--spacing-lg);
border-top: 1px solid var(--border-color);
.link-button {
background: none;
border: none;
color: var(--primary-color);
font-size: var(--font-sm);
cursor: pointer;
transition: color 0.2s ease;
padding: 0;
&:hover:not(:disabled) {
color: var(--primary-hover);
text-decoration: underline;
}
&:disabled {
color: var(--text-muted);
cursor: not-allowed;
}
}
.separator {
color: var(--text-muted);
font-size: var(--font-sm);
}
}
// Responsive Design
@include screen(mobile) {
.auth-container {
padding: var(--spacing-md);
}
.logo-container {
.logo-circle {
width: 40px;
height: 40px;
}
.logo-text {
.coin-name {
font-size: var(--font-md);
}
}
}
.wallet-icon {
.icon-circle {
width: 64px;
height: 64px;
}
}
}
</style>

View File

@ -0,0 +1,328 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ButtonCommon, FormCommon } from '@/components'
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
import { useRouter } from 'vue-router'
const emit = defineEmits<{
success: []
error: [message: string]
}>()
const router = useRouter()
const { importFromKeystore } = useNeptuneWallet()
const password = ref('')
const passwordError = ref('')
const isLoading = ref(false)
const passwordValidation = computed(() => {
if (!password.value) return { isValid: false, message: '' }
const checks = {
length: password.value.length >= 8,
uppercase: /[A-Z]/.test(password.value),
lowercase: /[a-z]/.test(password.value),
number: /[0-9]/.test(password.value),
special: /[!@#$%^&*(),.?":{}|<>]/.test(password.value),
}
if (!checks.length) return { isValid: false, message: 'Password must be at least 8 characters' }
if (!checks.uppercase)
return { isValid: false, message: 'Password must contain uppercase letter' }
if (!checks.lowercase)
return { isValid: false, message: 'Password must contain lowercase letter' }
if (!checks.number) return { isValid: false, message: 'Password must contain number' }
if (!checks.special)
return { isValid: false, message: 'Password must contain special character' }
return { isValid: true, message: 'Password format is valid' }
})
const canProceed = computed(() => {
return password.value.length > 0 && passwordValidation.value.isValid
})
const handleSubmit = async () => {
if (!canProceed.value) {
if (password.value.length > 0 && !passwordValidation.value.isValid) {
passwordError.value = passwordValidation.value.message
} else {
passwordError.value = 'Please enter your password'
}
return
}
try {
isLoading.value = true
passwordError.value = ''
const keystore = localStorage.getItem('temp_keystore')
if (!keystore) {
throw new Error('No keystore found. Please try importing again.')
}
await importFromKeystore(keystore, password.value)
localStorage.removeItem('temp_keystore')
router.push({ name: 'home' })
emit('success')
} catch (err) {
const errorMsg = err instanceof Error ? err.message : 'Failed to access wallet'
passwordError.value = errorMsg
emit('error', errorMsg)
} finally {
isLoading.value = false
}
}
const handleBack = () => {
router.back()
}
</script>
<template>
<div class="password-keystore-step">
<div class="auth-card-header">
<div class="logo-container">
<div class="logo-circle">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 100 100"
class="neptune-logo"
>
<defs>
<linearGradient
id="neptuneGradient"
x1="0%"
y1="0%"
x2="100%"
y2="100%"
>
<stop offset="0%" style="stop-color: #007fcf; stop-opacity: 1" />
<stop offset="100%" style="stop-color: #0066a6; stop-opacity: 1" />
</linearGradient>
<linearGradient id="ringGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color: #007fcf; stop-opacity: 0.3" />
<stop offset="50%" style="stop-color: #007fcf; stop-opacity: 0.6" />
<stop
offset="100%"
style="stop-color: #007fcf; stop-opacity: 0.3"
/>
</linearGradient>
</defs>
<circle cx="50" cy="50" r="28" fill="url(#neptuneGradient)" />
<ellipse cx="50" cy="45" rx="22" ry="6" fill="rgba(255, 255, 255, 0.1)" />
<ellipse cx="50" cy="55" rx="20" ry="5" fill="rgba(0, 0, 0, 0.1)" />
<ellipse
cx="50"
cy="50"
rx="42"
ry="12"
fill="none"
stroke="url(#ringGradient)"
stroke-width="4"
opacity="0.8"
/>
<circle cx="42" cy="42" r="6" fill="rgba(255, 255, 255, 0.4)" />
</svg>
</div>
<div class="logo-text">
<span class="coin-name">Neptune</span>
<span class="coin-symbol">NPTUN</span>
</div>
</div>
<h1 class="auth-title">Access Wallet</h1>
<p class="auth-subtitle">Enter your keystore password to unlock your wallet</p>
</div>
<div class="auth-card-content">
<div class="form-group">
<FormCommon
v-model="password"
type="password"
label="Keystore Password"
placeholder="Enter your keystore password"
show-password-toggle
required
:error="passwordError"
@input="passwordError = ''"
/>
</div>
<p class="helper-text">
Password must be at least 8 characters with uppercase, lowercase, numbers, and
special characters.
</p>
<div class="auth-button-group">
<ButtonCommon
type="primary"
size="large"
class="auth-button"
block
:disabled="!canProceed || isLoading"
:loading="isLoading"
@click="handleSubmit"
>
Access Wallet
</ButtonCommon>
<div class="secondary-actions">
<button class="link-button" @click="handleBack">Back to Import</button>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.password-keystore-step {
width: 100%;
}
.auth-card-header {
text-align: center;
margin-bottom: var(--spacing-2xl);
padding-bottom: var(--spacing-xl);
border-bottom: 1px solid var(--border-color);
.logo-container {
display: flex;
align-items: center;
justify-content: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
.logo-circle {
width: 48px;
height: 48px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary-light), var(--bg-white));
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing-sm);
box-shadow: 0 2px 8px rgba(0, 127, 207, 0.15);
.neptune-logo {
width: 100%;
height: 100%;
}
}
.logo-text {
display: flex;
flex-direction: column;
align-items: flex-start;
.coin-name {
font-size: var(--font-lg);
font-weight: var(--font-bold);
color: var(--text-primary);
line-height: 1;
margin-bottom: 2px;
}
.coin-symbol {
font-size: var(--font-xs);
font-weight: var(--font-medium);
color: var(--primary-color);
background: var(--primary-light);
padding: 2px 8px;
border-radius: var(--radius-sm);
}
}
}
.auth-title {
font-size: var(--font-2xl);
font-weight: var(--font-bold);
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
}
.auth-subtitle {
font-size: var(--font-sm);
color: var(--text-secondary);
margin: 0;
}
}
.auth-card-content {
.form-group {
margin-bottom: var(--spacing-xl);
}
}
.helper-text {
font-size: var(--font-xs);
color: var(--text-muted);
margin: 0 0 var(--spacing-xl);
line-height: var(--leading-normal);
}
.auth-button {
width: fit-content;
margin: 0 auto;
}
.auth-button-group {
margin-top: var(--spacing-2xl);
display: flex;
flex-direction: column;
.secondary-actions {
display: flex;
justify-content: center;
align-items: center;
gap: var(--spacing-sm);
margin-top: var(--spacing-md);
}
.link-button {
background: none;
border: none;
color: var(--primary-color);
font-size: var(--font-sm);
cursor: pointer;
transition: color 0.2s ease;
padding: 0;
&:hover {
color: var(--primary-hover);
text-decoration: underline;
}
}
.separator {
color: var(--text-muted);
font-size: var(--font-sm);
}
}
@include screen(mobile) {
.auth-card-header {
.logo-container {
.logo-circle {
width: 40px;
height: 40px;
}
.logo-text {
.coin-name {
font-size: var(--font-md);
}
}
}
.auth-title {
font-size: var(--font-xl);
}
}
}
</style>

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { validateSeedPhrase18 } from '@/utils'
import { ref } from 'vue'
import { validateSeedPhrase18 } from '@/utils/helpers/seedPhrase'
const emit = defineEmits<{
(e: 'update:valid', valid: boolean): void
@ -12,7 +12,7 @@ const seedError = ref('')
const updateValidity = () => {
const words = seedWords.value.filter((w) => w.trim())
const isValid = words.length === 18 && !seedError.value
const isValid = validateSeedPhrase18(words) && !seedError.value
emit('update:valid', isValid)
}
@ -29,9 +29,9 @@ const handlePaste = (event: ClipboardEvent) => {
event.preventDefault()
const pastedData = event.clipboardData?.getData('text') || ''
const words = pastedData
.trim()
.split(/\s+/)
.filter((w) => w)
.trim()
.split(/\s+/)
.filter((w) => w)
if (words.length === 0) return
@ -42,23 +42,16 @@ const handlePaste = (event: ClipboardEvent) => {
const validateSeed = () => {
const words = seedWords.value.filter((w) => w.trim())
if (words.length !== 18) {
seedError.value = 'Please enter all 18 words.'
return false
}
if (!validateSeedPhrase18(words)) {
seedError.value = 'One or more words are invalid.'
return false
}
seedError.value = ''
return true
return validateSeedPhrase18(words)
}
const handleSubmit = () => {
if (validateSeed()) {
emit('submit', seedWords.value.filter((w) => w.trim()))
}
if (validateSeed()) seedError.value = ''
emit(
'submit',
seedWords.value.filter((w) => w.trim())
)
updateValidity()
}
@ -91,7 +84,7 @@ defineExpose({
:class="{ error: seedError && !word.trim() }"
@focus="seedError = ''"
@input="handleGridInput(i, ($event.target as HTMLInputElement).value)"
@paste="handlePaste($event, i)"
@paste="handlePaste($event)"
/>
</div>
</div>
@ -168,4 +161,3 @@ defineExpose({
}
}
</style>

View File

@ -8,7 +8,6 @@ export { default as KeystoreTab } from './KeystoreTab.vue'
// Auth Components
export { default as OnboardingComponent } from './OnboardingComponent.vue'
export { default as OpenWalletComponent } from './OpenWalletComponent.vue'
export { default as CreateWalletComponent } from './CreateWalletComponent.vue'
export { default as RecoverySeedComponent } from './RecoverySeedComponent.vue'
export { default as ConfirmSeedComponent } from './ConfirmSeedComponent.vue'

View File

@ -5,7 +5,7 @@ import WalletInfo from '@/components/WalletInfo.vue'
import { WalletTab, NetworkTab, UTXOTab } from './components'
const activeTab = ref('UTXOs')
const network = ref('kaspa-mainnet')
const network = ref('neptune-mainnet')
</script>
<template>

View File

@ -0,0 +1,49 @@
<script setup lang="ts">
import PasswordKeystoreComponent from '@/views/Auth/components/PasswordKeystoreComponent.vue'
const handleSuccess = () => {
console.log('Wallet accessed successfully')
}
const handleError = (message: string) => {
console.error('Error accessing wallet:', message)
}
</script>
<template>
<div class="password-keystore-view">
<div class="auth-container">
<PasswordKeystoreComponent @success="handleSuccess" @error="handleError" />
</div>
</div>
</template>
<style lang="scss" scoped>
.password-keystore-view {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-primary);
padding: var(--spacing-lg);
}
.auth-container {
width: 100%;
max-width: 480px;
background: var(--bg-white);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-primary);
padding: var(--spacing-2xl);
}
@include screen(mobile) {
.password-keystore-view {
padding: var(--spacing-md);
}
.auth-container {
padding: var(--spacing-xl);
}
}
</style>

View File

@ -1,3 +1,4 @@
export const Home = () => import('@/views/Home/HomeView.vue')
export const NotFound = () => import('@/views/NotFound/NotFoundView.vue')
export const Auth = () => import('@/views/Auth/AuthView.vue')
export const PasswordKeystore = () => import('@/views/PasswordKeystore/PasswordKeystoreView.vue')

View File

@ -1,6 +1,6 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "electron/*"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,

View File

@ -1,6 +1,6 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"include": ["vite.config.*", "vitest.config.*"],
"include": ["vite.config.*", "vitest.config.*", "vite.config.ts", "env.d.ts"],
"compilerOptions": {
"composite": true,
"noEmit": true,

View File

@ -8,6 +8,7 @@ export default defineConfig({
server: {
port: 3008,
},
base: './',
plugins: [vue(), vueJsx(), VueDevTools()],
optimizeDeps: {
exclude: ['@neptune/wasm'],

2912
yarn.lock

File diff suppressed because it is too large Load Diff