1790 lines
85 KiB
HTML
1790 lines
85 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Algorand Wallet - RanchiMall</title>
|
|
<link rel="icon" type="image/png" sizes="32x32" href="algo_favicon.png?v=1">
|
|
|
|
<link rel="stylesheet" href="style.css">
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
|
</head>
|
|
<body>
|
|
<!-- Header -->
|
|
<header class="header">
|
|
<div class="header-content">
|
|
<div id="logo" class="app-brand">
|
|
<svg id="main_logo" class="icon" viewBox="0 0 27.25 32">
|
|
<title>RanchiMall</title>
|
|
<path
|
|
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.80,2.33q-.18.34-.39.69Z"
|
|
/>
|
|
</svg>
|
|
<div class="app-name">
|
|
<div class="app-name__company">RanchiMall</div>
|
|
<h4 class="app-name__title">Algorand Wallet</h4>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="header-actions">
|
|
<button class="theme-toggle" id="themeToggle" title="Toggle theme">
|
|
<i class="fas fa-moon" id="themeIcon"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Sidebar Overlay (for mobile) -->
|
|
<div id="sidebarOverlay" class="sidebar-overlay"></div>
|
|
|
|
<!-- Sidebar Navigation (Desktop) -->
|
|
<aside id="sidebar" class="sidebar">
|
|
<ul class="sidebar-menu">
|
|
<li>
|
|
<a href="#" class="nav-link active" data-page="generate">
|
|
<i class="fas fa-plus-circle"></i>
|
|
<span>Generate</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="#" class="nav-link" data-page="transactions">
|
|
<i class="fas fa-history"></i>
|
|
<span>Transactions</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="#" class="nav-link" data-page="send">
|
|
<i class="fas fa-paper-plane"></i>
|
|
<span>Send</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="#" class="nav-link" data-page="recover">
|
|
<i class="fas fa-key"></i>
|
|
<span>Recover</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</aside>
|
|
|
|
<div class="container">
|
|
<main class="main-content">
|
|
<!-- Generate Page -->
|
|
<div id="generate-tab" class="page tab-content active">
|
|
<div class="page-header">
|
|
<h2><i class="fas fa-wallet"></i> Generate Multi-Blockchain Addresses</h2>
|
|
<p>Generate addresses for BTC, FLO, ALGO from a single private key</p>
|
|
</div>
|
|
|
|
<div class="generate-wallet-intro">
|
|
<div class="intro-icon">
|
|
<i class="fas fa-wallet"></i>
|
|
</div>
|
|
<div class="intro-content">
|
|
<h3>One Key, Multiple Blockchains</h3>
|
|
<p>Generate a single private key that works across BTC, FLO, ALGO networks. This creates a unified experience across multiple blockchains.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card generate-actions">
|
|
<button id="generateBtn" class="btn btn-primary btn-block" onclick="generateWallet()">
|
|
<i class="fas fa-wallet"></i> Generate
|
|
</button>
|
|
</div>
|
|
|
|
<div id="generate-results" class="output" style="display: none;">
|
|
<!-- ALGO Section -->
|
|
<div class="blockchain-section algo-primary">
|
|
<div class="blockchain-header">
|
|
<h4><svg class="chain-icon-svg" viewBox="0 0 507.56 509.36" fill="currentColor"><polygon points="88.04 509.36 161.7 381.8 235.37 254.68 308.58 127.12 320.71 106.9 326.1 127.12 348.56 211.11 323.4 254.68 249.74 381.8 176.53 509.36 264.56 509.36 338.23 381.8 376.41 315.77 394.37 381.8 428.51 509.36 507.56 509.36 473.43 381.8 439.29 254.68 430.31 221.89 485.11 127.12 405.15 127.12 402.46 117.68 374.61 13.47 371.02 0 294.21 0 292.41 2.69 220.54 127.12 146.88 254.68 73.66 381.8 0 509.36 88.04 509.36"/></svg> ALGO</h4>
|
|
<span class="blockchain-badge primary">Primary</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>Address</label>
|
|
<div class="detail-value-wrapper">
|
|
<code id="algo-address" class="detail-value">-</code>
|
|
<button class="input-action-btn clear-btn" onclick="copyToClipboard('algo-address')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>Private Key</label>
|
|
<div class="input-with-actions">
|
|
<input type="password" id="algo-privateKey" class="form-input" value="-" readonly />
|
|
<button type="button" class="input-action-btn password-toggle" onclick="toggleVisibility('algo-privateKey')">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button type="button" class="input-action-btn clear-btn" onclick="copyPrivateKey('algo-privateKey')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FLO Section -->
|
|
<div class="blockchain-section">
|
|
<div class="blockchain-header">
|
|
<h4><i class="fas fa-spa"></i> FLO</h4>
|
|
<span class="blockchain-badge secondary">Secondary</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>Address</label>
|
|
<div class="detail-value-wrapper">
|
|
<code id="flo-address" class="detail-value">-</code>
|
|
<button class="input-action-btn clear-btn" onclick="copyToClipboard('flo-address')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>Private Key</label>
|
|
<div class="input-with-actions">
|
|
<input type="password" id="flo-privateKey" class="form-input" value="-" readonly />
|
|
<button type="button" class="input-action-btn password-toggle" onclick="toggleVisibility('flo-privateKey')">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button type="button" class="input-action-btn clear-btn" onclick="copyPrivateKey('flo-privateKey')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- BTC Section -->
|
|
<div class="blockchain-section">
|
|
<div class="blockchain-header">
|
|
<h4><i class="fab fa-bitcoin"></i> BTC</h4>
|
|
<span class="blockchain-badge secondary">Secondary</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>Address</label>
|
|
<div class="detail-value-wrapper">
|
|
<code id="btc-address" class="detail-value">-</code>
|
|
<button class="input-action-btn clear-btn" onclick="copyToClipboard('btc-address')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>Private Key</label>
|
|
<div class="input-with-actions">
|
|
<input type="password" id="btc-privateKey" class="form-input" value="-" readonly />
|
|
<button type="button" class="input-action-btn password-toggle" onclick="toggleVisibility('btc-privateKey')">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button type="button" class="input-action-btn clear-btn" onclick="copyPrivateKey('btc-privateKey')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wallet-security-notice">
|
|
<div class="notice-icon">
|
|
<i class="fas fa-shield-alt"></i>
|
|
</div>
|
|
<div class="notice-content">
|
|
<h4>Security Notice</h4>
|
|
<p>Your private keys are generated locally and never leave your device. Make sure to backup your keys securely before using.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transactions Page -->
|
|
<div id="transactions-tab" class="page tab-content hidden">
|
|
<div class="page-header">
|
|
<h2><i class="fas fa-exchange-alt"></i> ALGO Transactions</h2>
|
|
<p>Check balance and transaction history for any ALGO address</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<!-- Search Type Selector -->
|
|
<div class="search-type-selector">
|
|
<div class="search-type-label">Search Type</div>
|
|
<div class="search-type-options">
|
|
<label class="radio-button-container active" id="addressSearchType">
|
|
<input type="radio" name="searchType" value="address" checked />
|
|
<div class="radio-icon"><i class="fas fa-wallet"></i></div>
|
|
<span>ALGO Address</span>
|
|
</label>
|
|
<label class="radio-button-container" id="hashSearchType">
|
|
<input type="radio" name="searchType" value="hash" />
|
|
<div class="radio-icon"><i class="fas fa-fingerprint"></i></div>
|
|
<span>Transaction Hash</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Address Search Section -->
|
|
<div id="address-search" class="search-section">
|
|
<div class="form-group">
|
|
<label for="tx-search-input"><i class="fas fa-wallet"></i> ALGO Address or Private Key</label>
|
|
<div class="input-with-actions">
|
|
<input type="text" id="tx-search-input" class="form-input" placeholder="Enter ALGO address or private key (WIF/hex)" />
|
|
<button type="button" class="input-action-btn clear-btn" onclick="clearInput('tx-search-input')">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="form-text">Enter an ALGO address to view transactions, or use BTC/FLO/ALGO private key to derive address</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transaction Hash Search Section -->
|
|
<div id="txhash-search" class="search-section" style="display: none;">
|
|
<div class="form-group">
|
|
<label for="txhash-input"><i class="fas fa-fingerprint"></i> Transaction ID</label>
|
|
<div class="input-with-actions">
|
|
<input type="text" id="txhash-input" class="form-input" placeholder="Enter transaction ID/hash" />
|
|
<button type="button" class="input-action-btn clear-btn" onclick="clearInput('txhash-input')">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="btn btn-primary btn-block" onclick="handleSearch()" id="searchBtn">
|
|
<i class="fas fa-search"></i> Search
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Address Results - Balance Display -->
|
|
<div id="tx-results" class="balance-info card" style="display: none;">
|
|
<div class="balance-header">
|
|
<h3><i class="fas fa-wallet"></i> Balance</h3>
|
|
</div>
|
|
<div class="balance-display">
|
|
<div class="balance-amount" id="algo-balance">0 <span class="currency">ALGO</span></div>
|
|
</div>
|
|
<div class="address-display">
|
|
<span class="address-label">Address:</span>
|
|
<span class="address-value" id="algo-display-address">-</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transaction History Section -->
|
|
<div id="transactionFilterSection" class="transaction-section" style="display: none;">
|
|
<div class="transaction-header">
|
|
<h3>Transactions</h3>
|
|
<div class="transaction-filters">
|
|
<button class="filter-btn active" data-filter="all" onclick="filterTransactions('all')" title="All Transactions">
|
|
<i class="fas fa-exchange-alt"></i>
|
|
<span class="filter-text">All</span>
|
|
</button>
|
|
<button class="filter-btn" data-filter="received" onclick="filterTransactions('received')" title="Received">
|
|
<i class="fas fa-arrow-down"></i>
|
|
<span class="filter-text">Received</span>
|
|
</button>
|
|
<button class="filter-btn" data-filter="sent" onclick="filterTransactions('sent')" title="Sent">
|
|
<i class="fas fa-arrow-up"></i>
|
|
<span class="filter-text">Sent</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="tx-history" class="output"></div>
|
|
|
|
<div id="tx-pagination" class="pagination-section" style="display: none;">
|
|
<div class="pagination-info">
|
|
<span id="paginationInfo">Page 1 of 1</span>
|
|
</div>
|
|
<div class="pagination-controls">
|
|
<button class="pagination-btn" id="prev-page-btn" onclick="prevPage()">
|
|
<i class="fas fa-chevron-left"></i>
|
|
<span class="btn-text">Prev</span>
|
|
</button>
|
|
<button class="pagination-btn" id="next-page-btn" onclick="nextPage()">
|
|
<span class="btn-text">Next</span>
|
|
<i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Searches -->
|
|
<div id="recent-searches" class="recent-searches" style="display: none;">
|
|
<div class="recent-header">
|
|
<h4><i class="fas fa-history"></i> Recent Searches</h4>
|
|
<button class="btn-clear-all" onclick="clearAllRecentSearches()" title="Clear All">
|
|
<i class="fas fa-trash-alt"></i> Clear All
|
|
</button>
|
|
</div>
|
|
<div id="recent-searches-list" class="recent-list">
|
|
<!-- Recent searches will be inserted here -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transaction Hash Results -->
|
|
<div id="txhash-results" style="display: none;">
|
|
<div class="tx-details-card card">
|
|
<div class="tx-details-header">
|
|
<div class="tx-status" id="tx-status">
|
|
<i class="fa-solid fa-circle-check"></i>
|
|
<span>Confirmed</span>
|
|
</div>
|
|
<a class="explorer-link" id="tx-explorer-link" href="#" target="_blank">
|
|
<i class="fa-solid fa-external-link-alt"></i> View on Explorer
|
|
</a>
|
|
</div>
|
|
|
|
<div class="tx-details-body">
|
|
<div class="tx-detail-row">
|
|
<span class="detail-label">Transaction ID</span>
|
|
<div class="detail-value-wrapper">
|
|
<code class="detail-value" id="tx-detail-id">-</code>
|
|
<button class="input-action-btn clear-btn" onclick="copyToClipboard('tx-detail-id')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="tx-detail-row">
|
|
<span class="detail-label">From</span>
|
|
<div class="detail-value-wrapper">
|
|
<code class="detail-value" id="tx-detail-from">-</code>
|
|
<button class="input-action-btn clear-btn" onclick="copyToClipboard('tx-detail-from')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="tx-detail-row">
|
|
<span class="detail-label">To</span>
|
|
<div class="detail-value-wrapper">
|
|
<code class="detail-value" id="tx-detail-to">-</code>
|
|
<button class="input-action-btn clear-btn" onclick="copyToClipboard('tx-detail-to')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="tx-detail-row highlight">
|
|
<span class="detail-label">Amount</span>
|
|
<span class="detail-value amount" id="tx-detail-amount">-</span>
|
|
</div>
|
|
<div class="tx-detail-row">
|
|
<span class="detail-label">Fee</span>
|
|
<span class="detail-value fee" id="tx-detail-fee">-</span>
|
|
</div>
|
|
<div class="tx-detail-row">
|
|
<span class="detail-label">Round</span>
|
|
<span class="detail-value" id="tx-detail-round">-</span>
|
|
</div>
|
|
<div class="tx-detail-row">
|
|
<span class="detail-label">Timestamp</span>
|
|
<span class="detail-value" id="tx-detail-time">-</span>
|
|
</div>
|
|
<div class="tx-detail-row" id="tx-note-row" style="display: none;">
|
|
<span class="detail-label">Note</span>
|
|
<span class="detail-value" id="tx-detail-note">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Send Page -->
|
|
<div id="send-tab" class="page tab-content hidden">
|
|
<div class="page-header">
|
|
<h2><i class="fas fa-paper-plane"></i> Send Transaction</h2>
|
|
<p>Send ALGO to another address</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<form id="sendForm" onsubmit="event.preventDefault(); sendAlgo();">
|
|
<div class="form-group">
|
|
<label for="send-privatekey"><i class="fas fa-key"></i> Private Key (BTC/FLO/ALGO)</label>
|
|
<div class="input-with-actions">
|
|
<input type="password" id="send-privatekey" class="form-input" placeholder="Enter BTC/FLO/ALGO private key" required onchange="loadSendWalletInfo()" />
|
|
<button type="button" class="input-action-btn password-toggle" onclick="toggleSendKeyVisibility()">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button type="button" class="input-action-btn clear-btn" onclick="clearInput('send-privatekey')">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="send-wallet-info" class="balance-info card" style="display: none; margin-bottom: 1.5rem;">
|
|
<div class="balance-header">
|
|
<h3><i class="fas fa-wallet"></i> Sender Balance</h3>
|
|
</div>
|
|
<div class="balance-display">
|
|
<div class="balance-amount" id="send-balance">0 <span class="currency">ALGO</span></div>
|
|
</div>
|
|
<div class="address-display">
|
|
<span class="address-label">Address:</span>
|
|
<span class="address-value" id="send-from-address">-</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="send-recipient"><i class="fas fa-user"></i> To Address</label>
|
|
<div class="input-with-actions">
|
|
<input type="text" id="send-recipient" class="form-input" placeholder="Enter recipient address (58 chars)" required />
|
|
<button type="button" class="input-action-btn clear-btn" onclick="clearInput('send-recipient')">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="send-amount"><i class="fas fa-coins"></i> Amount (ALGO)</label>
|
|
<div class="input-with-actions">
|
|
<input type="number" id="send-amount" class="form-input" placeholder="Enter the amount to send" min="0.000001" step="0.000001" required />
|
|
<button type="button" class="input-action-btn clear-btn" onclick="clearInput('send-amount')">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary btn-block send-btn">
|
|
<i class="fas fa-paper-plane"></i> Send Transaction
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div id="sendOutput" class="output"></div>
|
|
</div>
|
|
|
|
<!-- Recover Page -->
|
|
<div id="recover-tab" class="page tab-content hidden">
|
|
<div class="page-header">
|
|
<h2><i class="fas fa-sync-alt"></i> Recover Multi-Blockchain Addresses</h2>
|
|
<p>Recover all blockchain addresses (BTC, FLO, ALGO) from a single private key</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="form-group">
|
|
<label for="privateKeyInput"><i class="fas fa-key"></i> Private Key (BTC/FLO/ALGO)</label>
|
|
<div class="input-with-actions">
|
|
<input type="password" id="privateKeyInput" class="form-input" placeholder="Enter BTC/FLO/ALGO private key" required />
|
|
<button type="button" class="input-action-btn password-toggle" onclick="toggleRecoverKeyVisibility()">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button type="button" class="input-action-btn clear-btn" onclick="clearInput('privateKeyInput')">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="form-text">Only private keys accepted</div>
|
|
</div>
|
|
|
|
<button id="recoverBtn" class="btn btn-primary btn-block" onclick="recoverWallet()">
|
|
<i class="fas fa-sync-alt"></i> Recover
|
|
</button>
|
|
|
|
<div id="recover-results" class="output" style="display: none;">
|
|
<!-- ALGO Section -->
|
|
<div class="blockchain-section algo-primary">
|
|
<div class="blockchain-header">
|
|
<h4><svg class="chain-icon-svg" viewBox="0 0 507.56 509.36" fill="currentColor"><polygon points="88.04 509.36 161.7 381.8 235.37 254.68 308.58 127.12 320.71 106.9 326.1 127.12 348.56 211.11 323.4 254.68 249.74 381.8 176.53 509.36 264.56 509.36 338.23 381.8 376.41 315.77 394.37 381.8 428.51 509.36 507.56 509.36 473.43 381.8 439.29 254.68 430.31 221.89 485.11 127.12 405.15 127.12 402.46 117.68 374.61 13.47 371.02 0 294.21 0 292.41 2.69 220.54 127.12 146.88 254.68 73.66 381.8 0 509.36 88.04 509.36"/></svg> ALGO</h4>
|
|
<span class="blockchain-badge primary">Primary</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>Address</label>
|
|
<div class="detail-value-wrapper">
|
|
<code id="recover-algo-address" class="detail-value">-</code>
|
|
<button class="input-action-btn clear-btn" onclick="copyToClipboard('recover-algo-address')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>Private Key</label>
|
|
<div class="input-with-actions">
|
|
<input type="password" id="recover-algo-privateKey" class="form-input" value="-" readonly />
|
|
<button type="button" class="input-action-btn password-toggle" onclick="toggleVisibility('recover-algo-privateKey')">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button type="button" class="input-action-btn clear-btn" onclick="copyPrivateKey('recover-algo-privateKey')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FLO Section -->
|
|
<div class="blockchain-section">
|
|
<div class="blockchain-header">
|
|
<h4><i class="fas fa-spa"></i> FLO</h4>
|
|
<span class="blockchain-badge secondary">Secondary</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>Address</label>
|
|
<div class="detail-value-wrapper">
|
|
<code id="recover-flo-address" class="detail-value">-</code>
|
|
<button class="input-action-btn clear-btn" onclick="copyToClipboard('recover-flo-address')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>Private Key</label>
|
|
<div class="input-with-actions">
|
|
<input type="password" id="recover-flo-privateKey" class="form-input" value="-" readonly />
|
|
<button type="button" class="input-action-btn password-toggle" onclick="toggleVisibility('recover-flo-privateKey')">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button type="button" class="input-action-btn clear-btn" onclick="copyPrivateKey('recover-flo-privateKey')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- BTC Section -->
|
|
<div class="blockchain-section">
|
|
<div class="blockchain-header">
|
|
<h4><i class="fab fa-bitcoin"></i> BTC</h4>
|
|
<span class="blockchain-badge secondary">Secondary</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>Address</label>
|
|
<div class="detail-value-wrapper">
|
|
<code id="recover-btc-address" class="detail-value">-</code>
|
|
<button class="input-action-btn clear-btn" onclick="copyToClipboard('recover-btc-address')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>Private Key</label>
|
|
<div class="input-with-actions">
|
|
<input type="password" id="recover-btc-privateKey" class="form-input" value="-" readonly />
|
|
<button type="button" class="input-action-btn password-toggle" onclick="toggleVisibility('recover-btc-privateKey')">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button type="button" class="input-action-btn clear-btn" onclick="copyPrivateKey('recover-btc-privateKey')">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wallet-security-notice">
|
|
<div class="notice-icon">
|
|
<i class="fas fa-shield-alt"></i>
|
|
</div>
|
|
<div class="notice-content">
|
|
<h4>Privacy Notice</h4>
|
|
<p>Your private key is processed locally and never transmitted to any server. All recovery operations happen in your browser.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Confirmation Modal -->
|
|
<div id="confirm-modal" class="modal-overlay" style="display: none;">
|
|
<div class="modal-content confirm-modal">
|
|
<div class="modal-header">
|
|
<h3><i class="fa-solid fa-paper-plane"></i> Confirm Transaction</h3>
|
|
<button class="modal-close" onclick="closeConfirmModal()">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="confirm-details">
|
|
<div class="confirm-row">
|
|
<span class="confirm-label">From:</span>
|
|
<span class="confirm-value" id="confirm-from">-</span>
|
|
</div>
|
|
<div class="confirm-row">
|
|
<span class="confirm-label">To:</span>
|
|
<span class="confirm-value" id="confirm-to">-</span>
|
|
</div>
|
|
<div class="confirm-row highlight">
|
|
<span class="confirm-label">Amount:</span>
|
|
<span class="confirm-value" id="confirm-amount">0.000000 ALGO</span>
|
|
</div>
|
|
<div class="confirm-row fee-row">
|
|
<span class="confirm-label">Network Fee:</span>
|
|
<span class="confirm-value fee" id="confirm-fee">0.001 ALGO</span>
|
|
</div>
|
|
<div class="confirm-divider"></div>
|
|
<div class="confirm-row total-row">
|
|
<span class="confirm-label">Total:</span>
|
|
<span class="confirm-value total" id="confirm-total">0.001 ALGO</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="modal-btn cancel" onclick="closeConfirmModal()">
|
|
<i class="fa-solid fa-xmark"></i> Cancel
|
|
</button>
|
|
<button class="modal-btn confirm" id="confirm-send-btn" onclick="confirmAndSend()">
|
|
<i class="fa-solid fa-check"></i> Confirm & Send
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Modal -->
|
|
<div id="success-modal" class="modal-overlay" style="display: none;">
|
|
<div class="modal-content success-modal">
|
|
<div class="modal-header success">
|
|
<div class="success-icon">
|
|
<i class="fa-solid fa-check-circle"></i>
|
|
</div>
|
|
<h3>Transaction Sent!</h3>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="success-details">
|
|
<div class="success-row">
|
|
<span class="success-label">Transaction ID:</span>
|
|
<div class="tx-id-wrapper">
|
|
<code id="success-txid">-</code>
|
|
<button class="input-action-btn clear-btn" onclick="copyTxId()">
|
|
<i class="fa-regular fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="success-row">
|
|
<span class="success-label">Amount Sent:</span>
|
|
<span class="success-value" id="success-amount">0.000000 ALGO</span>
|
|
</div>
|
|
<div class="success-row">
|
|
<span class="success-label">To:</span>
|
|
<span class="success-value" id="success-to">-</span>
|
|
</div>
|
|
<div class="success-row">
|
|
<span class="success-label">Fee:</span>
|
|
<span class="success-value" id="success-fee">0.001 ALGO</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="modal-btn secondary" onclick="viewOnExplorer()">
|
|
<i class="fa-solid fa-external-link-alt"></i> View on Explorer
|
|
</button>
|
|
<button class="modal-btn confirm" onclick="closeSuccessModal()">
|
|
<i class="fa-solid fa-check"></i> Done
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- required libraries -->
|
|
<script src="https://cdn.jsdelivr.net/npm/tweetnacl@1.0.3/nacl.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/js-sha512@0.8.0/build/sha512.min.js"></script>
|
|
<script src="lib.algo.js"></script>
|
|
<script src="algoCrypto.js"></script>
|
|
<script src="algoBlockchainAPI.js"></script>
|
|
<script src="algoSearchDB.js"></script>
|
|
|
|
<script>
|
|
let currentAlgoAddress = null;
|
|
let currentAlgoPrivateKey = null;
|
|
let txNextToken = null;
|
|
let currentSearchType = 'address';
|
|
let searchDB = new SearchedAddressDB();
|
|
let currentTxFilter = 'all'; // Transaction filter state
|
|
|
|
function filterTransactions(type) {
|
|
currentTxFilter = type;
|
|
|
|
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
if (btn.getAttribute('data-filter') === type) {
|
|
btn.classList.add('active');
|
|
}
|
|
});
|
|
|
|
|
|
currentPage = 1; // Reset to first page
|
|
displayCurrentPage();
|
|
}
|
|
|
|
// Clear input helper
|
|
function clearInput(inputId) {
|
|
const input = document.getElementById(inputId);
|
|
if (input) {
|
|
input.value = '';
|
|
input.focus();
|
|
}
|
|
}
|
|
|
|
// Handle unified search button
|
|
function handleSearch() {
|
|
if (currentSearchType === 'address') {
|
|
searchAlgoAddress();
|
|
} else {
|
|
searchTransactionHash();
|
|
}
|
|
}
|
|
|
|
// Initialize search type radio buttons
|
|
function initializeSearchTypeSelectors() {
|
|
const addressType = document.getElementById('addressSearchType');
|
|
const hashType = document.getElementById('hashSearchType');
|
|
|
|
if (addressType) {
|
|
addressType.addEventListener('click', function() {
|
|
switchSearchType('address');
|
|
});
|
|
}
|
|
if (hashType) {
|
|
hashType.addEventListener('click', function() {
|
|
switchSearchType('txhash');
|
|
});
|
|
}
|
|
}
|
|
|
|
function switchTab(tab) {
|
|
// Remove active from all tab buttons
|
|
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
|
|
|
// Hide all tab content
|
|
document.querySelectorAll('.tab-content').forEach(content => {
|
|
content.classList.remove('active');
|
|
content.classList.add('hidden');
|
|
});
|
|
|
|
// Show selected tab content
|
|
const tabContent = document.getElementById(`${tab}-tab`);
|
|
if (tabContent) {
|
|
tabContent.classList.add('active');
|
|
tabContent.classList.remove('hidden');
|
|
}
|
|
|
|
// Update old tab buttons if present
|
|
if (event && event.target) {
|
|
const btn = event.target.closest('.tab-btn');
|
|
if (btn) btn.classList.add('active');
|
|
}
|
|
}
|
|
|
|
async function generateWallet() {
|
|
try {
|
|
showLoading(true);
|
|
const result = await algoCrypto.generateMultiChain();
|
|
|
|
// Display in Generate tab
|
|
document.getElementById('btc-address').textContent = result.BTC.address;
|
|
document.getElementById('btc-privateKey').value = result.BTC.privateKey;
|
|
document.getElementById('flo-address').textContent = result.FLO.address;
|
|
document.getElementById('flo-privateKey').value = result.FLO.privateKey;
|
|
document.getElementById('algo-address').textContent = result.ALGO.address;
|
|
document.getElementById('algo-privateKey').value = result.ALGO.privateKey;
|
|
|
|
// Store ALGO keys
|
|
currentAlgoAddress = result.ALGO.address;
|
|
currentAlgoPrivateKey = result.ALGO.privateKey;
|
|
|
|
document.getElementById('generate-results').style.display = 'block';
|
|
showNotification(' New address generated!', 'success');
|
|
} catch (error) {
|
|
console.error('Error generating wallet:', error);
|
|
showNotification('❌ Error: ' + error.message, 'error');
|
|
} finally {
|
|
showLoading(false);
|
|
}
|
|
}
|
|
|
|
async function recoverWallet() {
|
|
const privateKey = document.getElementById('privateKeyInput').value.trim();
|
|
|
|
if (!privateKey) {
|
|
showNotification('⚠️ Please enter a private key', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Validate input - reject addresses, only accept private keys
|
|
const hexOnly = /^[0-9a-fA-F]+$/.test(privateKey);
|
|
const isHexKey = hexOnly && (privateKey.length === 64 || privateKey.length === 128);
|
|
const isBase58Key = !hexOnly && privateKey.length >= 50;
|
|
|
|
// Check if it looks like an address (ALGO address is 58 chars, BTC/FLO addresses are shorter)
|
|
if (privateKey.length === 58 || (privateKey.length >= 25 && privateKey.length <= 35 && !isBase58Key)) {
|
|
showNotification('⚠️ Addresses are not allowed. Please enter a BTC/FLO/ALGO private key', 'error');
|
|
return;
|
|
}
|
|
|
|
// Validate private key format
|
|
if (!isHexKey && !isBase58Key) {
|
|
showNotification('⚠️ Invalid private key format. Please enter a valid BTC/FLO/ALGO private key', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
showLoading(true);
|
|
const result = await algoCrypto.generateMultiChain(privateKey);
|
|
|
|
// Display in Recover tab
|
|
document.getElementById('recover-btc-address').textContent = result.BTC.address;
|
|
document.getElementById('recover-btc-privateKey').value = result.BTC.privateKey;
|
|
document.getElementById('recover-flo-address').textContent = result.FLO.address;
|
|
document.getElementById('recover-flo-privateKey').value = result.FLO.privateKey;
|
|
document.getElementById('recover-algo-address').textContent = result.ALGO.address;
|
|
document.getElementById('recover-algo-privateKey').value = result.ALGO.privateKey;
|
|
|
|
// Store ALGO keys
|
|
currentAlgoAddress = result.ALGO.address;
|
|
currentAlgoPrivateKey = result.ALGO.privateKey;
|
|
|
|
document.getElementById('recover-results').style.display = 'block';
|
|
showNotification(' Address recovered!', 'success');
|
|
} catch (error) {
|
|
console.error('Error recovering address:', error);
|
|
showNotification('❌ Error: ' + error.message, 'error');
|
|
} finally {
|
|
showLoading(false);
|
|
}
|
|
}
|
|
|
|
function switchSearchType(type) {
|
|
currentSearchType = type;
|
|
|
|
// Update old toggle buttons if present
|
|
document.querySelectorAll('.toggle-buttons .toggle-btn').forEach(btn => btn.classList.remove('active'));
|
|
if (event && event.target) {
|
|
const btn = event.target.closest('.toggle-btn');
|
|
if (btn) btn.classList.add('active');
|
|
}
|
|
|
|
// Update new radio button selectors
|
|
const addressType = document.getElementById('addressSearchType');
|
|
const hashType = document.getElementById('hashSearchType');
|
|
if (addressType && hashType) {
|
|
if (type === 'address') {
|
|
addressType.classList.add('active');
|
|
hashType.classList.remove('active');
|
|
} else {
|
|
addressType.classList.remove('active');
|
|
hashType.classList.add('active');
|
|
}
|
|
}
|
|
|
|
// Show/hide search sections
|
|
// Show/hide search sections and clear results
|
|
if (type === 'address') {
|
|
document.getElementById('address-search').style.display = 'block';
|
|
document.getElementById('txhash-search').style.display = 'none';
|
|
|
|
// Show history container
|
|
document.getElementById('tx-history').style.display = 'block';
|
|
|
|
// Clear Hash Results
|
|
document.getElementById('txhash-results').style.display = 'none';
|
|
document.getElementById('txhash-input').value = '';
|
|
|
|
// Load recent searches for address mode
|
|
loadRecentSearches();
|
|
|
|
} else {
|
|
document.getElementById('address-search').style.display = 'none';
|
|
document.getElementById('txhash-search').style.display = 'block';
|
|
|
|
// Clear Address Results
|
|
document.getElementById('tx-results').style.display = 'none';
|
|
document.getElementById('transactionFilterSection').style.display = 'none';
|
|
document.getElementById('tx-search-input').value = '';
|
|
currentAlgoAddress = null;
|
|
|
|
// Reset pagination and hide history
|
|
allTransactions = [];
|
|
currentPage = 1;
|
|
totalPages = 1;
|
|
document.getElementById('tx-history').innerHTML = '';
|
|
document.getElementById('tx-history').style.display = 'none';
|
|
document.getElementById('tx-pagination').style.display = 'none';
|
|
|
|
// Hide recent searches in transaction hash mode
|
|
document.getElementById('recent-searches').style.display = 'none';
|
|
return;
|
|
}
|
|
}
|
|
|
|
function updateUrl(type, value) {
|
|
try {
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.delete('address');
|
|
url.searchParams.delete('tx');
|
|
if (value) {
|
|
url.searchParams.set(type, value);
|
|
}
|
|
window.history.pushState({}, '', url.toString());
|
|
console.log('URL updated:', url.toString());
|
|
} catch (error) {
|
|
console.error('Error updating URL:', error);
|
|
}
|
|
}
|
|
|
|
async function searchAlgoAddress() {
|
|
const input = document.getElementById('tx-search-input').value.trim();
|
|
|
|
if (!input) {
|
|
showNotification('⚠️ Please enter an address or private key', 'warning');
|
|
return;
|
|
}
|
|
|
|
const searchBtn = document.getElementById('searchBtn');
|
|
const originalContent = searchBtn.innerHTML;
|
|
searchBtn.disabled = true;
|
|
searchBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Searching...';
|
|
|
|
let address = input;
|
|
let sourceInfo = null;
|
|
|
|
try {
|
|
// Check if input is an address (58 chars) or private key (any format)
|
|
if (input.length === 58) {
|
|
// It's an ALGO address
|
|
address = input;
|
|
} else if (input.length >= 50) {
|
|
// It's a private key (WIF or hex), derive the ALGO address
|
|
const result = await algoCrypto.generateMultiChain(input);
|
|
address = result.ALGO.address;
|
|
sourceInfo = {
|
|
privateKey: input,
|
|
btcAddress: result.BTC.address,
|
|
floAddress: result.FLO.address
|
|
};
|
|
} else {
|
|
showNotification('⚠️ Invalid format. Enter address or private key', 'warning');
|
|
searchBtn.disabled = false;
|
|
searchBtn.innerHTML = originalContent;
|
|
return;
|
|
}
|
|
|
|
// Update URL with the address (whether directly entered or derived from private key)
|
|
updateUrl('address', address);
|
|
|
|
currentAlgoAddress = address;
|
|
document.getElementById('algo-display-address').textContent = address;
|
|
document.getElementById('tx-results').style.display = 'block';
|
|
document.getElementById('transactionFilterSection').style.display = 'block';
|
|
|
|
await refreshAlgoData();
|
|
|
|
// Save to database after successful balance fetch
|
|
const accountInfo = await algoAPI.getBalance(address);
|
|
await searchDB.saveSearchedAddress(
|
|
address,
|
|
accountInfo.balanceAlgo,
|
|
Date.now(),
|
|
sourceInfo
|
|
);
|
|
|
|
await loadRecentSearches();
|
|
|
|
} catch (error) {
|
|
console.error('Error searching:', error);
|
|
showNotification('❌ Error: ' + error.message, 'error');
|
|
} finally {
|
|
searchBtn.disabled = false;
|
|
searchBtn.innerHTML = '<i class="fas fa-search"></i> Search';
|
|
}
|
|
}
|
|
|
|
async function searchTransactionHash() {
|
|
const txId = document.getElementById('txhash-input').value.trim();
|
|
|
|
if (!txId) {
|
|
showNotification('⚠️ Please enter a transaction ID', 'warning');
|
|
return;
|
|
}
|
|
|
|
const searchBtn = document.getElementById('searchBtn');
|
|
const originalContent = searchBtn.innerHTML;
|
|
searchBtn.disabled = true;
|
|
searchBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Searching...';
|
|
|
|
try {
|
|
const tx = await algoAPI.getTransaction(txId);
|
|
|
|
updateUrl('tx', txId);
|
|
|
|
// Update UI
|
|
document.getElementById('tx-detail-id').textContent = tx.id;
|
|
document.getElementById('tx-detail-from').textContent = tx.sender;
|
|
document.getElementById('tx-detail-to').textContent = tx.receiver || '-';
|
|
document.getElementById('tx-detail-amount').textContent = tx.amountAlgo.toFixed(6) + ' ALGO';
|
|
document.getElementById('tx-detail-fee').textContent = (tx.fee / 1000000).toFixed(6) + ' ALGO';
|
|
document.getElementById('tx-detail-round').textContent = tx.confirmedRound;
|
|
|
|
const date = new Date(tx.roundTime * 1000);
|
|
document.getElementById('tx-detail-time').textContent = date.toLocaleString();
|
|
|
|
|
|
const noteRow = document.getElementById('tx-note-row');
|
|
if (tx.note) {
|
|
document.getElementById('tx-detail-note').textContent = tx.note;
|
|
noteRow.style.display = 'flex';
|
|
} else {
|
|
noteRow.style.display = 'none';
|
|
}
|
|
|
|
// Update Explorer Link
|
|
const explorerLink = document.getElementById('tx-explorer-link');
|
|
explorerLink.href = `https://allo.info/tx/${tx.id}`;
|
|
|
|
document.getElementById('txhash-results').style.display = 'block';
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching transaction:', error);
|
|
showNotification('❌ Transaction not found', 'error');
|
|
} finally {
|
|
searchBtn.disabled = false;
|
|
searchBtn.innerHTML = '<i class="fas fa-search"></i> Search';
|
|
}
|
|
}
|
|
|
|
function checkUrlParams() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const address = urlParams.get('address');
|
|
const tx = urlParams.get('tx');
|
|
|
|
console.log('Checking URL params:', { address, tx });
|
|
|
|
if (address || tx) {
|
|
// Switch to Transactions tab
|
|
switchTabByPage('transactions');
|
|
|
|
setTimeout(() => {
|
|
if (address) {
|
|
console.log('Setting up address search for:', address);
|
|
switchSearchType('address');
|
|
document.getElementById('tx-search-input').value = address;
|
|
searchAlgoAddress();
|
|
} else if (tx) {
|
|
console.log('Setting up transaction hash search for:', tx);
|
|
switchSearchType('txhash');
|
|
document.getElementById('txhash-input').value = tx;
|
|
searchTransactionHash();
|
|
}
|
|
}, 300);
|
|
}
|
|
}
|
|
|
|
async function loadSendWalletInfo() {
|
|
const privateKey = document.getElementById('send-privatekey').value.trim();
|
|
|
|
if (!privateKey || privateKey.length < 50) {
|
|
document.getElementById('send-wallet-info').style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const result = await algoCrypto.generateMultiChain(privateKey);
|
|
const address = result.ALGO.address;
|
|
|
|
const accountInfo = await algoAPI.getBalance(address);
|
|
|
|
|
|
const balanceEl = document.getElementById('send-balance');
|
|
balanceEl.innerHTML = accountInfo.balanceAlgo.toFixed(6) + ' <span class="currency">ALGO</span>';
|
|
document.getElementById('send-from-address').textContent = address.substring(0, 12) + '...' + address.substring(50);
|
|
document.getElementById('send-wallet-info').style.display = 'block';
|
|
} catch (error) {
|
|
document.getElementById('send-wallet-info').style.display = 'none';
|
|
}
|
|
}
|
|
|
|
async function refreshAlgoData() {
|
|
if (!currentAlgoAddress) return;
|
|
|
|
const refreshBtn = document.querySelector('.transaction-header .btn-icon i');
|
|
if (refreshBtn) refreshBtn.classList.add('fa-spin');
|
|
|
|
try {
|
|
const accountInfo = await algoAPI.getBalance(currentAlgoAddress);
|
|
|
|
const balanceEl = document.getElementById('algo-balance');
|
|
balanceEl.innerHTML = accountInfo.balanceAlgo.toFixed(6) + ' <span class="currency">ALGO</span>';
|
|
|
|
txNextToken = null;
|
|
await loadTransactions(true);
|
|
} catch (error) {
|
|
console.error('Error fetching data:', error);
|
|
const balanceEl = document.getElementById('algo-balance');
|
|
balanceEl.innerHTML = '0 <span class="currency">ALGO</span>';
|
|
document.getElementById('tx-history').innerHTML = '<div class="tx-empty">Unable to load</div>';
|
|
} finally {
|
|
if (refreshBtn) refreshBtn.classList.remove('fa-spin');
|
|
}
|
|
}
|
|
|
|
let currentPage = 1;
|
|
let totalPages = 1;
|
|
let allTransactions = [];
|
|
const ITEMS_PER_PAGE = 10;
|
|
|
|
async function loadTransactions(reset = false) {
|
|
if (!currentAlgoAddress) return;
|
|
|
|
try {
|
|
if (reset) {
|
|
allTransactions = [];
|
|
currentPage = 1;
|
|
txNextToken = null;
|
|
}
|
|
|
|
// Load all available transactions
|
|
let hasMore = true;
|
|
while (hasMore && allTransactions.length < 100) { // Limit to 100 for performance
|
|
const result = await algoAPI.getTransactions(currentAlgoAddress, {
|
|
limit: 50,
|
|
next: txNextToken
|
|
});
|
|
|
|
allTransactions = [...allTransactions, ...result.transactions];
|
|
txNextToken = result.nextToken;
|
|
hasMore = result.hasMore;
|
|
|
|
if (!hasMore) break;
|
|
}
|
|
|
|
totalPages = Math.ceil(allTransactions.length / ITEMS_PER_PAGE) || 1;
|
|
displayCurrentPage();
|
|
|
|
} catch (error) {
|
|
console.error('Error loading transactions:', error);
|
|
}
|
|
}
|
|
|
|
function displayCurrentPage() {
|
|
const historyEl = document.getElementById('tx-history');
|
|
const paginationEl = document.getElementById('tx-pagination');
|
|
|
|
historyEl.innerHTML = '';
|
|
|
|
// Apply filter
|
|
let filteredTransactions = allTransactions;
|
|
if (currentTxFilter === 'sent') {
|
|
filteredTransactions = allTransactions.filter(tx => tx.sender === currentAlgoAddress);
|
|
} else if (currentTxFilter === 'received') {
|
|
filteredTransactions = allTransactions.filter(tx => tx.sender !== currentAlgoAddress);
|
|
}
|
|
|
|
if (filteredTransactions.length === 0) {
|
|
historyEl.innerHTML = '<div class="tx-empty">No transactions found</div>';
|
|
paginationEl.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
// Update total pages based on filtered results
|
|
totalPages = Math.ceil(filteredTransactions.length / ITEMS_PER_PAGE) || 1;
|
|
|
|
// Ensure current page is within bounds
|
|
if (currentPage > totalPages) {
|
|
currentPage = totalPages;
|
|
}
|
|
|
|
const startIdx = (currentPage - 1) * ITEMS_PER_PAGE;
|
|
const endIdx = Math.min(startIdx + ITEMS_PER_PAGE, filteredTransactions.length);
|
|
const pageTransactions = filteredTransactions.slice(startIdx, endIdx);
|
|
|
|
pageTransactions.forEach(tx => {
|
|
historyEl.appendChild(createTxElement(tx));
|
|
});
|
|
|
|
// Update pagination info
|
|
document.getElementById('paginationInfo').textContent = `Page ${currentPage} of ${totalPages}`;
|
|
|
|
// Update button states
|
|
document.getElementById('prev-page-btn').disabled = currentPage <= 1;
|
|
document.getElementById('next-page-btn').disabled = currentPage >= totalPages;
|
|
|
|
paginationEl.style.display = filteredTransactions.length > 0 ? 'flex' : 'none';
|
|
}
|
|
|
|
function prevPage() {
|
|
if (currentPage > 1) {
|
|
currentPage--;
|
|
displayCurrentPage();
|
|
}
|
|
}
|
|
|
|
function nextPage() {
|
|
if (currentPage < totalPages) {
|
|
currentPage++;
|
|
displayCurrentPage();
|
|
}
|
|
}
|
|
|
|
function createTxElement(tx) {
|
|
const div = document.createElement('div');
|
|
div.className = 'tx-item';
|
|
|
|
const isSent = tx.sender === currentAlgoAddress;
|
|
const date = new Date(tx.roundTime * 1000);
|
|
const formattedDate = date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
|
|
const otherAddress = isSent ? (tx.receiver || 'Unknown') : tx.sender;
|
|
const shortAddr = otherAddress ? otherAddress.substring(0, 8) + '...' + otherAddress.substring(50) : 'Unknown';
|
|
const shortTxId = tx.id.substring(0, 8) + '...' + tx.id.substring(tx.id.length - 8);
|
|
|
|
div.innerHTML = `
|
|
<div class="tx-icon ${isSent ? 'sent' : 'received'}">
|
|
<i class="fa-solid fa-arrow-${isSent ? 'up' : 'down'}"></i>
|
|
</div>
|
|
<div class="tx-details">
|
|
<div class="tx-type">${isSent ? 'Sent' : 'Received'}</div>
|
|
<div class="tx-address">${shortAddr}</div>
|
|
<div class="tx-date">${formattedDate}</div>
|
|
<div class="tx-id" title="${tx.id}">TX ID: ${shortTxId}</div>
|
|
</div>
|
|
<div class="tx-amount ${isSent ? 'sent' : 'received'}">
|
|
${isSent ? '-' : '+'}${tx.amountAlgo.toFixed(6)} ALGO
|
|
</div>
|
|
`;
|
|
|
|
div.onclick = () => viewTransactionDetails(tx.id);
|
|
return div;
|
|
}
|
|
|
|
function viewTransactionDetails(txId) {
|
|
// Switch to hash search type
|
|
switchSearchType('txhash');
|
|
|
|
// Set the transaction ID in the input
|
|
document.getElementById('txhash-input').value = txId;
|
|
|
|
// Trigger the search
|
|
searchTransactionHash();
|
|
}
|
|
|
|
async function loadRecentSearches() {
|
|
try {
|
|
const searches = await searchDB.getSearchedAddresses();
|
|
const listEl = document.getElementById('recent-searches-list');
|
|
const containerEl = document.getElementById('recent-searches');
|
|
|
|
if (searches.length === 0) {
|
|
containerEl.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
containerEl.style.display = 'block';
|
|
listEl.innerHTML = '';
|
|
|
|
searches.forEach(search => {
|
|
const item = document.createElement('div');
|
|
item.className = 'recent-item';
|
|
item.setAttribute('data-current-address', search.algoAddress); // Track current displayed address
|
|
|
|
const shortAddr = search.algoAddress.substring(0, 12) + '...' + search.algoAddress.substring(50);
|
|
const date = new Date(search.timestamp);
|
|
const formattedDate = date.toLocaleDateString();
|
|
|
|
item.innerHTML = `
|
|
${search.isFromPrivateKey ? `
|
|
<div class="recent-chain-buttons">
|
|
<button class="chain-btn active" onclick="showAddressForChain(${search.id}, 'ALGO', '${search.algoAddress}')" title="ALGO">
|
|
<svg class="chain-icon-svg" viewBox="0 0 507.56 509.36" fill="currentColor"><polygon points="88.04 509.36 161.7 381.8 235.37 254.68 308.58 127.12 320.71 106.9 326.1 127.12 348.56 211.11 323.4 254.68 249.74 381.8 176.53 509.36 264.56 509.36 338.23 381.8 376.41 315.77 394.37 381.8 428.51 509.36 507.56 509.36 473.43 381.8 439.29 254.68 430.31 221.89 485.11 127.12 405.15 127.12 402.46 117.68 374.61 13.47 371.02 0 294.21 0 292.41 2.69 220.54 127.12 146.88 254.68 73.66 381.8 0 509.36 88.04 509.36"/></svg>
|
|
</button>
|
|
<button class="chain-btn" onclick="showAddressForChain(${search.id}, 'BTC', '${search.btcAddress}')" title="BTC">
|
|
<i class="fab fa-bitcoin"></i>
|
|
</button>
|
|
<button class="chain-btn" onclick="showAddressForChain(${search.id}, 'FLO', '${search.floAddress}')" title="FLO">
|
|
<i class="fas fa-spa"></i>
|
|
</button>
|
|
</div>
|
|
` : ''}
|
|
<div class="recent-address" title="${search.algoAddress}">
|
|
${shortAddr}
|
|
</div>
|
|
<div class="recent-bottom-row">
|
|
<div class="recent-balance-row">
|
|
<span class="recent-balance">${search.formattedBalance}</span>
|
|
<span class="recent-date">• ${formattedDate}</span>
|
|
</div>
|
|
<div class="recent-actions">
|
|
<button class="action-btn copy-recent-btn" title="Copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
<button class="action-btn" onclick="recheckAddress('${search.algoAddress}')" title="Recheck">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
<button class="action-btn" onclick="deleteRecentSearch(${search.id})" title="Delete">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
listEl.appendChild(item);
|
|
});
|
|
|
|
document.querySelectorAll('.copy-recent-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const recentItem = this.closest('.recent-item');
|
|
const currentAddress = recentItem.getAttribute('data-current-address');
|
|
copyAddress(currentAddress);
|
|
});
|
|
});
|
|
} catch (error) {
|
|
console.error('Error loading recent searches:', error);
|
|
}
|
|
}
|
|
|
|
function showAddressForChain(searchId, chain, address) {
|
|
event.target.closest('.recent-chain-buttons').querySelectorAll('.chain-btn').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
});
|
|
event.target.closest('.chain-btn').classList.add('active');
|
|
|
|
// Update the displayed address
|
|
const recentItem = event.target.closest('.recent-item');
|
|
const addressEl = recentItem.querySelector('.recent-address');
|
|
const shortAddr = address.substring(0, 12) + '...' + address.substring(address.length - 8);
|
|
addressEl.textContent = shortAddr;
|
|
addressEl.title = address;
|
|
|
|
// Update the current address data attribute
|
|
recentItem.setAttribute('data-current-address', address);
|
|
}
|
|
|
|
async function recheckAddress(address) {
|
|
document.getElementById('tx-search-input').value = address;
|
|
await searchAlgoAddress();
|
|
}
|
|
|
|
async function deleteRecentSearch(id) {
|
|
try {
|
|
await searchDB.deleteSearchedAddress(id);
|
|
await loadRecentSearches();
|
|
showNotification(' Search deleted', 'success');
|
|
} catch (error) {
|
|
console.error('Error deleting search:', error);
|
|
showNotification('❌ Error deleting search', 'error');
|
|
}
|
|
}
|
|
|
|
async function clearAllRecentSearches() {
|
|
try {
|
|
if (confirm('Are you sure you want to clear all recent searches?')) {
|
|
await searchDB.clearAllSearchedAddresses();
|
|
document.getElementById('recent-searches').style.display = 'none';
|
|
showNotification(' All recent searches cleared', 'success');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error clearing searches:', error);
|
|
showNotification('❌ Error clearing searches', 'error');
|
|
}
|
|
}
|
|
|
|
async function copyAddress(address) {
|
|
try {
|
|
await navigator.clipboard.writeText(address);
|
|
showNotification(' Address copied to clipboard', 'success');
|
|
} catch (error) {
|
|
console.error('Error copying address:', error);
|
|
showNotification('❌ Error copying address', 'error');
|
|
}
|
|
}
|
|
|
|
function toggleSendKeyVisibility() {
|
|
const input = document.getElementById('send-privatekey');
|
|
|
|
let icon = event.target.closest('.password-toggle')?.querySelector('i');
|
|
if (!icon) {
|
|
icon = event.target.closest('.toggle-btn')?.querySelector('i');
|
|
}
|
|
if (!icon) {
|
|
icon = event.target.closest('button')?.querySelector('i');
|
|
}
|
|
|
|
if (input.type === 'password') {
|
|
input.type = 'text';
|
|
if (icon) icon.className = 'fas fa-eye-slash';
|
|
} else {
|
|
input.type = 'password';
|
|
if (icon) icon.className = 'fas fa-eye';
|
|
}
|
|
}
|
|
|
|
function toggleRecoverKeyVisibility() {
|
|
const input = document.getElementById('privateKeyInput');
|
|
// Try to find icon in new structure first, then fall back to old structure
|
|
let icon = event.target.closest('.password-toggle')?.querySelector('i');
|
|
if (!icon) {
|
|
icon = event.target.closest('.toggle-btn')?.querySelector('i');
|
|
}
|
|
if (!icon) {
|
|
icon = event.target.closest('button')?.querySelector('i');
|
|
}
|
|
|
|
if (input.type === 'password') {
|
|
input.type = 'text';
|
|
if (icon) icon.className = 'fas fa-eye-slash';
|
|
} else {
|
|
input.type = 'password';
|
|
if (icon) icon.className = 'fas fa-eye';
|
|
}
|
|
}
|
|
|
|
// Pending transaction data
|
|
let pendingTx = null;
|
|
let lastTxId = null;
|
|
|
|
async function sendAlgo() {
|
|
const privateKey = document.getElementById('send-privatekey').value.trim();
|
|
const recipient = document.getElementById('send-recipient').value.trim();
|
|
const amount = parseFloat(document.getElementById('send-amount').value);
|
|
|
|
if (!privateKey || privateKey.length < 50) {
|
|
showNotification('⚠️ Please enter a valid private key', 'warning');
|
|
return;
|
|
}
|
|
if (!recipient || recipient.length !== 58) {
|
|
showNotification('⚠️ Please enter valid recipient address (58 chars)', 'warning');
|
|
return;
|
|
}
|
|
if (!amount || amount <= 0) {
|
|
showNotification('⚠️ Please enter valid amount', 'warning');
|
|
return;
|
|
}
|
|
|
|
const sendBtn = document.querySelector('.send-btn');
|
|
sendBtn.disabled = true;
|
|
sendBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Loading...';
|
|
|
|
try {
|
|
// Derive address from private key
|
|
const walletResult = await algoCrypto.generateMultiChain(privateKey);
|
|
const fromAddress = walletResult.ALGO.address;
|
|
const algoPrivateKey = walletResult.ALGO.privateKey;
|
|
|
|
// Get current balance
|
|
const accountInfo = await algoAPI.getBalance(fromAddress);
|
|
const currentBalance = accountInfo.balanceAlgo;
|
|
|
|
// Get transaction parameters
|
|
const txParams = await algoAPI.getTransactionParams();
|
|
const feeAlgo = txParams.fee / 1000000;
|
|
const totalAlgo = amount + feeAlgo;
|
|
|
|
// Check if balance is sufficient
|
|
if (totalAlgo > currentBalance) {
|
|
const errorMsg = `Insufficient balance! You need ${totalAlgo.toFixed(6)} ALGO (${amount.toFixed(6)} + ${feeAlgo.toFixed(6)} fee) but only have ${currentBalance.toFixed(6)} ALGO available.`;
|
|
showNotification('❌ ' + errorMsg, 'error');
|
|
|
|
// Show error in output area as well
|
|
const outputEl = document.getElementById('sendOutput');
|
|
outputEl.innerHTML = `
|
|
<div class="tx-details-card card" style="border-left: 4px solid var(--error-color);">
|
|
<div class="tx-details-header">
|
|
<div class="tx-status" style="color: var(--error-color);">
|
|
<i class="fa-solid fa-circle-exclamation"></i>
|
|
<span>Insufficient Balance</span>
|
|
</div>
|
|
</div>
|
|
<div class="tx-details-body">
|
|
<div class="tx-detail-row">
|
|
<span class="detail-label">Current Balance</span>
|
|
<span class="detail-value">${currentBalance.toFixed(6)} ALGO</span>
|
|
</div>
|
|
<div class="tx-detail-row">
|
|
<span class="detail-label">Amount to Send</span>
|
|
<span class="detail-value">${amount.toFixed(6)} ALGO</span>
|
|
</div>
|
|
<div class="tx-detail-row">
|
|
<span class="detail-label">Transaction Fee</span>
|
|
<span class="detail-value fee">${feeAlgo.toFixed(6)} ALGO</span>
|
|
</div>
|
|
<div class="tx-detail-row highlight" style="color: var(--error-color);">
|
|
<span class="detail-label">Total Required</span>
|
|
<span class="detail-value">${totalAlgo.toFixed(6)} ALGO</span>
|
|
</div>
|
|
<div class="tx-detail-row" style="color: var(--error-color); font-weight: 600;">
|
|
<span class="detail-label">Shortfall</span>
|
|
<span class="detail-value">${(totalAlgo - currentBalance).toFixed(6)} ALGO</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
outputEl.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
// Store pending transaction data
|
|
pendingTx = {
|
|
from: fromAddress,
|
|
to: recipient,
|
|
amount: amount,
|
|
microAmount: Math.floor(amount * 1000000),
|
|
fee: txParams.fee,
|
|
feeAlgo: feeAlgo,
|
|
total: totalAlgo,
|
|
privateKey: algoPrivateKey,
|
|
txParams: txParams
|
|
};
|
|
|
|
// Show confirmation modal
|
|
showConfirmModal();
|
|
|
|
} catch (error) {
|
|
console.error('Error preparing transaction:', error);
|
|
showNotification('❌ Error: ' + error.message, 'error');
|
|
} finally {
|
|
sendBtn.disabled = false;
|
|
sendBtn.innerHTML = '<i class="fa-solid fa-paper-plane"></i> Send';
|
|
}
|
|
}
|
|
|
|
function showConfirmModal() {
|
|
document.getElementById('confirm-from').textContent = pendingTx.from.substring(0, 12) + '...' + pendingTx.from.substring(50);
|
|
document.getElementById('confirm-to').textContent = pendingTx.to.substring(0, 12) + '...' + pendingTx.to.substring(50);
|
|
document.getElementById('confirm-amount').textContent = pendingTx.amount.toFixed(6) + ' ALGO';
|
|
document.getElementById('confirm-fee').textContent = pendingTx.feeAlgo.toFixed(6) + ' ALGO';
|
|
document.getElementById('confirm-total').textContent = pendingTx.total.toFixed(6) + ' ALGO';
|
|
|
|
document.getElementById('confirm-modal').style.display = 'flex';
|
|
}
|
|
|
|
function closeConfirmModal() {
|
|
document.getElementById('confirm-modal').style.display = 'none';
|
|
pendingTx = null;
|
|
}
|
|
|
|
async function confirmAndSend() {
|
|
if (!pendingTx) return;
|
|
|
|
const confirmBtn = document.getElementById('confirm-send-btn');
|
|
confirmBtn.disabled = true;
|
|
confirmBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Sending...';
|
|
|
|
try {
|
|
// Build and sign the transaction
|
|
const signedTxBytes = await algoCrypto.createSignedPaymentTx({
|
|
from: pendingTx.from,
|
|
to: pendingTx.to,
|
|
amount: pendingTx.microAmount,
|
|
fee: pendingTx.fee,
|
|
firstRound: pendingTx.txParams.firstRound,
|
|
lastRound: pendingTx.txParams.lastRound,
|
|
genesisId: pendingTx.txParams.genesisId,
|
|
genesisHash: pendingTx.txParams.genesisHash
|
|
}, pendingTx.privateKey);
|
|
|
|
// Broadcast transaction
|
|
const result = await algoAPI.sendTransaction(signedTxBytes);
|
|
lastTxId = result.txId;
|
|
|
|
// Save values before closing modal (closeConfirmModal sets pendingTx to null)
|
|
const txAmount = pendingTx.amount;
|
|
const txTo = pendingTx.to;
|
|
const txFee = pendingTx.feeAlgo;
|
|
|
|
// Close confirm modal and show success
|
|
closeConfirmModal();
|
|
showSuccessModal(result.txId, txAmount, txTo, txFee);
|
|
|
|
// Clear all form fields
|
|
document.getElementById('send-privatekey').value = '';
|
|
document.getElementById('send-recipient').value = '';
|
|
document.getElementById('send-amount').value = '';
|
|
|
|
// Hide balance info
|
|
document.getElementById('send-wallet-info').style.display = 'none';
|
|
|
|
// Refresh balance after delay
|
|
setTimeout(() => loadSendWalletInfo(), 5000);
|
|
|
|
} catch (error) {
|
|
console.error('Error sending:', error);
|
|
showNotification('❌ Failed: ' + error.message, 'error');
|
|
} finally {
|
|
confirmBtn.disabled = false;
|
|
confirmBtn.innerHTML = '<i class="fa-solid fa-check"></i> Confirm & Send';
|
|
}
|
|
}
|
|
|
|
function showSuccessModal(txId, amount, to, fee) {
|
|
document.getElementById('success-txid').textContent = txId;
|
|
document.getElementById('success-amount').textContent = amount.toFixed(6) + ' ALGO';
|
|
document.getElementById('success-to').textContent = to.substring(0, 12) + '...' + to.substring(50);
|
|
document.getElementById('success-fee').textContent = fee.toFixed(6) + ' ALGO';
|
|
|
|
document.getElementById('success-modal').style.display = 'flex';
|
|
}
|
|
|
|
function closeSuccessModal() {
|
|
document.getElementById('success-modal').style.display = 'none';
|
|
}
|
|
|
|
function viewOnExplorer() {
|
|
if (lastTxId) {
|
|
window.open(`https://allo.info/tx/${lastTxId}`, '_blank');
|
|
}
|
|
}
|
|
|
|
function copyTxId() {
|
|
const txId = document.getElementById('success-txid').textContent;
|
|
navigator.clipboard.writeText(txId).then(() => {
|
|
showNotification('Transaction ID copied!', 'success');
|
|
});
|
|
}
|
|
|
|
function copyToClipboard(elementId) {
|
|
const text = document.getElementById(elementId).textContent;
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
showNotification('Copied!', 'success');
|
|
});
|
|
}
|
|
|
|
function copyPrivateKey(elementId) {
|
|
const text = document.getElementById(elementId).value;
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
showNotification('Copied!', 'success');
|
|
});
|
|
}
|
|
|
|
function toggleVisibility(elementId) {
|
|
const input = document.getElementById(elementId);
|
|
|
|
let icon = input.closest('.input-with-actions')?.querySelector('.password-toggle i');
|
|
if (!icon) {
|
|
icon = input.closest('.field-wrapper')?.querySelector('.toggle-btn i');
|
|
}
|
|
if (!icon) {
|
|
icon = input.closest('.detail-row')?.querySelector('.password-toggle i');
|
|
}
|
|
|
|
if (input.type === 'password') {
|
|
input.type = 'text';
|
|
if (icon) icon.className = 'fas fa-eye-slash';
|
|
} else {
|
|
input.type = 'password';
|
|
if (icon) icon.className = 'fas fa-eye';
|
|
}
|
|
}
|
|
|
|
function showNotification(message, type) {
|
|
const existing = document.querySelector('.notification');
|
|
if (existing) existing.remove();
|
|
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification ${type}`;
|
|
notification.textContent = message;
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => notification.classList.add('show'), 10);
|
|
setTimeout(() => {
|
|
notification.classList.remove('show');
|
|
setTimeout(() => notification.remove(), 300);
|
|
}, 3000);
|
|
}
|
|
|
|
function showLoading(show) {
|
|
document.querySelectorAll('.primary-btn').forEach(btn => {
|
|
btn.disabled = show;
|
|
show ? btn.classList.add('loading') : btn.classList.remove('loading');
|
|
});
|
|
}
|
|
|
|
// Theme Management
|
|
function initializeTheme() {
|
|
const savedTheme = localStorage.getItem('theme') || 'dark';
|
|
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
updateThemeIcon(savedTheme);
|
|
}
|
|
|
|
function updateThemeIcon(theme) {
|
|
const icon = document.querySelector('#themeToggle i');
|
|
if (icon) {
|
|
icon.className = theme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
|
|
}
|
|
}
|
|
|
|
// Initialize theme toggle
|
|
document.getElementById('themeToggle')?.addEventListener('click', () => {
|
|
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
document.documentElement.setAttribute('data-theme', newTheme);
|
|
localStorage.setItem('theme', newTheme);
|
|
updateThemeIcon(newTheme);
|
|
});
|
|
|
|
// Navigation for sidebar and bottom nav
|
|
function initializeNavigation() {
|
|
const navLinks = document.querySelectorAll('.nav-link, .nav-btn');
|
|
const sidebarOverlay = document.getElementById('sidebarOverlay');
|
|
const sidebar = document.getElementById('sidebar');
|
|
|
|
navLinks.forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const page = link.getAttribute('data-page');
|
|
|
|
// Use existing switchTab function
|
|
switchTabByPage(page);
|
|
|
|
// Update active states for both sidebar and bottom nav
|
|
document.querySelectorAll('.nav-link, .nav-btn').forEach(l => l.classList.remove('active'));
|
|
document.querySelectorAll(`[data-page="${page}"]`).forEach(l => l.classList.add('active'));
|
|
|
|
document.querySelectorAll('.tabs .tab-btn').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
if (btn.getAttribute('onclick')?.includes(page)) {
|
|
btn.classList.add('active');
|
|
}
|
|
});
|
|
|
|
// Close sidebar on mobile
|
|
if (sidebar) sidebar.classList.remove('active');
|
|
if (sidebarOverlay) sidebarOverlay.classList.remove('active');
|
|
});
|
|
});
|
|
|
|
if (sidebarOverlay) {
|
|
sidebarOverlay.addEventListener('click', () => {
|
|
sidebar?.classList.remove('active');
|
|
sidebarOverlay.classList.remove('active');
|
|
});
|
|
}
|
|
}
|
|
|
|
function switchTabByPage(page) {
|
|
// Hide all tab contents
|
|
document.querySelectorAll('.tab-content').forEach(content => {
|
|
content.classList.remove('active');
|
|
content.classList.add('hidden');
|
|
});
|
|
// Show selected tab
|
|
const targetTab = document.getElementById(`${page}-tab`);
|
|
if (targetTab) {
|
|
targetTab.classList.add('active');
|
|
targetTab.classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
|
|
// Initialize on DOM ready
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initializeTheme();
|
|
initializeNavigation();
|
|
initializeSearchTypeSelectors();
|
|
checkUrlParams();
|
|
loadRecentSearches();
|
|
});
|
|
|
|
// Handle browser back/forward navigation
|
|
window.addEventListener('popstate', () => {
|
|
console.log('Browser navigation detected, checking URL params...');
|
|
checkUrlParams();
|
|
});
|
|
</script>
|
|
|
|
<!-- Bottom Navigation (Mobile) -->
|
|
<nav class="nav-box">
|
|
<button class="nav-btn active" data-page="generate">
|
|
<i class="fas fa-plus-circle"></i>
|
|
<span>Generate</span>
|
|
</button>
|
|
<button class="nav-btn" data-page="transactions">
|
|
<i class="fas fa-history"></i>
|
|
<span>Transactions</span>
|
|
</button>
|
|
<button class="nav-btn" data-page="send">
|
|
<i class="fas fa-paper-plane"></i>
|
|
<span>Send</span>
|
|
</button>
|
|
<button class="nav-btn" data-page="recover">
|
|
<i class="fas fa-key"></i>
|
|
<span>Recover</span>
|
|
</button>
|
|
</nav>
|
|
</body>
|
|
</html>
|