diff --git a/.env.example b/.env.example
index 3dc32fb..b28cbae 100644
--- a/.env.example
+++ b/.env.example
@@ -1 +1,2 @@
VITE_APP_API=
+VITE_NODE_NETWORK=
diff --git a/.gitignore b/.gitignore
index 8620aeb..e8b2d7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,7 @@ dist
dist-ssr
coverage
*.local
-.vite/build/*
+.vite/*/**
wallets/*
/cypress/videos/
diff --git a/electron/ipcHandlers.ts b/electron/ipcHandlers.ts
index 700c06d..a137f07 100644
--- a/electron/ipcHandlers.ts
+++ b/electron/ipcHandlers.ts
@@ -1,24 +1,56 @@
-import { ipcMain } from 'electron';
-import type { HDNodeWallet } from 'ethers';
-import { Wallet } from 'ethers';
-import fs from 'fs';
-import path from 'path';
+import { ipcMain } from 'electron'
+import { Wallet } from 'ethers'
+import fs from 'fs'
+import path from 'path'
ipcMain.handle('wallet:createKeystore', async (_event, seed, password) => {
- const wallet = Wallet.fromPhrase(seed);
- const keystore = await wallet.encrypt(password);
+ try {
+ const wallet = Wallet.fromPhrase(seed)
+ const keystore = await wallet.encrypt(password)
- const savePath = path.join(process.cwd(), 'wallets');
- fs.mkdirSync(savePath, { recursive: true });
+ const savePath = path.join(process.cwd(), 'wallets')
+ fs.mkdirSync(savePath, { recursive: true })
- const filePath = path.join(savePath, `${wallet.address}.json`);
- fs.writeFileSync(filePath, keystore);
+ const filePath = path.join(savePath, `${wallet.address}.json`)
+ fs.writeFileSync(filePath, keystore)
- return { address: wallet.address, filePath };
-});
+ return { address: wallet.address, filePath, error: null }
+ } catch (error) {
+ console.error('Error creating keystore:', error)
+ return { address: null, filePath: null, error: String(error) }
+ }
+})
ipcMain.handle('wallet:decryptKeystore', async (_event, filePath, password) => {
- const json = fs.readFileSync(filePath, 'utf-8');
- const wallet = await Wallet.fromEncryptedJson(json, password) as HDNodeWallet;
- return { address: wallet.address, phrase: wallet.mnemonic };
-});
+ try {
+ const json = fs.readFileSync(filePath, 'utf-8')
+ const wallet = await Wallet.fromEncryptedJson(json, password)
+
+ let phrase: string | undefined
+ if ('mnemonic' in wallet && wallet.mnemonic) {
+ phrase = wallet.mnemonic.phrase
+ }
+
+ return { address: wallet.address, phrase, error: null }
+ } catch (error) {
+ console.error('Error decrypting keystore ipc:', error)
+ return { address: null, phrase: null, error: String(error) }
+ }
+})
+
+ipcMain.handle('wallet:checkKeystore', async () => {
+ try {
+ const walletDir = path.join(process.cwd(), 'wallets')
+ if (!fs.existsSync(walletDir))
+ return { exists: false, filePath: null, error: 'Wallet directory not found' }
+
+ const file = fs.readdirSync(walletDir).find((f) => f.endsWith('.json'))
+ if (!file) return { exists: false, filePath: null, error: 'Keystore file not found' }
+
+ const filePath = path.join(walletDir, file)
+ return { exists: true, filePath, error: null }
+ } catch (error) {
+ console.error('Error checking keystore:', error)
+ return { exists: false, filePath: null, error: String(error) }
+ }
+})
diff --git a/electron/preload.ts b/electron/preload.ts
index 7c35ce6..1ffceeb 100644
--- a/electron/preload.ts
+++ b/electron/preload.ts
@@ -7,4 +7,5 @@ contextBridge.exposeInMainWorld('walletApi', {
ipcRenderer.invoke('wallet:createKeystore', seed, password),
decryptKeystore: (filePath: string, password: string) =>
ipcRenderer.invoke('wallet:decryptKeystore', filePath, password),
+ checkKeystore: () => ipcRenderer.invoke('wallet:checkKeystore'),
})
diff --git a/src/components/WalletInfo.vue b/src/components/WalletInfo.vue
index 9b160bf..1059f58 100644
--- a/src/components/WalletInfo.vue
+++ b/src/components/WalletInfo.vue
@@ -15,6 +15,8 @@ const currentDaaScore = ref(0)
const isLoadingData = ref(false)
const receiveAddress = computed(() => neptuneStore.getWallet?.address || '')
+const isAddressExpanded = ref(false)
+
const walletStatus = computed(() => {
if (neptuneStore.getLoading) return 'Loading...'
if (neptuneStore.getError) return 'Error'
@@ -22,6 +24,10 @@ const walletStatus = computed(() => {
return 'Offline'
})
+const toggleAddressExpanded = () => {
+ isAddressExpanded.value = !isAddressExpanded.value
+}
+
const copyAddress = async () => {
if (!receiveAddress.value) {
message.error('No address available')
@@ -109,7 +115,11 @@ onMounted(() => {
Receive Address:
-
+
{{ receiveAddress || 'No address available' }}
+
@@ -231,9 +248,26 @@ onMounted(() => {
cursor: pointer;
transition: var(--transition-all);
display: flex;
- align-items: center;
+ align-items: flex-start;
gap: var(--spacing-sm);
border: 2px solid transparent;
+ line-height: 1.5;
+ position: relative;
+
+ &.collapsed {
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &.expanded {
+ display: flex;
+ flex-direction: column;
+ word-break: break-all;
+ }
&:hover {
background: var(--bg-hover);
@@ -245,6 +279,29 @@ onMounted(() => {
height: 18px;
flex-shrink: 0;
color: var(--primary-color);
+ margin-top: 2px;
+ align-self: flex-start;
+ }
+ }
+
+ .toggle-address-btn {
+ margin-top: var(--spacing-md);
+ padding: var(--spacing-xs) var(--spacing-md);
+ background: none;
+ border: none;
+ color: var(--primary-color);
+ font-size: var(--font-sm);
+ font-weight: var(--font-medium);
+ cursor: pointer;
+ text-decoration: underline;
+ transition: color 0.2s ease;
+
+ &:hover {
+ color: var(--primary-hover);
+ }
+
+ &:active {
+ opacity: 0.8;
}
}
}
diff --git a/src/components/common/FormCommon.vue b/src/components/common/FormCommon.vue
index 8228a5d..e11da64 100644
--- a/src/components/common/FormCommon.vue
+++ b/src/components/common/FormCommon.vue
@@ -22,7 +22,12 @@ const emit = defineEmits(['update:modelValue', 'focus', 'blur'])
const showPassword = ref(false)
const isFocused = ref(false)
-const inputType = computed(() => props.type)
+const inputType = computed(() => {
+ if (props.type === 'password' ) {
+ return showPassword.value ? 'text' : 'password'
+ }
+ return props.type
+})
const togglePassword = () => (showPassword.value = !showPassword.value)
const handleInput = (e: Event) => emit('update:modelValue', (e.target as HTMLInputElement).value)
@@ -56,14 +61,13 @@ const handleBlur = (e: FocusEvent) => {
@blur="handleBlur"
/>
-
+
{{ error }}
diff --git a/src/components/common/PasswordForm.vue b/src/components/common/PasswordForm.vue
new file mode 100644
index 0000000..01cefee
--- /dev/null
+++ b/src/components/common/PasswordForm.vue
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+ {{ errorMessage }}
+
+
+
+
+ {{ props.backButtonText }}
+
+
+ {{ props.buttonText }}
+
+
+
+
+
+
diff --git a/src/components/index.ts b/src/components/index.ts
index 7aa6125..8eb46d8 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -1,7 +1,8 @@
import LayoutVue from './common/LayoutVue.vue'
import ButtonCommon from './common/ButtonCommon.vue'
import FormCommon from './common/FormCommon.vue'
+import PasswordForm from './common/PasswordForm.vue'
import SpinnerCommon from './common/SpinnerCommon.vue'
import { IconCommon } from './icon'
-export { LayoutVue, ButtonCommon, FormCommon, SpinnerCommon, IconCommon }
+export { LayoutVue, ButtonCommon, FormCommon, PasswordForm, SpinnerCommon, IconCommon }
diff --git a/src/composables/useNeptuneWallet.ts b/src/composables/useNeptuneWallet.ts
index 3ecd638..7f85a99 100644
--- a/src/composables/useNeptuneWallet.ts
+++ b/src/composables/useNeptuneWallet.ts
@@ -1,4 +1,3 @@
-import { computed } from 'vue'
import { useNeptuneStore } from '@/stores/neptuneStore'
import * as API from '@/api/neptuneApi'
import type { GenerateSeedResult, ViewKeyResult } from '@/interface'
@@ -7,7 +6,6 @@ import initWasm, {
get_viewkey,
address_from_seed,
validate_seed_phrase,
- decode_viewkey,
} from '@neptune/wasm'
let wasmInitialized = false
@@ -57,13 +55,11 @@ export function useNeptuneWallet() {
const resultJson = generate_seed()
const result: GenerateSeedResult = JSON.parse(resultJson)
- console.log('result :>> ', result);
store.setSeedPhrase(result.seed_phrase)
store.setReceiverId(result.receiver_identifier)
- const viewKeyResult = await getViewKeyFromSeed(result.seed_phrase, store.getNetwork)
-
+ const viewKeyResult = await getViewKeyFromSeed(result.seed_phrase)
store.setViewKey(viewKeyResult.view_key)
store.setAddress(viewKeyResult.address)
@@ -77,20 +73,14 @@ export function useNeptuneWallet() {
}
}
- const getViewKeyFromSeed = async (
- seedPhrase: string[],
- network: 'mainnet' | 'testnet'
- ): Promise => {
+ const getViewKeyFromSeed = async (seedPhrase: string[]): Promise => {
await ensureWasmInitialized()
const seedPhraseJson = JSON.stringify(seedPhrase)
- const resultJson = get_viewkey(seedPhraseJson, network)
+ const resultJson = get_viewkey(seedPhraseJson, store.getNetwork)
return JSON.parse(resultJson)
}
- const importWallet = async (
- seedPhrase: string[],
- network: 'mainnet' | 'testnet' = 'testnet'
- ): Promise => {
+ const importWallet = async (seedPhrase: string[]): Promise => {
try {
store.setLoading(true)
store.setError(null)
@@ -100,13 +90,12 @@ export function useNeptuneWallet() {
throw new Error('Invalid seed phrase')
}
- const result = await getViewKeyFromSeed(seedPhrase, network)
+ const result = await getViewKeyFromSeed(seedPhrase)
store.setSeedPhrase(seedPhrase)
store.setReceiverId(result.receiver_identifier)
store.setViewKey(result.view_key)
store.setAddress(result.address)
- store.setNetwork(network)
return result
} catch (err) {
@@ -118,13 +107,10 @@ export function useNeptuneWallet() {
}
}
- const getAddressFromSeed = async (
- seedPhrase: string[],
- network: 'mainnet' | 'testnet'
- ): Promise => {
+ const getAddressFromSeed = async (seedPhrase: string[]): Promise => {
await ensureWasmInitialized()
const seedPhraseJson = JSON.stringify(seedPhrase)
- return address_from_seed(seedPhraseJson, network)
+ return address_from_seed(seedPhraseJson, store.getNetwork)
}
const validateSeedPhrase = async (seedPhrase: string[]): Promise => {
@@ -138,10 +124,27 @@ export function useNeptuneWallet() {
}
}
- const decodeViewKey = async (viewKeyHex: string): Promise<{ receiver_identifier: string }> => {
- await ensureWasmInitialized()
- const resultJson = decode_viewkey(viewKeyHex)
- return JSON.parse(resultJson)
+ const decryptKeystore = async (password: string): Promise => {
+ try {
+ const result = await (window as any).walletApi.decryptKeystore(
+ store.getKeystorePath || '',
+ password
+ )
+ if (result.error) {
+ console.error('Error decrypting keystore composable:', result.error)
+ return
+ }
+
+ const seedPhrase = result.phrase.trim().split(/\s+/)
+ const viewKeyResult = await getViewKeyFromSeed(seedPhrase)
+
+ store.setSeedPhrase(seedPhrase)
+ store.setAddress(viewKeyResult.address)
+ store.setViewKey(viewKeyResult.view_key)
+ store.setReceiverId(viewKeyResult.receiver_identifier)
+ } catch (err) {
+ console.error('Error decrypting keystore composable:', err)
+ }
}
// ===== API METHODS =====
@@ -256,7 +259,7 @@ export function useNeptuneWallet() {
store.setNetwork(network)
if (store.getSeedPhrase) {
- const viewKeyResult = await getViewKeyFromSeed(store.getSeedPhrase, network)
+ const viewKeyResult = await getViewKeyFromSeed(store.getSeedPhrase)
store.setAddress(viewKeyResult.address)
store.setViewKey(viewKeyResult.view_key)
}
@@ -268,24 +271,19 @@ export function useNeptuneWallet() {
}
return {
- walletState: computed(() => store.getWallet),
- isLoading: computed(() => store.getLoading),
- error: computed(() => store.getError),
- hasWallet: computed(() => store.hasWallet),
-
initWasm: ensureWasmInitialized,
generateWallet,
importWallet,
getViewKeyFromSeed,
getAddressFromSeed,
validateSeedPhrase,
- decodeViewKey,
getUtxos,
getBalance,
getBlockHeight,
getNetworkInfo,
sendTransaction,
+ decryptKeystore,
clearWallet,
setNetwork,
diff --git a/src/router/route.ts b/src/router/route.ts
index f628f20..73575d7 100644
--- a/src/router/route.ts
+++ b/src/router/route.ts
@@ -1,19 +1,30 @@
import * as Page from '@/views'
import { useNeptuneStore } from '@/stores/neptuneStore'
import { useAuthStore } from '@/stores/authStore'
-
-export const ifAuthenticated = (to: any, from: any, next: any) => {
+const ifAuthenticated = (to: any, from: any, next: any) => {
const neptuneStore = useNeptuneStore()
- const authStore = useAuthStore()
- if (neptuneStore.getReceiverId) {
+ if (neptuneStore.hasWallet) {
next()
return
}
- authStore.setState('login')
+
next('/auth')
}
+
+const ifNotAuthenticated = async (to: any, from: any, next: any) => {
+ const neptuneStore = useNeptuneStore()
+ const authStore = useAuthStore()
+
+ const keystoreFile = await (window as any).walletApi.checkKeystore()
+ if (keystoreFile.exists) {
+ neptuneStore.setKeystorePath(keystoreFile.filePath)
+ authStore.setState('login')
+ }
+ next()
+}
+
export const routes: any = [
{
path: '/',
@@ -25,6 +36,7 @@ export const routes: any = [
path: '/auth',
name: 'auth',
component: Page.Auth,
+ beforeEnter: ifNotAuthenticated,
},
{
path: '/:pathMatch(.*)*',
diff --git a/src/stores/neptuneStore.ts b/src/stores/neptuneStore.ts
index 6389f66..e78f477 100644
--- a/src/stores/neptuneStore.ts
+++ b/src/stores/neptuneStore.ts
@@ -3,6 +3,8 @@ import { ref, computed } from 'vue'
import type { WalletState } from '@/interface'
export const useNeptuneStore = defineStore('neptune', () => {
+ const defaultNetwork = (import.meta.env.VITE_NODE_NETWORK || 'testnet') as 'mainnet' | 'testnet'
+
// ===== STATE =====
const wallet = ref({
seedPhrase: null,
@@ -10,11 +12,13 @@ export const useNeptuneStore = defineStore('neptune', () => {
receiverId: null,
viewKey: null,
address: null,
- network: 'testnet',
+ network: defaultNetwork,
balance: null,
utxos: [],
})
+ const keystorePath = ref(null)
+
const isLoading = ref(false)
const error = ref(null)
@@ -64,6 +68,10 @@ export const useNeptuneStore = defineStore('neptune', () => {
wallet.value = { ...wallet.value, ...walletData }
}
+ const setKeystorePath = (path: string | null) => {
+ keystorePath.value = path
+ }
+
const clearWallet = () => {
wallet.value = {
seedPhrase: null,
@@ -71,7 +79,7 @@ export const useNeptuneStore = defineStore('neptune', () => {
receiverId: null,
viewKey: null,
address: null,
- network: 'testnet',
+ network: defaultNetwork,
balance: null,
utxos: [],
}
@@ -92,7 +100,7 @@ export const useNeptuneStore = defineStore('neptune', () => {
const hasWallet = computed(() => wallet.value.address !== null)
const getLoading = computed(() => isLoading.value)
const getError = computed(() => error.value)
-
+ const getKeystorePath = computed(() => keystorePath.value)
return {
getWallet,
getSeedPhrase,
@@ -107,7 +115,7 @@ export const useNeptuneStore = defineStore('neptune', () => {
hasWallet,
getLoading,
getError,
-
+ getKeystorePath,
setSeedPhrase,
setPassword,
setReceiverId,
@@ -119,6 +127,7 @@ export const useNeptuneStore = defineStore('neptune', () => {
setLoading,
setError,
setWallet,
+ setKeystorePath,
clearWallet,
}
})
diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts
index aab3691..1ea0b48 100644
--- a/src/types/electron.d.ts
+++ b/src/types/electron.d.ts
@@ -1,12 +1,12 @@
// Global type definitions for Electron API
declare global {
- interface Window {
- electron: {
- send: (channel: string, data: any) => void;
- receive: (channel: string, func: (...args: any[]) => void) => void;
- removeAllListeners: (channel: string) => void;
- };
- }
+ interface Window {
+ electron: {
+ send: (channel: string, data: any) => void
+ receive: (channel: string, func: (...args: any[]) => void) => void
+ removeAllListeners: (channel: string) => void
+ }
+ }
}
-export {};
+export {}
diff --git a/src/views/Auth/components/CreateTab.vue b/src/views/Auth/components/CreateTab.vue
index 5f951b4..b3b451f 100644
--- a/src/views/Auth/components/CreateTab.vue
+++ b/src/views/Auth/components/CreateTab.vue
@@ -12,9 +12,7 @@ const handleNavigateToRecoverWallet = () => {
-
+
diff --git a/src/views/Auth/components/LoginTab.vue b/src/views/Auth/components/LoginTab.vue
index 2ca953a..645b793 100644
--- a/src/views/Auth/components/LoginTab.vue
+++ b/src/views/Auth/components/LoginTab.vue
@@ -1,12 +1,236 @@
+
+
diff --git a/src/views/Auth/components/OnboardingTab.vue b/src/views/Auth/components/OnboardingTab.vue
index 1e05fe7..796228e 100644
--- a/src/views/Auth/components/OnboardingTab.vue
+++ b/src/views/Auth/components/OnboardingTab.vue
@@ -31,7 +31,7 @@ const handleRecover = () => {
Create new wallet
- Recover wallet
+ Recover wallet
Thank you for being a part of the Neptune community!
diff --git a/src/views/Auth/components/create/ConfirmSeedComponent.vue b/src/views/Auth/components/create/ConfirmSeedComponent.vue
index 3119114..736156e 100644
--- a/src/views/Auth/components/create/ConfirmSeedComponent.vue
+++ b/src/views/Auth/components/create/ConfirmSeedComponent.vue
@@ -83,28 +83,28 @@ const handleAnswerSelect = (answer: string) => {
const handleNext = () => {
emit('next')
- // if (isCorrect.value) {
- // correctCount.value++
- // askedPositions.value.add(quizData.value!.position)
+ if (isCorrect.value) {
+ correctCount.value++
+ askedPositions.value.add(quizData.value!.position)
- // if (correctCount.value >= totalQuestions) {
- // emit('next')
- // } else {
- // showResult.value = false
- // selectedAnswer.value = ''
- // const newQuiz = generateQuiz()
- // if (newQuiz) {
- // quizData.value = newQuiz
- // }
- // }
- // } else {
- // showResult.value = false
- // selectedAnswer.value = ''
- // const newQuiz = generateQuiz()
- // if (newQuiz) {
- // quizData.value = newQuiz
- // }
- // }
+ if (correctCount.value >= totalQuestions) {
+ emit('next')
+ } else {
+ showResult.value = false
+ selectedAnswer.value = ''
+ const newQuiz = generateQuiz()
+ if (newQuiz) {
+ quizData.value = newQuiz
+ }
+ }
+ } else {
+ showResult.value = false
+ selectedAnswer.value = ''
+ const newQuiz = generateQuiz()
+ if (newQuiz) {
+ quizData.value = newQuiz
+ }
+ }
}
const handleBack = () => {
@@ -216,11 +216,7 @@ onMounted(() => {
>
CONTINUE
-->
-
+
CONTINUE
diff --git a/src/views/Auth/components/create/CreatePasswordStep.vue b/src/views/Auth/components/create/CreatePasswordStep.vue
index c7c633b..69c4898 100644
--- a/src/views/Auth/components/create/CreatePasswordStep.vue
+++ b/src/views/Auth/components/create/CreatePasswordStep.vue
@@ -1,11 +1,11 @@
@@ -192,9 +190,14 @@ const handleIHaveWallet = (e: Event) => {
Create Wallet
-
+
diff --git a/src/views/Auth/components/create/WalletCreatedStep.vue b/src/views/Auth/components/create/WalletCreatedStep.vue
index 7aa86b1..04cded3 100644
--- a/src/views/Auth/components/create/WalletCreatedStep.vue
+++ b/src/views/Auth/components/create/WalletCreatedStep.vue
@@ -1,6 +1,6 @@
-
Import Wallet
+
Recover Wallet
Enter your recovery seed phrase
{
>Continue
+
+
+
+