diff --git a/.gitignore b/.gitignore index e8b2d7e..beb9639 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ dist dist-ssr coverage *.local -.vite/*/** +.vite/ wallets/* /cypress/videos/ diff --git a/.vite/build/neptune-web-wallet.cjs b/.vite/build/neptune-web-wallet.cjs deleted file mode 100644 index 94c0a41..0000000 --- a/.vite/build/neptune-web-wallet.cjs +++ /dev/null @@ -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(); - } -}); diff --git a/.vite/build/preload.js b/.vite/build/preload.js deleted file mode 100644 index 3918c74..0000000 --- a/.vite/build/preload.js +++ /dev/null @@ -1 +0,0 @@ -"use strict"; diff --git a/electron/ipcHandlers.ts b/electron/ipcHandlers.ts index a137f07..fddc0d2 100644 --- a/electron/ipcHandlers.ts +++ b/electron/ipcHandlers.ts @@ -1,54 +1,77 @@ -import { ipcMain } from 'electron' -import { Wallet } from 'ethers' +import { ipcMain, dialog, app } from 'electron' import fs from 'fs' import path from 'path' +import { encrypt, fromEncryptedJson } from './utils/keystore' +// Create keystore into default wallets directory ipcMain.handle('wallet:createKeystore', async (_event, seed, password) => { try { - const wallet = Wallet.fromPhrase(seed) - const keystore = await wallet.encrypt(password) + const keystore = await encrypt(seed, password) const savePath = path.join(process.cwd(), 'wallets') 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) - return { address: wallet.address, filePath, error: null } + return { filePath } } catch (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) => { try { const json = fs.readFileSync(filePath, 'utf-8') - const wallet = await Wallet.fromEncryptedJson(json, password) + const phrase = await fromEncryptedJson(json, password) - let phrase: string | undefined - if ('mnemonic' in wallet && wallet.mnemonic) { - phrase = wallet.mnemonic.phrase - } - - return { address: wallet.address, phrase, error: null } + return { phrase } } catch (error) { console.error('Error decrypting keystore ipc:', error) - return { address: null, phrase: null, error: String(error) } + throw error } }) ipcMain.handle('wallet:checkKeystore', async () => { try { const walletDir = path.join(process.cwd(), 'wallets') - if (!fs.existsSync(walletDir)) - return { exists: false, filePath: null, error: 'Wallet directory not found' } + if (!fs.existsSync(walletDir)) return { exists: false, filePath: null } 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) - return { exists: true, filePath, error: null } + return { exists: true, filePath} } catch (error) { console.error('Error checking keystore:', error) return { exists: false, filePath: null, error: String(error) } diff --git a/electron/main.ts b/electron/main.ts index e2a70fc..59a63c1 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -9,7 +9,7 @@ if (started) { const createWindow = () => { const mainWindow = new BrowserWindow({ - width: 1000, + width: 800, height: 800, webPreferences: { preload: path.join(__dirname, 'preload.js'), diff --git a/electron/preload.ts b/electron/preload.ts index 1ffceeb..dd9af25 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -5,6 +5,8 @@ import { contextBridge, ipcRenderer } from 'electron' contextBridge.exposeInMainWorld('walletApi', { createKeystore: (seed: string, password: string) => ipcRenderer.invoke('wallet:createKeystore', seed, password), + saveKeystoreAs: (seed: string, password: string) => + ipcRenderer.invoke('wallet:saveKeystoreAs', seed, password), decryptKeystore: (filePath: string, password: string) => ipcRenderer.invoke('wallet:decryptKeystore', filePath, password), checkKeystore: () => ipcRenderer.invoke('wallet:checkKeystore'), diff --git a/electron/utils/index.ts b/electron/utils/index.ts new file mode 100644 index 0000000..50741d8 --- /dev/null +++ b/electron/utils/index.ts @@ -0,0 +1 @@ +export * from './keystore' diff --git a/electron/utils/keystore.ts b/electron/utils/keystore.ts new file mode 100644 index 0000000..ec17d98 --- /dev/null +++ b/electron/utils/keystore.ts @@ -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((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((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') +} diff --git a/package-lock.json b/package-lock.json index 3a5492c..b8b95d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,8 @@ "@neptune/wasm": "file:./packages/neptune-wasm", "ant-design-vue": "^4.2.6", "axios": "^1.6.8", - "dayjs": "^1.11.10", + "crypto": "^1.0.1", "electron-squirrel-startup": "^1.0.1", - "ethers": "^6.15.0", "pinia": "^2.1.7", "vue": "^3.4.21", "vue-router": "^4.3.0", @@ -53,12 +52,6 @@ "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": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", @@ -2533,30 +2526,6 @@ "resolved": "packages/neptune-wasm", "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": { "version": "2.1.5", "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" } }, - "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": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -5338,6 +5301,13 @@ "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -6734,49 +6704,6 @@ "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": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -11351,12 +11278,6 @@ "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": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -12181,27 +12102,6 @@ "dev": true, "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": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", diff --git a/package.json b/package.json index 7d3b17a..68e7195 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,8 @@ "@neptune/wasm": "file:./packages/neptune-wasm", "ant-design-vue": "^4.2.6", "axios": "^1.6.8", - "dayjs": "^1.11.10", + "crypto": "^1.0.1", "electron-squirrel-startup": "^1.0.1", - "ethers": "^6.15.0", "pinia": "^2.1.7", "vue": "^3.4.21", "vue-router": "^4.3.0", diff --git a/src/api/config/index.ts b/src/api/config/index.ts index 7cf0169..4a93c26 100644 --- a/src/api/config/index.ts +++ b/src/api/config/index.ts @@ -1,3 +1,4 @@ +import { STATUS_CODE_SUCCESS } from '@/utils/constants/code' import axios from 'axios' 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) => { instance.defaults.headers.common['lang'] = locale } diff --git a/src/api/neptuneApi.ts b/src/api/neptuneApi.ts index 40a751d..3e73965 100644 --- a/src/api/neptuneApi.ts +++ b/src/api/neptuneApi.ts @@ -1,18 +1,18 @@ import { callJsonRpc } from '@/api/request' -export const getUtxosFromViewKey = ( +export const getUtxosFromViewKey = async ( viewKey: string, startBlock: number = 0, endBlock: number | null = null, maxSearchDepth: number = 1000 -) => { +): Promise => { const params = { viewKey, startBlock, endBlock, maxSearchDepth, } - return callJsonRpc('wallet_getUtxosFromViewKey', params) + return await callJsonRpc('wallet_getUtxosFromViewKey', params) } export const getBalance = async (): Promise => { diff --git a/src/assets/scss/__base.scss b/src/assets/scss/__base.scss index fb5eb84..512a0dd 100644 --- a/src/assets/scss/__base.scss +++ b/src/assets/scss/__base.scss @@ -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 { - font-family: 'Noto Sans JP'; + font-family: var(--font-primary); 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; 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); + } +} diff --git a/src/assets/scss/__variables.scss b/src/assets/scss/__variables.scss index 9c893f4..4cc3848 100644 --- a/src/assets/scss/__variables.scss +++ b/src/assets/scss/__variables.scss @@ -74,9 +74,8 @@ // ==================== TYPOGRAPHY ==================== // 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-noto: 'Noto Sans JP'; // Font Sizes --font-xs: 0.75rem; // 12px @@ -87,7 +86,7 @@ --font-xl: 1.1rem; // 17.6px --font-2xl: 1.2rem; // 19.2px --font-3xl: 1.5rem; // 24px - --font-4xl: 3rem; // 48px + --font-4xl: 2rem; // 32px // Font Weights --font-normal: 400; @@ -106,26 +105,6 @@ --tracking-wide: 0.5px; --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 ==================== // Card @@ -139,13 +118,6 @@ --btn-padding-y: 0.75rem; --btn-padding-x: 1rem; --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-height: 3px; diff --git a/src/components/WalletInfo.vue b/src/components/WalletInfo.vue deleted file mode 100644 index 1059f58..0000000 --- a/src/components/WalletInfo.vue +++ /dev/null @@ -1,333 +0,0 @@ - - - - - diff --git a/src/components/common/CardBase.vue b/src/components/common/CardBase.vue new file mode 100644 index 0000000..8308529 --- /dev/null +++ b/src/components/common/CardBase.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/src/components/common/CardBaseScrollable.vue b/src/components/common/CardBaseScrollable.vue new file mode 100644 index 0000000..9aaeb49 --- /dev/null +++ b/src/components/common/CardBaseScrollable.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/src/components/common/LayoutVue.vue b/src/components/common/LayoutVue.vue index 621d83f..7ef88c7 100644 --- a/src/components/common/LayoutVue.vue +++ b/src/components/common/LayoutVue.vue @@ -1,12 +1,32 @@ + + diff --git a/src/components/common/PasswordForm.vue b/src/components/common/PasswordForm.vue index 01cefee..ac6b25a 100644 --- a/src/components/common/PasswordForm.vue +++ b/src/components/common/PasswordForm.vue @@ -1,6 +1,6 @@ diff --git a/src/views/Home/components/NetworkTab.vue b/src/views/Home/components/NetworkTab.vue index 2c36e8c..38c81bf 100644 --- a/src/views/Home/components/NetworkTab.vue +++ b/src/views/Home/components/NetworkTab.vue @@ -1,46 +1,32 @@ diff --git a/src/views/Home/components/index.ts b/src/views/Home/components/index.ts index 395285a..7e349f5 100644 --- a/src/views/Home/components/index.ts +++ b/src/views/Home/components/index.ts @@ -1,3 +1,3 @@ -export { default as WalletTab } from './WalletTab.vue' export { default as NetworkTab } from './NetworkTab.vue' export { default as UTXOTab } from './UTXOTab.vue' +export { WalletInfo, WalletBalanceAndAddress, WalletTab } from './wallet-tab' diff --git a/src/views/Home/components/wallet-tab/WalletBalanceAndAddress.vue b/src/views/Home/components/wallet-tab/WalletBalanceAndAddress.vue new file mode 100644 index 0000000..3b512c5 --- /dev/null +++ b/src/views/Home/components/wallet-tab/WalletBalanceAndAddress.vue @@ -0,0 +1,239 @@ + + + + + + diff --git a/src/views/Home/components/wallet-tab/WalletInfo.vue b/src/views/Home/components/wallet-tab/WalletInfo.vue new file mode 100644 index 0000000..09ff633 --- /dev/null +++ b/src/views/Home/components/wallet-tab/WalletInfo.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/src/views/Home/components/wallet-tab/WalletTab.vue b/src/views/Home/components/wallet-tab/WalletTab.vue new file mode 100644 index 0000000..273ae52 --- /dev/null +++ b/src/views/Home/components/wallet-tab/WalletTab.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/src/views/Home/components/wallet-tab/index.ts b/src/views/Home/components/wallet-tab/index.ts new file mode 100644 index 0000000..eb89156 --- /dev/null +++ b/src/views/Home/components/wallet-tab/index.ts @@ -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' diff --git a/tsconfig.app.json b/tsconfig.app.json index dfb086b..81f45cb 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,6 +1,6 @@ { "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__/*"], "compilerOptions": { "composite": true,