2025-10-24 22:49:46 +07:00
|
|
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
|
|
|
|
import * as API from '@/api/neptuneApi'
|
|
|
|
|
import type { GenerateSeedResult, ViewKeyResult } from '@/interface'
|
|
|
|
|
import initWasm, {
|
|
|
|
|
generate_seed,
|
|
|
|
|
get_viewkey,
|
|
|
|
|
address_from_seed,
|
|
|
|
|
validate_seed_phrase,
|
|
|
|
|
} from '@neptune/wasm'
|
|
|
|
|
|
|
|
|
|
let wasmInitialized = false
|
|
|
|
|
let initPromise: Promise<void> | null = null
|
|
|
|
|
|
|
|
|
|
export function useNeptuneWallet() {
|
|
|
|
|
const store = useNeptuneStore()
|
|
|
|
|
|
|
|
|
|
// ===== WASM METHODS =====
|
|
|
|
|
const ensureWasmInitialized = async (): Promise<void> => {
|
|
|
|
|
if (wasmInitialized) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (initPromise) {
|
|
|
|
|
return initPromise
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
initPromise = (async () => {
|
|
|
|
|
try {
|
|
|
|
|
store.setLoading(true)
|
|
|
|
|
store.setError(null)
|
|
|
|
|
|
|
|
|
|
await initWasm()
|
|
|
|
|
|
|
|
|
|
wasmInitialized = true
|
|
|
|
|
} catch (err) {
|
|
|
|
|
wasmInitialized = false
|
|
|
|
|
const errorMsg = 'Failed to initialize Neptune WASM'
|
|
|
|
|
store.setError(errorMsg)
|
|
|
|
|
console.error('WASM init error:', err)
|
|
|
|
|
throw new Error(errorMsg)
|
|
|
|
|
} finally {
|
|
|
|
|
store.setLoading(false)
|
|
|
|
|
}
|
|
|
|
|
})()
|
|
|
|
|
|
|
|
|
|
return initPromise
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const generateWallet = async (): Promise<GenerateSeedResult> => {
|
|
|
|
|
try {
|
|
|
|
|
store.setLoading(true)
|
|
|
|
|
store.setError(null)
|
|
|
|
|
|
|
|
|
|
await ensureWasmInitialized()
|
|
|
|
|
|
|
|
|
|
const resultJson = generate_seed()
|
|
|
|
|
const result: GenerateSeedResult = JSON.parse(resultJson)
|
|
|
|
|
|
|
|
|
|
store.setSeedPhrase(result.seed_phrase)
|
|
|
|
|
store.setReceiverId(result.receiver_identifier)
|
|
|
|
|
|
2025-10-31 21:56:47 +07:00
|
|
|
const viewKeyResult = await getViewKeyFromSeed(result.seed_phrase)
|
2025-10-24 22:49:46 +07:00
|
|
|
store.setViewKey(viewKeyResult.view_key)
|
|
|
|
|
store.setAddress(viewKeyResult.address)
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const errorMsg = err instanceof Error ? err.message : 'Failed to generate wallet'
|
|
|
|
|
store.setError(errorMsg)
|
|
|
|
|
throw err
|
|
|
|
|
} finally {
|
|
|
|
|
store.setLoading(false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:56:47 +07:00
|
|
|
const getViewKeyFromSeed = async (seedPhrase: string[]): Promise<ViewKeyResult> => {
|
2025-10-24 22:49:46 +07:00
|
|
|
await ensureWasmInitialized()
|
|
|
|
|
const seedPhraseJson = JSON.stringify(seedPhrase)
|
2025-10-31 21:56:47 +07:00
|
|
|
const resultJson = get_viewkey(seedPhraseJson, store.getNetwork)
|
2025-10-24 22:49:46 +07:00
|
|
|
return JSON.parse(resultJson)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:56:47 +07:00
|
|
|
const importWallet = async (seedPhrase: string[]): Promise<ViewKeyResult> => {
|
2025-10-24 22:49:46 +07:00
|
|
|
try {
|
|
|
|
|
store.setLoading(true)
|
|
|
|
|
store.setError(null)
|
|
|
|
|
|
|
|
|
|
const isValid = await validateSeedPhrase(seedPhrase)
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
throw new Error('Invalid seed phrase')
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:56:47 +07:00
|
|
|
const result = await getViewKeyFromSeed(seedPhrase)
|
2025-10-24 22:49:46 +07:00
|
|
|
|
|
|
|
|
store.setSeedPhrase(seedPhrase)
|
|
|
|
|
store.setReceiverId(result.receiver_identifier)
|
|
|
|
|
store.setViewKey(result.view_key)
|
|
|
|
|
store.setAddress(result.address)
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const errorMsg = err instanceof Error ? err.message : 'Failed to import wallet'
|
|
|
|
|
store.setError(errorMsg)
|
|
|
|
|
throw err
|
|
|
|
|
} finally {
|
|
|
|
|
store.setLoading(false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:56:47 +07:00
|
|
|
const getAddressFromSeed = async (seedPhrase: string[]): Promise<string> => {
|
2025-10-24 22:49:46 +07:00
|
|
|
await ensureWasmInitialized()
|
|
|
|
|
const seedPhraseJson = JSON.stringify(seedPhrase)
|
2025-10-31 21:56:47 +07:00
|
|
|
return address_from_seed(seedPhraseJson, store.getNetwork)
|
2025-10-24 22:49:46 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const validateSeedPhrase = async (seedPhrase: string[]): Promise<boolean> => {
|
|
|
|
|
try {
|
|
|
|
|
await ensureWasmInitialized()
|
|
|
|
|
const seedPhraseJson = JSON.stringify(seedPhrase)
|
|
|
|
|
return validate_seed_phrase(seedPhraseJson)
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Validation error:', err)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:56:47 +07:00
|
|
|
const decryptKeystore = async (password: string): Promise<void> => {
|
|
|
|
|
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)
|
|
|
|
|
}
|
2025-10-24 22:49:46 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== API METHODS =====
|
|
|
|
|
|
|
|
|
|
const getUtxos = async (
|
|
|
|
|
startBlock: number = 0,
|
|
|
|
|
endBlock: number | null = null,
|
|
|
|
|
maxSearchDepth: number = 1000
|
|
|
|
|
): Promise<any> => {
|
|
|
|
|
try {
|
|
|
|
|
if (!store.getViewKey) {
|
|
|
|
|
throw new Error('No view key available. Please import or generate a wallet first.')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
store.setLoading(true)
|
|
|
|
|
store.setError(null)
|
|
|
|
|
|
|
|
|
|
const response = await API.getUtxosFromViewKey(
|
|
|
|
|
store.getViewKey,
|
|
|
|
|
startBlock,
|
|
|
|
|
endBlock,
|
|
|
|
|
maxSearchDepth
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const result = response.data?.result || response.data
|
|
|
|
|
store.setUtxos(result.utxos || result || [])
|
|
|
|
|
return result
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const errorMsg = err instanceof Error ? err.message : 'Failed to get UTXOs'
|
|
|
|
|
store.setError(errorMsg)
|
|
|
|
|
throw err
|
|
|
|
|
} finally {
|
|
|
|
|
store.setLoading(false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getBalance = async (): Promise<any> => {
|
|
|
|
|
try {
|
|
|
|
|
store.setLoading(true)
|
|
|
|
|
store.setError(null)
|
|
|
|
|
|
|
|
|
|
const response = await API.getBalance()
|
|
|
|
|
const result = response.data?.result || response.data
|
|
|
|
|
store.setBalance(result.balance || result)
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const errorMsg = err instanceof Error ? err.message : 'Failed to get balance'
|
|
|
|
|
store.setError(errorMsg)
|
|
|
|
|
throw err
|
|
|
|
|
} finally {
|
|
|
|
|
store.setLoading(false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getBlockHeight = async (): Promise<any> => {
|
|
|
|
|
try {
|
|
|
|
|
store.setLoading(true)
|
|
|
|
|
store.setError(null)
|
|
|
|
|
|
|
|
|
|
const response = await API.getBlockHeight()
|
|
|
|
|
const result = response.data?.result || response.data
|
|
|
|
|
return result.height || result
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const errorMsg = err instanceof Error ? err.message : 'Failed to get block height'
|
|
|
|
|
store.setError(errorMsg)
|
|
|
|
|
throw err
|
|
|
|
|
} finally {
|
|
|
|
|
store.setLoading(false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getNetworkInfo = async (): Promise<any> => {
|
|
|
|
|
try {
|
|
|
|
|
store.setLoading(true)
|
|
|
|
|
store.setError(null)
|
|
|
|
|
|
|
|
|
|
const response = await API.getNetworkInfo()
|
|
|
|
|
const result = response.data?.result || response.data
|
|
|
|
|
return result
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const errorMsg = err instanceof Error ? err.message : 'Failed to get network info'
|
|
|
|
|
store.setError(errorMsg)
|
|
|
|
|
throw err
|
|
|
|
|
} finally {
|
|
|
|
|
store.setLoading(false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sendTransaction = async (
|
|
|
|
|
toAddress: string,
|
|
|
|
|
amount: string,
|
|
|
|
|
fee: string
|
|
|
|
|
): Promise<any> => {
|
|
|
|
|
try {
|
|
|
|
|
store.setLoading(true)
|
|
|
|
|
store.setError(null)
|
|
|
|
|
|
|
|
|
|
const response = await API.sendTransaction(toAddress, amount, fee)
|
|
|
|
|
const result = response.data?.result || response.data
|
|
|
|
|
return result
|
|
|
|
|
} catch (err) {
|
|
|
|
|
const errorMsg = err instanceof Error ? err.message : 'Failed to send transaction'
|
|
|
|
|
store.setError(errorMsg)
|
|
|
|
|
throw err
|
|
|
|
|
} finally {
|
|
|
|
|
store.setLoading(false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const setNetwork = async (network: 'mainnet' | 'testnet') => {
|
|
|
|
|
store.setNetwork(network)
|
|
|
|
|
|
|
|
|
|
if (store.getSeedPhrase) {
|
2025-10-31 21:56:47 +07:00
|
|
|
const viewKeyResult = await getViewKeyFromSeed(store.getSeedPhrase)
|
2025-10-24 22:49:46 +07:00
|
|
|
store.setAddress(viewKeyResult.address)
|
|
|
|
|
store.setViewKey(viewKeyResult.view_key)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== UTILITY METHODS =====
|
|
|
|
|
const clearWallet = () => {
|
|
|
|
|
store.clearWallet()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
initWasm: ensureWasmInitialized,
|
|
|
|
|
generateWallet,
|
|
|
|
|
importWallet,
|
|
|
|
|
getViewKeyFromSeed,
|
|
|
|
|
getAddressFromSeed,
|
|
|
|
|
validateSeedPhrase,
|
|
|
|
|
|
|
|
|
|
getUtxos,
|
|
|
|
|
getBalance,
|
|
|
|
|
getBlockHeight,
|
|
|
|
|
getNetworkInfo,
|
|
|
|
|
sendTransaction,
|
2025-10-31 21:56:47 +07:00
|
|
|
decryptKeystore,
|
2025-10-24 22:49:46 +07:00
|
|
|
|
|
|
|
|
clearWallet,
|
|
|
|
|
setNetwork,
|
|
|
|
|
}
|
|
|
|
|
}
|