Compare commits

...

6 Commits

38 changed files with 901 additions and 592 deletions

109
README.md
View File

@ -1,31 +1,118 @@
# web-wallet
# Neptune Web Wallet
## Project Setup
A secure Electron-based desktop wallet for the Neptune blockchain network.
## 🚀 Features
- 🔐 Secure keystore encryption (AES-256-GCM)
- 💼 Create and recover wallets from seed phrases
- 💸 Send and receive Neptune tokens
- 📊 View transaction history and UTXOs
- 🛡️ Enhanced security with strict Content Security Policy
## 📋 Prerequisites
- Node.js >= 16
- npm or yarn
## 🔧 Installation
Install dependencies:
```sh
yarn
npm install
```
### Compile and Hot-Reload for Development
## 💻 Development
### Start Electron app in development mode
```sh
yarn dev
npm run start:electron
```
### Type-Check, Compile and Minify for Production
### Start Vite dev server only
```sh
yarn build-only
npm run dev
```
### Lint with [ESLint](https://eslint.org/)
### Type-check
```sh
yarn lint
npm run type-check
```
### Format code
### Lint with ESLint
```sh
yarn format
npm run lint
```
### Format code with Prettier
```sh
npm run format
```
## 📦 Production Build
### Package the application
```sh
npm run package:electron
```
### Create distributables (installers)
```sh
npm run make:electron
```
The installers will be generated in `out/make/` directory:
- **Windows**: `.exe` installer in `out/make/squirrel.windows/x64/`
- **macOS**: `.zip` in `out/make/zip/darwin/`
- **Linux**: `.deb` and `.rpm` in respective directories
## 🔐 Security Features
- ✅ Context isolation enabled
- ✅ Node integration disabled
- ✅ Sandbox enabled
- ✅ Strict Content Security Policy in production
- ✅ Certificate validation
- ✅ Path traversal protection
- ✅ Input validation for all IPC handlers
- ✅ Secure keystore encryption with scrypt and AES-256-GCM
## 🌍 Environment Variables
Create a `.env` file in the root directory:
```env
# Neptune API URL
VITE_APP_API=
# Network (mainnet or testnet)
VITE_NODE_NETWORK=mainnet
```
## 🛠️ Tech Stack
- **Framework**: Electron + Vue 3 + TypeScript
- **Build Tool**: Vite
- **State Management**: Pinia
## 📝 License
Apache-2.0
## 👥 Authors
Neptune Team
## 🔗 Links
- [Neptune Blockchain](https://neptune.cash)
- [Documentation](#)
- [Report Issues](#)

View File

@ -2,36 +2,107 @@ import { ipcMain, dialog, app } from 'electron'
import fs from 'fs'
import path from 'path'
import { encrypt, fromEncryptedJson } from './utils/keystore'
import logger from './logger'
const neptuneNative = require('@neptune/native')
let neptuneNative: any = null
function loadNativeModule() {
try {
// Try normal require first (works in dev)
neptuneNative = require('@neptune/native')
logger.info('[Native] Loaded native module')
return
} catch (err) {
logger.error('[Native] Failed to load native module')
}
// Fallback: load from Resources in packaged app
try {
const platform = process.platform
const arch = process.arch
// Determine the correct .node file
let nodeFileName = ''
if (platform === 'win32' && arch === 'x64') {
nodeFileName = 'neptune-native.win32-x64-msvc.node'
} else if (platform === 'linux' && arch === 'x64') {
nodeFileName = 'neptune-native.linux-x64-gnu.node'
} else if (platform === 'darwin' && arch === 'arm64') {
nodeFileName = 'neptune-native.darwin-arm64.node'
} else {
throw new Error(`Unsupported platform: ${platform}-${arch}`)
}
// Try multiple possible locations
const possiblePaths = [
path.join(process.resourcesPath, 'packages', 'neptune-native', nodeFileName),
path.join(process.resourcesPath, 'neptune-native', nodeFileName),
path.join(process.resourcesPath, nodeFileName),
path.join(process.resourcesPath, 'app.asar.unpacked', 'packages', 'neptune-native', nodeFileName),
path.join(process.resourcesPath, 'app.asar.unpacked', 'node_modules', '@neptune', 'native', nodeFileName),
]
let nativeBinding: any = null
for (const nodePath of possiblePaths) {
if (fs.existsSync(nodePath)) {
nativeBinding = require(nodePath)
break
}
}
if (!nativeBinding) {
throw new Error(`Native module not found`)
}
neptuneNative = nativeBinding
logger.info('[Native] Successfully loaded native module')
} catch (error) {
logger.error('[Native] Failed to load native module:')
throw error
}
}
// Initialize the native module
loadNativeModule()
const WALLETS_DIR = path.resolve(app.getPath('userData'), 'wallets')
fs.mkdirSync(WALLETS_DIR, { recursive: true })
function assertBasename(name: string) {
if (!name || name !== path.basename(name)) {
throw new Error('Invalid file name')
}
}
function safeResolvePath(fileName: string) {
assertBasename(fileName)
const candidate = path.join(WALLETS_DIR, fileName)
const realBase = fs.realpathSync(WALLETS_DIR)
const realCandidate = fs.existsSync(candidate) ? fs.realpathSync(candidate) : candidate
if (!(realCandidate === realBase || realCandidate.startsWith(realBase + path.sep))) {
throw new Error('Access denied')
}
return realCandidate
}
// Create keystore into default wallets directory
ipcMain.handle('wallet:createKeystore', async (_event, seed, password) => {
try {
const keystore = await encrypt(seed, password)
const savePath = path.join(process.cwd(), 'wallets')
fs.mkdirSync(savePath, { recursive: true })
// 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 { filePath }
} catch (error) {
console.error('Error creating keystore:', error)
const filePath = path.join(WALLETS_DIR, fileName)
fs.writeFileSync(filePath, keystore, 'utf-8')
return { success: true, fileName }
} catch (error: any) {
logger.error('Error creating keystore:', error.message)
throw error
}
})
// New handler: let user choose folder and filename to save keystore
ipcMain.handle('wallet:saveKeystoreAs', async (_event, seed: string, password: string) => {
ipcMain.handle('wallet:saveKeystoreAs', async (_event, fileName: string) => {
try {
const keystore = await encrypt(seed, password)
// Use timestamp for default filename
const pathToFileName = safeResolvePath(fileName)
const keystore = fs.readFileSync(pathToFileName, 'utf-8')
const timestamp = Date.now()
const defaultName = `neptune-wallet-${timestamp}.json`
const { canceled, filePath } = await dialog.showSaveDialog({
@ -40,155 +111,154 @@ ipcMain.handle('wallet:saveKeystoreAs', async (_event, seed: string, password: s
filters: [{ name: 'JSON', extensions: ['json'] }],
})
if (canceled || !filePath) return { filePath: null }
if (canceled || !filePath) return { success: false, filePath: null }
if (path.extname(filePath).toLowerCase() !== '.json') {
throw new Error('Invalid file extension. Please save as a .json file.')
}
fs.mkdirSync(path.dirname(filePath), { recursive: true })
fs.writeFileSync(filePath, keystore)
return { filePath }
} catch (error) {
console.error('Error saving keystore (Save As):', error)
fs.writeFileSync(filePath, keystore, 'utf-8')
return { success: true, filePath }
} catch (error: any) {
logger.error('Error saving keystore (Save As):', error.message)
throw error
}
})
ipcMain.handle('wallet:decryptKeystore', async (_event, filePath, password) => {
ipcMain.handle('wallet:decryptKeystore', async (_event, fileName: string, password: string) => {
try {
const filePath = safeResolvePath(fileName)
const json = fs.readFileSync(filePath, 'utf-8')
const phrase = await fromEncryptedJson(json, password)
return { phrase }
} catch (error) {
console.error('Error decrypting keystore ipc:', error)
} catch (error: any) {
logger.error('Error decrypting keystore ipc:', error.message)
throw error
}
})
ipcMain.handle('wallet:checkKeystore', async () => {
try {
const walletDir = path.join(process.cwd(), 'wallets')
if (!fs.existsSync(walletDir)) return { exists: false, filePath: null }
if (!fs.existsSync(WALLETS_DIR)) return { exists: false, fileName: null }
const newestFile = fs
.readdirSync(walletDir)
.readdirSync(WALLETS_DIR)
.filter((f) => f.endsWith('.json'))
.sort(
(a, b) =>
fs.statSync(path.join(walletDir, b)).mtime.getTime() -
fs.statSync(path.join(walletDir, a)).mtime.getTime()
fs.statSync(path.join(WALLETS_DIR, b)).mtime.getTime() -
fs.statSync(path.join(WALLETS_DIR, a)).mtime.getTime()
)[0]
if (!newestFile) return { exists: false, filePath: null }
const resolvedPath = path.join(walletDir, newestFile)
if (!newestFile) return { exists: false, fileName: null }
const resolvedPath = safeResolvePath(newestFile)
let minBlockHeight: number | null = null
try {
const json = fs.readFileSync(resolvedPath, 'utf-8')
const data = JSON.parse(json)
const height = data?.minBlockHeight
if (typeof height === 'number' && Number.isFinite(height)) {
minBlockHeight = height
}
} catch (error) {
console.warn('Unable to read minBlockHeight from keystore:', error)
if (Number.isFinite(height)) minBlockHeight = height
} catch (error: any) {
logger.warn('Unable to read minBlockHeight from keystore:', error.message)
}
return { exists: true, filePath: resolvedPath, minBlockHeight }
} catch (error) {
console.error('Error checking keystore ipc:', error)
return { exists: false, filePath: null, minBlockHeight: null, error: String(error) }
return { exists: true, fileName: newestFile, minBlockHeight }
} catch (error: any) {
logger.error('Error checking keystore ipc:', error.message)
return { exists: false, fileName: null, minBlockHeight: null, error: error.message }
}
})
ipcMain.handle(
'wallet:updateMinBlockHeight',
async (_event, filePath: string | null, minBlockHeight: number | null) => {
if (!filePath) {
return { success: false, error: 'No keystore file path provided.' }
async (_event, fileName: string | null, minBlockHeight: number | null) => {
if (!fileName) {
return { success: false, error: 'No keystore file name provided.' }
}
try {
const normalizedPath = path.isAbsolute(filePath)
? filePath
: path.join(process.cwd(), filePath)
if (!fs.existsSync(normalizedPath)) {
const filePath = safeResolvePath(fileName)
if (!fs.existsSync(filePath)) {
return { success: false, error: 'Keystore file not found.' }
}
const fileContents = fs.readFileSync(normalizedPath, 'utf-8')
const fileContents = fs.readFileSync(filePath, 'utf-8')
const walletJson = JSON.parse(fileContents)
if (minBlockHeight === null || Number.isNaN(minBlockHeight)) {
walletJson.minBlockHeight = null
} else {
walletJson.minBlockHeight = minBlockHeight
}
fs.writeFileSync(normalizedPath, JSON.stringify(walletJson, null, 2), 'utf-8')
walletJson.minBlockHeight = Number.isFinite(minBlockHeight) ? minBlockHeight : null
fs.writeFileSync(filePath, JSON.stringify(walletJson, null, 2), 'utf-8')
return { success: true, minBlockHeight }
} catch (error) {
console.error('Error updating min block height:', error)
return { success: false, error: String(error) }
} catch (error: any) {
logger.error('Error updating min block height:', error.message)
return { success: false, error: error.message }
}
}
)
ipcMain.handle(
'wallet:getMinBlockHeight',
async (_event, filePath: string | null) => {
if (!filePath) {
return { success: false, error: 'No keystore file path provided.', minBlockHeight: null }
}
try {
const normalizedPath = path.isAbsolute(filePath)
? filePath
: path.join(process.cwd(), filePath)
if (!fs.existsSync(normalizedPath)) {
return { success: false, error: 'Keystore file not found.', minBlockHeight: null }
}
const fileContents = fs.readFileSync(normalizedPath, 'utf-8')
const walletJson = JSON.parse(fileContents)
const height = walletJson?.minBlockHeight
if (typeof height === 'number' && Number.isFinite(height)) {
return { success: true, minBlockHeight: height }
}
return { success: true, minBlockHeight: null }
} catch (error) {
console.error('Error reading min block height:', error)
return { success: false, error: String(error), minBlockHeight: null }
}
ipcMain.handle('wallet:getMinBlockHeight', async (_event, fileName: string | null) => {
if (!fileName) {
return { success: false, error: 'No keystore file name provided.', minBlockHeight: null }
}
)
try {
const filePath = safeResolvePath(fileName)
if (!fs.existsSync(filePath)) {
return { success: false, error: 'Keystore file not found.', minBlockHeight: null }
}
const fileContents = fs.readFileSync(filePath, 'utf-8')
const walletJson = JSON.parse(fileContents)
const height = walletJson?.minBlockHeight
return {
success: true,
minBlockHeight: Number.isFinite(height) ? height : null,
}
} catch (error: any) {
logger.error('Error reading min block height:', error.message)
return { success: false, error: error.message, minBlockHeight: null }
}
})
ipcMain.handle('wallet:generateKeysFromSeed', async (_event, seedPhrase: string[]) => {
try {
const wallet = new neptuneNative.WalletManager()
return wallet.generateKeysFromSeed(seedPhrase)
} catch (error) {
console.error('Error generating keys from seed ipc:', error)
return JSON.parse(wallet.generateKeysFromSeed(seedPhrase))
} catch (error: any) {
logger.error('Error generating keys from seed ipc:', error.message)
throw error
}
})
ipcMain.handle('wallet:buildTransaction', async (_event, args) => {
const { spendingKeyHex, inputAdditionRecords, outputAddresses, outputAmounts, fee } = args
try {
if (typeof spendingKeyHex !== 'string' || !/^(0x)?[0-9a-fA-F]+$/.test(spendingKeyHex)) {
throw new Error('Invalid spending key')
}
if (
!Array.isArray(inputAdditionRecords) ||
!inputAdditionRecords.every((r) => typeof r === 'string')
) {
throw new Error('Invalid inputAdditionRecords')
}
if (
!Array.isArray(outputAddresses) ||
!outputAddresses.every((a) => typeof a === 'string')
) {
throw new Error('Invalid outputAddresses')
}
if (
!Array.isArray(outputAmounts) ||
!outputAmounts.every((a) => typeof a === 'string' && /^\d+(\.\d+)?$/.test(a))
) {
throw new Error('Invalid outputAmounts')
}
if (typeof fee !== 'string' || !/^\d+(\.\d+)?$/.test(fee)) {
throw new Error('Invalid fee')
}
const builder = new neptuneNative.SimpleTransactionBuilder()
const result = await builder.buildTransaction(
import.meta.env.VITE_APP_API,
spendingKeyHex,
inputAdditionRecords,
// pass minBlockHeight from args if provided, default 0
typeof args?.minBlockHeight === 'number' && Number.isFinite(args.minBlockHeight)
? args.minBlockHeight
: 0,
@ -197,8 +267,12 @@ ipcMain.handle('wallet:buildTransaction', async (_event, args) => {
fee
)
return JSON.parse(result)
} catch (error) {
console.error('Error building transaction with primitive proof ipc:', error)
} catch (error: any) {
logger.error('Error building transaction ipc:', error.message)
throw error
}
})
ipcMain.on('log:info', (_, ...msg: string[]) => logger.info(...msg))
ipcMain.on('log:warn', (_, ...msg: string[]) => logger.warn(...msg))
ipcMain.on('log:error', (_, ...msg: string[]) => logger.error(...msg))

27
electron/logger.ts Normal file
View File

@ -0,0 +1,27 @@
import { app } from 'electron'
import log from 'electron-log'
const isDev = app.isPackaged
if (!isDev) {
log.transports.file.level = 'info'
log.transports.console.level = false
log.transports.file.format = '{y}-{m}-{d} {h}:{i}:{s} [{level}] {text}'
}
export const logger = {
info: (...args: string[]) => {
if (!isDev) log.info(...args)
},
warn: (...args: string[]) => {
if (!isDev) log.warn(...args)
},
error: (...args: string[]) => {
if (!isDev) log.error(...args)
},
debug: (...args: string[]) => {
if (!isDev) log.debug(...args)
},
}
export default logger

View File

@ -2,12 +2,29 @@ import { app, BrowserWindow } from 'electron'
import path from 'node:path'
import started from 'electron-squirrel-startup'
import './ipcHandlers'
import logger from './logger'
const isDev = !app.isPackaged
if (started) {
app.quit()
}
// Security: Handle certificate errors - reject invalid certificates to prevent phishing
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
if (isDev && (url.startsWith('http://localhost') || url.startsWith('http://127.0.0.1'))) {
event.preventDefault()
callback(true)
return
}
logger.warn(`Certificate error for ${url}: ${error}`)
event.preventDefault()
callback(false)
})
const createWindow = () => {
logger.info('App started')
const mainWindow = new BrowserWindow({
width: 800,
height: 800,
@ -18,6 +35,8 @@ const createWindow = () => {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
devTools: isDev,
sandbox: true,
},
})
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
@ -26,7 +45,48 @@ const createWindow = () => {
mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`))
}
mainWindow.webContents.openDevTools()
// Security: Prevent opening new windows - block all window.open() calls
mainWindow.webContents.setWindowOpenHandler(() => {
logger.warn('Blocked attempt to open new window')
return { action: 'deny' }
})
// Security: Control file downloads - validate and log all downloads
mainWindow.webContents.session.on('will-download', (event, item) => {
const fileName = item.getFilename()
const totalBytes = item.getTotalBytes()
logger.info(`Download started: ${fileName} (${totalBytes} bytes)`)
// Validate file extension - only allow safe file types
const allowedExtensions = ['.json', '.txt', '.csv']
const fileExt = path.extname(fileName).toLowerCase()
if (!allowedExtensions.includes(fileExt)) {
logger.warn(`Blocked download of potentially unsafe file: ${fileName}`)
item.cancel()
return
}
// Log download progress
item.on('updated', () => {
logger.info(
`Download progress: ${fileName} - ${item.getReceivedBytes()}/${totalBytes} bytes`
)
})
item.on('done', (event, state) => {
if (state === 'completed') {
logger.info(`Download completed: ${fileName}`)
} else if (state === 'cancelled') {
logger.warn(`Download cancelled: ${fileName}`)
} else {
logger.error(`Download interrupted: ${fileName} - ${state}`)
}
})
})
if (!isDev) mainWindow.removeMenu()
}
// This method will be called when Electron has finished
@ -38,6 +98,7 @@ app.on('ready', createWindow)
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
logger.info('App closed')
if (process.platform !== 'darwin') {
app.quit()
}

View File

@ -5,16 +5,21 @@ 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),
saveKeystoreAs: (fileName: string) => ipcRenderer.invoke('wallet:saveKeystoreAs', fileName),
decryptKeystore: (fileName: string, password: string) =>
ipcRenderer.invoke('wallet:decryptKeystore', fileName, password),
checkKeystore: () => ipcRenderer.invoke('wallet:checkKeystore'),
generateKeysFromSeed: (seedPhrase: string[]) =>
ipcRenderer.invoke('wallet:generateKeysFromSeed', seedPhrase),
buildTransaction: (args: any) => ipcRenderer.invoke('wallet:buildTransaction', args),
updateMinBlockHeight: (filePath: string | null, minBlockHeight: number | null) =>
ipcRenderer.invoke('wallet:updateMinBlockHeight', filePath, minBlockHeight),
getMinBlockHeight: (filePath: string | null) =>
ipcRenderer.invoke('wallet:getMinBlockHeight', filePath),
updateMinBlockHeight: (fileName: string | null, minBlockHeight: number | null) =>
ipcRenderer.invoke('wallet:updateMinBlockHeight', fileName, minBlockHeight),
getMinBlockHeight: (fileName: string | null) =>
ipcRenderer.invoke('wallet:getMinBlockHeight', fileName),
})
contextBridge.exposeInMainWorld('logger', {
info: (...msg: any[]) => ipcRenderer.send('log:info', ...msg),
warn: (...msg: any[]) => ipcRenderer.send('log:warn', ...msg),
error: (...msg: any[]) => ipcRenderer.send('log:error', ...msg),
})

View File

@ -6,14 +6,26 @@ 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';
import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives';
const config: ForgeConfig = {
packagerConfig: {
asar: true,
extraResource: [
'./packages/neptune-native',
'./packages/neptune-wasm',
]
},
rebuildConfig: {
onlyModules: ['@neptune/native'],
force: true,
},
rebuildConfig: {},
makers: [
new MakerSquirrel({}),
new MakerSquirrel({
name: 'neptune-web-wallet',
authors: 'Neptune Team',
description: 'Neptune Blockchain Wallet - Secure cryptocurrency wallet for Neptune network',
}),
new MakerZIP({}, ['darwin']),
new MakerRpm({}),
new MakerDeb({}),
@ -53,6 +65,9 @@ const config: ForgeConfig = {
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
new AutoUnpackNativesPlugin({
packageJsonPropNames: ['@neptune/native'],
}),
],
};

View File

@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Neptune Web Wallet</title>
</head>
<body>

494
package-lock.json generated
View File

@ -15,10 +15,10 @@
"@neptune/wasm": "file:./packages/neptune-wasm",
"ant-design-vue": "^4.2.6",
"axios": "^1.6.8",
"crypto": "^1.0.1",
"electron-log": "^5.4.3",
"electron-squirrel-startup": "^1.0.1",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue": "^3.5.24",
"vue-router": "^4.3.0",
"vue3-i18n": "^1.1.5"
},
@ -32,12 +32,12 @@
"@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",
"@rushstack/eslint-patch": "^1.15.0",
"@tsconfig/node20": "^20.1.4",
"@types/electron-squirrel-startup": "^1.0.2",
"@types/node": "^20.12.5",
"@types/node": "^20.19.25",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vitejs/plugin-vue-jsx": "^5.1.1",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1",
@ -46,11 +46,11 @@
"eslint-plugin-vue": "^9.23.0",
"npm-run-all2": "^6.1.2",
"prettier": "^3.2.5",
"sass": "^1.75.0",
"sass": "^1.94.0",
"typescript": "~5.4.0",
"unplugin-auto-import": "^20.2.0",
"unplugin-vue-components": "^30.0.0",
"vite": "^5.2.8",
"vite": "^5.4.21",
"vite-plugin-vue-devtools": "^7.0.25",
"vue-tsc": "^2.0.11"
}
@ -1976,9 +1976,9 @@
}
},
"node_modules/@inquirer/core/node_modules/@types/node": {
"version": "22.19.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.0.tgz",
"integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==",
"version": "22.19.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz",
"integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -2016,9 +2016,9 @@
}
},
"node_modules/@inquirer/figures": {
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.14.tgz",
"integrity": "sha512-DbFgdt+9/OZYFM+19dbpXOSeAstPy884FPy1KjDu4anWwymZeOYhMY1mdFri172htv6mvc/uvIAAi7b7tvjJBQ==",
"version": "1.0.15",
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz",
"integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==",
"dev": true,
"license": "MIT",
"engines": {
@ -2699,6 +2699,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.50",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz",
"integrity": "sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==",
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/pluginutils": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
@ -2736,9 +2743,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz",
"integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz",
"integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==",
"cpu": [
"arm"
],
@ -2750,9 +2757,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz",
"integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz",
"integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==",
"cpu": [
"arm64"
],
@ -2764,9 +2771,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz",
"integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz",
"integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==",
"cpu": [
"arm64"
],
@ -2778,9 +2785,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz",
"integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz",
"integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==",
"cpu": [
"x64"
],
@ -2792,9 +2799,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz",
"integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz",
"integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==",
"cpu": [
"arm64"
],
@ -2806,9 +2813,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz",
"integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz",
"integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==",
"cpu": [
"x64"
],
@ -2820,9 +2827,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz",
"integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz",
"integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==",
"cpu": [
"arm"
],
@ -2834,9 +2841,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz",
"integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz",
"integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==",
"cpu": [
"arm"
],
@ -2848,9 +2855,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz",
"integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz",
"integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==",
"cpu": [
"arm64"
],
@ -2862,9 +2869,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz",
"integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz",
"integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==",
"cpu": [
"arm64"
],
@ -2876,9 +2883,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz",
"integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz",
"integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==",
"cpu": [
"loong64"
],
@ -2890,9 +2897,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz",
"integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz",
"integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==",
"cpu": [
"ppc64"
],
@ -2904,9 +2911,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz",
"integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz",
"integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==",
"cpu": [
"riscv64"
],
@ -2918,9 +2925,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz",
"integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz",
"integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==",
"cpu": [
"riscv64"
],
@ -2932,9 +2939,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz",
"integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz",
"integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==",
"cpu": [
"s390x"
],
@ -2946,9 +2953,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz",
"integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz",
"integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==",
"cpu": [
"x64"
],
@ -2960,9 +2967,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz",
"integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz",
"integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==",
"cpu": [
"x64"
],
@ -2974,9 +2981,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz",
"integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz",
"integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==",
"cpu": [
"arm64"
],
@ -2988,9 +2995,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz",
"integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz",
"integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==",
"cpu": [
"arm64"
],
@ -3002,9 +3009,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz",
"integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz",
"integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==",
"cpu": [
"ia32"
],
@ -3016,9 +3023,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz",
"integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz",
"integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==",
"cpu": [
"x64"
],
@ -3030,9 +3037,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz",
"integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz",
"integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==",
"cpu": [
"x64"
],
@ -3044,9 +3051,9 @@
]
},
"node_modules/@rushstack/eslint-patch": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.14.1.tgz",
"integrity": "sha512-jGTk8UD/RdjsNZW8qq10r0RBvxL8OWtoT+kImlzPDFilmozzM+9QmIJsmze9UiSBrFU45ZxhTYBypn9q9z/VfQ==",
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz",
"integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==",
"dev": true,
"license": "MIT"
},
@ -3117,9 +3124,9 @@
}
},
"node_modules/@tsconfig/node20": {
"version": "20.1.6",
"resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.6.tgz",
"integrity": "sha512-sz+Hqx9zwZDpZIV871WSbUzSqNIsXzghZydypnfgzPKLltVJfkINfUeTct31n/tTSa9ZE1ZOfKdRre1uHHquYQ==",
"version": "20.1.7",
"resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.7.tgz",
"integrity": "sha512-Lt137u6AoCCJNNklpNtfGIYFEywma7Hd3B083jqMga37opSEVw7beWsD4OTXyzKS0I3G2kbQJVoZpOxy9GnxBQ==",
"dev": true,
"license": "MIT"
},
@ -3218,9 +3225,9 @@
}
},
"node_modules/@types/node": {
"version": "20.19.24",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz",
"integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==",
"version": "20.19.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -3496,21 +3503,23 @@
}
},
"node_modules/@vitejs/plugin-vue-jsx": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-3.1.0.tgz",
"integrity": "sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-5.1.1.tgz",
"integrity": "sha512-uQkfxzlF8SGHJJVH966lFTdjM/lGcwJGzwAHpVqAPDD/QcsqoUGa+q31ox1BrUfi+FLP2ChVp7uLXE3DkHyDdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.23.3",
"@babel/plugin-transform-typescript": "^7.23.3",
"@vue/babel-plugin-jsx": "^1.1.5"
"@babel/core": "^7.28.3",
"@babel/plugin-syntax-typescript": "^7.27.1",
"@babel/plugin-transform-typescript": "^7.28.0",
"@rolldown/pluginutils": "^1.0.0-beta.34",
"@vue/babel-plugin-jsx": "^1.5.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"vite": "^4.0.0 || ^5.0.0",
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
"vue": "^3.0.0"
}
},
@ -3604,39 +3613,39 @@
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.23.tgz",
"integrity": "sha512-nW7THWj5HOp085ROk65LwaoxuzDsjIxr485F4iu63BoxsXoSqKqmsUUoP4A7Gl67DgIgi0zJ8JFgHfvny/74MA==",
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.24.tgz",
"integrity": "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/shared": "3.5.23",
"@vue/shared": "3.5.24",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.23.tgz",
"integrity": "sha512-AT8RMw0vEzzzO0JU5gY0F6iCzaWUIh/aaRVordzMBKXRpoTllTT4kocHDssByPsvodNCfump/Lkdow2mT/O5KQ==",
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz",
"integrity": "sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.23",
"@vue/shared": "3.5.23"
"@vue/compiler-core": "3.5.24",
"@vue/shared": "3.5.24"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.23.tgz",
"integrity": "sha512-3QTEUo4qg7FtQwaDJa8ou1CUikx5WTtZlY61rRRDu3lK2ZKrGoAGG8mvDgOpDsQ4A1bez9s+WtBB6DS2KuFCPw==",
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.24.tgz",
"integrity": "sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@vue/compiler-core": "3.5.23",
"@vue/compiler-dom": "3.5.23",
"@vue/compiler-ssr": "3.5.23",
"@vue/shared": "3.5.23",
"@vue/compiler-core": "3.5.24",
"@vue/compiler-dom": "3.5.24",
"@vue/compiler-ssr": "3.5.24",
"@vue/shared": "3.5.24",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.6",
@ -3644,13 +3653,13 @@
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.23",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.23.tgz",
"integrity": "sha512-Hld2xphbMjXs9Q9WKxPf2EqmE+Rq/FEDnK/wUBtmYq74HCV4XDdSCheAaB823OQXIIFGq9ig/RbAZkF9s4U0Ow==",
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.24.tgz",
"integrity": "sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.23",
"@vue/shared": "3.5.23"
"@vue/compiler-dom": "3.5.24",
"@vue/shared": "3.5.24"
}
},
"node_modules/@vue/compiler-vue2": {
@ -3671,14 +3680,14 @@
"license": "MIT"
},
"node_modules/@vue/devtools-core": {
"version": "7.7.7",
"resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.7.tgz",
"integrity": "sha512-9z9TLbfC+AjAi1PQyWX+OErjIaJmdFlbDHcD+cAMYKY6Bh5VlsAtCeGyRMrXwIlMEQPukvnWt3gZBLwTAIMKzQ==",
"version": "7.7.8",
"resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.8.tgz",
"integrity": "sha512-EVLQTYML/v77JFA3Q8zvVANCvEv1WtG0TMo+HQR5eZ7PpEzSmVbEcBp2C1/OXyn8EJO4mHEeParMLpp43prUOw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vue/devtools-kit": "^7.7.7",
"@vue/devtools-shared": "^7.7.7",
"@vue/devtools-kit": "^7.7.8",
"@vue/devtools-shared": "^7.7.8",
"mitt": "^3.0.1",
"nanoid": "^5.1.0",
"pathe": "^2.0.3",
@ -3708,13 +3717,13 @@
}
},
"node_modules/@vue/devtools-kit": {
"version": "7.7.7",
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz",
"integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==",
"version": "7.7.8",
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.8.tgz",
"integrity": "sha512-4Y8op+AoxOJhB9fpcEF6d5vcJXWKgHxC3B0ytUB8zz15KbP9g9WgVzral05xluxi2fOeAy6t140rdQ943GcLRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vue/devtools-shared": "^7.7.7",
"@vue/devtools-shared": "^7.7.8",
"birpc": "^2.3.0",
"hookable": "^5.5.3",
"mitt": "^3.0.1",
@ -3724,9 +3733,9 @@
}
},
"node_modules/@vue/devtools-shared": {
"version": "7.7.7",
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz",
"integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==",
"version": "7.7.8",
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.8.tgz",
"integrity": "sha512-XHpO3jC5nOgYr40M9p8Z4mmKfTvUxKyRcUnpBAYg11pE78eaRFBKb0kG5yKLroMuJeeNH9LWmKp2zMU5LUc7CA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -3825,53 +3834,53 @@
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.23",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.23.tgz",
"integrity": "sha512-ji5w0qvrPyBmBx5Ldv4QGNsw0phgRreEvjt0iUf1lei2Sm8//9ZAi78uM2ZjsT5gk0YZilLuoRCIMvtuZlHMJw==",
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz",
"integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.23"
"@vue/shared": "3.5.24"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.23",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.23.tgz",
"integrity": "sha512-LMB0S6/G7mFJcpQeQaZrbsthFbWrIX8FVTzu5x9U3Ec8YW5MY1CGAnBBHNj+TPOBu3pIbtPpjrXtcaN04X+aBw==",
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz",
"integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.23",
"@vue/shared": "3.5.23"
"@vue/reactivity": "3.5.24",
"@vue/shared": "3.5.24"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.23",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.23.tgz",
"integrity": "sha512-r/PYc8W9THzEL0UExpTkV+d31zO+Jid/RMZIDG6aS/NekOEUHuCJkJgftySWZw7JTJO/+q9Kxkg8p+i7Q7Q+ew==",
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz",
"integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.23",
"@vue/runtime-core": "3.5.23",
"@vue/shared": "3.5.23",
"@vue/reactivity": "3.5.24",
"@vue/runtime-core": "3.5.24",
"@vue/shared": "3.5.24",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.23",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.23.tgz",
"integrity": "sha512-NiWZsNCsXA20/VufcrW5u+Trt/PyFlpMmxaB2KERYM8eZgUoKUjXxJQb9ypq+LZ0Sp3XHJGNBR8DkhRnkKAMUw==",
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz",
"integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.23",
"@vue/shared": "3.5.23"
"@vue/compiler-ssr": "3.5.24",
"@vue/shared": "3.5.24"
},
"peerDependencies": {
"vue": "3.5.23"
"vue": "3.5.24"
}
},
"node_modules/@vue/shared": {
"version": "3.5.23",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.23.tgz",
"integrity": "sha512-0YZ1DYuC5o/YJPf6pFdt2KYxVGDxkDbH/1NYJnVJWUkzr8ituBEmFVQRNX2gCaAsFEjEDnLkWpgqlZA7htgS/g==",
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz",
"integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==",
"license": "MIT"
},
"node_modules/@vue/tsconfig": {
@ -4392,9 +4401,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.8.25",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz",
"integrity": "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==",
"version": "2.8.28",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.28.tgz",
"integrity": "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@ -4402,9 +4411,9 @@
}
},
"node_modules/birpc": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.7.0.tgz",
"integrity": "sha512-tub/wFGH49vNCm0xraykcY3TcRgX/3JsALYq/Lwrtti+bTyFHkCUAWF5wgYoie8P41wYwig2mIKiqoocr1EkEQ==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.8.0.tgz",
"integrity": "sha512-Bz2a4qD/5GRhiHSwj30c/8kC8QGj12nNDwz3D4ErQ4Xhy35dsSDvF+RA/tWpjyU0pdGtSDiEk6B5fBGE1qNVhw==",
"dev": true,
"license": "MIT",
"funding": {
@ -4471,9 +4480,9 @@
}
},
"node_modules/browserslist": {
"version": "4.27.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz",
"integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==",
"version": "4.28.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
"integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
"dev": true,
"funding": [
{
@ -4491,10 +4500,10 @@
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.8.19",
"caniuse-lite": "^1.0.30001751",
"electron-to-chromium": "^1.5.238",
"node-releases": "^2.0.26",
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
"electron-to-chromium": "^1.5.249",
"node-releases": "^2.0.27",
"update-browserslist-db": "^1.1.4"
},
"bin": {
@ -5092,13 +5101,6 @@
"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",
@ -5186,9 +5188,9 @@
"license": "MIT"
},
"node_modules/default-browser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz",
"integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.3.0.tgz",
"integrity": "sha512-Qq68+VkJlc8tjnPV1i7HtbIn7ohmjZa88qUvHMIK0ZKUXMCuV45cT7cEXALPUmeXCe0q1DWQkQTemHVaLIFSrg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -5387,9 +5389,9 @@
"license": "MIT"
},
"node_modules/electron": {
"version": "39.1.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-39.1.0.tgz",
"integrity": "sha512-vPRbKKQUzKWZZX68fuYdz4iS/eavGcQkHOGK4ylv0YJLbBRxxUlflPRdqRGflFjwid+sja7gbNul2lArevYwrw==",
"version": "39.1.2",
"resolved": "https://registry.npmjs.org/electron/-/electron-39.1.2.tgz",
"integrity": "sha512-+/TwT9NWxyQGTm5WemJEJy+bWCpnKJ4PLPswI1yn1P63bzM0/8yAeG05yS+NfFaWH4yNQtGXZmAv87Bxa5RlLg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@ -5807,6 +5809,15 @@
"node": ">=10"
}
},
"node_modules/electron-log": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.4.3.tgz",
"integrity": "sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/electron-squirrel-startup": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/electron-squirrel-startup/-/electron-squirrel-startup-1.0.1.tgz",
@ -5832,9 +5843,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.245",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.245.tgz",
"integrity": "sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ==",
"version": "1.5.250",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.250.tgz",
"integrity": "sha512-/5UMj9IiGDMOFBnN4i7/Ry5onJrAGSbOGo3s9FEKmwobGq6xw832ccET0CE3CkkMBZ8GJSlUIesZofpyurqDXw==",
"dev": true,
"license": "ISC"
},
@ -5921,9 +5932,9 @@
}
},
"node_modules/electron/node_modules/@types/node": {
"version": "22.19.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.0.tgz",
"integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==",
"version": "22.19.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz",
"integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -6563,9 +6574,9 @@
"license": "Apache-2.0"
},
"node_modules/exsolve": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
"integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
"dev": true,
"license": "MIT"
},
@ -7565,9 +7576,9 @@
}
},
"node_modules/ip-address": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
"dev": true,
"license": "MIT",
"engines": {
@ -7847,9 +7858,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -8752,10 +8763,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/neptune-native": {
"resolved": "packages/neptune-native",
"link": true
},
"node_modules/nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@ -8764,9 +8771,9 @@
"license": "MIT"
},
"node_modules/node-abi": {
"version": "3.80.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.80.0.tgz",
"integrity": "sha512-LyPuZJcI9HVwzXK1GPxWNzrr+vr8Hp/3UqlmWxxh8p54U1ZbclOqbSog9lWHaCX+dBaiGi6n/hIX+mKu74GmPA==",
"version": "3.82.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.82.0.tgz",
"integrity": "sha512-aQuSU0xnqUUNU7rVmEB8M8pAJ/1SA/DL0tdAtr+X63+x61UJMo1Ulsp/3liX33EuA4kM8GG762/pyHtgag5PmQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -10085,9 +10092,9 @@
}
},
"node_modules/rollup": {
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz",
"integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -10101,28 +10108,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.52.5",
"@rollup/rollup-android-arm64": "4.52.5",
"@rollup/rollup-darwin-arm64": "4.52.5",
"@rollup/rollup-darwin-x64": "4.52.5",
"@rollup/rollup-freebsd-arm64": "4.52.5",
"@rollup/rollup-freebsd-x64": "4.52.5",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.5",
"@rollup/rollup-linux-arm-musleabihf": "4.52.5",
"@rollup/rollup-linux-arm64-gnu": "4.52.5",
"@rollup/rollup-linux-arm64-musl": "4.52.5",
"@rollup/rollup-linux-loong64-gnu": "4.52.5",
"@rollup/rollup-linux-ppc64-gnu": "4.52.5",
"@rollup/rollup-linux-riscv64-gnu": "4.52.5",
"@rollup/rollup-linux-riscv64-musl": "4.52.5",
"@rollup/rollup-linux-s390x-gnu": "4.52.5",
"@rollup/rollup-linux-x64-gnu": "4.52.5",
"@rollup/rollup-linux-x64-musl": "4.52.5",
"@rollup/rollup-openharmony-arm64": "4.52.5",
"@rollup/rollup-win32-arm64-msvc": "4.52.5",
"@rollup/rollup-win32-ia32-msvc": "4.52.5",
"@rollup/rollup-win32-x64-gnu": "4.52.5",
"@rollup/rollup-win32-x64-msvc": "4.52.5",
"@rollup/rollup-android-arm-eabi": "4.53.2",
"@rollup/rollup-android-arm64": "4.53.2",
"@rollup/rollup-darwin-arm64": "4.53.2",
"@rollup/rollup-darwin-x64": "4.53.2",
"@rollup/rollup-freebsd-arm64": "4.53.2",
"@rollup/rollup-freebsd-x64": "4.53.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.53.2",
"@rollup/rollup-linux-arm-musleabihf": "4.53.2",
"@rollup/rollup-linux-arm64-gnu": "4.53.2",
"@rollup/rollup-linux-arm64-musl": "4.53.2",
"@rollup/rollup-linux-loong64-gnu": "4.53.2",
"@rollup/rollup-linux-ppc64-gnu": "4.53.2",
"@rollup/rollup-linux-riscv64-gnu": "4.53.2",
"@rollup/rollup-linux-riscv64-musl": "4.53.2",
"@rollup/rollup-linux-s390x-gnu": "4.53.2",
"@rollup/rollup-linux-x64-gnu": "4.53.2",
"@rollup/rollup-linux-x64-musl": "4.53.2",
"@rollup/rollup-openharmony-arm64": "4.53.2",
"@rollup/rollup-win32-arm64-msvc": "4.53.2",
"@rollup/rollup-win32-ia32-msvc": "4.53.2",
"@rollup/rollup-win32-x64-gnu": "4.53.2",
"@rollup/rollup-win32-x64-msvc": "4.53.2",
"fsevents": "~2.3.2"
}
},
@ -10192,9 +10199,9 @@
"license": "MIT"
},
"node_modules/sass": {
"version": "1.93.3",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.93.3.tgz",
"integrity": "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==",
"version": "1.94.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.94.0.tgz",
"integrity": "sha512-Dqh7SiYcaFtdv5Wvku6QgS5IGPm281L+ZtVD1U2FJa7Q0EFRlq8Z3sjYtz6gYObsYThUOz9ArwFqPZx+1azILQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -11655,15 +11662,15 @@
}
},
"node_modules/vite-plugin-vue-devtools": {
"version": "7.7.7",
"resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.7.7.tgz",
"integrity": "sha512-d0fIh3wRcgSlr4Vz7bAk4va1MkdqhQgj9ANE/rBhsAjOnRfTLs2ocjFMvSUOsv6SRRXU9G+VM7yMgqDb6yI4iQ==",
"version": "7.7.8",
"resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.7.8.tgz",
"integrity": "sha512-04jowFsal5f9Gbso0X5Ff/mtvik7VP/PBYcKDCQHnTLH0x+juWSj7v1QJfDtXnWrrxU7/yrljEP8KZTm4skvkg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vue/devtools-core": "^7.7.7",
"@vue/devtools-kit": "^7.7.7",
"@vue/devtools-shared": "^7.7.7",
"@vue/devtools-core": "^7.7.8",
"@vue/devtools-kit": "^7.7.8",
"@vue/devtools-shared": "^7.7.8",
"execa": "^9.5.2",
"sirv": "^3.0.1",
"vite-plugin-inspect": "0.8.9",
@ -11792,16 +11799,16 @@
"license": "MIT"
},
"node_modules/vue": {
"version": "3.5.23",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.23.tgz",
"integrity": "sha512-CfvZv/vI52xUhumUvHtD6iFIS78nGWfX4IJnHfBGhpqMI0CwDq2YEngXOeaBFMRmiArcqczuVrLxurvesTYT9w==",
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.24.tgz",
"integrity": "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.23",
"@vue/compiler-sfc": "3.5.23",
"@vue/runtime-dom": "3.5.23",
"@vue/server-renderer": "3.5.23",
"@vue/shared": "3.5.23"
"@vue/compiler-dom": "3.5.24",
"@vue/compiler-sfc": "3.5.24",
"@vue/runtime-dom": "3.5.24",
"@vue/server-renderer": "3.5.24",
"@vue/shared": "3.5.24"
},
"peerDependencies": {
"typescript": "*"
@ -12315,6 +12322,7 @@
}
},
"packages/neptune-native": {
"name": "@neptune/native",
"version": "0.1.0",
"license": "Apache-2.0",
"devDependencies": {

View File

@ -1,6 +1,8 @@
{
"name": "neptune-web-wallet",
"version": "0.0.0",
"description": "Neptune Blockchain Wallet - Secure cryptocurrency wallet for Neptune network",
"author": "Neptune Team",
"private": true,
"type": "module",
"workspaces": [
@ -25,10 +27,10 @@
"@neptune/wasm": "file:./packages/neptune-wasm",
"ant-design-vue": "^4.2.6",
"axios": "^1.6.8",
"crypto": "^1.0.1",
"electron-log": "^5.4.3",
"electron-squirrel-startup": "^1.0.1",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue": "^3.5.24",
"vue-router": "^4.3.0",
"vue3-i18n": "^1.1.5"
},
@ -42,12 +44,12 @@
"@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",
"@rushstack/eslint-patch": "^1.15.0",
"@tsconfig/node20": "^20.1.4",
"@types/electron-squirrel-startup": "^1.0.2",
"@types/node": "^20.12.5",
"@types/node": "^20.19.25",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vitejs/plugin-vue-jsx": "^5.1.1",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/tsconfig": "^0.5.1",
@ -56,11 +58,11 @@
"eslint-plugin-vue": "^9.23.0",
"npm-run-all2": "^6.1.2",
"prettier": "^3.2.5",
"sass": "^1.75.0",
"sass": "^1.94.0",
"typescript": "~5.4.0",
"unplugin-auto-import": "^20.2.0",
"unplugin-vue-components": "^30.0.0",
"vite": "^5.2.8",
"vite": "^5.4.21",
"vite-plugin-vue-devtools": "^7.0.25",
"vue-tsc": "^2.0.11"
}

View File

@ -1,5 +1,5 @@
{
"name": "neptune-native",
"name": "@neptune/native",
"version": "0.1.0",
"description": "Native Node.js addon for Neptune transaction building",
"main": "index.js",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 975 KiB

View File

@ -11,15 +11,6 @@ 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)

View File

@ -1,8 +1,9 @@
import { callJsonRpc } from '@/api/request'
import { DEFAULT_MIN_BLOCK_HEIGHT } from '@/utils/constants/constants'
export const getUtxosFromViewKey = async (
viewKey: string,
startBlock: number | null = 0,
startBlock: number = DEFAULT_MIN_BLOCK_HEIGHT,
endBlock: number | null = null,
maxSearchDepth: number = 1000
): Promise<any> => {
@ -17,7 +18,7 @@ export const getUtxosFromViewKey = async (
export const getBalance = async (
viewKey: string,
startBlock: number | null = 0,
startBlock: number = DEFAULT_MIN_BLOCK_HEIGHT,
endBlock: number | null = null,
maxSearchDepth: number = 1000
): Promise<any> => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 975 KiB

View File

@ -1,13 +1,14 @@
import { useNeptuneStore } from '@/stores/neptuneStore'
import * as API from '@/api/neptuneApi'
import type {
BalanceResult,
GenerateSeedResult,
PayloadBuildTransaction,
ViewKeyResult,
WalletState,
} from '@/interface'
import initWasm, { generate_seed, address_from_seed, validate_seed_phrase } from '@neptune/wasm'
import { toFiniteNumber } from '@/utils'
import { DEFAULT_MIN_BLOCK_HEIGHT, toFiniteNumber } from '@/utils'
let wasmInitialized = false
let initPromise: Promise<void> | null = null
@ -30,11 +31,10 @@ export function useNeptuneWallet() {
await initWasm()
wasmInitialized = true
} catch (err) {
} catch (err: any) {
wasmInitialized = false
const errorMsg = 'Failed to initialize Neptune WASM'
console.error('WASM init error:', err)
throw new Error(errorMsg)
await (window as any).logger.error('WASM init error:')
throw new Error('Failed to initialize Neptune WASM')
}
})()
@ -48,7 +48,6 @@ export function useNeptuneWallet() {
const resultJson = generate_seed()
const result: GenerateSeedResult = JSON.parse(resultJson)
store.setSeedPhrase(result.seed_phrase)
store.setReceiverId(result.receiver_identifier)
const viewKeyResult = await getViewKeyFromSeed(result.seed_phrase)
@ -59,25 +58,23 @@ export function useNeptuneWallet() {
store.setAddress(addressResult)
return result
} catch (err) {
console.error('Error generating wallet:', err)
} catch (err: any) {
await (window as any).logger.error('Error generating wallet: ', err.message)
throw err
}
}
const getViewKeyFromSeed = async (seedPhrase: string[]): Promise<ViewKeyResult> => {
const result = await (window as any).walletApi.generateKeysFromSeed([...seedPhrase])
return JSON.parse(result)
return await (window as any).walletApi.generateKeysFromSeed([...seedPhrase])
}
const recoverWalletFromSeed = async (seedPhrase: string[]): Promise<WalletState> => {
try {
await ensureWasmInitialized()
const isValid = validate_seed_phrase(JSON.stringify(seedPhrase))
if (!isValid) throw new Error('Invalid seed phrase')
const result = await getViewKeyFromSeed(seedPhrase)
store.setSeedPhrase(seedPhrase)
store.setReceiverId(result.receiver_identifier)
store.setViewKey(result.view_key_hex)
store.setSpendingKey(result.spending_key_hex)
@ -86,15 +83,14 @@ export function useNeptuneWallet() {
store.setAddress(addressResult)
return {
seedPhrase: seedPhrase,
network: store.getNetwork,
receiverId: result.receiver_identifier,
viewKey: result.view_key_hex,
spendingKey: result.spending_key_hex,
address: addressResult,
}
} catch (err) {
console.error('Error recovering wallet from seed:', err)
} catch (err: any) {
await (window as any).logger.error('Error recovering wallet from seed: ', err.message)
throw err
}
}
@ -105,21 +101,19 @@ export function useNeptuneWallet() {
return address_from_seed(seedPhraseJson, store.getNetwork)
}
const decryptKeystore = async (password: string): Promise<void> => {
const decryptKeystore = async (password: string): Promise<{ seedPhrase: string[] }> => {
try {
const keystorePath = store.getKeystorePath
if (!keystorePath) await checkKeystore()
const keystoreFileName = store.getKeystoreFileName
if (!keystoreFileName) await checkKeystore()
const result = await (window as any).walletApi.decryptKeystore(
store.getKeystorePath,
keystoreFileName,
password
)
const seedPhrase = result.phrase.trim().split(/\s+/)
const viewKeyResult = await getViewKeyFromSeed(seedPhrase)
store.setPassword(password)
store.setSeedPhrase(seedPhrase)
store.setViewKey(viewKeyResult.view_key_hex)
store.setReceiverId(viewKeyResult.receiver_identifier)
store.setSpendingKey(viewKeyResult.spending_key_hex)
@ -128,62 +122,67 @@ export function useNeptuneWallet() {
store.setAddress(addressResult)
await loadMinBlockHeightFromKeystore()
} catch (err) {
return { seedPhrase }
} catch (err: any) {
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)
await (window as any).logger.error('Invalid password')
} else await (window as any).logger.error('Error decrypting keystore: ', err.message)
throw err
}
}
const createKeystore = async (seed: string, password: string): Promise<string> => {
const createKeystore = async (seed: string, password: string): Promise<string | null> => {
try {
const result = await (window as any).walletApi.createKeystore(seed, password)
store.setKeystorePath(result.filePath)
store.setMinBlockHeight(null)
return result.filePath
} catch (err) {
console.error('Error creating keystore:', err)
if (!result.success) return null
store.setKeystoreFileName(result.fileName)
store.setMinBlockHeight(DEFAULT_MIN_BLOCK_HEIGHT)
return result.fileName
} catch (err: any) {
await (window as any).logger.error('Error creating keystore: ', err.message)
throw err
}
}
const saveKeystoreAs = async (seed: string, password: string): Promise<string> => {
const saveKeystoreAs = async (): Promise<void> => {
try {
const result = await (window as any).walletApi.saveKeystoreAs(seed, password)
const keystoreFileName = store.getKeystoreFileName
if (!keystoreFileName) throw new Error('No file to save')
const result = await (window as any).walletApi.saveKeystoreAs(keystoreFileName)
if (!result.filePath) throw new Error('User canceled')
return result.filePath
} catch (err) {
console.error('Error saving keystore:', err)
} catch (err: any) {
await (window as any).logger.error('Error saving keystore: ', err.message)
throw err
}
}
const checkKeystore = async (): Promise<boolean> => {
try {
const keystoreFile = await (window as any).walletApi.checkKeystore()
const keystoreFile = await (window as any).walletApi.checkKeystore(store.getKeystoreFileName)
if (!keystoreFile.exists) return false
store.setKeystorePath(keystoreFile.filePath)
store.setKeystoreFileName(keystoreFile.fileName)
if ('minBlockHeight' in keystoreFile) {
const height = keystoreFile.minBlockHeight
store.setMinBlockHeight(toFiniteNumber(height))
}
return true
} catch (err) {
console.error('Error checking keystore:', err)
} catch (err: any) {
await (window as any).logger.error('Error checking keystore: ', err.message)
throw err
}
}
const persistMinBlockHeight = async (utxos: any[]) => {
const keystorePath = store.getKeystorePath
if (!keystorePath) return
const keystoreFileName = store.getKeystoreFileName
if (!keystoreFileName) return
try {
const minBlockHeight = utxos.reduce((min, utxo) => {
@ -197,31 +196,31 @@ export function useNeptuneWallet() {
}, null)
const response = await (window as any).walletApi.updateMinBlockHeight(
keystorePath,
keystoreFileName,
minBlockHeight
)
if (!response.success) throw new Error('Failed to update min block height')
store.setMinBlockHeight(minBlockHeight)
} catch (err) {
console.error('Error saving min block height:', err)
} catch (err: any) {
await (window as any).logger.error('Error saving min block height: ', err.message)
throw err
}
}
const loadMinBlockHeightFromKeystore = async (): Promise<number | null> => {
const keystorePath = store.getKeystorePath
if (!keystorePath) return null
const loadMinBlockHeightFromKeystore = async (): Promise<number> => {
const keystoreFileName = store.getKeystoreFileName
if (!keystoreFileName) return DEFAULT_MIN_BLOCK_HEIGHT
try {
const response = await (window as any).walletApi.getMinBlockHeight(keystorePath)
if (!response?.success) return null
const response = await (window as any).walletApi.getMinBlockHeight(keystoreFileName)
if (!response?.success) throw new Error(String(response.error))
const minBlockHeight = toFiniteNumber(response.minBlockHeight)
store.setMinBlockHeight(minBlockHeight)
return minBlockHeight
} catch (err) {
console.error('Error loading min block height:', err)
} catch (err: any) {
await (window as any).logger.error('Error loading min block height: ', err.message)
throw err
}
}
@ -238,7 +237,7 @@ export function useNeptuneWallet() {
const response = await API.getUtxosFromViewKey(
store.getViewKey || '',
toFiniteNumber(startBlock, 0)
toFiniteNumber(startBlock)
)
const result = response?.result || response
@ -249,23 +248,23 @@ export function useNeptuneWallet() {
await persistMinBlockHeight(utxoList)
return result
} catch (err) {
console.error('Error getting UTXOs:', err)
} catch (err: any) {
await (window as any).logger.error('Error getting UTXOs: ', err.message)
throw err
}
}
const getBalance = async (): Promise<any> => {
const getBalance = async (): Promise<BalanceResult> => {
try {
let startBlock: number | null | undefined = store.getMinBlockHeight
if (startBlock === null || startBlock === undefined) {
if (startBlock == null) {
startBlock = await loadMinBlockHeightFromKeystore()
}
const response = await API.getBalance(
store.getViewKey || '',
toFiniteNumber(startBlock, 0)
toFiniteNumber(startBlock)
)
const result = response?.result || response
@ -275,8 +274,8 @@ export function useNeptuneWallet() {
balance: result?.balance || result,
pendingBalance: result?.pendingBalance || result,
}
} catch (err) {
console.error('Error getting balance:', err)
} catch (err: any) {
await (window as any).logger.error('Error getting balance: ', err.message)
throw err
}
}
@ -286,8 +285,8 @@ export function useNeptuneWallet() {
const response = await API.getBlockHeight()
const result = response?.result || response
return result?.height || result
} catch (err) {
console.error('Error getting block height:', err)
} catch (err: any) {
await (window as any).logger.error('Error getting block height: ', err.message)
throw err
}
}
@ -299,8 +298,8 @@ export function useNeptuneWallet() {
store.setNetwork((result.network + 'net') as 'mainnet' | 'testnet')
return result
} catch (err) {
console.error('Error getting network info:', err)
} catch (err: any) {
await (window as any).logger.error('Error getting network info: ', err.message)
throw err
}
}
@ -314,7 +313,7 @@ export function useNeptuneWallet() {
const payload = {
spendingKeyHex: store.getSpendingKey,
inputAdditionRecords: args.inputAdditionRecords,
minBlockHeight: toFiniteNumber(minBlockHeight, 0),
minBlockHeight: toFiniteNumber(minBlockHeight),
outputAddresses: args.outputAddresses,
outputAmounts: args.outputAmounts,
fee: args.fee,
@ -327,26 +326,8 @@ export function useNeptuneWallet() {
const response = await API.broadcastSignedTransaction(transactionHex)
const result = response?.result || response
return result
} catch (err) {
console.error('Error sending transaction:', err)
throw err
}
}
const setNetwork = async (network: 'mainnet' | 'testnet') => {
try {
store.setNetwork(network)
if (store.getSeedPhrase) {
const viewKeyResult = await getViewKeyFromSeed(store.getSeedPhrase)
store.setViewKey(viewKeyResult.view_key_hex)
store.setSpendingKey(viewKeyResult.spending_key_hex)
const addressResult = await getAddressFromSeed(store.getSeedPhrase)
store.setAddress(addressResult)
}
} catch (err) {
console.error('Error setting network:', err)
} catch (err: any) {
await (window as any).logger.error('Error sending transaction: ', err.message)
throw err
}
}
@ -375,6 +356,5 @@ export function useNeptuneWallet() {
checkKeystore,
clearWallet,
setNetwork,
}
}

View File

@ -1,6 +1,4 @@
export interface WalletState {
seedPhrase: string[] | null
password?: string | null
receiverId: string | null
viewKey: string | null
spendingKey?: string | null
@ -42,3 +40,8 @@ export interface Utxo {
blockHeight: number
utxoHash: string
}
export interface BalanceResult {
balance: string | null
pendingBalance: string | null
}

4
src/lang/en/notFound.ts Normal file
View File

@ -0,0 +1,4 @@
export default {
title: 'The page you requested was not found.',
back: 'Back to Home',
}

View File

@ -1,15 +1,13 @@
import { createI18n } from 'vue3-i18n'
import { en } from './en'
import { jp } from './jp'
const messages = {
en: en,
jp: jp,
}
const i18n = createI18n({
locale: 'jp',
fallbackLocale: 'jp',
locale: 'en',
fallbackLocale: 'en',
messages,
} as any)

View File

@ -1,8 +1,8 @@
import { createRouter, createWebHistory } from 'vue-router'
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
import { routes } from './route'
const router = createRouter({
history: createWebHistory(),
history: import.meta.env.DEV ? createWebHistory() : createWebHashHistory(),
routes,
})

View File

@ -1,15 +1,13 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { WalletState } from '@/interface'
import { toFiniteNumber } from '@/utils'
import { DEFAULT_MIN_BLOCK_HEIGHT, toFiniteNumber } from '@/utils'
export const useNeptuneStore = defineStore('neptune', () => {
const defaultNetwork = (import.meta.env.VITE_NODE_NETWORK || 'mainnet') as 'mainnet' | 'testnet'
// ===== STATE =====
const wallet = ref<WalletState>({
seedPhrase: null,
password: null,
receiverId: null,
viewKey: null,
spendingKey: null,
@ -21,18 +19,10 @@ export const useNeptuneStore = defineStore('neptune', () => {
minBlockHeight: null,
})
const keystorePath = ref<null | string>(null)
const keystoreFileName = ref<string | null>(null)
// ===== SETTERS =====
const setSeedPhrase = (seedPhrase: string[] | null) => {
wallet.value.seedPhrase = seedPhrase
}
const setPassword = (password: string | null) => {
wallet.value.password = password
}
const setReceiverId = (receiverId: string | null) => {
wallet.value.receiverId = receiverId
}
@ -65,7 +55,7 @@ export const useNeptuneStore = defineStore('neptune', () => {
wallet.value.utxos = utxos
}
const setMinBlockHeight = (minBlockHeight: number | null) => {
const setMinBlockHeight = (minBlockHeight: number = DEFAULT_MIN_BLOCK_HEIGHT) => {
wallet.value.minBlockHeight = toFiniteNumber(minBlockHeight)
}
@ -73,14 +63,12 @@ export const useNeptuneStore = defineStore('neptune', () => {
wallet.value = { ...wallet.value, ...walletData }
}
const setKeystorePath = (path: string | null) => {
keystorePath.value = path
const setKeystoreFileName = (fileName: string | null) => {
keystoreFileName.value = fileName
}
const clearWallet = () => {
wallet.value = {
seedPhrase: null,
password: null,
receiverId: null,
viewKey: null,
spendingKey: null,
@ -95,9 +83,6 @@ export const useNeptuneStore = defineStore('neptune', () => {
// ===== GETTERS =====
const getWallet = computed(() => wallet.value)
const getSeedPhrase = computed(() => wallet.value.seedPhrase)
const getSeedPhraseString = computed(() => wallet.value.seedPhrase?.join(' ') || '')
const getPassword = computed(() => wallet.value.password)
const getReceiverId = computed(() => wallet.value.receiverId)
const getViewKey = computed(() => wallet.value.viewKey)
const getSpendingKey = computed(() => wallet.value.spendingKey)
@ -108,12 +93,9 @@ export const useNeptuneStore = defineStore('neptune', () => {
const getUtxos = computed(() => wallet.value.utxos)
const getMinBlockHeight = computed(() => wallet.value.minBlockHeight ?? null)
const hasWallet = computed(() => wallet.value.address !== null)
const getKeystorePath = computed(() => keystorePath.value)
const getKeystoreFileName = computed(() => keystoreFileName.value ?? null)
return {
getWallet,
getSeedPhrase,
getSeedPhraseString,
getPassword,
getReceiverId,
getViewKey,
getSpendingKey,
@ -124,9 +106,7 @@ export const useNeptuneStore = defineStore('neptune', () => {
getUtxos,
getMinBlockHeight,
hasWallet,
getKeystorePath,
setSeedPhrase,
setPassword,
getKeystoreFileName,
setReceiverId,
setViewKey,
setSpendingKey,
@ -137,7 +117,7 @@ export const useNeptuneStore = defineStore('neptune', () => {
setUtxos,
setMinBlockHeight,
setWallet,
setKeystorePath,
setKeystoreFileName,
clearWallet,
}
})

View File

@ -1,3 +1,4 @@
export const PAGE_FIRST = 1
export const PER_PAGE = 20
export const POLLING_INTERVAL = 1000 * 60 // 1 minute
export const DEFAULT_MIN_BLOCK_HEIGHT = 0

View File

@ -1,3 +1,9 @@
export function toFiniteNumber(value: number | null, defaultValue?: number): number | null {
return Number.isFinite(value) ? value : (defaultValue ?? null)
import { DEFAULT_MIN_BLOCK_HEIGHT } from '@/utils/constants/constants'
export function toFiniteNumber(
value: number,
defaultValue: number = DEFAULT_MIN_BLOCK_HEIGHT
): number {
return Number.isFinite(value) ? value : defaultValue
}

View File

@ -33,6 +33,7 @@ const handleGoToRecover = () => {
<style lang="scss" scoped>
.auth-container {
min-height: 100vh;
background: var(--bg-white);
}
.complete-state {

View File

@ -41,7 +41,6 @@ const handleRecover = () => {
<style lang="scss" scoped>
.welcome-page {
min-height: 100vh;
background-color: var(--bg-light);
position: relative;
.welcome-card {

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useNeptuneStore } from '@/stores/neptuneStore'
import { message } from 'ant-design-vue'
interface Props {
seedPhrase: string[]
backButton?: boolean
nextButton?: boolean
backButtonText?: string
@ -22,13 +22,11 @@ const emit = defineEmits<{
back: []
}>()
const neptuneStore = useNeptuneStore()
const seedWords = computed(() => neptuneStore.getSeedPhrase || [])
const seedWords = computed(() => props.seedPhrase || [])
const handleNext = () => {
if (!seedWords.value || seedWords.value.length === 0) {
message.error('Cannot proceed without seed phrase')
message.error('Cannot proceed without seed phrase')
return
}
emit('next')
@ -97,7 +95,7 @@ const handleCopySeed = async () => {
</button>
</div>
<div v-else class="no-seed-warning">
<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 class="recovery-actions">

View File

@ -1,16 +1,19 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { useNeptuneStore } from '@/stores/neptuneStore'
import { message } from 'ant-design-vue'
interface Props {
seedPhrase: string[]
}
const props = defineProps<Props>()
const emit = defineEmits<{
next: []
back: []
}>()
const neptuneStore = useNeptuneStore()
const seedWords = computed(() => neptuneStore.getSeedPhrase || [])
const seedWords = computed(() => props.seedPhrase || [])
const currentQuestionIndex = ref(0)
const selectedAnswer = ref('')
const isCorrect = ref(false)
@ -110,7 +113,7 @@ const handleBack = () => {
}
onMounted(() => {
const seeds = neptuneStore.getSeedPhrase
const seeds = props.seedPhrase
if (!seeds || seeds.length === 0) {
message.warning('No seed phrase found. Please go back and generate a wallet first.')

View File

@ -1,14 +1,11 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useNeptuneStore } from '@/stores'
const emit = defineEmits<{
next: []
next: [password: string]
navigateToRecoverWallet: []
}>()
const neptuneStore = useNeptuneStore()
const password = ref('')
const confirmPassword = ref('')
const passwordError = ref('')
@ -56,8 +53,7 @@ const handleNext = () => {
}
return
}
neptuneStore.setPassword(password.value)
emit('next')
emit('next', password.value)
}
const handleIHaveWallet = () => {

View File

@ -14,6 +14,8 @@ const { initWasm, generateWallet, clearWallet } = useNeptuneWallet()
const router = useRouter()
const step = ref(1)
const seedPhrase = ref<string[]>([])
const password = ref('')
onMounted(async () => {
try {
@ -35,9 +37,11 @@ const handleNextToWalletCreated = () => {
step.value = 4
}
const handleNextFromPassword = async () => {
const handleNextFromPassword = async (pwd: string) => {
try {
await generateWallet()
const result = await generateWallet()
seedPhrase.value = result.seed_phrase
password.value = pwd
step.value = 2
} catch (err) {
message.error('Failed to generate wallet')
@ -58,6 +62,8 @@ const handleAccessWallet = () => {
function resetAll() {
step.value = 1
seedPhrase.value = []
password.value = ''
clearWallet()
}
</script>
@ -75,6 +81,7 @@ function resetAll() {
<!-- Step 2: Recovery Seed -->
<SeedPhraseDisplayComponent
v-else-if="step === 2"
:seed-phrase="seedPhrase"
@back="handleBackToCreatePassword"
@next="handleNextToConfirmSeed"
/>
@ -82,6 +89,7 @@ function resetAll() {
<!-- Step 3: Confirm Seed -->
<ConfirmSeedComponent
v-else-if="step === 3"
:seed-phrase="seedPhrase"
@back="handleBackToSeedPhrase"
@next="handleNextToWalletCreated"
/>
@ -89,6 +97,8 @@ function resetAll() {
<!-- Step 4: Success -->
<WalletCreatedStep
v-else-if="step === 4"
:seed-phrase="seedPhrase"
:password="password"
@access-wallet="handleAccessWallet"
@create-another="resetAll"
/>
@ -103,7 +113,6 @@ function resetAll() {
align-items: center;
justify-content: center;
padding: var(--spacing-xl);
background: var(--bg-light);
}
.auth-card {

View File

@ -1,9 +1,12 @@
<script setup lang="ts">
import { useNeptuneStore } from '@/stores/neptuneStore'
import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
import { message } from 'ant-design-vue'
const neptuneStore = useNeptuneStore()
const props = defineProps<{
seedPhrase: string[]
password: string
}>()
const { createKeystore } = useNeptuneWallet()
const emit = defineEmits<{
@ -13,15 +16,14 @@ const emit = defineEmits<{
const handleAccessWallet = async () => {
try {
const seedPhrase = neptuneStore.getSeedPhraseString
const password = neptuneStore.getPassword!
if (!seedPhrase || !password) {
const seedPhraseString = props.seedPhrase.join(' ')
if (!seedPhraseString || !props.password) {
message.error('Missing seed or password')
return
}
await createKeystore(seedPhrase, password)
await createKeystore(seedPhraseString, props.password)
emit('accessWallet')
} catch (err) {
message.error('Failed to create keystore')

View File

@ -132,7 +132,7 @@ const handleCancel = () => {
}
@include screen(mobile) {
.import-wallet {
padding: 16px 5px;
padding: 20px;
}
}
</style>

View File

@ -32,7 +32,6 @@ const loadUtxos = async () => {
loading.value = true
const result = await getUtxos()
if (result.error) {
console.error(result.error.message)
message.error('Failed to load UTXOs')
loading.value = false
return
@ -40,7 +39,6 @@ const loadUtxos = async () => {
loading.value = false
} catch (err) {
message.error('Failed to load UTXOs')
console.error('Error loading UTXOs:', err)
} finally {
loading.value = false
}
@ -63,8 +61,8 @@ watch(
<div class="debug-header">
<h3 class="debug-title">IN USE UTXOS</h3>
<div class="debug-info">
<p><span>COUNT</span> {{ inUseUtxosCount }}</p>
<p><span>AMOUNT</span> {{ inUseUtxosAmount }} <strong>NPT</strong></p>
<p><span>COUNT</span> <strong>{{ inUseUtxosCount }}</strong></p>
<p><span>AMOUNT</span> <strong>{{ inUseUtxosAmount }}</strong> <strong>NPT</strong></p>
</div>
</div>
@ -116,6 +114,10 @@ watch(
font-weight: var(--font-bold);
margin-right: var(--spacing-sm);
}
strong {
color: var(--primary-color);
}
}
}
}

View File

@ -1,9 +1,10 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { BalanceResult } from '@/interface'
interface Props {
isLoading?: boolean
availableBalance?: string
availableBalance?: BalanceResult['balance']
}
const props = withDefaults(defineProps<Props>(), {
@ -27,7 +28,7 @@ const isAmountValid = computed(() => {
if (isNaN(num) || num <= 0) return false
// Check if amount exceeds available balance
const balance = parseFloat(props.availableBalance)
const balance = parseFloat(props.availableBalance ?? '0.00000000')
if (!isNaN(balance) && num > balance) return false
return true
@ -43,7 +44,7 @@ const amountErrorMessage = computed(() => {
const num = parseFloat(outputAmounts.value)
if (isNaN(num) || num <= 0) return 'Invalid amount'
const balance = parseFloat(props.availableBalance)
const balance = parseFloat(props.availableBalance ?? '0.00000000')
if (!isNaN(balance) && num > balance) {
return `Insufficient balance. Available: ${props.availableBalance} NPT`
}
@ -158,9 +159,11 @@ const displayAddress = computed(() => {
<div class="amount-row">
<div class="amount-field">
<div class="form-group">
<label class="form-label">
Amount <span class="required">*</span>
<span class="balance-info">Available: {{ availableBalance }} NPT</span>
<label class="form-label-extra">
<span class="form-label"> Amount <span class="required">*</span> </span>
<span class="balance-info"
>Available: <strong>{{ availableBalance }}</strong> NPT</span
>
</label>
<input
v-model="outputAmounts"
@ -285,12 +288,17 @@ const displayAddress = computed(() => {
gap: var(--spacing-xs);
}
.form-label-extra {
display: flex;
justify-content: space-between;
align-items: center;
}
.form-label {
font-size: var(--font-sm);
font-weight: var(--font-medium);
color: var(--text-primary);
display: flex;
justify-content: space-between;
align-items: center;
.required {

View File

@ -1,8 +1,10 @@
<script setup lang="ts">
import type { BalanceResult } from '@/interface'
interface Props {
isLoadingData: boolean
availableBalance: string
pendingBalance: string
availableBalance: BalanceResult['balance']
pendingBalance: BalanceResult['pendingBalance']
}
const props = defineProps<Props>()
@ -36,8 +38,8 @@ const props = defineProps<Props>()
flex-shrink: 0;
.balance-label {
color: var(--text-muted);
font-size: var(--font-base);
font-weight: var(--font-medium);
margin-bottom: var(--spacing-sm);
text-transform: uppercase;
letter-spacing: var(--tracking-wider);
@ -45,8 +47,8 @@ const props = defineProps<Props>()
.balance-amount {
font-size: var(--font-4xl);
font-weight: var(--font-bold);
color: var(--text-primary);
font-weight: var(--font-semibold);
color: var(--primary-color);
margin-bottom: var(--spacing-lg);
letter-spacing: var(--tracking-tight);
}
@ -56,7 +58,6 @@ const props = defineProps<Props>()
justify-content: center;
align-items: center;
gap: var(--spacing-sm);
color: var(--text-secondary);
font-size: var(--font-md);
.pending-label {
@ -65,6 +66,7 @@ const props = defineProps<Props>()
.pending-amount {
font-weight: var(--font-semibold);
color: var(--primary-color);
}
}
}

View File

@ -6,7 +6,7 @@ import { message } from 'ant-design-vue'
import SeedPhraseDisplayComponent from '@/views/Auth/components/SeedPhraseDisplayComponent.vue'
import SendTransactionComponent from './SendTransactionComponent.vue'
import { WalletAddress, WalletBalance } from '.'
import type { Utxo } from '@/interface'
import type { BalanceResult, Utxo } from '@/interface'
const neptuneStore = useNeptuneStore()
const {
@ -18,15 +18,17 @@ const {
} = useNeptuneWallet()
const activeTabKey = inject<ComputedRef<string>>('activeTabKey')
const availableBalance = ref<string>('0.00000000')
const pendingBalance = ref<string>('0.00000000')
const availableBalance = ref<BalanceResult['balance']>('0.00000000')
const pendingBalance = ref< BalanceResult['pendingBalance']>('0.00000000')
const loading = ref(false)
const sendingLoading = ref(false)
const error = ref<string | null>(null)
const showSeedModal = ref(false)
const isSendingMode = ref(false)
const isVerifyingPassword = ref(false)
const passwordError = ref(false)
const isVerifying = ref(false)
const seedPhrase = ref<string[]>([])
const receiveAddress = computed(() => neptuneStore.getWallet?.address || '')
@ -63,7 +65,7 @@ const handleSendTransaction = async (data: {
fee: string
}) => {
try {
loading.value = true
sendingLoading.value = true
const payload = {
spendingKeyHex: neptuneStore.getSpendingKey || '',
inputAdditionRecords: neptuneStore.getUtxos?.map((utxo: Utxo) => utxo.additionRecord),
@ -89,21 +91,13 @@ const handleSendTransaction = async (data: {
} catch (error) {
message.error('Failed to send transaction')
} finally {
loading.value = false
sendingLoading.value = false
}
}
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)
await saveKeystoreAs()
message.success('Keystore saved successfully')
} catch (err) {
if (err instanceof Error && err.message.includes('User canceled')) return
@ -125,7 +119,8 @@ const handlePasswordVerify = async (password: string) => {
isVerifying.value = true
passwordError.value = false
await decryptKeystore(password)
const result = await decryptKeystore(password)
seedPhrase.value = result.seedPhrase
isVerifyingPassword.value = false
showSeedModal.value = true
@ -149,8 +144,8 @@ const loadWalletData = async () => {
try {
const result = await getBalance()
availableBalance.value = result?.balance || result || '0.00000000'
pendingBalance.value = result?.pendingBalance || result || '0.00000000'
availableBalance.value = result?.balance ?? '0.00000000'
pendingBalance.value = result?.pendingBalance ?? '0.00000000'
} catch (error) {
message.error('Failed to load wallet data')
} finally {
@ -235,14 +230,14 @@ watch(
<!-- Send Transaction View -->
<div v-else-if="isSendingMode" class="send-transaction-wrapper">
<SendTransactionComponent
:is-loading="loading"
:is-loading="sendingLoading"
:available-balance="availableBalance"
@cancel="handleCancelSend"
@send="handleSendTransaction"
/>
<!-- Loading Overlay -->
<div v-if="loading" class="sending-overlay">
<div v-if="sendingLoading" class="sending-overlay">
<div class="sending-content">
<SpinnerCommon size="large" />
<p class="sending-text">Sending...</p>
@ -266,7 +261,7 @@ watch(
@cancel="handleCloseModal"
>
<div class="seed-modal-content">
<SeedPhraseDisplayComponent :back-button="false" :next-button="false" />
<SeedPhraseDisplayComponent :seed-phrase="seedPhrase" :back-button="false" :next-button="false" />
</div>
</ModalCommon>
</template>

View File

@ -17,9 +17,9 @@ const { t } = useI18n()
<h3 class="h2">
{{ t('notFound.title') }}
</h3>
<a href="/reservations" class="link_404">
<router-link to="/auth" class="link_404">
{{ t('notFound.back') }}
</a>
</router-link>
</div>
</div>
</div>

View File

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

34
vite-plugin-csp.ts Normal file
View File

@ -0,0 +1,34 @@
import type { Plugin } from 'vite'
export function cspPlugin(): Plugin {
return {
name: 'vite-plugin-csp',
transformIndexHtml(html) {
const isDev = process.env.NODE_ENV === 'development'
const apiUrl = process.env.VITE_APP_API || 'http://zsmartex.com:8080'
const devUrl = apiUrl
const prodUrl = apiUrl.replace(/^http:/, 'https:')
const cspContent = isDev
? `default-src 'self' blob: data:;
script-src 'self' 'wasm-unsafe-eval' blob:;
style-src 'self' 'unsafe-inline';
connect-src 'self' ${devUrl};
img-src 'self' data:;`
: `default-src 'self' blob: data:;
script-src 'self' 'wasm-unsafe-eval' blob:;
style-src 'self';
connect-src 'self' ${devUrl};
img-src 'self' data:;`
return html.replace(
/<meta\s+http-equiv="Content-Security-Policy"[\s\S]*?\/>/,
`<meta
http-equiv="Content-Security-Policy"
content="${cspContent}"
/>`
)
},
}
}

View File

@ -5,27 +5,37 @@ import vueJsx from '@vitejs/plugin-vue-jsx'
import VueDevTools from 'vite-plugin-vue-devtools'
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
import { cspPlugin } from './vite-plugin-csp'
export default defineConfig({
server: {
port: 3008,
},
base: './',
plugins: [vue(), vueJsx(), VueDevTools(), Components({
resolvers: [AntDesignVueResolver({importStyle: false})],
})],
plugins: [
cspPlugin(),
vue(),
vueJsx(),
VueDevTools(),
Components({
resolvers: [AntDesignVueResolver({ importStyle: false })],
}),
],
optimizeDeps: {
exclude: ['@neptune/wasm', '@neptune/native'],
exclude: ['@neptune/native'],
},
build: {
rollupOptions: {
external: ['@neptune/wasm', '@neptune/native'],
external: ['@neptune/native'],
output: {
format: 'es',
},
},
},
},
css: {
preprocessorOptions: {
scss: {
silenceDeprecations: ['import'],
silenceDeprecations: ['import', 'legacy-js-api'],
additionalData: `
@import "@/assets/scss/__variables.scss";
@import "@/assets/scss/__mixin.scss";