stellarwallet/index.html

1981 lines
97 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stellar Wallet - RanchiMall</title>
<link rel="icon" type="image/svg+xml" href="stellar_favicon.svg">
<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">Stellar 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, XLM 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, XLM 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;">
<!-- XLM Section -->
<div class="blockchain-section xlm-primary">
<div class="blockchain-header">
<h4>
<svg class="chain-icon-svg" viewBox="0 0 236.36 200" fill="currentColor" style="width: 20px; height: 20px;">
<path d="M203,26.16l-28.46,14.5-137.43,70a82.49,82.49,0,0,1-.7-10.69A81.87,81.87,0,0,1,158.2,28.6l16.29-8.3,2.43-1.24A100,100,0,0,0,18.18,100q0,3.82.29,7.61a18.19,18.19,0,0,1-9.88,17.58L0,129.57V150l25.29-12.89,0,0,8.19-4.18,8.07-4.11v0L186.43,55l16.28-8.29,33.65-17.15V9.14Z"/>
<path d="M236.36,50,49.78,145,33.5,153.31,0,170.38v20.41l33.27-16.95,28.46-14.5L199.3,89.24A83.45,83.45,0,0,1,200,100,81.87,81.87,0,0,1,78.09,171.36l-1,.53-17.66,9A100,100,0,0,0,218.18,100c0-2.57-.1-5.14-.29-7.68a18.2,18.2,0,0,1,9.87-17.58l8.6-4.38Z"/>
</svg>
XLM (Stellar)
</h4>
<span class="blockchain-badge primary">Primary</span>
</div>
<div class="detail-row">
<label>Address</label>
<div class="detail-value-wrapper">
<code id="xlm-address" class="detail-value">-</code>
<button class="input-action-btn clear-btn" onclick="copyToClipboard('xlm-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="xlm-privateKey" class="form-input" value="-" readonly />
<button type="button" class="input-action-btn password-toggle" onclick="toggleVisibility('xlm-privateKey')">
<i class="fas fa-eye"></i>
</button>
<button type="button" class="input-action-btn clear-btn" onclick="copyPrivateKey('xlm-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> XLM Transactions</h2>
<p>Check balance and transaction history for any Stellar (XLM) 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>XLM 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> Stellar Address or Private Key</label>
<div class="input-with-actions">
<input type="text" id="tx-search-input" class="form-input" placeholder="Enter Stellar address or private key (XLM/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 a Stellar address or BTC/FLO/XLM 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="xlm-balance">0 <span class="currency">XLM</span></div>
</div>
<div class="address-display">
<span class="address-label">Address:</span>
<span class="address-value" id="xlm-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 XLM to another Stellar address</p>
</div>
<div class="card">
<form id="sendForm" onsubmit="event.preventDefault(); sendxlm();">
<div class="form-group">
<label for="send-privatekey"><i class="fas fa-key"></i> Private Key (BTC/FLO/XLM)</label>
<div class="input-with-actions">
<input type="password" id="send-privatekey" class="form-input" placeholder="Enter BTC/FLO/XLM 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">XLM</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 Stellar address (56 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 (XLM)</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, XLM) 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/XLM)</label>
<div class="input-with-actions">
<input type="password" id="privateKeyInput" class="form-input" placeholder="Enter BTC/FLO/XLM 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;">
<!-- XLM Section -->
<div class="blockchain-section xlm-primary">
<div class="blockchain-header">
<h4>
<svg class="chain-icon-svg" viewBox="0 0 236.36 200" fill="currentColor" style="width: 20px; height: 20px;">
<path d="M203,26.16l-28.46,14.5-137.43,70a82.49,82.49,0,0,1-.7-10.69A81.87,81.87,0,0,1,158.2,28.6l16.29-8.3,2.43-1.24A100,100,0,0,0,18.18,100q0,3.82.29,7.61a18.19,18.19,0,0,1-9.88,17.58L0,129.57V150l25.29-12.89,0,0,8.19-4.18,8.07-4.11v0L186.43,55l16.28-8.29,33.65-17.15V9.14Z"/>
<path d="M236.36,50,49.78,145,33.5,153.31,0,170.38v20.41l33.27-16.95,28.46-14.5L199.3,89.24A83.45,83.45,0,0,1,200,100,81.87,81.87,0,0,1,78.09,171.36l-1,.53-17.66,9A100,100,0,0,0,218.18,100c0-2.57-.1-5.14-.29-7.68a18.2,18.2,0,0,1,9.87-17.58l8.6-4.38Z"/>
</svg>
XLM (Stellar)
</h4>
<span class="blockchain-badge primary">Primary</span>
</div>
<div class="detail-row">
<label>Address</label>
<div class="detail-value-wrapper">
<code id="recover-xlm-address" class="detail-value">-</code>
<button class="input-action-btn clear-btn" onclick="copyToClipboard('recover-xlm-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-xlm-privateKey" class="form-input" value="-" readonly />
<button type="button" class="input-action-btn password-toggle" onclick="toggleVisibility('recover-xlm-privateKey')">
<i class="fas fa-eye"></i>
</button>
<button type="button" class="input-action-btn clear-btn" onclick="copyPrivateKey('recover-xlm-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.0000000 XLM</span>
</div>
<div class="confirm-row fee-row">
<span class="confirm-label">Network Fee:</span>
<span class="confirm-value fee" id="confirm-fee">0.00001 XLM</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.00001 XLM</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.0000000 XLM</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.00001 XLM</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>
<!-- Stellar SDK for transaction building and signing -->
<script src="https://cdn.jsdelivr.net/npm/stellar-sdk@11.3.0/dist/stellar-sdk.min.js"></script>
<script src="lib.stellar.js"></script>
<script src="stellarCrypto.js"></script>
<!-- Stellar blockchain API integration -->
<script src="stellarBlockchainAPI.js"></script>
<script src="stellarSearchDB.js"></script>
<script>
let currentXlmAddress = null;
let currentXlmPrivateKey = null;
let txNextToken = null;
let currentSearchType = 'address';
// SearchDB for storing recent searches
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') {
searchxlmAddress();
} 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 stellarCrypto.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('xlm-address').textContent = result.XLM.address;
document.getElementById('xlm-privateKey').value = result.XLM.privateKey;
// Store XLM keys
currentxlmAddress = result.XLM.address;
currentxlmPrivateKey = result.XLM.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;
const isStellarSecret = privateKey.startsWith('S') && privateKey.length === 56 && /^[A-Z2-7]+$/.test(privateKey);
// Reject if it's not a valid private key format
if (!isHexKey && !isWifKey && !isStellarSecret) {
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 if (privateKey.startsWith('G') && privateKey.length === 56) {
showNotification('⚠️ This is a Stellar public address, not a secret key. Please enter the secret key (starts with S).', 'error');
} else {
showNotification('⚠️ Invalid private key format. Please enter a valid private key (hex, WIF, or Stellar secret key starting with S)', 'error');
}
return;
}
try {
showLoading(true);
const result = await stellarCrypto.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-xlm-address').textContent = result.XLM.address;
document.getElementById('recover-xlm-privateKey').value = result.XLM.privateKey;
// Store XLM keys
currentxlmAddress = result.XLM.address;
currentxlmPrivateKey = result.XLM.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 = '';
currentxlmAddress = 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 searchxlmAddress() {
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 a Stellar address (56 chars, starts with G)
if (input.length === 56 && input.startsWith('G')) {
// It's a Stellar address - validate it contains only valid base32 characters
const validStellarChars = /^[A-Z2-7]+$/;
if (!validStellarChars.test(input)) {
showNotification('⚠️ Invalid Stellar address format', 'warning');
searchBtn.disabled = false;
searchBtn.innerHTML = originalContent;
return;
}
address = input;
} else if (input.length === 56 && input.startsWith('S')) {
// It's a Stellar secret key - derive the address
const result = await stellarCrypto.generateMultiChain(input);
address = result.XLM.address;
sourceInfo = {
privateKey: input,
btcAddress: result.BTC.address,
floAddress: result.FLO.address
};
} else {
// Check if it's a valid BTC/FLO 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;
// Check if it's a hex transaction hash (64 hex chars, all lowercase or mixed case)
if (hexOnly && input.length === 64 && /[a-f]/.test(input.toLowerCase())) {
showNotification('⚠️ This looks like a transaction hash. Please use "Transaction Hash" search instead', 'warning');
searchBtn.disabled = false;
searchBtn.innerHTML = originalContent;
return;
}
if (isHexKey || isWifKey) {
// It's a BTC/FLO private key (WIF or hex), derive the Stellar address
const result = await stellarCrypto.generateMultiChain(input);
address = result.XLM.address;
sourceInfo = {
privateKey: input,
btcAddress: result.BTC.address,
floAddress: result.FLO.address
};
} else if (/^[A-Z2-7]+$/.test(input) && input.length === 64) {
showNotification('⚠️ This looks like a transaction hash. Please use "Transaction Hash" search instead', 'warning');
searchBtn.disabled = false;
searchBtn.innerHTML = originalContent;
return;
} else {
showNotification('⚠️ Invalid format. Enter a valid Stellar address (56 chars, starts with G) 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);
currentxlmAddress = address;
document.getElementById('xlm-display-address').textContent = address;
document.getElementById('tx-results').style.display = 'block';
document.getElementById('transactionFilterSection').style.display = 'block';
// Try to fetch balance and transactions
let balance = 0;
let isInactive = false;
try {
const accountInfo = await xlmAPI.getBalance(address);
balance = accountInfo.balanceXlm;
const balanceEl = document.getElementById('xlm-balance');
balanceEl.innerHTML = balance.toFixed(6) + ' <span class="currency">XLM</span>';
txNextToken = null;
await loadTransactions(true);
} catch (error) {
console.error('Error fetching data:', error);
isInactive = true;
// Show 0 balance for inactive accounts
const balanceEl = document.getElementById('xlm-balance');
balanceEl.innerHTML = '0 <span class="currency">XLM</span>';
document.getElementById('tx-history').innerHTML = '<div class="tx-empty">Account is inactive or not funded yet</div>';
}
// Save to database regardless of active/inactive status
await searchDB.saveSearchedAddress(
address,
balance,
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 xlmAPI.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.amountXlm || 0).toFixed(7) + ' XLM';
document.getElementById('tx-detail-fee').textContent = (tx.feeXlm || 0).toFixed(7) + ' XLM';
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://stellar.expert/explorer/public/tx/${tx.hash}`;
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;
searchxlmAddress();
} 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);
const isStellarSecret = privateKey.startsWith('S') && privateKey.length === 56 && /^[A-Z2-7]+$/.test(privateKey);
// 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 && !isStellarSecret) {
document.getElementById('send-wallet-info').style.display = 'none';
return;
}
try {
const result = await stellarCrypto.generateMultiChain(privateKey);
const address = result.XLM.address;
const accountInfo = await xlmAPI.getBalance(address);
const balanceEl = document.getElementById('send-balance');
balanceEl.innerHTML = accountInfo.balanceXlm.toFixed(7) + ' <span class="currency">XLM</span>';
document.getElementById('send-from-address').textContent = address.substring(0, 12) + '...' + address.substring(address.length - 12);
document.getElementById('send-wallet-info').style.display = 'block';
} catch (error) {
document.getElementById('send-wallet-info').style.display = 'none';
}
}
async function refreshxlmData() {
if (!currentxlmAddress) return;
const refreshBtn = document.querySelector('.transaction-header .btn-icon i');
if (refreshBtn) refreshBtn.classList.add('fa-spin');
try {
const accountInfo = await xlmAPI.getBalance(currentxlmAddress);
const balanceEl = document.getElementById('xlm-balance');
balanceEl.innerHTML = accountInfo.balanceXlm.toFixed(6) + ' <span class="currency">xlm</span>';
txNextToken = null;
await loadTransactions(true);
} catch (error) {
console.error('Error fetching data:', error);
const balanceEl = document.getElementById('xlm-balance');
balanceEl.innerHTML = '0 <span class="currency">xlm</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 (!currentxlmAddress) return;
try {
if (reset) {
allTransactions = [];
currentPage = 1;
txNextToken = null;
hasMoreTransactions = true;
}
// Load transactions - fetch one extra to check if there's more
if (hasMoreTransactions) {
// Fetch ITEMS_PER_PAGE + 1 to check if there's a next page
const result = await xlmAPI.getTransactions(currentxlmAddress, {
limit: ITEMS_PER_PAGE + 1,
next: txNextToken
});
// Get all fetched transactions
const fetchedTransactions = result.transactions || [];
// Add all fetched transactions to our array
allTransactions = [...allTransactions, ...fetchedTransactions];
// Determine if there are more transactions:
// If we got exactly limit+1 transactions, there might be more
// Also check the API's hasMore flag
if (fetchedTransactions.length === ITEMS_PER_PAGE + 1 && result.hasMore) {
hasMoreTransactions = true;
txNextToken = result.nextToken;
} else {
hasMoreTransactions = false;
txNextToken = null;
}
}
// If we have no transactions at all
if (allTransactions.length === 0) {
hasMoreTransactions = false;
}
totalPages = Math.ceil(allTransactions.length / ITEMS_PER_PAGE) || 1;
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 === currentxlmAddress);
} else if (currentTxFilter === 'received') {
filteredTransactions = allTransactions.filter(tx => tx.sender !== currentxlmAddress);
}
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;
// Disable next button if we're on the last page AND there are no more transactions to load
const isLastPage = currentPage >= totalPages;
document.getElementById('next-page-btn').disabled = isLastPage && !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 === currentxlmAddress;
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.amountXlm.toFixed(7)} XLM
</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.xlmAddress); // Track current displayed address
const shortAddr = search.xlmAddress.substring(0, 12) + '...' + search.xlmAddress.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}, 'xlm', '${search.xlmAddress}')" title="XLM">
<svg class="chain-icon-svg" viewBox="0 0 236.36 200" fill="currentColor">
<path d="M203,26.16l-28.46,14.5-137.43,70a82.49,82.49,0,0,1-.7-10.69A81.87,81.87,0,0,1,158.2,28.6l16.29-8.3,2.43-1.24A100,100,0,0,0,18.18,100q0,3.82.29,7.61a18.19,18.19,0,0,1-9.88,17.58L0,129.57V150l25.29-12.89,0,0,8.19-4.18,8.07-4.11v0L186.43,55l16.28-8.29,33.65-17.15V9.14Z"/>
<path d="M236.36,50,49.78,145,33.5,153.31,0,170.38v20.41l33.27-16.95,28.46-14.5L199.3,89.24A83.45,83.45,0,0,1,200,100,81.87,81.87,0,0,1,78.09,171.36l-1,.53-17.66,9A100,100,0,0,0,218.18,100c0-2.57-.1-5.14-.29-7.68a18.2,18.2,0,0,1,9.87-17.58l8.6-4.38Z"/>
</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.xlmAddress}">
${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.xlmAddress}')" 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 searchxlmAddress();
}
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 sendxlm() {
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);
const isStellarSecret = privateKey.startsWith('S') && privateKey.length === 56 && /^[A-Z2-7]+$/.test(privateKey);
// 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 && !isStellarSecret) {
if (isBase32 && privateKey.length === 64) {
showNotification('⚠️ This looks like a transaction hash, not a private key. Please enter a valid private key.', 'warning');
} else if (privateKey.startsWith('G') && privateKey.length === 56) {
showNotification('⚠️ This is a public address, not a secret key. Please enter a secret key (starts with S).', 'warning');
} else {
showNotification('⚠️ Invalid private key format. Please enter a valid private key (hex, WIF, or Stellar secret key)', 'warning');
}
return;
}
if (!recipient || recipient.length !== 56 || !recipient.startsWith('G')) {
showNotification('⚠️ Please enter valid Stellar recipient address (56 chars, starts with G)', '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 stellarCrypto.generateMultiChain(privateKey);
const fromAddress = walletResult.XLM.address;
const xlmPrivateKey = walletResult.XLM.privateKey;
// Get current balance
const accountInfo = await xlmAPI.getBalance(fromAddress);
const currentBalance = accountInfo.balanceXlm;
const minBalance = accountInfo.minBalance; // Already in XLM
// Get transaction parameters
const txParams = await xlmAPI.getTransactionParams();
const feeXlm = txParams.fee / 10000000; // Convert stroops to XLM
const totalXlm = amount + feeXlm;
// Calculate remaining balance after transaction
const remainingBalance = currentBalance - totalXlm;
// Check if balance is sufficient (must have enough for amount + fee)
if (totalXlm > currentBalance) {
const errorMsg = `Insufficient balance! You need ${totalXlm.toFixed(7)} XLM (${amount.toFixed(7)} + ${feeXlm.toFixed(7)} fee) but only have ${currentBalance.toFixed(7)} XLM 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)} XLM</span>
</div>
<div class="tx-detail-row">
<span class="detail-label">Amount to Send</span>
<span class="detail-value">${amount.toFixed(6)} XLM</span>
</div>
<div class="tx-detail-row">
<span class="detail-label">Transaction Fee</span>
<span class="detail-value fee">${feeXLM.toFixed(6)} XLM</span>
</div>
<div class="tx-detail-row highlight" style="color: var(--error-color);">
<span class="detail-label">Total Required</span>
<span class="detail-value">${totalXLM.toFixed(6)} XLM</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">${(totalXLM - currentBalance).toFixed(6)} XLM</span>
</div>
</div>
</div>
`;
outputEl.style.display = 'block';
return;
}
// Check if remaining balance meets minimum balance requirement
if (remainingBalance < minBalance) {
const maxSendable = currentBalance - minBalance - feeXlm;
const errorMsg = `Transaction would leave account below minimum balance! Minimum balance required: ${minBalance.toFixed(7)} XLM. After sending ${amount.toFixed(7)} XLM + ${feeXlm.toFixed(7)} fee, only ${remainingBalance.toFixed(7)} XLM 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)} xlm</span>
</div>
<div class="tx-detail-row">
<span class="detail-label">Amount to Send</span>
<span class="detail-value">${amount.toFixed(6)} xlm</span>
</div>
<div class="tx-detail-row">
<span class="detail-label">Transaction Fee</span>
<span class="detail-value fee">${feeXlm.toFixed(7)} XLM</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)} xlm</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)} xlm</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'} xlm</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,
feeXlm: feeXlm,
total: totalXlm,
privateKey: xlmPrivateKey,
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(7) + ' XLM';
document.getElementById('confirm-fee').textContent = pendingTx.feeXlm.toFixed(7) + ' XLM';
document.getElementById('confirm-total').textContent = pendingTx.total.toFixed(7) + ' XLM';
document.getElementById('confirm-modal').style.display = 'flex';
}
function closeConfirmModal() {
document.getElementById('confirm-modal').style.display = 'none';
pendingTx = null;
}
async function confirmAndSend() {
if (!pendingTx) return;
// Check if Stellar SDK is initialized
if (!stellarAPI.isInitialized()) {
showNotification('❌ Stellar SDK not initialized. Attempting to initialize...', 'error');
const initSuccess = stellarAPI.forceInit();
if (!initSuccess) {
showNotification('❌ Failed to initialize Stellar SDK. Please refresh the page.', 'error');
closeConfirmModal();
return;
}
showNotification('Stellar SDK initialized! Please try sending again.', 'success');
closeConfirmModal();
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 using Stellar SDK
const signedTx = await stellarAPI.buildAndSignTransaction({
sourceAddress: pendingTx.from,
destinationAddress: pendingTx.to,
amount: pendingTx.amount.toString(),
secretKey: pendingTx.privateKey,
memo: null
});
// Submit transaction to network
const result = await stellarAPI.submitTransaction(signedTx.xdr);
lastTxId = result.hash;
// Save values before closing modal (closeConfirmModal sets pendingTx to null)
const txAmount = pendingTx.amount;
const txTo = pendingTx.to;
const txFee = pendingTx.feeXlm;
// Close confirm modal and show success
closeConfirmModal();
showSuccessModal(result.hash, 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 ? amount.toFixed(7) : '0.0000000') + ' XLM';
document.getElementById('success-to').textContent = to ? (to.substring(0, 12) + '...' + to.substring(50)) : '-';
document.getElementById('success-fee').textContent = (fee ? fee.toFixed(7) : '0.0000100') + ' XLM';
document.getElementById('success-modal').style.display = 'flex';
}
function closeSuccessModal() {
document.getElementById('success-modal').style.display = 'none';
}
function viewOnExplorer() {
if (lastTxId) {
window.open(`https://stellar.expert/explorer/public/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>