dappbundle/algorandwallet/index.html

1887 lines
92 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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 (ALGO/FLO/BTC)" />
<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 or BTC/FLO/ALGO private key to view transactions</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">Block</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 oninput="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()">&times;</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 isWifKey = !hexOnly && !(/^[A-Z2-7]+$/.test(privateKey)) && privateKey.length >= 51 && privateKey.length <= 52;
// Reject if it's not a valid private key format
if (!isHexKey && !isWifKey) {
if (/^[A-Z2-7]+$/.test(privateKey) && privateKey.length === 52) {
showNotification('⚠️ This looks like a transaction ID, not a private key. Private keys cannot be recovered from transaction IDs.', 'error');
} else {
showNotification('⚠️ Invalid private key format. Please enter a valid 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 (hex/WIF format)
if (input.length === 58) {
// It's an ALGO address - validate it contains only valid base32 characters
const validAlgoChars = /^[A-Z2-7]+$/;
if (!validAlgoChars.test(input)) {
showNotification('⚠️ Invalid ALGO address format', 'warning');
searchBtn.disabled = false;
searchBtn.innerHTML = originalContent;
return;
}
address = input;
} else {
// Check if it's a valid private key format
const hexOnly = /^[0-9a-fA-F]+$/.test(input);
const isHexKey = hexOnly && (input.length === 64 || input.length === 128);
const isWifKey = !hexOnly && !(/^[A-Z2-7]+$/.test(input)) && input.length >= 51 && input.length <= 52;
if (isHexKey || isWifKey) {
// 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 if (/^[A-Z2-7]+$/.test(input) && input.length === 52) {
showNotification('⚠️ This looks like a transaction ID. Please use "Transaction Hash" search instead', 'warning');
searchBtn.disabled = false;
searchBtn.innerHTML = originalContent;
return;
} else {
showNotification('⚠️ Invalid format. Enter a valid ALGO address (58 chars) 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();
// Validate private key format
const hexOnly = /^[0-9a-fA-F]+$/.test(privateKey);
const isHexKey = hexOnly && (privateKey.length === 64 || privateKey.length === 128);
// Check for WIF key (Base58: 51-52 chars, NOT Base32)
const isBase32 = /^[A-Z2-7]+$/.test(privateKey); // Transaction IDs are Base32
const isWifKey = !hexOnly && !isBase32 && privateKey.length >= 51 && privateKey.length <= 52;
if (!isHexKey && !isWifKey) {
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;
let hasMoreTransactions = true;
async function loadTransactions(reset = false) {
if (!currentAlgoAddress) return;
try {
if (reset) {
allTransactions = [];
currentPage = 1;
txNextToken = null;
hasMoreTransactions = true;
}
// Load only one batch of transactions
if (hasMoreTransactions) {
const result = await algoAPI.getTransactions(currentAlgoAddress, {
limit: 10,
next: txNextToken
});
allTransactions = [...allTransactions, ...result.transactions];
txNextToken = result.nextToken;
hasMoreTransactions = result.hasMore;
}
totalPages = Math.ceil(allTransactions.length / ITEMS_PER_PAGE);
if (hasMoreTransactions) {
totalPages++; // Add one more page to show "next" is available
}
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;
// Enable next button if we're not on last page OR if there are more transactions to load
document.getElementById('next-page-btn').disabled = currentPage >= totalPages && !hasMoreTransactions;
paginationEl.style.display = filteredTransactions.length > 0 ? 'flex' : 'none';
}
function prevPage() {
if (currentPage > 1) {
currentPage--;
displayCurrentPage();
}
}
async function nextPage() {
// Check if we need to load more transactions
const neededTransactions = (currentPage + 1) * ITEMS_PER_PAGE;
if (neededTransactions > allTransactions.length && hasMoreTransactions) {
currentPage++;
await loadTransactions();
} else 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);
// Validate private key format
const hexOnly = /^[0-9a-fA-F]+$/.test(privateKey);
const isHexKey = hexOnly && (privateKey.length === 64 || privateKey.length === 128);
// Check for WIF key (Base58: 51-52 chars, NOT Base32)
const isBase32 = /^[A-Z2-7]+$/.test(privateKey); // Transaction IDs are Base32
const isWifKey = !hexOnly && !isBase32 && privateKey.length >= 51 && privateKey.length <= 52;
if (!isHexKey && !isWifKey) {
if (isBase32 && privateKey.length === 52) {
showNotification('⚠️ This looks like a transaction ID, not a private key. Please enter a valid private key.', 'warning');
} else {
showNotification('⚠️ Invalid private key format. 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;
const minBalance = accountInfo.minBalance / 1000000; // Convert microAlgos to ALGO
// Get transaction parameters
const txParams = await algoAPI.getTransactionParams();
const feeAlgo = txParams.fee / 1000000;
const totalAlgo = amount + feeAlgo;
// Calculate remaining balance after transaction
const remainingBalance = currentBalance - totalAlgo;
// Check if balance is sufficient (must have enough for amount + fee)
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;
}
// Check if remaining balance meets minimum balance requirement
if (remainingBalance < minBalance) {
const maxSendable = currentBalance - minBalance - feeAlgo;
const errorMsg = `Transaction would leave account below minimum balance! Minimum balance required: ${minBalance.toFixed(6)} ALGO. After sending ${amount.toFixed(6)} ALGO + ${feeAlgo.toFixed(6)} fee, only ${remainingBalance.toFixed(6)} ALGO would remain.`;
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>Below Minimum 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(--warning-color);">
<span class="detail-label">Remaining After TX</span>
<span class="detail-value">${remainingBalance.toFixed(6)} ALGO</span>
</div>
<div class="tx-detail-row" style="color: var(--error-color); font-weight: 600;">
<span class="detail-label">Minimum Required</span>
<span class="detail-value">${minBalance.toFixed(6)} ALGO</span>
</div>
<div class="tx-detail-row" style="color: var(--success-color); font-weight: 600;">
<span class="detail-label">Max You Can Send</span>
<span class="detail-value">${maxSendable > 0 ? maxSendable.toFixed(6) : '0.000000'} 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>