2025-10-24 22:49:46 +07:00
|
|
|
import { useNeptuneStore } from '@/stores/neptuneStore'
|
|
|
|
|
import * as API from '@/api/neptuneApi'
|
2025-11-07 18:29:58 +07:00
|
|
|
import type {
|
|
|
|
|
GenerateSeedResult,
|
|
|
|
|
PayloadBuildTransaction,
|
|
|
|
|
ViewKeyResult,
|
|
|
|
|
WalletState,
|
|
|
|
|
} from '@/interface'
|
2025-11-07 18:27:37 +07:00
|
|
|
import initWasm, { generate_seed, address_from_seed, validate_seed_phrase } from '@neptune/wasm'
|
2025-10-24 22:49:46 +07:00
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
await initWasm()
|
|
|
|
|
|
|
|
|
|
wasmInitialized = true
|
|
|
|
|
} catch (err) {
|
|
|
|
|
wasmInitialized = false
|
|
|
|
|
const errorMsg = 'Failed to initialize Neptune WASM'
|
|
|
|
|
console.error('WASM init error:', err)
|
|
|
|
|
throw new Error(errorMsg)
|
|
|
|
|
}
|
|
|
|
|
})()
|
|
|
|
|
|
|
|
|
|
return initPromise
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const generateWallet = async (): Promise<GenerateSeedResult> => {
|
|
|
|
|
try {
|
|
|
|
|
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-11-07 18:27:37 +07:00
|
|
|
store.setViewKey(viewKeyResult.view_key_hex)
|
|
|
|
|
store.setSpendingKey(viewKeyResult.spending_key_hex)
|
|
|
|
|
|
|
|
|
|
const addressResult = await getAddressFromSeed(result.seed_phrase)
|
|
|
|
|
store.setAddress(addressResult)
|
2025-10-24 22:49:46 +07:00
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
} catch (err) {
|
2025-11-05 04:14:37 +07:00
|
|
|
console.error('Error generating wallet:', err)
|
2025-10-24 22:49:46 +07:00
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:56:47 +07:00
|
|
|
const getViewKeyFromSeed = async (seedPhrase: string[]): Promise<ViewKeyResult> => {
|
2025-11-07 18:27:37 +07:00
|
|
|
const result = await (window as any).walletApi.generateKeysFromSeed([...seedPhrase])
|
|
|
|
|
return JSON.parse(result)
|
2025-10-24 22:49:46 +07:00
|
|
|
}
|
|
|
|
|
|
2025-11-07 18:27:37 +07:00
|
|
|
const recoverWalletFromSeed = async (seedPhrase: string[]): Promise<WalletState> => {
|
2025-10-24 22:49:46 +07:00
|
|
|
try {
|
2025-11-05 04:14:37 +07:00
|
|
|
const isValid = validate_seed_phrase(JSON.stringify(seedPhrase))
|
|
|
|
|
if (!isValid) throw new Error('Invalid seed phrase')
|
2025-10-24 22:49:46 +07:00
|
|
|
|
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)
|
2025-11-07 18:27:37 +07:00
|
|
|
store.setViewKey(result.view_key_hex)
|
|
|
|
|
store.setSpendingKey(result.spending_key_hex)
|
|
|
|
|
|
|
|
|
|
const addressResult = await getAddressFromSeed(seedPhrase)
|
|
|
|
|
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,
|
|
|
|
|
}
|
2025-10-24 22:49:46 +07:00
|
|
|
} catch (err) {
|
2025-11-05 04:14:37 +07:00
|
|
|
console.error('Error recovering wallet from seed:', err)
|
2025-10-24 22:49:46 +07:00
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:56:47 +07:00
|
|
|
const getAddressFromSeed = async (seedPhrase: string[]): Promise<string> => {
|
2025-11-07 18:27:37 +07:00
|
|
|
await ensureWasmInitialized()
|
|
|
|
|
const seedPhraseJson = JSON.stringify(seedPhrase)
|
|
|
|
|
return address_from_seed(seedPhraseJson, store.getNetwork)
|
2025-10-24 22:49:46 +07:00
|
|
|
}
|
|
|
|
|
|
2025-10-31 21:56:47 +07:00
|
|
|
const decryptKeystore = async (password: string): Promise<void> => {
|
|
|
|
|
try {
|
2025-11-05 04:14:37 +07:00
|
|
|
const keystorePath = store.getKeystorePath
|
|
|
|
|
if (!keystorePath) await checkKeystore()
|
|
|
|
|
|
2025-10-31 21:56:47 +07:00
|
|
|
const result = await (window as any).walletApi.decryptKeystore(
|
2025-11-05 04:14:37 +07:00
|
|
|
store.getKeystorePath,
|
2025-10-31 21:56:47 +07:00
|
|
|
password
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const seedPhrase = result.phrase.trim().split(/\s+/)
|
|
|
|
|
const viewKeyResult = await getViewKeyFromSeed(seedPhrase)
|
|
|
|
|
|
2025-11-05 04:14:37 +07:00
|
|
|
store.setPassword(password)
|
2025-10-31 21:56:47 +07:00
|
|
|
store.setSeedPhrase(seedPhrase)
|
2025-11-07 18:27:37 +07:00
|
|
|
store.setViewKey(viewKeyResult.view_key_hex)
|
2025-10-31 21:56:47 +07:00
|
|
|
store.setReceiverId(viewKeyResult.receiver_identifier)
|
2025-11-07 18:27:37 +07:00
|
|
|
store.setSpendingKey(viewKeyResult.spending_key_hex)
|
|
|
|
|
|
|
|
|
|
const addressResult = await getAddressFromSeed(seedPhrase)
|
|
|
|
|
store.setAddress(addressResult)
|
2025-10-31 21:56:47 +07:00
|
|
|
} catch (err) {
|
2025-11-05 04:14:37 +07:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const createKeystore = async (seed: string, password: string): Promise<string> => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await (window as any).walletApi.createKeystore(seed, password)
|
2025-11-07 18:27:37 +07:00
|
|
|
store.setKeystorePath(result.filePath)
|
2025-11-05 04:14:37 +07:00
|
|
|
return result.filePath
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error creating keystore:', err)
|
|
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const saveKeystoreAs = async (seed: string, password: string): Promise<string> => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await (window as any).walletApi.saveKeystoreAs(seed, password)
|
|
|
|
|
if (!result.filePath) throw new Error('User canceled')
|
|
|
|
|
return result.filePath
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error saving keystore:', err)
|
|
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const checkKeystore = async (): Promise<boolean> => {
|
|
|
|
|
try {
|
|
|
|
|
const keystoreFile = await (window as any).walletApi.checkKeystore()
|
|
|
|
|
if (!keystoreFile.exists) return false
|
|
|
|
|
|
|
|
|
|
store.setKeystorePath(keystoreFile.filePath)
|
|
|
|
|
return true
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error checking keystore:', err)
|
|
|
|
|
throw err
|
2025-10-31 21:56:47 +07:00
|
|
|
}
|
2025-10-24 22:49:46 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== API METHODS =====
|
|
|
|
|
|
2025-11-07 18:27:37 +07:00
|
|
|
const getUtxos = async (): Promise<any> => {
|
2025-10-24 22:49:46 +07:00
|
|
|
try {
|
|
|
|
|
if (!store.getViewKey) {
|
|
|
|
|
throw new Error('No view key available. Please import or generate a wallet first.')
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-07 18:27:37 +07:00
|
|
|
const response = await API.getUtxosFromViewKey(store.getViewKey || '')
|
2025-10-24 22:49:46 +07:00
|
|
|
|
2025-11-05 04:14:37 +07:00
|
|
|
const result = response?.result || response
|
2025-10-24 22:49:46 +07:00
|
|
|
store.setUtxos(result.utxos || result || [])
|
|
|
|
|
return result
|
|
|
|
|
} catch (err) {
|
2025-11-05 04:14:37 +07:00
|
|
|
console.error('Error getting UTXOs:', err)
|
2025-10-24 22:49:46 +07:00
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getBalance = async (): Promise<any> => {
|
|
|
|
|
try {
|
2025-11-07 18:27:37 +07:00
|
|
|
const response = await API.getBalance(store.getViewKey || '')
|
2025-11-05 04:14:37 +07:00
|
|
|
const result = response?.result || response
|
2025-11-07 18:27:37 +07:00
|
|
|
store.setBalance(result?.balance || result)
|
|
|
|
|
store.setPendingBalance(result?.pendingBalance || result)
|
|
|
|
|
return {
|
|
|
|
|
balance: result?.balance || result,
|
|
|
|
|
pendingBalance: result?.pendingBalance || result,
|
|
|
|
|
}
|
2025-10-24 22:49:46 +07:00
|
|
|
} catch (err) {
|
2025-11-05 04:14:37 +07:00
|
|
|
console.error('Error getting balance:', err)
|
2025-10-24 22:49:46 +07:00
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getBlockHeight = async (): Promise<any> => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await API.getBlockHeight()
|
2025-11-05 04:14:37 +07:00
|
|
|
const result = response?.result || response
|
|
|
|
|
return result?.height || result
|
2025-10-24 22:49:46 +07:00
|
|
|
} catch (err) {
|
2025-11-05 04:14:37 +07:00
|
|
|
console.error('Error getting block height:', err)
|
2025-10-24 22:49:46 +07:00
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getNetworkInfo = async (): Promise<any> => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await API.getNetworkInfo()
|
2025-11-05 04:14:37 +07:00
|
|
|
const result = response?.result || response
|
|
|
|
|
store.setNetwork((result.network + 'net') as 'mainnet' | 'testnet')
|
|
|
|
|
|
2025-10-24 22:49:46 +07:00
|
|
|
return result
|
|
|
|
|
} catch (err) {
|
2025-11-05 04:14:37 +07:00
|
|
|
console.error('Error getting network info:', err)
|
2025-10-24 22:49:46 +07:00
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-07 18:29:58 +07:00
|
|
|
const buildTransactionWithPrimitiveProof = async (
|
|
|
|
|
args: PayloadBuildTransaction
|
|
|
|
|
): Promise<any> => {
|
2025-11-07 18:27:37 +07:00
|
|
|
const payload = {
|
|
|
|
|
spendingKeyHex: store.getSpendingKey,
|
|
|
|
|
inputAdditionRecords: args.inputAdditionRecords,
|
|
|
|
|
outputAddresses: args.outputAddresses,
|
|
|
|
|
outputAmounts: args.outputAmounts,
|
|
|
|
|
fee: args.fee,
|
|
|
|
|
}
|
|
|
|
|
return await (window as any).walletApi.buildTransactionWithPrimitiveProof(payload)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const broadcastSignedTransaction = async (transactionHex: string): Promise<any> => {
|
2025-10-24 22:49:46 +07:00
|
|
|
try {
|
2025-11-07 18:27:37 +07:00
|
|
|
const response = await API.broadcastSignedTransaction(transactionHex)
|
2025-11-05 04:14:37 +07:00
|
|
|
const result = response?.result || response
|
2025-10-24 22:49:46 +07:00
|
|
|
return result
|
|
|
|
|
} catch (err) {
|
2025-11-05 04:14:37 +07:00
|
|
|
console.error('Error sending transaction:', err)
|
2025-10-24 22:49:46 +07:00
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const setNetwork = async (network: 'mainnet' | 'testnet') => {
|
2025-11-05 04:14:37 +07:00
|
|
|
try {
|
|
|
|
|
store.setNetwork(network)
|
2025-10-24 22:49:46 +07:00
|
|
|
|
2025-11-05 04:14:37 +07:00
|
|
|
if (store.getSeedPhrase) {
|
|
|
|
|
const viewKeyResult = await getViewKeyFromSeed(store.getSeedPhrase)
|
2025-11-07 18:27:37 +07:00
|
|
|
store.setViewKey(viewKeyResult.view_key_hex)
|
|
|
|
|
store.setSpendingKey(viewKeyResult.spending_key_hex)
|
|
|
|
|
|
|
|
|
|
const addressResult = await getAddressFromSeed(store.getSeedPhrase)
|
|
|
|
|
store.setAddress(addressResult)
|
2025-11-05 04:14:37 +07:00
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error setting network:', err)
|
|
|
|
|
throw err
|
2025-10-24 22:49:46 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== UTILITY METHODS =====
|
|
|
|
|
const clearWallet = () => {
|
|
|
|
|
store.clearWallet()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
initWasm: ensureWasmInitialized,
|
|
|
|
|
generateWallet,
|
2025-11-05 04:14:37 +07:00
|
|
|
recoverWalletFromSeed,
|
2025-10-24 22:49:46 +07:00
|
|
|
getViewKeyFromSeed,
|
|
|
|
|
getAddressFromSeed,
|
|
|
|
|
|
|
|
|
|
getUtxos,
|
|
|
|
|
getBalance,
|
|
|
|
|
getBlockHeight,
|
|
|
|
|
getNetworkInfo,
|
2025-11-07 18:27:37 +07:00
|
|
|
buildTransactionWithPrimitiveProof,
|
|
|
|
|
broadcastSignedTransaction,
|
2025-10-31 21:56:47 +07:00
|
|
|
decryptKeystore,
|
2025-11-05 04:14:37 +07:00
|
|
|
createKeystore,
|
|
|
|
|
saveKeystoreAs,
|
|
|
|
|
checkKeystore,
|
2025-10-24 22:49:46 +07:00
|
|
|
|
|
|
|
|
clearWallet,
|
|
|
|
|
setNetwork,
|
|
|
|
|
}
|
|
|
|
|
}
|