| … | … | @@ -0,0 +1,78 @@ |
| 1 | # USDT Contract Addresses | |
| 2 | ||
| 3 | ## Mainnet (Ethereum) | |
| 4 | - **Contract**: `0xdAC17F958D2ee523a2206206994597C13D831ec7` | |
| 5 | - **Decimals**: 6 | |
| 6 | - **Verified**: ✅ Official Tether USDT contract | |
| 7 | - **Explorer**: https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7 | |
| 8 | ||
| 9 | ## Testnet (Sepolia) | |
| 10 | - **Contract**: `0x7169D38820dfd117C3FA1f22a697dBA58d90BA06` | |
| 11 | - **Decimals**: 6 | |
| 12 | - **Status**: ⚠️ NEEDS VERIFICATION | |
| 13 | - **Explorer**: https://sepolia.etherscan.io/token/0x7169D38820dfd117C3FA1f22a697dBA58d90BA06 | |
| 14 | ||
| 15 | ### ⚠️ Important Notes for Testnet USDT: | |
| 16 | ||
| 17 | 1. **This is a test token**, not official Tether USDT | |
| 18 | 2. **You need to mint testnet USDT** to your address | |
| 19 | 3. **Check the contract exists** on Sepolia Etherscan | |
| 20 | 4. If contract doesn't work, alternative Sepolia USDT contracts: | |
| 21 | - Aave Faucet USDT: `0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0` | |
| 22 | - Other test USDT contracts can be found at: https://faucet.circle.com/ | |
| 23 | ||
| 24 | ### How to Get Testnet USDT: | |
| 25 | ||
| 26 | 1. **Get Sepolia ETH first** (for gas): | |
| 27 | - https://sepoliafaucet.com/ | |
| 28 | - https://www.alchemy.com/faucets/ethereum-sepolia | |
| 29 | ||
| 30 | 2. **Get test USDT**: | |
| 31 | - Use a testnet USDT faucet (if available) | |
| 32 | - OR deploy your own ERC-20 test token | |
| 33 | - OR ask in Discord/Telegram for test tokens | |
| 34 | ||
| 35 | ### Debugging Zero Balance: | |
| 36 | ||
| 37 | If balance shows as zero or inconsistent: | |
| 38 | ||
| 39 | 1. **Verify the contract address is correct**: | |
| 40 | ```bash | |
| 41 | # Check on Sepolia Etherscan | |
| 42 | https://sepolia.etherscan.io/token/0x7169D38820dfd117C3FA1f22a697dBA58d90BA06 | |
| 43 | ``` | |
| 44 | ||
| 45 | 2. **Verify you have tokens at your address**: | |
| 46 | ```bash | |
| 47 | # Use the debug script | |
| 48 | cd hyper-fleeky/hyper-wallet-desktop | |
| 49 | bare debug-usdt-balance.cjs <your-password> | |
| 50 | ``` | |
| 51 | ||
| 52 | 3. **Check if the contract is a valid ERC-20**: | |
| 53 | - Does it have `balanceOf(address)` function? | |
| 54 | - Does it have `decimals()` function? | |
| 55 | - Does it have `symbol()` function? | |
| 56 | ||
| 57 | 4. **Try alternative testnet contract**: | |
| 58 | - Update `lib/wdk-integration.cjs` with a different testnet address | |
| 59 | - Restart the wallet server | |
| 60 | ||
| 61 | ### Common Issues: | |
| 62 | ||
| 63 | #### Balance shows zero but you have tokens: | |
| 64 | - ✅ Fixed: Contract address checksum (mainnet) | |
| 65 | - ⚠️ Check: Wrong account index | |
| 66 | - ⚠️ Check: Wrong network (testnet vs mainnet) | |
| 67 | - ⚠️ Check: RPC provider issue (try different provider) | |
| 68 | ||
| 69 | #### Balance is inconsistent (sometimes 0, sometimes correct): | |
| 70 | - ⚠️ Network timeout during RPC call | |
| 71 | - ⚠️ RPC provider rate limiting | |
| 72 | - ⚠️ Caching issue in WDK | |
| 73 | - ✅ Fixed: Added detailed logging to track this | |
| 74 | ||
| 75 | #### Cannot see transactions: | |
| 76 | - ⚠️ Blockscout/Etherscan might not index testnet tokens immediately | |
| 77 | - ⚠️ Check if transaction actually succeeded on chain | |
| 78 | - ⚠️ Use manual sync button after sending/receiving |
| … | … | @@ -0,0 +1,124 @@ |
| 1 | #!/usr/bin/env bare | |
| 2 | ||
| 3 | // Debug script to test USDT balance loading | |
| 4 | require('bare-node-runtime/global') | |
| 5 | require('bare-node-runtime/imports') | |
| 6 | ||
| 7 | const { initializeWDK, getTokenInfo, getBaseChain } = require('./lib/wdk-integration.cjs') | |
| 8 | const { loadWalletMnemonic } = require('./lib/wdk-integration.cjs') | |
| 9 | const { loadIdentity } = require('./lib/wallet-storage.cjs') | |
| 10 | const { initializeStorage } = require('./lib/wallet-storage.cjs') | |
| 11 | const { getCurrentAddress } = require('./lib/wallet-storage.cjs') | |
| 12 | ||
| 13 | async function debugUSDTBalance() { | |
| 14 | console.log('🔍 USDT Balance Debug Script') | |
| 15 | console.log('=' .repeat(60)) | |
| 16 | ||
| 17 | try { | |
| 18 | // 1. Load wallet mnemonic | |
| 19 | console.log('\n1️⃣ Loading wallet mnemonic...') | |
| 20 | const password = process.argv[2] | |
| 21 | if (!password) { | |
| 22 | console.error('❌ Usage: bare debug-usdt-balance.cjs <password>') | |
| 23 | process.exit(1) | |
| 24 | } | |
| 25 | ||
| 26 | const walletMnemonic = loadWalletMnemonic(password) | |
| 27 | if (!walletMnemonic) { | |
| 28 | console.error('❌ Wallet mnemonic not found') | |
| 29 | process.exit(1) | |
| 30 | } | |
| 31 | console.log('✅ Wallet mnemonic loaded') | |
| 32 | ||
| 33 | // 2. Initialize storage | |
| 34 | console.log('\n2️⃣ Initializing storage...') | |
| 35 | const identity = await loadIdentity() | |
| 36 | await initializeStorage(identity) | |
| 37 | console.log('✅ Storage initialized') | |
| 38 | ||
| 39 | // 3. Get current address for USDT | |
| 40 | console.log('\n3️⃣ Getting current USDT address...') | |
| 41 | const chain = 'ethereum-usdt' | |
| 42 | const currentAddr = await getCurrentAddress(chain) | |
| 43 | const accountIndex = currentAddr ? currentAddr.index : 0 | |
| 44 | console.log(` Address index: ${accountIndex}`) | |
| 45 | if (currentAddr) { | |
| 46 | console.log(` Address: ${currentAddr.address}`) | |
| 47 | } | |
| 48 | ||
| 49 | // 4. Test both testnet and mainnet | |
| 50 | for (const network of ['testnet', 'mainnet']) { | |
| 51 | console.log(`\n4️⃣ Testing ${network.toUpperCase()}...`) | |
| 52 | console.log('-'.repeat(60)) | |
| 53 | ||
| 54 | try { | |
| 55 | // Get token info | |
| 56 | const tokenInfo = getTokenInfo(chain, network === 'testnet') | |
| 57 | console.log(` Token: ${tokenInfo.symbol}`) | |
| 58 | console.log(` Contract: ${tokenInfo.contractAddress}`) | |
| 59 | console.log(` Decimals: ${tokenInfo.decimals}`) | |
| 60 | ||
| 61 | // Initialize WDK | |
| 62 | console.log(`\n Initializing WDK for ${network}...`) | |
| 63 | const { wdk } = await initializeWDK(walletMnemonic, { testnet: network === 'testnet' }) | |
| 64 | console.log(' ✅ WDK initialized') | |
| 65 | ||
| 66 | // Get base chain account | |
| 67 | const baseChain = getBaseChain(chain) | |
| 68 | console.log(`\n Getting ${baseChain} account at index ${accountIndex}...`) | |
| 69 | const account = await wdk.getAccount(baseChain, accountIndex) | |
| 70 | console.log(' ✅ Account retrieved') | |
| 71 | ||
| 72 | // Get address | |
| 73 | const address = await account.getAddress() | |
| 74 | console.log(` Address: ${address}`) | |
| 75 | ||
| 76 | // Check if stored address matches | |
| 77 | if (currentAddr && currentAddr.address.toLowerCase() !== address.toLowerCase()) { | |
| 78 | console.warn(` ⚠️ WARNING: Stored address mismatch!`) | |
| 79 | console.warn(` Stored: ${currentAddr.address}`) | |
| 80 | console.warn(` Derived: ${address}`) | |
| 81 | } | |
| 82 | ||
| 83 | // Get token balance | |
| 84 | console.log(`\n Fetching USDT balance...`) | |
| 85 | console.log(` Calling: account.getTokenBalance("${tokenInfo.contractAddress}")`) | |
| 86 | ||
| 87 | const startTime = Date.now() | |
| 88 | const balance = await account.getTokenBalance(tokenInfo.contractAddress) | |
| 89 | const duration = Date.now() - startTime | |
| 90 | ||
| 91 | console.log(` ✅ Balance retrieved in ${duration}ms`) | |
| 92 | console.log(` Raw balance: ${balance.toString()}`) | |
| 93 | ||
| 94 | // Format balance | |
| 95 | const balanceDecimal = Number(balance) / Math.pow(10, tokenInfo.decimals) | |
| 96 | console.log(` Formatted: ${balanceDecimal.toFixed(2)} ${tokenInfo.symbol}`) | |
| 97 | ||
| 98 | // Also get ETH balance for gas | |
| 99 | console.log(`\n Checking ETH balance (for gas)...`) | |
| 100 | const ethBalance = await account.getBalance() | |
| 101 | const ethBalanceDecimal = Number(ethBalance) / 1e18 | |
| 102 | console.log(` ETH balance: ${ethBalanceDecimal.toFixed(6)} ETH`) | |
| 103 | ||
| 104 | if (ethBalance === 0n) { | |
| 105 | console.warn(` ⚠️ WARNING: No ETH for gas fees!`) | |
| 106 | } | |
| 107 | ||
| 108 | } catch (err) { | |
| 109 | console.error(` ❌ ${network} failed:`, err.message) | |
| 110 | console.error(` Stack:`, err.stack) | |
| 111 | } | |
| 112 | } | |
| 113 | ||
| 114 | console.log('\n' + '='.repeat(60)) | |
| 115 | console.log('✅ Debug complete') | |
| 116 | ||
| 117 | } catch (err) { | |
| 118 | console.error('\n❌ Fatal error:', err.message) | |
| 119 | console.error('Stack:', err.stack) | |
| 120 | process.exit(1) | |
| 121 | } | |
| 122 | } | |
| 123 | ||
| 124 | debugUSDTBalance() |
| … | … | @@ -0,0 +1,42 @@ |
| 1 | #!/usr/bin/env bash | |
| 2 | # DEPLOYMENT SCRIPT FOR HYPER-WALLET-DESKTOP | |
| 3 | # This ensures we ALWAYS deploy to the correct channel | |
| 4 | ||
| 5 | set -e | |
| 6 | ||
| 7 | # ⚠️ CRITICAL: Always use the channel name, NOT the key | |
| 8 | CHANNEL="hyper-wallet-desktop" | |
| 9 | ||
| 10 | echo "🍐 Hyper Wallet Desktop Deployment" | |
| 11 | echo "==================================" | |
| 12 | echo "" | |
| 13 | echo "Channel: $CHANNEL" | |
| 14 | echo "" | |
| 15 | ||
| 16 | # Confirm deployment | |
| 17 | read -p "Deploy to $CHANNEL? (y/N): " confirm | |
| 18 | if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then | |
| 19 | echo "❌ Deployment cancelled" | |
| 20 | exit 1 | |
| 21 | fi | |
| 22 | ||
| 23 | echo "" | |
| 24 | echo "📦 Staging changes..." | |
| 25 | pear stage $CHANNEL . | |
| 26 | ||
| 27 | echo "" | |
| 28 | echo "🚀 Releasing..." | |
| 29 | pear release $CHANNEL | |
| 30 | ||
| 31 | echo "" | |
| 32 | echo "📡 Updating seeder in tmux..." | |
| 33 | echo "" | |
| 34 | echo "To restart the seeder in tmux, run:" | |
| 35 | echo " cd /storage/projects/code/hypercore-kb/hyper-fleeky" | |
| 36 | echo " ./scripts/restage-seed.sh hyper-wallet-desktop" | |
| 37 | echo "" | |
| 38 | echo "Or manually update pear-seed:5:" | |
| 39 | echo " tmux attach -t pear-seed" | |
| 40 | echo " (Navigate to window 5)" | |
| 41 | echo " Ctrl+C to stop seeder" | |
| 42 | echo " pear seed $CHANNEL ." |
| … | … | @@ -1502,6 +1502,7 @@ const SUPPORTED_CHAINS = { |
| 1502 | 1502 | icon: '₮', |
| 1503 | 1503 | testnetName: 'Sepolia', |
| 1504 | 1504 | mainnetName: 'Mainnet', |
| 1505 | decimals: 6, // USDT has 6 decimals | |
| 1505 | 1506 | formatBalance: (balance) => { |
| 1506 | 1507 | const usdt = Number(balance) / 1e6 // USDT has 6 decimals |
| 1507 | 1508 | return `${usdt.toFixed(2)} USDT` |
| … | … | @@ -1676,7 +1677,12 @@ async function loadCurrentBalance() { |
| 1676 | 1677 | element.innerHTML = '<div class="spinner"></div>' |
| 1677 | 1678 | |
| 1678 | 1679 | try { |
| 1679 | const res = await authFetch(`/api/balance/${selectedNetwork}-${selectedCurrency}`) | |
| 1680 | // Get currently selected address index | |
| 1681 | const select = document.getElementById('wallet-address-select') | |
| 1682 | const rawIndex = select ? parseInt(select.value) : 0 | |
| 1683 | const accountIndex = isNaN(rawIndex) ? 0 : rawIndex | |
| 1684 | ||
| 1685 | const res = await authFetch(`/api/balance/${selectedNetwork}-${selectedCurrency}?accountIndex=${accountIndex}`) | |
| 1680 | 1686 | const data = await res.json() |
| 1681 | 1687 | |
| 1682 | 1688 | if (data.error) { |
| … | … | @@ -1698,8 +1704,15 @@ async function loadCurrentBalance() { |
| 1698 | 1704 | if (selectedNetwork === 'mainnet' && pricesLoaded) { |
| 1699 | 1705 | const price = prices[chain.priceId] |
| 1700 | 1706 | if (price && price > 0) { |
| 1701 | // Convert to base unit (varies by chain) | |
| 1702 | const divisor = selectedCurrency === 'bitcoin' ? 100000000 : 1e18 | |
| 1707 | // Convert to base unit (varies by chain - respect token decimals) | |
| 1708 | let divisor | |
| 1709 | if (selectedCurrency === 'bitcoin') { | |
| 1710 | divisor = 100000000 // 8 decimals | |
| 1711 | } else if (chain.decimals) { | |
| 1712 | divisor = Math.pow(10, chain.decimals) // Use chain-specific decimals (e.g., 6 for USDT) | |
| 1713 | } else { | |
| 1714 | divisor = 1e18 // Default to 18 for ETH | |
| 1715 | } | |
| 1703 | 1716 | const baseAmount = Number(data.balance) / divisor |
| 1704 | 1717 | const usdValue = baseAmount * price |
| 1705 | 1718 | usdHTML = `<div class="balance-usd">${formatUSD(usdValue)}</div>` |
| … | … | @@ -1892,14 +1905,23 @@ async function syncTransactions() { |
| 1892 | 1905 | syncBtn.disabled = true |
| 1893 | 1906 | syncBtn.textContent = '🔄 Syncing...' |
| 1894 | 1907 | |
| 1895 | // Sync current currency chain | |
| 1908 | // Get currently selected address index | |
| 1909 | const select = document.getElementById('wallet-address-select') | |
| 1910 | const rawIndex = select ? parseInt(select.value) : 0 | |
| 1911 | const accountIndex = isNaN(rawIndex) ? 0 : rawIndex | |
| 1912 | ||
| 1913 | // Sync current currency chain with selected account index | |
| 1896 | 1914 | const chain = selectedCurrency |
| 1897 | 1915 | const res = await authFetch(`/api/transactions/sync`, { |
| 1898 | 1916 | method: 'POST', |
| 1899 | 1917 | headers: { |
| 1900 | 1918 | 'Content-Type': 'application/json' |
| 1901 | 1919 | }, |
| 1902 | body: JSON.stringify({ chain }) | |
| 1920 | body: JSON.stringify({ | |
| 1921 | chain, | |
| 1922 | network: selectedNetwork, // Add network parameter | |
| 1923 | accountIndex | |
| 1924 | }) | |
| 1903 | 1925 | }) |
| 1904 | 1926 | |
| 1905 | 1927 | const data = await res.json() |
| … | … | @@ -2597,9 +2619,17 @@ async function showSend(chain) { |
| 2597 | 2619 | const data = await res.json() |
| 2598 | 2620 | currentBalance = Number(data.balance) |
| 2599 | 2621 | |
| 2600 | const unit = chain === 'bitcoin' ? 'sats' : 'wei' | |
| 2601 | document.getElementById('send-amount-helper').textContent = | |
| 2602 | `Balance: ${currentBalance.toLocaleString()} ${unit}` | |
| 2622 | // Format balance display based on chain type | |
| 2623 | const chainConfig = SUPPORTED_CHAINS[chain] | |
| 2624 | let balanceDisplay | |
| 2625 | if (chainConfig && chainConfig.formatBalance) { | |
| 2626 | balanceDisplay = chainConfig.formatBalance(currentBalance) | |
| 2627 | } else { | |
| 2628 | const unit = chain === 'bitcoin' ? 'sats' : 'wei' | |
| 2629 | balanceDisplay = `${currentBalance.toLocaleString()} ${unit}` | |
| 2630 | } | |
| 2631 | ||
| 2632 | document.getElementById('send-amount-helper').textContent = `Balance: ${balanceDisplay}` | |
| 2603 | 2633 | } catch (err) { |
| 2604 | 2634 | console.error('Failed to load balance:', err) |
| 2605 | 2635 | document.getElementById('send-amount-helper').textContent = 'Balance: unavailable' |
| … | … | @@ -2819,9 +2849,27 @@ function confirmSend() { |
| 2819 | 2849 | if (!amount || isNaN(amount) || Number(amount) <= 0) { |
| 2820 | 2850 | document.getElementById('send-amount-error').textContent = 'Invalid amount' |
| 2821 | 2851 | hasError = true |
| 2822 | } else if (Number(amount) > currentBalance) { | |
| 2823 | document.getElementById('send-amount-error').textContent = 'Insufficient balance' | |
| 2824 | hasError = true | |
| 2852 | } else { | |
| 2853 | // Check balance based on chain type | |
| 2854 | const chain = SUPPORTED_CHAINS[currentSendChain] | |
| 2855 | let hasInsufficientBalance = false | |
| 2856 | ||
| 2857 | if (chain && chain.isToken) { | |
| 2858 | // For tokens, currentBalance is in smallest unit (e.g., 1 USDT = 1000000) | |
| 2859 | // User enters decimal amount (e.g., "1.5") | |
| 2860 | // Need to convert user input to smallest unit for comparison | |
| 2861 | const decimals = chain.decimals || 6 // USDT has 6 decimals | |
| 2862 | const amountInSmallestUnit = Number(amount) * Math.pow(10, decimals) | |
| 2863 | hasInsufficientBalance = amountInSmallestUnit > currentBalance | |
| 2864 | } else { | |
| 2865 | // For native tokens (BTC/ETH), balance and amount are both in smallest unit | |
| 2866 | hasInsufficientBalance = Number(amount) > currentBalance | |
| 2867 | } | |
| 2868 | ||
| 2869 | if (hasInsufficientBalance) { | |
| 2870 | document.getElementById('send-amount-error').textContent = 'Insufficient balance' | |
| 2871 | hasError = true | |
| 2872 | } | |
| 2825 | 2873 | } |
| 2826 | 2874 | |
| 2827 | 2875 | if (hasError) return |
| … | … | @@ -2839,12 +2887,47 @@ function confirmSend() { |
| 2839 | 2887 | const confirmModal = document.getElementById('confirm-modal') |
| 2840 | 2888 | const confirmContent = document.getElementById('confirm-content') |
| 2841 | 2889 | |
| 2842 | const amountFormatted = currentSendChain === 'bitcoin' | |
| 2843 | ? formatBTC(amount) | |
| 2844 | : formatETH(amount) | |
| 2845 | const feeFormatted = currentSendChain === 'bitcoin' | |
| 2846 | ? formatSats(estimatedFee) | |
| 2847 | : formatGwei(estimatedFee) | |
| 2890 | // Get chain configuration | |
| 2891 | const chain = SUPPORTED_CHAINS[currentSendChain] | |
| 2892 | ||
| 2893 | // Format amount based on chain type | |
| 2894 | let amountFormatted | |
| 2895 | if (chain && chain.isToken) { | |
| 2896 | // For tokens (like USDT), use the token's format function | |
| 2897 | // Convert user decimal input to smallest unit first | |
| 2898 | const decimals = chain.decimals || 6 | |
| 2899 | const amountInSmallestUnit = Number(amount) * Math.pow(10, decimals) | |
| 2900 | amountFormatted = chain.formatBalance ? chain.formatBalance(amountInSmallestUnit) : `${amount} ${chain.icon || ''}` | |
| 2901 | } else if (currentSendChain === 'bitcoin') { | |
| 2902 | amountFormatted = formatBTC(amount) | |
| 2903 | } else { | |
| 2904 | amountFormatted = formatETH(amount) | |
| 2905 | } | |
| 2906 | ||
| 2907 | // Format fee based on base chain (tokens use base chain for fees) | |
| 2908 | let feeFormatted | |
| 2909 | let feeChain = currentSendChain | |
| 2910 | if (chain && chain.baseChain) { | |
| 2911 | feeChain = chain.baseChain // Tokens use base chain for fees (e.g., ETH for USDT) | |
| 2912 | } | |
| 2913 | ||
| 2914 | if (feeChain === 'bitcoin') { | |
| 2915 | feeFormatted = formatSats(estimatedFee) | |
| 2916 | } else { | |
| 2917 | feeFormatted = formatGwei(estimatedFee) | |
| 2918 | } | |
| 2919 | ||
| 2920 | // Calculate total display | |
| 2921 | let totalDisplay | |
| 2922 | if (chain && chain.isToken) { | |
| 2923 | // For tokens, can't add token amount + base chain fee (different units!) | |
| 2924 | // Show separately | |
| 2925 | totalDisplay = `${amountFormatted} + ${feeFormatted} gas` | |
| 2926 | } else if (currentSendChain === 'bitcoin') { | |
| 2927 | totalDisplay = `${(Number(amount) + Number(estimatedFee)).toLocaleString()} sats` | |
| 2928 | } else { | |
| 2929 | totalDisplay = `${(Number(amount) + Number(estimatedFee)).toLocaleString()} wei` | |
| 2930 | } | |
| 2848 | 2931 | |
| 2849 | 2932 | const networkWarning = selectedNetwork === 'mainnet' |
| 2850 | 2933 | ? '<div class="confirm-warning">⚠️ WARNING: You are sending on MAINNET. This will use real funds!</div>' |
| … | … | @@ -2854,7 +2937,7 @@ function confirmSend() { |
| 2854 | 2937 | ${networkWarning} |
| 2855 | 2938 | <div class="confirm-row"> |
| 2856 | 2939 | <span class="confirm-label">Chain:</span> |
| 2857 | <span class="confirm-value">${currentSendChain.toUpperCase()} (${selectedNetwork})</span> | |
| 2940 | <span class="confirm-value">${chain ? chain.name : currentSendChain.toUpperCase()} (${selectedNetwork})</span> | |
| 2858 | 2941 | </div> |
| 2859 | 2942 | <div class="confirm-row"> |
| 2860 | 2943 | <span class="confirm-label">To:</span> |
| … | … | @@ -2870,7 +2953,7 @@ function confirmSend() { |
| 2870 | 2953 | </div> |
| 2871 | 2954 | <div class="confirm-row"> |
| 2872 | 2955 | <span class="confirm-label">Total:</span> |
| 2873 | <span class="confirm-value">${Number(amount) + Number(estimatedFee)} ${currentSendChain === 'bitcoin' ? 'sats' : 'wei'}</span> | |
| 2956 | <span class="confirm-value">${totalDisplay}</span> | |
| 2874 | 2957 | </div> |
| 2875 | 2958 | ` |
| 2876 | 2959 | |
| … | … | @@ -2933,10 +3016,19 @@ async function executeSend() { |
| 2933 | 3016 | setTimeout(async () => { |
| 2934 | 3017 | // First sync from blockchain to fetch the new transaction |
| 2935 | 3018 | try { |
| 3019 | // Get current account index for sync | |
| 3020 | const select = document.getElementById('wallet-address-select') | |
| 3021 | const rawIndex = select ? parseInt(select.value) : 0 | |
| 3022 | const accountIndex = isNaN(rawIndex) ? 0 : rawIndex | |
| 3023 | ||
| 2936 | 3024 | const syncRes = await authFetch(`/api/transactions/sync`, { |
| 2937 | 3025 | method: 'POST', |
| 2938 | 3026 | headers: { 'Content-Type': 'application/json' }, |
| 2939 | body: JSON.stringify({ chain: currentSendChain }) | |
| 3027 | body: JSON.stringify({ | |
| 3028 | chain: currentSendChain, | |
| 3029 | network: selectedNetwork, // Add network parameter | |
| 3030 | accountIndex | |
| 3031 | }) | |
| 2940 | 3032 | }) |
| 2941 | 3033 | const syncData = await syncRes.json() |
| 2942 | 3034 | console.log('Auto-sync after send:', syncData) |
| … | … | @@ -381,8 +381,9 @@ const routes = { |
| 381 | 381 | }) |
| 382 | 382 | }, |
| 383 | 383 | |
| 384 | // GET /api/balance/:chain (protected) | |
| 384 | // GET /api/balance/:chain?accountIndex=N (protected) | |
| 385 | 385 | // Supports both native tokens (bitcoin, ethereum) and ERC-20 tokens (ethereum-usdt) |
| 386 | // WDK design: Application must explicitly specify which account to query | |
| 386 | 387 | 'GET /api/balance/': requireAuth(async (req, res, chain) => { |
| 387 | 388 | try { |
| 388 | 389 | const walletMnemonic = loadWalletMnemonicCached() |
| … | … | @@ -390,27 +391,52 @@ const routes = { |
| 390 | 391 | return sendError(res, 'Wallet not initialized', 400) |
| 391 | 392 | } |
| 392 | 393 | |
| 393 | const network = chain.startsWith('testnet') ? 'testnet' : 'mainnet' | |
| 394 | const chainParam = chain.replace(/^(testnet|mainnet)-/, '') | |
| 394 | // Strip query string from chain parameter (route parsing includes it) | |
| 395 | const chainPath = chain.split('?')[0] | |
| 395 | 396 | |
| 396 | // Get current address index from storage (defaults to 0) | |
| 397 | const identity = await loadIdentity() | |
| 398 | await initializeStorage(identity) | |
| 397 | const network = chainPath.startsWith('testnet') ? 'testnet' : 'mainnet' | |
| 398 | const chainParam = chainPath.replace(/^(testnet|mainnet)-/, '') | |
| 399 | ||
| 400 | // Parse accountIndex from query string (e.g., ?accountIndex=2) | |
| 401 | const url = new URL(req.url, `http://${req.headers.host}`) | |
| 402 | const accountIndexParam = url.searchParams.get('accountIndex') | |
| 403 | const accountIndex = accountIndexParam !== null ? parseInt(accountIndexParam) : 0 | |
| 404 | ||
| 405 | console.log(`[BALANCE] chain=${chainParam}, accountIndex=${accountIndex}`) | |
| 399 | 406 | |
| 400 | 407 | // Check if this is a token balance request (e.g., 'ethereum-usdt') |
| 401 | 408 | if (isTokenChain(chainParam)) { |
| 409 | console.log(`[BALANCE] Token balance request:`) | |
| 410 | console.log(` Chain: ${chainParam}`) | |
| 411 | console.log(` Network: ${network}`) | |
| 412 | console.log(` Account Index: ${accountIndex}`) | |
| 413 | ||
| 402 | 414 | const tokenInfo = getTokenInfo(chainParam, network === 'testnet') |
| 403 | const baseChain = getBaseChain(chainParam) | |
| 415 | console.log(` Token: ${tokenInfo.symbol}`) | |
| 416 | console.log(` Contract: ${tokenInfo.contractAddress}`) | |
| 417 | console.log(` Decimals: ${tokenInfo.decimals}`) | |
| 404 | 418 | |
| 405 | // FIX: Use chainParam (ethereum-usdt) not baseChain (ethereum) for storage lookup | |
| 406 | const currentAddr = await getCurrentAddress(chainParam) | |
| 407 | const accountIndex = currentAddr ? currentAddr.index : 0 | |
| 419 | const baseChain = getBaseChain(chainParam) | |
| 420 | console.log(` Base Chain: ${baseChain}`) | |
| 408 | 421 | |
| 409 | 422 | const { wdk } = await initializeWDK(walletMnemonic, { testnet: network === 'testnet' }) |
| 423 | console.log(` ✅ WDK initialized`) | |
| 424 | ||
| 410 | 425 | const account = await wdk.getAccount(baseChain, accountIndex) |
| 426 | console.log(` ✅ Account retrieved`) | |
| 427 | ||
| 428 | const address = await account.getAddress() | |
| 429 | console.log(` Address: ${address}`) | |
| 411 | 430 | |
| 412 | 431 | // Get token balance using contract address |
| 432 | console.log(` Calling getTokenBalance...`) | |
| 433 | const startTime = Date.now() | |
| 413 | 434 | const balance = await account.getTokenBalance(tokenInfo.contractAddress) |
| 435 | const duration = Date.now() - startTime | |
| 436 | console.log(` ✅ Balance retrieved in ${duration}ms: ${balance.toString()}`) | |
| 437 | ||
| 438 | const balanceFormatted = (Number(balance) / Math.pow(10, tokenInfo.decimals)).toFixed(2) | |
| 439 | console.log(` Formatted: ${balanceFormatted} ${tokenInfo.symbol}`) | |
| 414 | 440 | |
| 415 | 441 | sendJSON(res, { |
| 416 | 442 | chain: chainParam, |
| … | … | @@ -421,10 +447,7 @@ const routes = { |
| 421 | 447 | isToken: true |
| 422 | 448 | }) |
| 423 | 449 | } else { |
| 424 | // Native token balance (existing code) | |
| 425 | const currentAddr = await getCurrentAddress(chainParam) | |
| 426 | const accountIndex = currentAddr ? currentAddr.index : 0 | |
| 427 | ||
| 450 | // Native token balance | |
| 428 | 451 | const { wdk } = await initializeWDK(walletMnemonic, { testnet: network === 'testnet' }) |
| 429 | 452 | const account = await wdk.getAccount(chainParam, accountIndex) |
| 430 | 453 | const balance = await account.getBalance() |
| … | … | @@ -717,6 +740,17 @@ const routes = { |
| 717 | 740 | const chainFilter = chain === 'all' ? 'all' : chain.replace(/^(testnet|mainnet)-/, '') |
| 718 | 741 | const transactions = await getTransactions(chainFilter) |
| 719 | 742 | |
| 743 | // Debug: Log ethereum-usdt transactions | |
| 744 | const usdtTxs = transactions.filter(tx => tx.chain === 'ethereum-usdt') | |
| 745 | if (usdtTxs.length > 0) { | |
| 746 | console.log(` 💰 Found ${usdtTxs.length} USDT transactions:`) | |
| 747 | usdtTxs.forEach(tx => { | |
| 748 | console.log(` ${tx.hash.substring(0, 16)}... - ${tx.direction} - ${tx.amount}`) | |
| 749 | }) | |
| 750 | } else { | |
| 751 | console.log(` ⚠️ No ethereum-usdt transactions found in database`) | |
| 752 | } | |
| 753 | ||
| 720 | 754 | // Note: Storage remains open (persistent daemon mode) |
| 721 | 755 | |
| 722 | 756 | sendJSON(res, { |
| … | … | @@ -746,7 +780,7 @@ const routes = { |
| 746 | 780 | 'POST /api/transactions/sync': withRateLimit(requireAuth(async (req, res) => { |
| 747 | 781 | try { |
| 748 | 782 | const body = await parseBody(req) |
| 749 | const { chain } = body | |
| 783 | const { chain, network, accountIndex } = body | |
| 750 | 784 | |
| 751 | 785 | if (!chain) { |
| 752 | 786 | return sendError(res, 'Missing required field: chain', 400) |
| … | … | @@ -761,20 +795,27 @@ const routes = { |
| 761 | 795 | return sendError(res, 'Wallet not initialized', 400) |
| 762 | 796 | } |
| 763 | 797 | |
| 764 | const { initializeWDK } = require('./lib/wdk-integration.cjs') | |
| 765 | const { wdk } = await initializeWDK(walletMnemonic, { testnet: true }) | |
| 798 | // Determine network - use explicit parameter if provided, otherwise try to extract from chain | |
| 799 | let finalNetwork = network | |
| 800 | if (!finalNetwork) { | |
| 801 | const networkMatch = chain.match(/^(testnet|mainnet)-/) | |
| 802 | finalNetwork = networkMatch ? networkMatch[1] : 'testnet' | |
| 803 | } | |
| 766 | 804 | |
| 767 | // Sync transactions | |
| 768 | // Extract network from chain (testnet-ethereum-usdt -> testnet) | |
| 769 | const networkMatch = chain.match(/^(testnet|mainnet)-/) | |
| 770 | const network = networkMatch ? networkMatch[1] : 'testnet' | |
| 805 | // Strip network prefix from chain if present (testnet-ethereum-usdt -> ethereum-usdt) | |
| 771 | 806 | const chainName = chain.replace(/^(testnet|mainnet)-/, '') |
| 772 | 807 | |
| 773 | console.log(`[SYNC] Syncing chain: ${chain} -> ${chainName}, network: ${network}`) | |
| 808 | // Use explicit account index (WDK design pattern) | |
| 809 | const finalAccountIndex = accountIndex !== undefined ? accountIndex : 0 | |
| 810 | console.log(`[SYNC] chain=${chainName}, network=${finalNetwork}, accountIndex=${finalAccountIndex}`) | |
| 811 | ||
| 812 | const { initializeWDK } = require('./lib/wdk-integration.cjs') | |
| 813 | const { wdk } = await initializeWDK(walletMnemonic, { testnet: finalNetwork === 'testnet' }) | |
| 774 | 814 | |
| 775 | 815 | const result = await syncTransactionsFromWDK(wdk, { |
| 776 | 816 | chains: [chainName], |
| 777 | network: network | |
| 817 | network: finalNetwork, | |
| 818 | accountIndex: finalAccountIndex // Pass explicit account index | |
| 778 | 819 | }) |
| 779 | 820 | |
| 780 | 821 | console.log(`[SYNC] Result: synced=${result.synced}, errors=${result.errors.length}`) |