neptune-web-wallet/src/composables/useNeptuneWallet.ts

316 lines
9.4 KiB
TypeScript
Raw Normal View History

2025-10-24 22:49:46 +07:00
import { computed } from 'vue'
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,
decode_viewkey,
} 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)
const viewKeyResult = await getViewKeyFromSeed(result.seed_phrase, store.getNetwork)
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)
}
}
const getViewKeyFromSeed = async (
seedPhrase: string[],
network: 'mainnet' | 'testnet'
): Promise<ViewKeyResult> => {
await ensureWasmInitialized()
const seedPhraseJson = JSON.stringify(seedPhrase)
const resultJson = get_viewkey(seedPhraseJson, network)
return JSON.parse(resultJson)
}
const importWallet = async (
seedPhrase: string[],
network: 'mainnet' | 'testnet' = 'testnet'
): Promise<ViewKeyResult> => {
try {
store.setLoading(true)
store.setError(null)
const isValid = await validateSeedPhrase(seedPhrase)
if (!isValid) {
throw new Error('Invalid seed phrase')
}
const result = await getViewKeyFromSeed(seedPhrase, network)
store.setSeedPhrase(seedPhrase)
store.setReceiverId(result.receiver_identifier)
store.setViewKey(result.view_key)
store.setAddress(result.address)
store.setNetwork(network)
return result
} catch (err) {
const errorMsg = err instanceof Error ? err.message : 'Failed to import wallet'
store.setError(errorMsg)
throw err
} finally {
store.setLoading(false)
}
}
const getAddressFromSeed = async (
seedPhrase: string[],
network: 'mainnet' | 'testnet'
): Promise<string> => {
await ensureWasmInitialized()
const seedPhraseJson = JSON.stringify(seedPhrase)
return address_from_seed(seedPhraseJson, network)
}
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
}
}
const decodeViewKey = async (viewKeyHex: string): Promise<{ receiver_identifier: string }> => {
await ensureWasmInitialized()
const resultJson = decode_viewkey(viewKeyHex)
return JSON.parse(resultJson)
}
const importFromViewKey = async (viewKeyHex: string): Promise<{ receiver_identifier: string }> => {
try {
store.setLoading(true)
store.setError(null)
const result = await decodeViewKey(viewKeyHex)
store.setViewKey(viewKeyHex)
store.setReceiverId(result.receiver_identifier)
// Note: When importing from viewkey, we don't have the seed phrase
// and address needs to be derived from viewkey
return result
} catch (err) {
const errorMsg = err instanceof Error ? err.message : 'Failed to import from view key'
store.setError(errorMsg)
throw err
} finally {
store.setLoading(false)
}
}
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) {
const viewKeyResult = await getViewKeyFromSeed(store.getSeedPhrase, network)
store.setAddress(viewKeyResult.address)
store.setViewKey(viewKeyResult.view_key)
}
}
// ===== UTILITY METHODS =====
const clearWallet = () => {
store.clearWallet()
}
return {
walletState: computed(() => store.getWallet),
isLoading: computed(() => store.getLoading),
error: computed(() => store.getError),
hasWallet: computed(() => store.hasWallet),
initWasm: ensureWasmInitialized,
generateWallet,
importWallet,
importFromViewKey,
2025-10-24 22:49:46 +07:00
getViewKeyFromSeed,
getAddressFromSeed,
validateSeedPhrase,
decodeViewKey,
getUtxos,
getBalance,
getBlockHeight,
getNetworkInfo,
sendTransaction,
clearWallet,
setNetwork,
}
}