Compare commits
No commits in common. "4c9febebd9db412ecbb8cc3a72076248f48c8da2" and "4bba111113baba9a1744ee6768c3b72d8c7a0ea2" have entirely different histories.
4c9febebd9
...
4bba111113
5
.gitignore
vendored
@ -55,8 +55,3 @@ src/components.d.ts
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Tauri
|
||||
src-tauri/target
|
||||
src-tauri/Cargo.lock
|
||||
src-tauri/WixTools
|
||||
|
||||
|
||||
@ -1,173 +0,0 @@
|
||||
# Tauri + WASM Integration Fix
|
||||
|
||||
## 🔍 Problem
|
||||
|
||||
WASM works in browser (`npm run dev`) but fails in Tauri webview (`npm run tauri:dev`) with error:
|
||||
```
|
||||
Cannot assign to read only property 'toString' of object '#<AxiosURLSearchParams>'
|
||||
```
|
||||
|
||||
## ✅ Solutions Applied
|
||||
|
||||
### 1. **CSP (Content Security Policy) Update**
|
||||
|
||||
**File:** `src-tauri/tauri.conf.json`
|
||||
|
||||
Added required CSP directives for WASM:
|
||||
|
||||
```json
|
||||
{
|
||||
"security": {
|
||||
"csp": {
|
||||
"default-src": "'self' 'unsafe-inline' asset: https://asset.localhost",
|
||||
"script-src": "'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval'",
|
||||
// ... other directives with asset: protocol
|
||||
},
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": ["**"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key changes:**
|
||||
- ✅ Added `'wasm-unsafe-eval'` to `script-src` - **REQUIRED for WASM execution in Tauri 2.x**
|
||||
- ✅ Added `asset:` and `https://asset.localhost` to relevant directives
|
||||
- ✅ Enabled `assetProtocol` to serve assets with proper MIME types
|
||||
|
||||
### 2. **Vite Config Update**
|
||||
|
||||
**File:** `vite.config.ts`
|
||||
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
optimizeDeps: {
|
||||
exclude: ['@neptune/native'], // Only exclude native module
|
||||
// ❌ NOT excluding @neptune/wasm - it must be bundled!
|
||||
},
|
||||
|
||||
build: {
|
||||
assetsInlineLimit: 0, // Don't inline WASM as base64
|
||||
},
|
||||
|
||||
assetsInclude: ['**/*.wasm'], // Serve WASM with correct MIME type
|
||||
})
|
||||
```
|
||||
|
||||
**Key changes:**
|
||||
- ✅ Removed `@neptune/wasm` from `exclude` and `external`
|
||||
- ✅ Added `assetsInlineLimit: 0` to prevent base64 encoding
|
||||
- ✅ Added `assetsInclude` for WASM MIME type
|
||||
|
||||
### 3. **Package.json Fix**
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9" // Fixed from invalid "1.13.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### 1. Browser (Should work)
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
Open http://localhost:5173/auth → Create wallet → Should work
|
||||
|
||||
### 2. Tauri (Should work now)
|
||||
```bash
|
||||
npm run tauri:dev
|
||||
```
|
||||
Create wallet → Should work without CSP/WASM errors
|
||||
|
||||
## 🐛 Additional Debugging
|
||||
|
||||
### If WASM still doesn't load:
|
||||
|
||||
#### Check 1: WASM File in Dist
|
||||
```bash
|
||||
npm run build
|
||||
ls dist/assets/*.wasm # Should see neptune_wasm_bg-*.wasm
|
||||
```
|
||||
|
||||
#### Check 2: Browser DevTools (in Tauri)
|
||||
1. Open Tauri app
|
||||
2. Right-click → Inspect Element
|
||||
3. Console tab → Check for errors
|
||||
4. Network tab → Filter `.wasm` → Check if WASM loads (200 status)
|
||||
|
||||
#### Check 3: CSP Errors
|
||||
In DevTools Console, look for:
|
||||
```
|
||||
Refused to execute WebAssembly script...
|
||||
```
|
||||
If you see this → CSP is still blocking WASM
|
||||
|
||||
### Temporary Debug: Disable CSP
|
||||
|
||||
If nothing works, temporarily disable CSP to isolate the issue:
|
||||
|
||||
```json
|
||||
// src-tauri/tauri.conf.json
|
||||
{
|
||||
"security": {
|
||||
"dangerousDisableAssetCspModification": true, // Disable CSP temporarily
|
||||
"csp": null // Remove CSP entirely for testing
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
⚠️ **WARNING:** Only use this for debugging! Re-enable CSP for production.
|
||||
|
||||
## 📝 Why This Happens
|
||||
|
||||
### Tauri vs Browser Differences
|
||||
|
||||
| Feature | Browser | Tauri Webview |
|
||||
|---------|---------|---------------|
|
||||
| CSP | Permissive by default | Strict by default |
|
||||
| WASM | Always allowed | Needs `'wasm-unsafe-eval'` |
|
||||
| Asset loading | HTTP(S) | Custom `asset://` protocol |
|
||||
| MIME types | Auto-detected | Must be configured |
|
||||
|
||||
### WASM Loading in Tauri
|
||||
|
||||
1. Vite bundles WASM file → `dist/assets/neptune_wasm_bg-*.wasm`
|
||||
2. Tauri serves it via `asset://localhost/assets/...`
|
||||
3. CSP must allow:
|
||||
- `script-src 'wasm-unsafe-eval'` → Execute WASM
|
||||
- `connect-src asset:` → Fetch WASM file
|
||||
4. AssetProtocol must serve with `Content-Type: application/wasm`
|
||||
|
||||
## 🔄 Next Steps After Fix
|
||||
|
||||
### 1. Test Full Wallet Flow
|
||||
- ✅ Generate wallet (WASM)
|
||||
- ✅ Display seed phrase
|
||||
- ✅ Confirm seed phrase
|
||||
- 🚧 Create keystore (needs Tauri commands)
|
||||
|
||||
### 2. Implement Tauri Commands
|
||||
See `TAURI_COMMANDS_TODO.md` (if it exists, otherwise create it)
|
||||
|
||||
### 3. Build & Test Production
|
||||
```bash
|
||||
npm run tauri:build
|
||||
```
|
||||
|
||||
## 📚 References
|
||||
|
||||
- [Tauri CSP Documentation](https://tauri.app/v2/reference/config/#securityconfig)
|
||||
- [Vite WASM Plugin](https://vitejs.dev/guide/features.html#webassembly)
|
||||
- [wasm-bindgen with Vite](https://rustwasm.github.io/wasm-bindgen/reference/deployment.html)
|
||||
|
||||
## 🎯 Summary
|
||||
|
||||
**Problem:** Tauri CSP blocked WASM execution
|
||||
**Solution:** Add `'wasm-unsafe-eval'` + `asset:` protocol + proper Vite config
|
||||
**Status:** Should work now! 🚀
|
||||
|
||||
@ -1,309 +0,0 @@
|
||||
# Tauri + WASM Setup Guide
|
||||
|
||||
## 🐛 Problem
|
||||
|
||||
**Symptom:**
|
||||
- ✅ WASM works in browser (`npm run dev`)
|
||||
- ❌ WASM fails in Tauri (`npm run tauri:dev`)
|
||||
- Error: `Cannot assign to read only property 'toString'`
|
||||
|
||||
**Root Cause:** Tauri webview requires special configuration to load WASM files correctly.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Solution: Vite Plugins for WASM
|
||||
|
||||
### 1. Install Required Plugins
|
||||
|
||||
```bash
|
||||
pnpm add -D vite-plugin-wasm vite-plugin-top-level-await
|
||||
```
|
||||
|
||||
**Why these plugins?**
|
||||
- `vite-plugin-wasm`: Handles WASM file loading and initialization
|
||||
- `vite-plugin-top-level-await`: Enables top-level await (required by WASM)
|
||||
|
||||
### 2. Update `vite.config.ts`
|
||||
|
||||
```typescript
|
||||
import wasm from 'vite-plugin-wasm'
|
||||
import topLevelAwait from 'vite-plugin-top-level-await'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
tailwindcss(),
|
||||
wasm(), // ✅ Add WASM support
|
||||
topLevelAwait(), // ✅ Add top-level await support
|
||||
// ... other plugins
|
||||
],
|
||||
|
||||
// Rest of config...
|
||||
})
|
||||
```
|
||||
|
||||
### 3. Tauri CSP Configuration
|
||||
|
||||
**File:** `src-tauri/tauri.conf.json`
|
||||
|
||||
Ensure CSP includes:
|
||||
```json
|
||||
{
|
||||
"security": {
|
||||
"csp": {
|
||||
"script-src": "'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval'"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key directive:** `'wasm-unsafe-eval'` is **REQUIRED** for WASM in Tauri 2.x
|
||||
|
||||
---
|
||||
|
||||
## 🎯 How It Works
|
||||
|
||||
### Before (Without Plugins)
|
||||
|
||||
```
|
||||
Tauri loads index.html
|
||||
↓
|
||||
Vite bundles JS (WASM as regular asset)
|
||||
↓
|
||||
Browser tries to load WASM
|
||||
↓
|
||||
💥 WASM initialization fails
|
||||
↓
|
||||
Error: Cannot assign to read only property
|
||||
```
|
||||
|
||||
**Why it fails:**
|
||||
- Vite doesn't know how to bundle WASM for Tauri
|
||||
- WASM file is treated as regular asset
|
||||
- Tauri webview can't initialize WASM correctly
|
||||
|
||||
### After (With Plugins)
|
||||
|
||||
```
|
||||
Tauri loads index.html
|
||||
↓
|
||||
vite-plugin-wasm handles WASM bundling
|
||||
↓
|
||||
WASM file served with correct headers
|
||||
↓
|
||||
vite-plugin-top-level-await enables async init
|
||||
↓
|
||||
✅ WASM loads successfully
|
||||
```
|
||||
|
||||
**Why it works:**
|
||||
- `vite-plugin-wasm` handles WASM as special asset type
|
||||
- Correct MIME type (`application/wasm`)
|
||||
- Proper initialization order
|
||||
- Compatible with Tauri's security model
|
||||
|
||||
---
|
||||
|
||||
## 📊 Comparison
|
||||
|
||||
| Aspect | Browser (dev) | Tauri (without plugins) | Tauri (with plugins) |
|
||||
|--------|---------------|-------------------------|----------------------|
|
||||
| WASM Loading | ✅ Works | ❌ Fails | ✅ Works |
|
||||
| MIME Type | Auto | ❌ Wrong | ✅ Correct |
|
||||
| Initialization | ✅ Success | ❌ Conflict | ✅ Success |
|
||||
| CSP Compatibility | N/A | ❌ Issues | ✅ Compatible |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Debugging
|
||||
|
||||
### Check if WASM is Loading
|
||||
|
||||
**In Browser DevTools (F12 in Tauri window):**
|
||||
|
||||
1. **Network Tab:**
|
||||
```
|
||||
Look for: neptune_wasm_bg.wasm
|
||||
Status: 200 OK
|
||||
Type: application/wasm
|
||||
```
|
||||
|
||||
2. **Console Tab:**
|
||||
```
|
||||
Should see: ✅ WASM initialized successfully
|
||||
Should NOT see: ❌ WASM init error
|
||||
```
|
||||
|
||||
3. **Sources Tab:**
|
||||
```
|
||||
Check if WASM file is listed under "webpack://" or "(no domain)"
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Issue 1: WASM file not found (404)
|
||||
**Cause:** WASM not bundled correctly
|
||||
**Fix:** Ensure `vite-plugin-wasm` is installed and configured
|
||||
|
||||
#### Issue 2: CSP violation
|
||||
**Cause:** Missing `'wasm-unsafe-eval'` in CSP
|
||||
**Fix:** Add to `script-src` in `tauri.conf.json`
|
||||
|
||||
#### Issue 3: Module initialization error
|
||||
**Cause:** Top-level await not supported
|
||||
**Fix:** Install `vite-plugin-top-level-await`
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Steps
|
||||
|
||||
### 1. Test in Browser (Should Work)
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
- Open http://localhost:5173
|
||||
- Navigate to `/auth` → Create Wallet
|
||||
- Check console: Should see "✅ WASM initialized successfully"
|
||||
|
||||
### 2. Test in Tauri (Now Should Work)
|
||||
```bash
|
||||
npm run tauri:dev
|
||||
```
|
||||
- Tauri window opens
|
||||
- Navigate to `/auth` → Create Wallet
|
||||
- Open DevTools (F12)
|
||||
- Check console: Should see "✅ WASM initialized successfully"
|
||||
- Should NOT see any `toString` errors
|
||||
|
||||
### 3. Test Wallet Generation
|
||||
```typescript
|
||||
// In CreateWalletFlow.vue
|
||||
const { generateWallet } = useNeptuneWallet()
|
||||
|
||||
// Click "Create Wallet" button
|
||||
const result = await generateWallet()
|
||||
|
||||
// Should return:
|
||||
{
|
||||
receiver_identifier: "...",
|
||||
seed_phrase: ["word1", "word2", ..., "word18"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Package Versions
|
||||
|
||||
**Installed:**
|
||||
```json
|
||||
{
|
||||
"devDependencies": {
|
||||
"vite-plugin-wasm": "^3.5.0",
|
||||
"vite-plugin-top-level-await": "^1.6.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Compatible with:**
|
||||
- Vite 7.x
|
||||
- Tauri 2.x
|
||||
- Vue 3.x
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configuration Files
|
||||
|
||||
### `vite.config.ts`
|
||||
```typescript
|
||||
import wasm from 'vite-plugin-wasm'
|
||||
import topLevelAwait from 'vite-plugin-top-level-await'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
wasm(),
|
||||
topLevelAwait(),
|
||||
// ... other plugins
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### `tauri.conf.json`
|
||||
```json
|
||||
{
|
||||
"app": {
|
||||
"security": {
|
||||
"csp": {
|
||||
"script-src": "'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `package.json`
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@neptune/wasm": "file:./packages/neptune-wasm"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite-plugin-wasm": "^3.5.0",
|
||||
"vite-plugin-top-level-await": "^1.6.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Why Browser Works But Tauri Doesn't
|
||||
|
||||
### Browser (Chrome/Firefox)
|
||||
- **Permissive WASM loading:** Browsers automatically handle WASM
|
||||
- **Built-in support:** No special config needed
|
||||
- **Dev server:** Vite dev server serves WASM with correct headers
|
||||
|
||||
### Tauri (Webview)
|
||||
- **Strict security:** CSP enforced by default
|
||||
- **Custom protocol:** Assets loaded via `tauri://` protocol
|
||||
- **WASM restrictions:** Requires `'wasm-unsafe-eval'` in CSP
|
||||
- **Asset handling:** Needs proper Vite configuration
|
||||
|
||||
**Tauri = Embedded Browser + Rust Backend**
|
||||
- More secure (CSP enforced)
|
||||
- More restrictive (needs explicit config)
|
||||
- Different asset loading (custom protocol)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Result
|
||||
|
||||
**Before:**
|
||||
```bash
|
||||
npm run dev # ✅ WASM works
|
||||
npm run tauri:dev # ❌ WASM fails (toString error)
|
||||
```
|
||||
|
||||
**After:**
|
||||
```bash
|
||||
npm run dev # ✅ WASM works
|
||||
npm run tauri:dev # ✅ WASM works! 🎉
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
- [vite-plugin-wasm GitHub](https://github.com/Menci/vite-plugin-wasm)
|
||||
- [vite-plugin-top-level-await GitHub](https://github.com/Menci/vite-plugin-top-level-await)
|
||||
- [Tauri Security Documentation](https://tauri.app/v2/reference/config/#securityconfig)
|
||||
- [WebAssembly with Vite](https://vitejs.dev/guide/features.html#webassembly)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Summary
|
||||
|
||||
**Problem:** Tauri can't load WASM without proper Vite configuration
|
||||
**Solution:** Install `vite-plugin-wasm` + `vite-plugin-top-level-await`
|
||||
**Result:** WASM works in both browser AND Tauri! 🚀
|
||||
|
||||
@ -1,319 +0,0 @@
|
||||
# UI Views Implementation
|
||||
|
||||
## ✅ Completed Views
|
||||
|
||||
Đã implement 3 views chính với Shadcn-vue components và mobile-first design:
|
||||
|
||||
### 1. **WalletView** (`src/views/Wallet/WalletView.vue`)
|
||||
|
||||
**Features:**
|
||||
|
||||
- ✅ Balance display (Available + Pending)
|
||||
- ✅ Receiving address with copy button
|
||||
- ✅ Action buttons (Send, Backup File, Backup Seed)
|
||||
- ✅ Wallet status indicator
|
||||
- ✅ Loading states
|
||||
- ✅ Mobile responsive design
|
||||
|
||||
**Components used:**
|
||||
|
||||
- Card (CardHeader, CardTitle, CardContent)
|
||||
- Button (primary, outline variants)
|
||||
- Label, Separator
|
||||
- Lucide icons (Wallet, Send, FileDown, Key, Copy, Check)
|
||||
|
||||
**Mock data:**
|
||||
|
||||
- Balance: `125.45678900 XNT`
|
||||
- Address: `nep1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh`
|
||||
|
||||
**TODO:**
|
||||
|
||||
- [ ] Integrate with `useNeptuneWallet` composable
|
||||
- [ ] Implement send transaction flow
|
||||
- [ ] Implement backup features with Tauri commands
|
||||
|
||||
---
|
||||
|
||||
### 2. **UTXOView** (`src/views/UTXO/UTXOView.vue`)
|
||||
|
||||
**Features:**
|
||||
|
||||
- ✅ Summary cards (Total Count + Total Amount)
|
||||
- ✅ UTXO list with mobile cards + desktop table
|
||||
- ✅ Refresh button
|
||||
- ✅ Loading & empty states
|
||||
- ✅ Responsive layout (cards on mobile, table on desktop)
|
||||
|
||||
**Components used:**
|
||||
|
||||
- Card (CardHeader, CardTitle, CardContent)
|
||||
- Button (outline, icon variants)
|
||||
- Label, Separator
|
||||
- Lucide icons (Database, RefreshCw)
|
||||
|
||||
**Mock data:**
|
||||
|
||||
- 3 UTXOs with hashes, amounts, block heights
|
||||
- Total: `125.50000000 XNT`
|
||||
|
||||
**Layout:**
|
||||
|
||||
- **Mobile (<768px):** Individual UTXO cards
|
||||
- **Desktop (≥768px):** Data table
|
||||
|
||||
**TODO:**
|
||||
|
||||
- [ ] Integrate with `getUtxos()` API
|
||||
- [ ] Add pagination for large lists
|
||||
- [ ] Add sorting/filtering
|
||||
|
||||
---
|
||||
|
||||
### 3. **NetworkView** (`src/views/Network/NetworkView.vue`)
|
||||
|
||||
**Features:**
|
||||
|
||||
- ✅ Network information display
|
||||
- ✅ Current block height
|
||||
- ✅ Last update time
|
||||
- ✅ Connection status indicators
|
||||
- ✅ Refresh button
|
||||
- ✅ Error state with retry
|
||||
- ✅ Loading states
|
||||
|
||||
**Components used:**
|
||||
|
||||
- Card (CardHeader, CardTitle, CardContent)
|
||||
- Button (outline variant)
|
||||
- Label, Separator
|
||||
- Lucide icons (Network, Activity, RefreshCw, AlertCircle)
|
||||
|
||||
**Mock data:**
|
||||
|
||||
- Network: `Neptune mainnet`
|
||||
- Block Height: `123,456`
|
||||
- Status: Connected, Synced
|
||||
|
||||
**Auto-refresh:** Ready for 60s polling (commented out)
|
||||
|
||||
**TODO:**
|
||||
|
||||
- [ ] Integrate with `getBlockHeight()` API
|
||||
- [ ] Enable auto-refresh polling
|
||||
- [ ] Add more network stats (peer count, etc.)
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Design System
|
||||
|
||||
### Colors (from Tailwind + Shadcn)
|
||||
|
||||
- **Primary:** Royal Blue `oklch(0.488 0.15 264.5)`
|
||||
- **Background:** White (light) / Dark blue tint (dark)
|
||||
- **Muted:** Light gray backgrounds
|
||||
- **Foreground:** Text colors
|
||||
- **Border:** Subtle borders
|
||||
- **Destructive:** Error/alert red
|
||||
|
||||
### Typography
|
||||
|
||||
- **Font:** Montserrat Variable Font
|
||||
- **Sizes:** text-xs, text-sm, text-base, text-lg, text-2xl, text-4xl
|
||||
- **Weights:** font-medium, font-semibold, font-bold
|
||||
|
||||
### Spacing
|
||||
|
||||
- **Padding:** p-3, p-4, p-6
|
||||
- **Gap:** gap-2, gap-3, gap-4, gap-6
|
||||
- **Margin:** Tailwind utilities
|
||||
|
||||
### Components
|
||||
|
||||
- **Card:** Border, shadow, rounded corners
|
||||
- **Button:** Primary (filled), Outline, Ghost, Icon
|
||||
- **Icons:** Lucide Vue Next (size-4, size-5, size-6)
|
||||
|
||||
---
|
||||
|
||||
## 📱 Mobile Optimization
|
||||
|
||||
### Responsive Breakpoints
|
||||
|
||||
- **Mobile:** < 768px (sm)
|
||||
- **Desktop:** ≥ 768px (md)
|
||||
|
||||
### Mobile Features
|
||||
|
||||
- ✅ Touch-optimized buttons (min 44px height)
|
||||
- ✅ Card-based layouts for mobile
|
||||
- ✅ Table view for desktop
|
||||
- ✅ Bottom navigation (4 tabs)
|
||||
- ✅ Safe area insets for notched devices
|
||||
- ✅ Smooth scrolling
|
||||
- ✅ No overscroll
|
||||
|
||||
### Layout Structure
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ Header (56px) │
|
||||
├─────────────────────┤
|
||||
│ │
|
||||
│ Main Content │
|
||||
│ (scrollable) │
|
||||
│ │
|
||||
├─────────────────────┤
|
||||
│ Bottom Nav (48px) │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Router Configuration
|
||||
|
||||
**Updated routes:**
|
||||
|
||||
```typescript
|
||||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
children: [
|
||||
{ path: '', name: 'wallet', component: WalletPage }, // Default
|
||||
{ path: '/utxo', name: 'utxo', component: UTXOPage },
|
||||
{ path: '/network', name: 'network', component: NetworkPage },
|
||||
{ path: '/transaction-history', name: 'transaction-history', component: TransactionHistoryPage },
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Bottom Navigation Order:**
|
||||
|
||||
1. 💰 Wallet (/) - Default
|
||||
2. 📦 UTXO (/utxo)
|
||||
3. 🌐 Network (/network)
|
||||
4. 📜 History (/transaction-history)
|
||||
|
||||
---
|
||||
|
||||
## 🚧 Commented Out Logic
|
||||
|
||||
### WASM-related code (temporarily disabled)
|
||||
|
||||
```typescript
|
||||
// import { useNeptuneWallet } from '@/composables/useNeptuneWallet'
|
||||
// const { getBalance, getUtxos, getBlockHeight } = useNeptuneWallet()
|
||||
```
|
||||
|
||||
### API Calls (ready to uncomment)
|
||||
|
||||
```typescript
|
||||
// const loadWalletData = async () => {
|
||||
// const result = await getBalance()
|
||||
// availableBalance.value = result.balance
|
||||
// }
|
||||
```
|
||||
|
||||
### Auto-refresh Polling (ready to enable)
|
||||
|
||||
```typescript
|
||||
// let pollingInterval: number | null = null
|
||||
// const startPolling = () => { ... }
|
||||
// onMounted(() => { startPolling() })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Integration Steps
|
||||
|
||||
### Phase 1: Enable API Calls (No WASM)
|
||||
|
||||
1. Uncomment `useNeptuneWallet` imports
|
||||
2. Uncomment API call functions
|
||||
3. Test with mock API data
|
||||
4. Remove mock data
|
||||
|
||||
### Phase 2: Enable WASM
|
||||
|
||||
1. Fix Tauri + WASM loading issues
|
||||
2. Uncomment WASM-related logic
|
||||
3. Test wallet generation flow
|
||||
4. Test full integration
|
||||
|
||||
### Phase 3: Implement Tauri Commands
|
||||
|
||||
1. `generate_keys_from_seed`
|
||||
2. `create_keystore`
|
||||
3. `decrypt_keystore`
|
||||
4. `build_transaction`
|
||||
|
||||
---
|
||||
|
||||
## 📊 Current Status
|
||||
|
||||
| View | UI | Mock Data | API Ready | WASM Ready | Status |
|
||||
| ------- | --- | --------- | --------- | ---------- | ----------- |
|
||||
| Wallet | ✅ | ✅ | 🚧 | ❌ | **UI Done** |
|
||||
| UTXO | ✅ | ✅ | 🚧 | ❌ | **UI Done** |
|
||||
| Network | ✅ | ✅ | 🚧 | ❌ | **UI Done** |
|
||||
|
||||
**Legend:**
|
||||
|
||||
- ✅ Complete
|
||||
- 🚧 Ready to integrate
|
||||
- ❌ Blocked on Tauri/WASM
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Manual Testing Steps
|
||||
|
||||
1. **Start dev server:** `npm run dev`
|
||||
2. **Navigate to each view:**
|
||||
- http://localhost:5173/ → Wallet
|
||||
- http://localhost:5173/utxo → UTXO
|
||||
- http://localhost:5173/network → Network
|
||||
3. **Test responsive:**
|
||||
- Desktop view (>768px)
|
||||
- Mobile view (<768px)
|
||||
- Chrome DevTools mobile emulation
|
||||
4. **Test interactions:**
|
||||
- Copy address button
|
||||
- Refresh buttons
|
||||
- Bottom navigation
|
||||
- Dark mode toggle
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
- **Dark Mode:** Fully supported via Shadcn theme variables
|
||||
- **Icons:** Lucide Vue Next (tree-shakeable)
|
||||
- **Animations:** Tailwind transitions + CSS animations
|
||||
- **Accessibility:** ARIA labels, keyboard navigation
|
||||
- **Performance:** Lazy-loaded routes, optimized re-renders
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
1. ✅ **UI Complete** - All 3 views designed and implemented
|
||||
2. 🚧 **Fix Tauri WASM** - Resolve CSP and asset loading issues
|
||||
3. 🚧 **Integrate APIs** - Connect to Neptune node
|
||||
4. 🚧 **Implement Tauri Commands** - Keystore, transaction signing
|
||||
5. 🚧 **Add Transaction History View** - List of past transactions
|
||||
6. 🚧 **E2E Testing** - Full wallet flow testing
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Screenshots (Recommended)
|
||||
|
||||
Take screenshots of:
|
||||
|
||||
- [ ] Wallet view (light + dark mode)
|
||||
- [ ] UTXO view (mobile cards + desktop table)
|
||||
- [ ] Network view (all states)
|
||||
- [ ] Bottom navigation active states
|
||||
|
||||
Store in: `docs/screenshots/`
|
||||
@ -14,12 +14,12 @@
|
||||
<meta name="theme-color" content="#3f51b5" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="Neptune Privacy" />
|
||||
<meta name="apple-mobile-web-app-title" content="Neptune Wallet" />
|
||||
|
||||
<!-- App Description -->
|
||||
<meta
|
||||
name="description"
|
||||
content="Neptune Privacy - Secure cryptocurrency wallet for Neptune network"
|
||||
content="Neptune Wallet - Secure cryptocurrency wallet for Neptune blockchain"
|
||||
/>
|
||||
|
||||
<!-- Google Fonts - Inter (Modern, clean, mobile-optimized) -->
|
||||
@ -30,7 +30,7 @@
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<title>Neptune Privacy</title>
|
||||
<title>Neptune Wallet</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
21
package.json
@ -1,28 +1,18 @@
|
||||
{
|
||||
"name": "neptune-privacy",
|
||||
"name": "neptune-wallet",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .eslintignore",
|
||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,vue,css,scss,json}\"",
|
||||
"tauri": "tauri",
|
||||
"tauri:dev": "tauri dev",
|
||||
"tauri:build": "tauri build"
|
||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,vue,css,scss,json}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@neptune/native": "file:./packages/neptune-native",
|
||||
"@neptune/wasm": "file:./packages/neptune-wasm",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@tanstack/vue-form": "^1.26.0",
|
||||
"@tauri-apps/api": "^2.9.0",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"axios": "^1.7.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@ -35,13 +25,10 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vue": "^3.5.24",
|
||||
"vue-i18n": "^10.0.8",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-sonner": "^2.0.9",
|
||||
"zod": "^4.1.13"
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.15.0",
|
||||
"@tauri-apps/cli": "^2.9.4",
|
||||
"@types/node": "^24.10.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
@ -56,8 +43,6 @@
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-vue-components": "^30.0.0",
|
||||
"vite": "^7.2.4",
|
||||
"vite-plugin-top-level-await": "^1.6.0",
|
||||
"vite-plugin-wasm": "^3.5.0",
|
||||
"vue-tsc": "^3.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
79
packages/neptune-native/index.d.ts
vendored
@ -1,79 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
export declare function initNativeModule(): string
|
||||
export declare function quickVmTest(): string
|
||||
export declare function getVersion(): string
|
||||
export declare class WalletManager {
|
||||
constructor()
|
||||
/**
|
||||
* Generate spending key from BIP39 seed phrase
|
||||
*
|
||||
* # Arguments
|
||||
* * `seed_phrase` - Array of words (12, 15, 18, 21, or 24 words)
|
||||
*
|
||||
* # Returns
|
||||
* JSON with spending_key_hex, view_key_hex, receiver_identifier
|
||||
*/
|
||||
generateKeysFromSeed(seedPhrase: Array<string>): string
|
||||
/**
|
||||
* Generate lock_script_and_witness (transaction signature)
|
||||
*
|
||||
* # Arguments
|
||||
* * `spending_key_hex` - Hex-encoded spending key from generate_keys_from_seed
|
||||
*
|
||||
* # Returns
|
||||
* JSON with lock_script_and_witness in hex format
|
||||
*/
|
||||
createLockScriptAndWitness(spendingKeyHex: string): string
|
||||
/** Derive ViewKey hex (0x-prefixed bincode) from a Generation spending key hex */
|
||||
spendingKeyToViewKeyHex(spendingKeyHex: string): string
|
||||
/** Call wallet_getAdditionRecordsFromViewKey and return JSON as string */
|
||||
getAdditionRecordsFromViewKeyCall(rpcUrl: string, viewKeyHex: string, startBlock: number, endBlock: number | undefined | null, maxSearchDepth: number): Promise<string>
|
||||
/**
|
||||
* Build RPC request to get UTXOs from view key
|
||||
*
|
||||
* # Arguments
|
||||
* * `view_key_hex` - Hex-encoded view key from generate_keys_from_seed
|
||||
* * `start_block` - Starting block height (0 for genesis)
|
||||
* * `end_block` - Ending block height (current tip)
|
||||
* * `max_search_depth` - Maximum blocks to search (default: 1000)
|
||||
*
|
||||
* # Returns
|
||||
* JSON-RPC request ready to send
|
||||
*
|
||||
* # Note
|
||||
* Method name format: namespace_method (e.g. wallet_getUtxosFromViewKey)
|
||||
*/
|
||||
buildGetUtxosRequest(viewKeyHex: string, startBlock: number, endBlock: number, maxSearchDepth?: number | undefined | null): string
|
||||
/** Build RPC request to test chain height (for connectivity testing) */
|
||||
buildTestRpcRequest(): string
|
||||
/** Build JSON-RPC request to fetch current chain height */
|
||||
buildChainHeightRequest(): string
|
||||
/** Build JSON-RPC request to fetch current chain header (tip) */
|
||||
buildChainHeaderRequest(): string
|
||||
/** Get network information */
|
||||
getNetworkInfo(): string
|
||||
getChainHeightCall(rpcUrl: string): Promise<string>
|
||||
/** Call node_getState to get server state information */
|
||||
getStateCall(rpcUrl: string): Promise<string>
|
||||
/** Call mempool_submitTransaction to broadcast a pre-built transaction */
|
||||
submitTransactionCall(rpcUrl: string, transactionHex: string): Promise<string>
|
||||
getUtxosFromViewKeyCall(rpcUrl: string, viewKeyHex: string, startBlock: number, maxSearchDepth?: number | undefined | null): Promise<string>
|
||||
getArchivalMutatorSet(rpcUrl: string): Promise<string>
|
||||
/**
|
||||
* Build JSON-RPC request to find the canonical block that created a UTXO (by addition_record)
|
||||
* Method: archival_getUtxoCreationBlock
|
||||
*/
|
||||
buildGetUtxoCreationBlockRequest(additionRecordHex: string, maxSearchDepth?: number | undefined | null): string
|
||||
/** Perform JSON-RPC call to find the canonical block that created a UTXO (by addition_record) */
|
||||
getUtxoCreationBlockCall(rpcUrl: string, additionRecordHex: string, maxSearchDepth?: number | undefined | null): Promise<string>
|
||||
/** Call wallet_sendWithSpendingKey to build and broadcast transaction */
|
||||
generateUtxoWithProofCall(rpcUrl: string, utxoHex: string, additionRecordHex: string, senderRandomnessHex: string, receiverPreimageHex: string, maxSearchDepth: string): Promise<string>
|
||||
}
|
||||
export declare class SimpleTransactionBuilder {
|
||||
constructor()
|
||||
buildTransaction(rpcUrl: string, spendingKeyHex: string, inputAdditionRecords: Array<string>, minBlockHeight: number, outputAddresses: Array<string>, outputAmounts: Array<string>, fee: string): Promise<string>
|
||||
}
|
||||
@ -1,319 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
const { existsSync, readFileSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
|
||||
const { platform, arch } = process
|
||||
|
||||
let nativeBinding = null
|
||||
let localFileExisted = false
|
||||
let loadError = null
|
||||
|
||||
function isMusl() {
|
||||
// For Node 10
|
||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
||||
try {
|
||||
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
||||
return readFileSync(lddPath, 'utf8').includes('musl')
|
||||
} catch (e) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header
|
||||
return !glibcVersionRuntime
|
||||
}
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case 'android':
|
||||
switch (arch) {
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(join(__dirname, 'neptune-native.android-arm64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.android-arm64.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-android-arm64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
localFileExisted = existsSync(join(__dirname, 'neptune-native.android-arm-eabi.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.android-arm-eabi.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-android-arm-eabi')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Android ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'win32':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.win32-x64-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.win32-x64-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-win32-x64-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'ia32':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.win32-ia32-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.win32-ia32-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-win32-ia32-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.win32-arm64-msvc.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.win32-arm64-msvc.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-win32-arm64-msvc')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Windows: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'darwin':
|
||||
localFileExisted = existsSync(join(__dirname, 'neptune-native.darwin-universal.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.darwin-universal.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-darwin-universal')
|
||||
}
|
||||
break
|
||||
} catch {}
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
localFileExisted = existsSync(join(__dirname, 'neptune-native.darwin-x64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.darwin-x64.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-darwin-x64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.darwin-arm64.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.darwin-arm64.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-darwin-arm64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on macOS: ${arch}`)
|
||||
}
|
||||
break
|
||||
case 'freebsd':
|
||||
if (arch !== 'x64') {
|
||||
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
|
||||
}
|
||||
localFileExisted = existsSync(join(__dirname, 'neptune-native.freebsd-x64.node'))
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.freebsd-x64.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-freebsd-x64')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
case 'linux':
|
||||
switch (arch) {
|
||||
case 'x64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.linux-x64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.linux-x64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-linux-x64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.linux-x64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.linux-x64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-linux-x64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'arm64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.linux-arm64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.linux-arm64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-linux-arm64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.linux-arm64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.linux-arm64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-linux-arm64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'arm':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.linux-arm-musleabihf.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.linux-arm-musleabihf.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-linux-arm-musleabihf')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.linux-arm-gnueabihf.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.linux-arm-gnueabihf.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-linux-arm-gnueabihf')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'riscv64':
|
||||
if (isMusl()) {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.linux-riscv64-musl.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.linux-riscv64-musl.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-linux-riscv64-musl')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
} else {
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.linux-riscv64-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.linux-riscv64-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-linux-riscv64-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
}
|
||||
break
|
||||
case 's390x':
|
||||
localFileExisted = existsSync(
|
||||
join(__dirname, 'neptune-native.linux-s390x-gnu.node')
|
||||
)
|
||||
try {
|
||||
if (localFileExisted) {
|
||||
nativeBinding = require('./neptune-native.linux-s390x-gnu.node')
|
||||
} else {
|
||||
nativeBinding = require('neptune-native-linux-s390x-gnu')
|
||||
}
|
||||
} catch (e) {
|
||||
loadError = e
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
||||
}
|
||||
|
||||
if (!nativeBinding) {
|
||||
if (loadError) {
|
||||
throw loadError
|
||||
}
|
||||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { initNativeModule, quickVmTest, getVersion, WalletManager, SimpleTransactionBuilder } = nativeBinding
|
||||
|
||||
module.exports.initNativeModule = initNativeModule
|
||||
module.exports.quickVmTest = quickVmTest
|
||||
module.exports.getVersion = getVersion
|
||||
module.exports.WalletManager = WalletManager
|
||||
module.exports.SimpleTransactionBuilder = SimpleTransactionBuilder
|
||||
@ -1,45 +0,0 @@
|
||||
{
|
||||
"name": "@neptune/native",
|
||||
"version": "0.1.0",
|
||||
"description": "Native Node.js addon for Neptune transaction building",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"*.node"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "npx napi build --platform --release",
|
||||
"build:debug": "npx napi build --platform",
|
||||
"prepublishOnly": "napi prepublish -t npm",
|
||||
"test": "cargo test",
|
||||
"universal": "napi universal"
|
||||
},
|
||||
"napi": {
|
||||
"name": "neptune-native",
|
||||
"triples": {
|
||||
"defaults": true,
|
||||
"additional": [
|
||||
"x86_64-unknown-linux-musl",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"aarch64-apple-darwin",
|
||||
"aarch64-unknown-linux-musl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.18.4"
|
||||
},
|
||||
"keywords": [
|
||||
"neptune",
|
||||
"blockchain",
|
||||
"native",
|
||||
"napi",
|
||||
"vm",
|
||||
"proof"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
}
|
||||
0
packages/neptune-wasm/.gitignore
vendored
209
packages/neptune-wasm/neptune_wasm.d.ts
vendored
@ -1,209 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generate a new random seed phrase (18 words, 192 bits entropy)
|
||||
* This is completely offline - uses browser's crypto.getRandomValues()
|
||||
*/
|
||||
export function generate_seed(): string;
|
||||
/**
|
||||
* Get view key and address from seed phrase (offline) - COMPATIBLE WITH NEPTUNE-CORE
|
||||
*
|
||||
* This derives the view key from a BIP39 seed phrase using neptune-crypto-core.
|
||||
* The view key can be used with wallet_getUtxosFromViewKey RPC method.
|
||||
*
|
||||
* Input: JSON string with seed_phrase array and network ("mainnet" or "testnet")
|
||||
* Output: JSON string with receiver_identifier, view_key (hex), and address (bech32m)
|
||||
*
|
||||
* Example:
|
||||
* ```javascript
|
||||
* const result = get_viewkey('["word1", "word2", ...]', "testnet");
|
||||
* const { receiver_identifier, view_key, address } = JSON.parse(result);
|
||||
* console.log('View key:', view_key); // Compatible with neptune-core!
|
||||
* ```
|
||||
*/
|
||||
export function get_viewkey(seed_phrase_json: string, network: string): string;
|
||||
/**
|
||||
* Get receiving address from seed phrase (offline)
|
||||
* Input: JSON string with seed_phrase array and network
|
||||
* Output: bech32m encoded address string
|
||||
*/
|
||||
export function address_from_seed(seed_phrase_json: string, network: string): string;
|
||||
/**
|
||||
* Validate a seed phrase (offline)
|
||||
*/
|
||||
export function validate_seed_phrase(seed_phrase_json: string): boolean;
|
||||
/**
|
||||
* Decode view key from hex string (offline)
|
||||
*/
|
||||
export function decode_viewkey(view_key_hex: string): string;
|
||||
/**
|
||||
* Prepare transaction data for server-side signing
|
||||
*
|
||||
* This prepares transaction details but does NOT sign locally.
|
||||
* Instead, it exports the spending key seed so the server can sign.
|
||||
*
|
||||
* Input: JSON string with BuildTxRequest
|
||||
* Output: JSON string with SignedTxData (contains seed for server signing)
|
||||
*
|
||||
* Example:
|
||||
* ```javascript
|
||||
* const request = {
|
||||
* seed_phrase: ["word1", "word2", ...],
|
||||
* inputs: [{addition_record: "0xabc..."}],
|
||||
* outputs: [{address: "nep1...", amount: "50.0"}],
|
||||
* fee: "0.01",
|
||||
* network: "testnet"
|
||||
* };
|
||||
* const txData = build_and_sign_tx(JSON.stringify(request));
|
||||
* // Now broadcast via JSON-RPC: wallet_broadcastSignedTransaction(txData)
|
||||
* ```
|
||||
*/
|
||||
export function build_and_sign_tx(request_json: string): string;
|
||||
/**
|
||||
* Get wallet balance via JSON-RPC (Neptune core ext format)
|
||||
* Uses wallet_balance method from Wallet namespace
|
||||
*/
|
||||
export function get_balance(rpc_url: string): Promise<string>;
|
||||
/**
|
||||
* Send transaction via JSON-RPC
|
||||
* Note: Neptune core ext may not have direct "send" method in public RPC
|
||||
* This is a placeholder - check actual available methods
|
||||
*/
|
||||
export function send_tx_jsonrpc(rpc_url: string, to_address: string, amount: string, fee: string): Promise<string>;
|
||||
/**
|
||||
* Get block height via JSON-RPC (Neptune core ext format)
|
||||
* Uses chain_height method from Chain namespace
|
||||
*/
|
||||
export function get_block_height(rpc_url: string): Promise<bigint>;
|
||||
/**
|
||||
* Get network info via JSON-RPC (Neptune core ext format)
|
||||
* Uses node_network method from Node namespace
|
||||
*/
|
||||
export function get_network_info(rpc_url: string): Promise<string>;
|
||||
/**
|
||||
* Get wallet addresses from Neptune core (PRODUCTION - EXACT Neptune addresses!)
|
||||
* Returns both generation_address and symmetric_address
|
||||
*/
|
||||
export function get_wallet_address(rpc_url: string): Promise<string>;
|
||||
/**
|
||||
* Get view key from Neptune core (RECOMMENDED)
|
||||
* This exports the proper view key format from Neptune core's wallet
|
||||
* Returns a hex-encoded view key that can be used with wallet_getUtxosFromViewKey
|
||||
*/
|
||||
export function get_viewkey_from_neptune(rpc_url: string): Promise<string>;
|
||||
/**
|
||||
* Get UTXOs from view key (scan blockchain)
|
||||
* This calls Neptune core's wallet_getUtxosFromViewKey JSON-RPC method
|
||||
*
|
||||
* Input: view_key (hex string from neptune-core format), start_block, end_block (optional)
|
||||
* Output: JSON string with list of UTXOs
|
||||
*/
|
||||
export function get_utxos_from_viewkey(rpc_url: string, view_key_hex: string, start_block: bigint, end_block?: bigint | null): Promise<string>;
|
||||
/**
|
||||
* Generate viewkey from BIP39 seed phrase
|
||||
*
|
||||
* # Arguments
|
||||
* * `phrase` - Space-separated BIP39 seed phrase (12-24 words)
|
||||
* * `key_index` - Key derivation index (default: 0)
|
||||
*
|
||||
* # Returns
|
||||
* Hex-encoded viewkey compatible with neptune-core
|
||||
*/
|
||||
export function generate_viewkey_from_phrase(phrase: string, key_index: bigint): string;
|
||||
/**
|
||||
* Generate receiving address from BIP39 seed phrase
|
||||
*
|
||||
* # Arguments
|
||||
* * `phrase` - Space-separated BIP39 seed phrase (12-24 words)
|
||||
* * `key_index` - Key derivation index (default: 0)
|
||||
* * `testnet` - Use testnet network (true) or mainnet (false)
|
||||
*
|
||||
* # Returns
|
||||
* Bech32m-encoded receiving address
|
||||
*/
|
||||
export function generate_address_from_phrase(phrase: string, key_index: bigint, testnet: boolean): string;
|
||||
/**
|
||||
* Get receiver identifier from viewkey hex
|
||||
*/
|
||||
export function get_receiver_id_from_viewkey(viewkey_hex: string): string;
|
||||
/**
|
||||
* Debug: Get detailed key derivation info
|
||||
*/
|
||||
export function debug_key_derivation(phrase: string, key_index: bigint): string;
|
||||
/**
|
||||
* Sign transaction offline (WASM client-side signing)
|
||||
* This creates lock_script_and_witness WITHOUT exposing spending key to server
|
||||
*
|
||||
* # Arguments
|
||||
* * `phrase` - BIP39 seed phrase (18 words)
|
||||
* * `utxos_json` - JSON array of UTXOs from get_utxos (with addition_record)
|
||||
* * `outputs_json` - JSON array of outputs [{address, amount}, ...]
|
||||
* * `fee` - Transaction fee as string
|
||||
* * `key_index` - Key derivation index (usually 0)
|
||||
* * `testnet` - Network selection
|
||||
*
|
||||
* # Returns
|
||||
* JSON containing:
|
||||
* - lock_script_and_witness: hex-encoded signature
|
||||
* - view_key: hex-encoded view key
|
||||
* - inputs: array of addition_records
|
||||
* - outputs: array of {address, amount}
|
||||
* - fee: fee string
|
||||
*/
|
||||
export function sign_transaction_offline(phrase: string, utxos_json: string, outputs_json: string, fee: string, key_index: bigint, testnet: boolean): string;
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
export interface InitOutput {
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly generate_seed: () => [number, number, number, number];
|
||||
readonly get_viewkey: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
||||
readonly address_from_seed: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
||||
readonly validate_seed_phrase: (a: number, b: number) => [number, number, number];
|
||||
readonly decode_viewkey: (a: number, b: number) => [number, number, number, number];
|
||||
readonly build_and_sign_tx: (a: number, b: number) => [number, number, number, number];
|
||||
readonly get_balance: (a: number, b: number) => any;
|
||||
readonly send_tx_jsonrpc: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => any;
|
||||
readonly get_block_height: (a: number, b: number) => any;
|
||||
readonly get_network_info: (a: number, b: number) => any;
|
||||
readonly get_wallet_address: (a: number, b: number) => any;
|
||||
readonly get_viewkey_from_neptune: (a: number, b: number) => any;
|
||||
readonly get_utxos_from_viewkey: (a: number, b: number, c: number, d: number, e: bigint, f: number, g: bigint) => any;
|
||||
readonly generate_viewkey_from_phrase: (a: number, b: number, c: bigint) => [number, number, number, number];
|
||||
readonly generate_address_from_phrase: (a: number, b: number, c: bigint, d: number) => [number, number, number, number];
|
||||
readonly get_receiver_id_from_viewkey: (a: number, b: number) => [number, number, number, number];
|
||||
readonly debug_key_derivation: (a: number, b: number, c: bigint) => [number, number, number, number];
|
||||
readonly sign_transaction_offline: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: bigint, j: number) => [number, number, number, number];
|
||||
readonly __wbindgen_exn_store: (a: number) => void;
|
||||
readonly __externref_table_alloc: () => number;
|
||||
readonly __wbindgen_export_2: WebAssembly.Table;
|
||||
readonly __wbindgen_export_3: WebAssembly.Table;
|
||||
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly __externref_table_dealloc: (a: number) => void;
|
||||
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
readonly closure49_externref_shim: (a: number, b: number, c: any) => void;
|
||||
readonly closure268_externref_shim: (a: number, b: number, c: any, d: any) => void;
|
||||
readonly __wbindgen_start: () => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
/**
|
||||
* Instantiates the given `module`, which can either be bytes or
|
||||
* a precompiled `WebAssembly.Module`.
|
||||
*
|
||||
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {InitOutput}
|
||||
*/
|
||||
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
||||
|
||||
/**
|
||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||
*
|
||||
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {Promise<InitOutput>}
|
||||
*/
|
||||
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|
||||
@ -1,984 +0,0 @@
|
||||
let wasm;
|
||||
|
||||
function addToExternrefTable0(obj) {
|
||||
const idx = wasm.__externref_table_alloc();
|
||||
wasm.__wbindgen_export_2.set(idx, obj);
|
||||
return idx;
|
||||
}
|
||||
|
||||
function handleError(f, args) {
|
||||
try {
|
||||
return f.apply(this, args);
|
||||
} catch (e) {
|
||||
const idx = addToExternrefTable0(e);
|
||||
wasm.__wbindgen_exn_store(idx);
|
||||
}
|
||||
}
|
||||
|
||||
let cachedUint8ArrayMemory0 = null;
|
||||
|
||||
function getUint8ArrayMemory0() {
|
||||
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
||||
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint8ArrayMemory0;
|
||||
}
|
||||
|
||||
function getArrayU8FromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
|
||||
}
|
||||
|
||||
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
|
||||
|
||||
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
function isLikeNone(x) {
|
||||
return x === undefined || x === null;
|
||||
}
|
||||
|
||||
const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
|
||||
? { register: () => {}, unregister: () => {} }
|
||||
: new FinalizationRegistry(state => {
|
||||
wasm.__wbindgen_export_3.get(state.dtor)(state.a, state.b)
|
||||
});
|
||||
|
||||
function makeMutClosure(arg0, arg1, dtor, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
||||
const real = (...args) => {
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
state.cnt++;
|
||||
const a = state.a;
|
||||
state.a = 0;
|
||||
try {
|
||||
return f(a, state.b, ...args);
|
||||
} finally {
|
||||
if (--state.cnt === 0) {
|
||||
wasm.__wbindgen_export_3.get(state.dtor)(a, state.b);
|
||||
CLOSURE_DTORS.unregister(state);
|
||||
} else {
|
||||
state.a = a;
|
||||
}
|
||||
}
|
||||
};
|
||||
real.original = state;
|
||||
CLOSURE_DTORS.register(real, state, state);
|
||||
return real;
|
||||
}
|
||||
|
||||
function debugString(val) {
|
||||
// primitive types
|
||||
const type = typeof val;
|
||||
if (type == 'number' || type == 'boolean' || val == null) {
|
||||
return `${val}`;
|
||||
}
|
||||
if (type == 'string') {
|
||||
return `"${val}"`;
|
||||
}
|
||||
if (type == 'symbol') {
|
||||
const description = val.description;
|
||||
if (description == null) {
|
||||
return 'Symbol';
|
||||
} else {
|
||||
return `Symbol(${description})`;
|
||||
}
|
||||
}
|
||||
if (type == 'function') {
|
||||
const name = val.name;
|
||||
if (typeof name == 'string' && name.length > 0) {
|
||||
return `Function(${name})`;
|
||||
} else {
|
||||
return 'Function';
|
||||
}
|
||||
}
|
||||
// objects
|
||||
if (Array.isArray(val)) {
|
||||
const length = val.length;
|
||||
let debug = '[';
|
||||
if (length > 0) {
|
||||
debug += debugString(val[0]);
|
||||
}
|
||||
for(let i = 1; i < length; i++) {
|
||||
debug += ', ' + debugString(val[i]);
|
||||
}
|
||||
debug += ']';
|
||||
return debug;
|
||||
}
|
||||
// Test for built-in
|
||||
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
|
||||
let className;
|
||||
if (builtInMatches && builtInMatches.length > 1) {
|
||||
className = builtInMatches[1];
|
||||
} else {
|
||||
// Failed to match the standard '[object ClassName]'
|
||||
return toString.call(val);
|
||||
}
|
||||
if (className == 'Object') {
|
||||
// we're a user defined class or Object
|
||||
// JSON.stringify avoids problems with cycles, and is generally much
|
||||
// easier than looping through ownProperties of `val`.
|
||||
try {
|
||||
return 'Object(' + JSON.stringify(val) + ')';
|
||||
} catch (_) {
|
||||
return 'Object';
|
||||
}
|
||||
}
|
||||
// errors
|
||||
if (val instanceof Error) {
|
||||
return `${val.name}: ${val.message}\n${val.stack}`;
|
||||
}
|
||||
// TODO we could test for more things here, like `Set`s and `Map`s.
|
||||
return className;
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
|
||||
|
||||
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
|
||||
? function (arg, view) {
|
||||
return cachedTextEncoder.encodeInto(arg, view);
|
||||
}
|
||||
: function (arg, view) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
view.set(buf);
|
||||
return {
|
||||
read: arg.length,
|
||||
written: buf.length
|
||||
};
|
||||
});
|
||||
|
||||
function passStringToWasm0(arg, malloc, realloc) {
|
||||
|
||||
if (realloc === undefined) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
const ptr = malloc(buf.length, 1) >>> 0;
|
||||
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
||||
WASM_VECTOR_LEN = buf.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let len = arg.length;
|
||||
let ptr = malloc(len, 1) >>> 0;
|
||||
|
||||
const mem = getUint8ArrayMemory0();
|
||||
|
||||
let offset = 0;
|
||||
|
||||
for (; offset < len; offset++) {
|
||||
const code = arg.charCodeAt(offset);
|
||||
if (code > 0x7F) break;
|
||||
mem[ptr + offset] = code;
|
||||
}
|
||||
|
||||
if (offset !== len) {
|
||||
if (offset !== 0) {
|
||||
arg = arg.slice(offset);
|
||||
}
|
||||
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
||||
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
||||
const ret = encodeString(arg, view);
|
||||
|
||||
offset += ret.written;
|
||||
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
||||
}
|
||||
|
||||
WASM_VECTOR_LEN = offset;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let cachedDataViewMemory0 = null;
|
||||
|
||||
function getDataViewMemory0() {
|
||||
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
||||
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
||||
}
|
||||
return cachedDataViewMemory0;
|
||||
}
|
||||
|
||||
function takeFromExternrefTable0(idx) {
|
||||
const value = wasm.__wbindgen_export_2.get(idx);
|
||||
wasm.__externref_table_dealloc(idx);
|
||||
return value;
|
||||
}
|
||||
/**
|
||||
* Generate a new random seed phrase (18 words, 192 bits entropy)
|
||||
* This is completely offline - uses browser's crypto.getRandomValues()
|
||||
* @returns {string}
|
||||
*/
|
||||
export function generate_seed() {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const ret = wasm.generate_seed();
|
||||
var ptr1 = ret[0];
|
||||
var len1 = ret[1];
|
||||
if (ret[3]) {
|
||||
ptr1 = 0; len1 = 0;
|
||||
throw takeFromExternrefTable0(ret[2]);
|
||||
}
|
||||
deferred2_0 = ptr1;
|
||||
deferred2_1 = len1;
|
||||
return getStringFromWasm0(ptr1, len1);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get view key and address from seed phrase (offline) - COMPATIBLE WITH NEPTUNE-CORE
|
||||
*
|
||||
* This derives the view key from a BIP39 seed phrase using neptune-crypto-core.
|
||||
* The view key can be used with wallet_getUtxosFromViewKey RPC method.
|
||||
*
|
||||
* Input: JSON string with seed_phrase array and network ("mainnet" or "testnet")
|
||||
* Output: JSON string with receiver_identifier, view_key (hex), and address (bech32m)
|
||||
*
|
||||
* Example:
|
||||
* ```javascript
|
||||
* const result = get_viewkey('["word1", "word2", ...]', "testnet");
|
||||
* const { receiver_identifier, view_key, address } = JSON.parse(result);
|
||||
* console.log('View key:', view_key); // Compatible with neptune-core!
|
||||
* ```
|
||||
* @param {string} seed_phrase_json
|
||||
* @param {string} network
|
||||
* @returns {string}
|
||||
*/
|
||||
export function get_viewkey(seed_phrase_json, network) {
|
||||
let deferred4_0;
|
||||
let deferred4_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(seed_phrase_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ptr1 = passStringToWasm0(network, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.get_viewkey(ptr0, len0, ptr1, len1);
|
||||
var ptr3 = ret[0];
|
||||
var len3 = ret[1];
|
||||
if (ret[3]) {
|
||||
ptr3 = 0; len3 = 0;
|
||||
throw takeFromExternrefTable0(ret[2]);
|
||||
}
|
||||
deferred4_0 = ptr3;
|
||||
deferred4_1 = len3;
|
||||
return getStringFromWasm0(ptr3, len3);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get receiving address from seed phrase (offline)
|
||||
* Input: JSON string with seed_phrase array and network
|
||||
* Output: bech32m encoded address string
|
||||
* @param {string} seed_phrase_json
|
||||
* @param {string} network
|
||||
* @returns {string}
|
||||
*/
|
||||
export function address_from_seed(seed_phrase_json, network) {
|
||||
let deferred4_0;
|
||||
let deferred4_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(seed_phrase_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ptr1 = passStringToWasm0(network, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.address_from_seed(ptr0, len0, ptr1, len1);
|
||||
var ptr3 = ret[0];
|
||||
var len3 = ret[1];
|
||||
if (ret[3]) {
|
||||
ptr3 = 0; len3 = 0;
|
||||
throw takeFromExternrefTable0(ret[2]);
|
||||
}
|
||||
deferred4_0 = ptr3;
|
||||
deferred4_1 = len3;
|
||||
return getStringFromWasm0(ptr3, len3);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a seed phrase (offline)
|
||||
* @param {string} seed_phrase_json
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function validate_seed_phrase(seed_phrase_json) {
|
||||
const ptr0 = passStringToWasm0(seed_phrase_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.validate_seed_phrase(ptr0, len0);
|
||||
if (ret[2]) {
|
||||
throw takeFromExternrefTable0(ret[1]);
|
||||
}
|
||||
return ret[0] !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode view key from hex string (offline)
|
||||
* @param {string} view_key_hex
|
||||
* @returns {string}
|
||||
*/
|
||||
export function decode_viewkey(view_key_hex) {
|
||||
let deferred3_0;
|
||||
let deferred3_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(view_key_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.decode_viewkey(ptr0, len0);
|
||||
var ptr2 = ret[0];
|
||||
var len2 = ret[1];
|
||||
if (ret[3]) {
|
||||
ptr2 = 0; len2 = 0;
|
||||
throw takeFromExternrefTable0(ret[2]);
|
||||
}
|
||||
deferred3_0 = ptr2;
|
||||
deferred3_1 = len2;
|
||||
return getStringFromWasm0(ptr2, len2);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare transaction data for server-side signing
|
||||
*
|
||||
* This prepares transaction details but does NOT sign locally.
|
||||
* Instead, it exports the spending key seed so the server can sign.
|
||||
*
|
||||
* Input: JSON string with BuildTxRequest
|
||||
* Output: JSON string with SignedTxData (contains seed for server signing)
|
||||
*
|
||||
* Example:
|
||||
* ```javascript
|
||||
* const request = {
|
||||
* seed_phrase: ["word1", "word2", ...],
|
||||
* inputs: [{addition_record: "0xabc..."}],
|
||||
* outputs: [{address: "nep1...", amount: "50.0"}],
|
||||
* fee: "0.01",
|
||||
* network: "testnet"
|
||||
* };
|
||||
* const txData = build_and_sign_tx(JSON.stringify(request));
|
||||
* // Now broadcast via JSON-RPC: wallet_broadcastSignedTransaction(txData)
|
||||
* ```
|
||||
* @param {string} request_json
|
||||
* @returns {string}
|
||||
*/
|
||||
export function build_and_sign_tx(request_json) {
|
||||
let deferred3_0;
|
||||
let deferred3_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(request_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.build_and_sign_tx(ptr0, len0);
|
||||
var ptr2 = ret[0];
|
||||
var len2 = ret[1];
|
||||
if (ret[3]) {
|
||||
ptr2 = 0; len2 = 0;
|
||||
throw takeFromExternrefTable0(ret[2]);
|
||||
}
|
||||
deferred3_0 = ptr2;
|
||||
deferred3_1 = len2;
|
||||
return getStringFromWasm0(ptr2, len2);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get wallet balance via JSON-RPC (Neptune core ext format)
|
||||
* Uses wallet_balance method from Wallet namespace
|
||||
* @param {string} rpc_url
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function get_balance(rpc_url) {
|
||||
const ptr0 = passStringToWasm0(rpc_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.get_balance(ptr0, len0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send transaction via JSON-RPC
|
||||
* Note: Neptune core ext may not have direct "send" method in public RPC
|
||||
* This is a placeholder - check actual available methods
|
||||
* @param {string} rpc_url
|
||||
* @param {string} to_address
|
||||
* @param {string} amount
|
||||
* @param {string} fee
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function send_tx_jsonrpc(rpc_url, to_address, amount, fee) {
|
||||
const ptr0 = passStringToWasm0(rpc_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ptr1 = passStringToWasm0(to_address, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
const ptr2 = passStringToWasm0(amount, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len2 = WASM_VECTOR_LEN;
|
||||
const ptr3 = passStringToWasm0(fee, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len3 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.send_tx_jsonrpc(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block height via JSON-RPC (Neptune core ext format)
|
||||
* Uses chain_height method from Chain namespace
|
||||
* @param {string} rpc_url
|
||||
* @returns {Promise<bigint>}
|
||||
*/
|
||||
export function get_block_height(rpc_url) {
|
||||
const ptr0 = passStringToWasm0(rpc_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.get_block_height(ptr0, len0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get network info via JSON-RPC (Neptune core ext format)
|
||||
* Uses node_network method from Node namespace
|
||||
* @param {string} rpc_url
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function get_network_info(rpc_url) {
|
||||
const ptr0 = passStringToWasm0(rpc_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.get_network_info(ptr0, len0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get wallet addresses from Neptune core (PRODUCTION - EXACT Neptune addresses!)
|
||||
* Returns both generation_address and symmetric_address
|
||||
* @param {string} rpc_url
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function get_wallet_address(rpc_url) {
|
||||
const ptr0 = passStringToWasm0(rpc_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.get_wallet_address(ptr0, len0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get view key from Neptune core (RECOMMENDED)
|
||||
* This exports the proper view key format from Neptune core's wallet
|
||||
* Returns a hex-encoded view key that can be used with wallet_getUtxosFromViewKey
|
||||
* @param {string} rpc_url
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function get_viewkey_from_neptune(rpc_url) {
|
||||
const ptr0 = passStringToWasm0(rpc_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.get_viewkey_from_neptune(ptr0, len0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get UTXOs from view key (scan blockchain)
|
||||
* This calls Neptune core's wallet_getUtxosFromViewKey JSON-RPC method
|
||||
*
|
||||
* Input: view_key (hex string from neptune-core format), start_block, end_block (optional)
|
||||
* Output: JSON string with list of UTXOs
|
||||
* @param {string} rpc_url
|
||||
* @param {string} view_key_hex
|
||||
* @param {bigint} start_block
|
||||
* @param {bigint | null} [end_block]
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export function get_utxos_from_viewkey(rpc_url, view_key_hex, start_block, end_block) {
|
||||
const ptr0 = passStringToWasm0(rpc_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ptr1 = passStringToWasm0(view_key_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.get_utxos_from_viewkey(ptr0, len0, ptr1, len1, start_block, !isLikeNone(end_block), isLikeNone(end_block) ? BigInt(0) : end_block);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate viewkey from BIP39 seed phrase
|
||||
*
|
||||
* # Arguments
|
||||
* * `phrase` - Space-separated BIP39 seed phrase (12-24 words)
|
||||
* * `key_index` - Key derivation index (default: 0)
|
||||
*
|
||||
* # Returns
|
||||
* Hex-encoded viewkey compatible with neptune-core
|
||||
* @param {string} phrase
|
||||
* @param {bigint} key_index
|
||||
* @returns {string}
|
||||
*/
|
||||
export function generate_viewkey_from_phrase(phrase, key_index) {
|
||||
let deferred3_0;
|
||||
let deferred3_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(phrase, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.generate_viewkey_from_phrase(ptr0, len0, key_index);
|
||||
var ptr2 = ret[0];
|
||||
var len2 = ret[1];
|
||||
if (ret[3]) {
|
||||
ptr2 = 0; len2 = 0;
|
||||
throw takeFromExternrefTable0(ret[2]);
|
||||
}
|
||||
deferred3_0 = ptr2;
|
||||
deferred3_1 = len2;
|
||||
return getStringFromWasm0(ptr2, len2);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate receiving address from BIP39 seed phrase
|
||||
*
|
||||
* # Arguments
|
||||
* * `phrase` - Space-separated BIP39 seed phrase (12-24 words)
|
||||
* * `key_index` - Key derivation index (default: 0)
|
||||
* * `testnet` - Use testnet network (true) or mainnet (false)
|
||||
*
|
||||
* # Returns
|
||||
* Bech32m-encoded receiving address
|
||||
* @param {string} phrase
|
||||
* @param {bigint} key_index
|
||||
* @param {boolean} testnet
|
||||
* @returns {string}
|
||||
*/
|
||||
export function generate_address_from_phrase(phrase, key_index, testnet) {
|
||||
let deferred3_0;
|
||||
let deferred3_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(phrase, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.generate_address_from_phrase(ptr0, len0, key_index, testnet);
|
||||
var ptr2 = ret[0];
|
||||
var len2 = ret[1];
|
||||
if (ret[3]) {
|
||||
ptr2 = 0; len2 = 0;
|
||||
throw takeFromExternrefTable0(ret[2]);
|
||||
}
|
||||
deferred3_0 = ptr2;
|
||||
deferred3_1 = len2;
|
||||
return getStringFromWasm0(ptr2, len2);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get receiver identifier from viewkey hex
|
||||
* @param {string} viewkey_hex
|
||||
* @returns {string}
|
||||
*/
|
||||
export function get_receiver_id_from_viewkey(viewkey_hex) {
|
||||
let deferred3_0;
|
||||
let deferred3_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(viewkey_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.get_receiver_id_from_viewkey(ptr0, len0);
|
||||
var ptr2 = ret[0];
|
||||
var len2 = ret[1];
|
||||
if (ret[3]) {
|
||||
ptr2 = 0; len2 = 0;
|
||||
throw takeFromExternrefTable0(ret[2]);
|
||||
}
|
||||
deferred3_0 = ptr2;
|
||||
deferred3_1 = len2;
|
||||
return getStringFromWasm0(ptr2, len2);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug: Get detailed key derivation info
|
||||
* @param {string} phrase
|
||||
* @param {bigint} key_index
|
||||
* @returns {string}
|
||||
*/
|
||||
export function debug_key_derivation(phrase, key_index) {
|
||||
let deferred3_0;
|
||||
let deferred3_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(phrase, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.debug_key_derivation(ptr0, len0, key_index);
|
||||
var ptr2 = ret[0];
|
||||
var len2 = ret[1];
|
||||
if (ret[3]) {
|
||||
ptr2 = 0; len2 = 0;
|
||||
throw takeFromExternrefTable0(ret[2]);
|
||||
}
|
||||
deferred3_0 = ptr2;
|
||||
deferred3_1 = len2;
|
||||
return getStringFromWasm0(ptr2, len2);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign transaction offline (WASM client-side signing)
|
||||
* This creates lock_script_and_witness WITHOUT exposing spending key to server
|
||||
*
|
||||
* # Arguments
|
||||
* * `phrase` - BIP39 seed phrase (18 words)
|
||||
* * `utxos_json` - JSON array of UTXOs from get_utxos (with addition_record)
|
||||
* * `outputs_json` - JSON array of outputs [{address, amount}, ...]
|
||||
* * `fee` - Transaction fee as string
|
||||
* * `key_index` - Key derivation index (usually 0)
|
||||
* * `testnet` - Network selection
|
||||
*
|
||||
* # Returns
|
||||
* JSON containing:
|
||||
* - lock_script_and_witness: hex-encoded signature
|
||||
* - view_key: hex-encoded view key
|
||||
* - inputs: array of addition_records
|
||||
* - outputs: array of {address, amount}
|
||||
* - fee: fee string
|
||||
* @param {string} phrase
|
||||
* @param {string} utxos_json
|
||||
* @param {string} outputs_json
|
||||
* @param {string} fee
|
||||
* @param {bigint} key_index
|
||||
* @param {boolean} testnet
|
||||
* @returns {string}
|
||||
*/
|
||||
export function sign_transaction_offline(phrase, utxos_json, outputs_json, fee, key_index, testnet) {
|
||||
let deferred6_0;
|
||||
let deferred6_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(phrase, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ptr1 = passStringToWasm0(utxos_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
const ptr2 = passStringToWasm0(outputs_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len2 = WASM_VECTOR_LEN;
|
||||
const ptr3 = passStringToWasm0(fee, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len3 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.sign_transaction_offline(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, key_index, testnet);
|
||||
var ptr5 = ret[0];
|
||||
var len5 = ret[1];
|
||||
if (ret[3]) {
|
||||
ptr5 = 0; len5 = 0;
|
||||
throw takeFromExternrefTable0(ret[2]);
|
||||
}
|
||||
deferred6_0 = ptr5;
|
||||
deferred6_1 = len5;
|
||||
return getStringFromWasm0(ptr5, len5);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred6_0, deferred6_1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function __wbg_adapter_24(arg0, arg1, arg2) {
|
||||
wasm.closure49_externref_shim(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function __wbg_adapter_83(arg0, arg1, arg2, arg3) {
|
||||
wasm.closure268_externref_shim(arg0, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
const __wbindgen_enum_RequestMode = ["same-origin", "no-cors", "cors", "navigate"];
|
||||
|
||||
async function __wbg_load(module, imports) {
|
||||
if (typeof Response === 'function' && module instanceof Response) {
|
||||
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||
try {
|
||||
return await WebAssembly.instantiateStreaming(module, imports);
|
||||
|
||||
} catch (e) {
|
||||
if (module.headers.get('Content-Type') != 'application/wasm') {
|
||||
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
||||
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bytes = await module.arrayBuffer();
|
||||
return await WebAssembly.instantiate(bytes, imports);
|
||||
|
||||
} else {
|
||||
const instance = await WebAssembly.instantiate(module, imports);
|
||||
|
||||
if (instance instanceof WebAssembly.Instance) {
|
||||
return { instance, module };
|
||||
|
||||
} else {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function __wbg_get_imports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbg_call_672a4d21634d4a24 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = arg0.call(arg1);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_call_7cccdd69e0791ae2 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = arg0.call(arg1, arg2);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_error_524f506f44df1645 = function(arg0) {
|
||||
console.error(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_fetch_b7bf320f681242d2 = function(arg0, arg1) {
|
||||
const ret = arg0.fetch(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_getRandomValues_bb689d73e9ab7af6 = function(arg0, arg1) {
|
||||
crypto.getRandomValues(getArrayU8FromWasm0(arg0, arg1));
|
||||
};
|
||||
imports.wbg.__wbg_headers_7852a8ea641c1379 = function(arg0) {
|
||||
const ret = arg0.headers;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_Response_f2cc20d9f7dfd644 = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = arg0 instanceof Response;
|
||||
} catch (_) {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_Window_def73ea0955fc569 = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = arg0 instanceof Window;
|
||||
} catch (_) {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_log_7fbbb36c3875e666 = function(arg0, arg1) {
|
||||
console.log(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
imports.wbg.__wbg_log_c222819a41e063d3 = function(arg0) {
|
||||
console.log(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) {
|
||||
try {
|
||||
var state0 = {a: arg0, b: arg1};
|
||||
var cb0 = (arg0, arg1) => {
|
||||
const a = state0.a;
|
||||
state0.a = 0;
|
||||
try {
|
||||
return __wbg_adapter_83(a, state0.b, arg0, arg1);
|
||||
} finally {
|
||||
state0.a = a;
|
||||
}
|
||||
};
|
||||
const ret = new Promise(cb0);
|
||||
return ret;
|
||||
} finally {
|
||||
state0.a = state0.b = 0;
|
||||
}
|
||||
};
|
||||
imports.wbg.__wbg_new_405e22f390576ce2 = function() {
|
||||
const ret = new Object();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) {
|
||||
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_newwithstrandinit_06c535e0a867c635 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = new Request(getStringFromWasm0(arg0, arg1), arg2);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) {
|
||||
queueMicrotask(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) {
|
||||
const ret = arg0.queueMicrotask;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) {
|
||||
const ret = Promise.resolve(arg0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_set_11cd83f45504cedf = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
arg0.set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_setbody_5923b78a95eedf29 = function(arg0, arg1) {
|
||||
arg0.body = arg1;
|
||||
};
|
||||
imports.wbg.__wbg_setmethod_3c5280fe5d890842 = function(arg0, arg1, arg2) {
|
||||
arg0.method = getStringFromWasm0(arg1, arg2);
|
||||
};
|
||||
imports.wbg.__wbg_setmode_5dc300b865044b65 = function(arg0, arg1) {
|
||||
arg0.mode = __wbindgen_enum_RequestMode[arg1];
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() {
|
||||
const ret = typeof global === 'undefined' ? null : global;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() {
|
||||
const ret = typeof globalThis === 'undefined' ? null : globalThis;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() {
|
||||
const ret = typeof self === 'undefined' ? null : self;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() {
|
||||
const ret = typeof window === 'undefined' ? null : window;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_text_7805bea50de2af49 = function() { return handleError(function (arg0) {
|
||||
const ret = arg0.text();
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) {
|
||||
const ret = arg0.then(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) {
|
||||
const ret = arg0.then(arg1, arg2);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_bigint_from_u64 = function(arg0) {
|
||||
const ret = BigInt.asUintN(64, arg0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_cb_drop = function(arg0) {
|
||||
const obj = arg0.original;
|
||||
if (obj.cnt-- == 1) {
|
||||
obj.a = 0;
|
||||
return true;
|
||||
}
|
||||
const ret = false;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper214 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 50, __wbg_adapter_24);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
|
||||
const ret = debugString(arg1);
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbindgen_init_externref_table = function() {
|
||||
const table = wasm.__wbindgen_export_2;
|
||||
const offset = table.grow(4);
|
||||
table.set(0, undefined);
|
||||
table.set(offset + 0, undefined);
|
||||
table.set(offset + 1, null);
|
||||
table.set(offset + 2, true);
|
||||
table.set(offset + 3, false);
|
||||
;
|
||||
};
|
||||
imports.wbg.__wbindgen_is_function = function(arg0) {
|
||||
const ret = typeof(arg0) === 'function';
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_is_undefined = function(arg0) {
|
||||
const ret = arg0 === undefined;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
|
||||
const obj = arg1;
|
||||
const ret = typeof(obj) === 'string' ? obj : undefined;
|
||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
function __wbg_init_memory(imports, memory) {
|
||||
|
||||
}
|
||||
|
||||
function __wbg_finalize_init(instance, module) {
|
||||
wasm = instance.exports;
|
||||
__wbg_init.__wbindgen_wasm_module = module;
|
||||
cachedDataViewMemory0 = null;
|
||||
cachedUint8ArrayMemory0 = null;
|
||||
|
||||
|
||||
wasm.__wbindgen_start();
|
||||
return wasm;
|
||||
}
|
||||
|
||||
function initSync(module) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
if (Object.getPrototypeOf(module) === Object.prototype) {
|
||||
({module} = module)
|
||||
} else {
|
||||
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
|
||||
}
|
||||
}
|
||||
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
__wbg_init_memory(imports);
|
||||
|
||||
if (!(module instanceof WebAssembly.Module)) {
|
||||
module = new WebAssembly.Module(module);
|
||||
}
|
||||
|
||||
const instance = new WebAssembly.Instance(module, imports);
|
||||
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
async function __wbg_init(module_or_path) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
|
||||
if (typeof module_or_path !== 'undefined') {
|
||||
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
|
||||
({module_or_path} = module_or_path)
|
||||
} else {
|
||||
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof module_or_path === 'undefined') {
|
||||
module_or_path = new URL('neptune_wasm_bg.wasm', import.meta.url);
|
||||
}
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
|
||||
module_or_path = fetch(module_or_path);
|
||||
}
|
||||
|
||||
__wbg_init_memory(imports);
|
||||
|
||||
const { instance, module } = await __wbg_load(await module_or_path, imports);
|
||||
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
export { initSync };
|
||||
export default __wbg_init;
|
||||
32
packages/neptune-wasm/neptune_wasm_bg.wasm.d.ts
vendored
@ -1,32 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const memory: WebAssembly.Memory;
|
||||
export const generate_seed: () => [number, number, number, number];
|
||||
export const get_viewkey: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
||||
export const address_from_seed: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
||||
export const validate_seed_phrase: (a: number, b: number) => [number, number, number];
|
||||
export const decode_viewkey: (a: number, b: number) => [number, number, number, number];
|
||||
export const build_and_sign_tx: (a: number, b: number) => [number, number, number, number];
|
||||
export const get_balance: (a: number, b: number) => any;
|
||||
export const send_tx_jsonrpc: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => any;
|
||||
export const get_block_height: (a: number, b: number) => any;
|
||||
export const get_network_info: (a: number, b: number) => any;
|
||||
export const get_wallet_address: (a: number, b: number) => any;
|
||||
export const get_viewkey_from_neptune: (a: number, b: number) => any;
|
||||
export const get_utxos_from_viewkey: (a: number, b: number, c: number, d: number, e: bigint, f: number, g: bigint) => any;
|
||||
export const generate_viewkey_from_phrase: (a: number, b: number, c: bigint) => [number, number, number, number];
|
||||
export const generate_address_from_phrase: (a: number, b: number, c: bigint, d: number) => [number, number, number, number];
|
||||
export const get_receiver_id_from_viewkey: (a: number, b: number) => [number, number, number, number];
|
||||
export const debug_key_derivation: (a: number, b: number, c: bigint) => [number, number, number, number];
|
||||
export const sign_transaction_offline: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: bigint, j: number) => [number, number, number, number];
|
||||
export const __wbindgen_exn_store: (a: number) => void;
|
||||
export const __externref_table_alloc: () => number;
|
||||
export const __wbindgen_export_2: WebAssembly.Table;
|
||||
export const __wbindgen_export_3: WebAssembly.Table;
|
||||
export const __wbindgen_malloc: (a: number, b: number) => number;
|
||||
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
export const __externref_table_dealloc: (a: number) => void;
|
||||
export const __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
export const closure49_externref_shim: (a: number, b: number, c: any) => void;
|
||||
export const closure268_externref_shim: (a: number, b: number, c: any, d: any) => void;
|
||||
export const __wbindgen_start: () => void;
|
||||
@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "@neptune/wasm",
|
||||
"type": "module",
|
||||
"version": "0.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"files": [
|
||||
"neptune_wasm_bg.wasm",
|
||||
"neptune_wasm.js",
|
||||
"neptune_wasm.d.ts"
|
||||
],
|
||||
"main": "neptune_wasm.js",
|
||||
"types": "neptune_wasm.d.ts",
|
||||
"sideEffects": [
|
||||
"./snippets/*"
|
||||
]
|
||||
}
|
||||
765
pnpm-lock.yaml
generated
@ -1,2 +0,0 @@
|
||||
packages:
|
||||
- 'packages/*'
|
||||
4
src-tauri/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/gen/schemas
|
||||
@ -1,31 +0,0 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.5.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4"
|
||||
tauri = { version = "2.9.2", features = [] }
|
||||
tauri-plugin-log = "2"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
jni = "0.21"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
objc = "0.2"
|
||||
@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#fff</color>
|
||||
</resources>
|
||||
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 695 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
@ -1,25 +0,0 @@
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
// Setup logging with rotation and file targets
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.target(tauri_plugin_log::Target::new(
|
||||
tauri_plugin_log::TargetKind::Stdout,
|
||||
))
|
||||
.target(tauri_plugin_log::Target::new(
|
||||
tauri_plugin_log::TargetKind::LogDir {
|
||||
file_name: Some("neptune-privacy".into())
|
||||
}
|
||||
))
|
||||
.max_file_size(10_485_760) // 10MB
|
||||
.rotation_strategy(tauri_plugin_log::RotationStrategy::KeepAll)
|
||||
.build(),
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
app_lib::run();
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "Neptune Privacy",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.neptune.privacy",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
"devUrl": "http://localhost:5173",
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
"beforeBuildCommand": "pnpm build"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Neptune Privacy",
|
||||
"width": 375,
|
||||
"height": 850,
|
||||
"minWidth": 375,
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
"center": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null,
|
||||
"dangerousDisableAssetCspModification": true,
|
||||
"freezePrototype": false
|
||||
},
|
||||
"withGlobalTauri": false,
|
||||
"macOSPrivateApi": false
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"iOS": {
|
||||
"minimumSystemVersion": "13.0"
|
||||
},
|
||||
"android": {
|
||||
"minSdkVersion": 24
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/App.vue
@ -1,10 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { Toaster } from '@/components/ui/sonner'
|
||||
import { ThemeToggle } from '@/components/ui/theme-toggle'
|
||||
import neptuneLogo from '@/assets/imgs/neptune_logo.jpg'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
<Toaster richColors position="top-center" :duration="2000" closeButton />
|
||||
<div class="min-h-screen bg-background">
|
||||
<!-- Header with Logo and Theme Toggle -->
|
||||
<header class="border-b border-border">
|
||||
<div class="container mx-auto flex items-center justify-between px-4 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<img
|
||||
:src="neptuneLogo"
|
||||
alt="Neptune Wallet Logo"
|
||||
class="h-12 w-12 rounded-lg object-cover"
|
||||
/>
|
||||
<span class="text-xl font-bold text-foreground">Neptune</span>
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main>
|
||||
<router-view />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import axios, { type AxiosInstance, type AxiosResponse, type AxiosError } from 'axios'
|
||||
import { STATUS_CODE_SUCCESS } from '@/utils/constants'
|
||||
|
||||
const STATUS_CODE_SUCCESS = 200
|
||||
|
||||
export const API_URL = import.meta.env.VITE_APP_API || ''
|
||||
|
||||
@ -13,6 +14,17 @@ const instance: AxiosInstance = axios.create({
|
||||
withCredentials: false,
|
||||
})
|
||||
|
||||
// Request interceptor
|
||||
instance.interceptors.request.use(
|
||||
config => {
|
||||
// Add request interceptors here (e.g., auth tokens)
|
||||
return config
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// Response interceptor
|
||||
instance.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
@ -29,4 +41,10 @@ instance.interceptors.response.use(
|
||||
}
|
||||
)
|
||||
|
||||
// Set locale for API requests
|
||||
export const setLocaleApi = (locale: string) => {
|
||||
instance.defaults.headers.common['lang'] = locale
|
||||
}
|
||||
|
||||
export default instance
|
||||
|
||||
|
||||
41
src/components/HelloWorld.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>()
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
@ -1,18 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ArrowLeft } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: []
|
||||
}>()
|
||||
|
||||
const handleClick = () => {
|
||||
emit('click')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button variant="ghost" size="icon-lg" @click="handleClick">
|
||||
<ArrowLeft />
|
||||
</Button>
|
||||
</template>
|
||||
@ -1 +0,0 @@
|
||||
export { default as ArrowLeftCommon } from './ArrowLeftCommon.vue'
|
||||
@ -1,4 +0,0 @@
|
||||
export * from './layout'
|
||||
export * from './logo'
|
||||
export * from './password-form'
|
||||
|
||||
@ -1,94 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { Database, Wallet, History, Network } from 'lucide-vue-next'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
// Navigation items for bottom tab bar
|
||||
const navItems = [
|
||||
{ name: 'Wallet', icon: Wallet, route: '/', label: 'Wallet' },
|
||||
{ name: 'UTXO', icon: Database, route: '/utxo', label: 'UTXO' },
|
||||
{ name: 'History', icon: History, route: '/transaction-history', label: 'History' },
|
||||
{ name: 'Network', icon: Network, route: '/network', label: 'Network' },
|
||||
]
|
||||
|
||||
const isActiveRoute = (routePath: string) => {
|
||||
return route.path === routePath
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-screen flex-col bg-background">
|
||||
<!-- Header -->
|
||||
<header class="fixed top-0 left-0 right-0 z-10 border-b border-border bg-card">
|
||||
<div class="flex h-14 items-center justify-between px-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<img
|
||||
src="@/assets/imgs/neptune_logo.jpg"
|
||||
alt="Neptune"
|
||||
class="h-8 w-8 rounded-lg object-cover"
|
||||
/>
|
||||
<span class="text-lg font-semibold text-foreground">Neptune Privacy</span>
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content Area (Scrollable) -->
|
||||
<main class="flex-1 overflow-y-auto pt-14 pb-16">
|
||||
<router-view />
|
||||
</main>
|
||||
|
||||
<!-- Bottom Navigation Bar -->
|
||||
<nav
|
||||
class="safe-area-bottom fixed bottom-0 left-0 right-0 z-10 border-t border-border bg-card shadow-[0_-4px_6px_-1px_rgb(0_0_0/0.1),0_-2px_4px_-2px_rgb(0_0_0/0.1)]"
|
||||
role="navigation"
|
||||
aria-label="Main navigation"
|
||||
>
|
||||
<div class="grid grid-cols-4">
|
||||
<button
|
||||
v-for="item in navItems"
|
||||
:key="item.name"
|
||||
type="button"
|
||||
class="flex flex-col items-center justify-center gap-1 py-2 transition-colors"
|
||||
:class="[
|
||||
isActiveRoute(item.route)
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground hover:text-foreground active:text-foreground',
|
||||
]"
|
||||
:aria-label="item.label"
|
||||
:aria-current="isActiveRoute(item.route) ? 'page' : undefined"
|
||||
@click="router.push(item.route)"
|
||||
>
|
||||
<component :is="item.icon" :class="['h-5 w-5', isActiveRoute(item.route) && 'stroke-[2.5]']" />
|
||||
<span class="text-xs font-medium">{{ item.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Safe area for notched devices (iPhone X, etc.) */
|
||||
.safe-area-bottom {
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
/* Prevent overscroll on iOS */
|
||||
main {
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* Active tab indicator animation */
|
||||
button {
|
||||
position: relative;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
/* Ripple effect for better touch feedback */
|
||||
button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export { default as Layout } from './Layout.vue'
|
||||
@ -1,25 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useColorMode } from '@vueuse/core'
|
||||
|
||||
const mode = useColorMode()
|
||||
const isDark = computed(() => mode.value === 'dark')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div
|
||||
v-if="isDark"
|
||||
class="absolute -inset-1 animate-pulse rounded-full bg-linear-to-r from-primary via-accent to-primary opacity-75 blur-xl"
|
||||
></div>
|
||||
<div
|
||||
class="relative flex h-24 w-24 items-center justify-center rounded-full bg-linear-to-br from-primary to-accent shadow-2xl ring-4 ring-background"
|
||||
>
|
||||
<img
|
||||
src="@/assets/imgs/neptune_logo.jpg"
|
||||
alt="Neptune Privacy"
|
||||
class="h-20 w-20 rounded-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1 +0,0 @@
|
||||
export { default as Logo } from './Logo.vue'
|
||||
@ -1,209 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useForm } from '@tanstack/vue-form'
|
||||
import { z } from 'zod'
|
||||
import { Eye, EyeOff, Lock, Loader2, ChevronLeft } from 'lucide-vue-next'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
|
||||
interface Props {
|
||||
label?: string
|
||||
placeholder?: string
|
||||
buttonText?: string
|
||||
hideBackButton?: boolean
|
||||
backButtonText?: string
|
||||
loading?: boolean
|
||||
error?: boolean
|
||||
errorMessage?: string
|
||||
validateFormat?: boolean
|
||||
autocomplete?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
label: 'Password',
|
||||
placeholder: 'Enter your password',
|
||||
buttonText: 'Submit',
|
||||
backButtonText: 'Back',
|
||||
hideBackButton: true,
|
||||
loading: false,
|
||||
error: false,
|
||||
errorMessage: 'Incorrect password. Please try again.',
|
||||
validateFormat: false,
|
||||
autocomplete: 'current-password',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [password: string]
|
||||
back: []
|
||||
}>()
|
||||
|
||||
const showPassword = ref(false)
|
||||
|
||||
// Password strength calculation
|
||||
const passwordStrength = computed(() => {
|
||||
const password = form.state.values?.password
|
||||
if (!password || !props.validateFormat) return { level: 0, text: '', color: '' }
|
||||
|
||||
let strength = 0
|
||||
const checks = {
|
||||
length: password.length >= 8,
|
||||
uppercase: /[A-Z]/.test(password),
|
||||
lowercase: /[a-z]/.test(password),
|
||||
number: /[0-9]/.test(password),
|
||||
special: /[!@#$%^&*(),.?":{}|<>]/.test(password),
|
||||
}
|
||||
strength = Object.values(checks).filter(Boolean).length
|
||||
|
||||
if (strength <= 2) return { level: 1, text: 'Weak', color: 'hsl(var(--destructive))' }
|
||||
if (strength <= 3) return { level: 2, text: 'Medium', color: 'hsl(48 96% 53%)' } // yellow
|
||||
if (strength <= 4) return { level: 3, text: 'Good', color: 'hsl(221 83% 53%)' } // blue
|
||||
return { level: 4, text: 'Strong', color: 'hsl(142 76% 36%)' } // green
|
||||
})
|
||||
|
||||
// Custom password schema with strength validation
|
||||
const createPasswordSchema = () => {
|
||||
let schema = z.string().min(1, 'Password is required')
|
||||
|
||||
if (props.validateFormat) {
|
||||
schema = schema
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.refine((val) => {
|
||||
const checks = {
|
||||
uppercase: /[A-Z]/.test(val),
|
||||
lowercase: /[a-z]/.test(val),
|
||||
number: /[0-9]/.test(val),
|
||||
}
|
||||
const strength = Object.values(checks).filter(Boolean).length
|
||||
return strength >= 2 // Medium level minimum
|
||||
}, 'Password is too weak. Use uppercase, lowercase, and numbers.')
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
password: '',
|
||||
},
|
||||
validators: {
|
||||
onChange: z.object({
|
||||
password: createPasswordSchema(),
|
||||
}),
|
||||
},
|
||||
onSubmit: async ({ value }) => {
|
||||
emit('submit', value.password)
|
||||
},
|
||||
})
|
||||
|
||||
const handleBack = () => {
|
||||
emit('back')
|
||||
}
|
||||
|
||||
function isInvalid(field: any) {
|
||||
return field.state.meta.isTouched && !field.state.meta.isValid
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="form.handleSubmit">
|
||||
<form.Field name="password">
|
||||
<template #default="{ field }">
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label :for="field.name" class="text-base">{{ label }}</Label>
|
||||
<div class="relative">
|
||||
<Lock
|
||||
:size="20"
|
||||
class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
|
||||
/>
|
||||
<Input
|
||||
:id="field.name"
|
||||
:name="field.name"
|
||||
v-model="field.state.value"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
:placeholder="placeholder"
|
||||
class="h-12 pl-11 pr-11 text-base"
|
||||
:class="{ 'border-destructive': error || isInvalid(field) }"
|
||||
:autocomplete="autocomplete"
|
||||
@blur="field.handleBlur"
|
||||
@input="(e: Event) => {
|
||||
field.handleChange((e.target as HTMLInputElement).value)
|
||||
}"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground transition-colors hover:text-foreground"
|
||||
@click="showPassword = !showPassword"
|
||||
>
|
||||
<Eye v-if="!showPassword" :size="20" />
|
||||
<EyeOff v-else :size="20" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<p v-if="error" class="text-sm text-destructive">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
|
||||
<!-- Validation Error from Zod -->
|
||||
<p v-else-if="isInvalid(field)" class="text-sm text-destructive">
|
||||
{{ field.state.meta.errors?.[0]?.message }}
|
||||
</p>
|
||||
|
||||
<!-- Password Strength Indicator -->
|
||||
<div v-if="validateFormat && field.state.value" class="space-y-2 pt-1">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="h-1 flex-1 overflow-hidden rounded-full bg-muted">
|
||||
<div
|
||||
class="h-full transition-all duration-300 ease-in-out"
|
||||
:style="{
|
||||
width: `${(passwordStrength.level / 4) * 100}%`,
|
||||
backgroundColor: passwordStrength.color,
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<span
|
||||
class="min-w-[60px] text-right text-xs font-semibold"
|
||||
:style="{ color: passwordStrength.color }"
|
||||
>
|
||||
{{ passwordStrength.text }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
Use at least 8 characters with uppercase, lowercase, and numbers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex flex-col gap-3">
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
class="h-12 w-full text-base font-semibold"
|
||||
:disabled="!field.state.value || loading"
|
||||
>
|
||||
<Loader2 v-if="loading" :size="20" class="mr-2 animate-spin" />
|
||||
{{ buttonText }}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
v-if="!hideBackButton"
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="lg"
|
||||
class="h-12 w-full text-base"
|
||||
@click="handleBack"
|
||||
>
|
||||
<ChevronLeft :size="20" class="mr-2" />
|
||||
{{ backButtonText }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</form.Field>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export { default as PasswordForm } from './PasswordForm.vue'
|
||||
@ -1,17 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import type { AlertVariants } from "."
|
||||
import { cn } from "@/lib/utils"
|
||||
import { alertVariants } from "."
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"]
|
||||
variant?: AlertVariants["variant"]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(alertVariants({ variant }), props.class)" role="alert">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@ -1,14 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('text-sm [&_p]:leading-relaxed', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@ -1,14 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h5 :class="cn('mb-1 font-medium leading-none tracking-tight', props.class)">
|
||||
<slot />
|
||||
</h5>
|
||||
</template>
|
||||
@ -1,24 +0,0 @@
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
export { default as Alert } from "./Alert.vue"
|
||||
export { default as AlertDescription } from "./AlertDescription.vue"
|
||||
export { default as AlertTitle } from "./AlertTitle.vue"
|
||||
|
||||
export const alertVariants = cva(
|
||||
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-background text-foreground",
|
||||
destructive:
|
||||
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export type AlertVariants = VariantProps<typeof alertVariants>
|
||||
@ -1,17 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import type { BadgeVariants } from "."
|
||||
import { cn } from "@/lib/utils"
|
||||
import { badgeVariants } from "."
|
||||
|
||||
const props = defineProps<{
|
||||
variant?: BadgeVariants["variant"]
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn(badgeVariants({ variant }), props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@ -1,26 +0,0 @@
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
export { default as Badge } from "./Badge.vue"
|
||||
|
||||
export const badgeVariants = cva(
|
||||
"inline-flex gap-1 items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
||||
@ -1,21 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'rounded-xl border bg-card text-card-foreground shadow',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@ -1,14 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('p-6 pt-0', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||