feat(ltc): Implement dynamic fee estimation and fix send functionality
Some checks failed
Workflow push to Dappbundle / Build (push) Has been cancelled
Some checks failed
Workflow push to Dappbundle / Build (push) Has been cancelled
- Added "View on Explorer" links for transactions. - Enhanced UI validation for send amounts and balances. - Optimized transaction confirmation popup with estimated fee display.
This commit is contained in:
parent
50f9c97362
commit
0ec93dd7d2
600
index.html
600
index.html
@ -1,42 +1,35 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
<meta name="title" content="RanchiMall Litecoin Web Wallet" />
|
<meta name="title" content="RanchiMall Litecoin Web Wallet" />
|
||||||
<meta
|
<meta name="description"
|
||||||
name="description"
|
content="RanchiMall Litecoin Web Wallet - Generate multi-blockchain addresses, send LTC, view transactions and manage your assets." />
|
||||||
content="RanchiMall Litecoin Web Wallet - Generate multi-blockchain addresses, send LTC, view transactions and manage your assets."
|
|
||||||
/>
|
|
||||||
|
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:title" content="RanchiMall Litecoin Web Wallet" />
|
<meta property="og:title" content="RanchiMall Litecoin Web Wallet" />
|
||||||
<meta
|
<meta property="og:description"
|
||||||
property="og:description"
|
content="RanchiMall Litecoin Web Wallet - Generate multi-blockchain addresses, send LTC, view transactions and manage your assets." />
|
||||||
content="RanchiMall Litecoin Web Wallet - Generate multi-blockchain addresses, send LTC, view transactions and manage your assets."
|
|
||||||
/>
|
|
||||||
|
|
||||||
<title>Litecoin Wallet</title>
|
<title>Litecoin Wallet</title>
|
||||||
<link
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
|
|
||||||
/>
|
|
||||||
<link rel="stylesheet" href="style.css" />
|
<link rel="stylesheet" href="style.css" />
|
||||||
<script src="litecoin/lib.litecoin.js"></script>
|
<script src="litecoin/lib.litecoin.js"></script>
|
||||||
<script src="litecoin/ltcCrypto.js"></script>
|
<script src="litecoin/ltcCrypto.js"></script>
|
||||||
<script src="litecoin/ltcBlockchainAPI.js"></script>
|
<script src="litecoin/ltcBlockchainAPI.js"></script>
|
||||||
<script src="litecoin/ltcSearchDB.js"></script>
|
<script src="litecoin/ltcSearchDB.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
|
||||||
|
<body>
|
||||||
<!-- Confirmation Popup -->
|
<!-- Confirmation Popup -->
|
||||||
<div id="confirmationPopup" class="modal">
|
<div id="confirmationPopup" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3 id="confirmTitle">Confirm Transaction</h3>
|
<h3 id="confirmTitle">Confirm Transaction</h3>
|
||||||
<span class="modal-close" onclick="closeConfirmationPopup()"
|
<span class="modal-close" onclick="closeConfirmationPopup()">×</span>
|
||||||
>×</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="confirm-details">
|
<div class="confirm-details">
|
||||||
@ -54,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="detail-group">
|
<div class="detail-group">
|
||||||
<label><i class="fas fa-money-bill-wave"></i> Fee:</label>
|
<label><i class="fas fa-money-bill-wave"></i> Fee:</label>
|
||||||
<div class="confirm-value">0.001 LTC (Network Fee)</div>
|
<div class="confirm-value">~0.00002 LTC (Network Fee)</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-group warning">
|
<div class="detail-group warning">
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
@ -83,8 +76,7 @@
|
|||||||
<svg id="main_logo" class="icon" viewBox="0 0 27.25 32">
|
<svg id="main_logo" class="icon" viewBox="0 0 27.25 32">
|
||||||
<title>RanchiMall</title>
|
<title>RanchiMall</title>
|
||||||
<path
|
<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.8,2.33q-.18.34-.39.69Z"
|
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.8,2.33q-.18.34-.39.69Z" />
|
||||||
/>
|
|
||||||
</svg>
|
</svg>
|
||||||
<div class="app-name">
|
<div class="app-name">
|
||||||
<div class="app-name__company">RanchiMall</div>
|
<div class="app-name__company">RanchiMall</div>
|
||||||
@ -92,11 +84,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<button
|
<button id="themeToggle" class="theme-toggle" title="Toggle dark/light mode">
|
||||||
id="themeToggle"
|
|
||||||
class="theme-toggle"
|
|
||||||
title="Toggle dark/light mode"
|
|
||||||
>
|
|
||||||
<i class="fas fa-sun" id="themeIcon"></i>
|
<i class="fas fa-sun" id="themeIcon"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -106,29 +94,20 @@
|
|||||||
<nav class="sidebar" id="sidebar">
|
<nav class="sidebar" id="sidebar">
|
||||||
<ul class="sidebar-menu">
|
<ul class="sidebar-menu">
|
||||||
<li>
|
<li>
|
||||||
<a href="#" onclick="showPage('walletPage')" class="nav-link active"
|
<a href="#" onclick="showPage('walletPage')" class="nav-link active"><i class="fas fa-wallet"></i>Generate</a>
|
||||||
><i class="fas fa-wallet"></i>Generate</a
|
|
||||||
>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" onclick="showPage('sendPage')" class="nav-link"
|
<a href="#" onclick="showPage('sendPage')" class="nav-link"><i class="fas fa-paper-plane"></i>Send</a>
|
||||||
><i class="fas fa-paper-plane"></i>Send</a
|
|
||||||
>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" onclick="showPage('transactionsPage')" class="nav-link"
|
<a href="#" onclick="showPage('transactionsPage')" class="nav-link"><i
|
||||||
><i class="fas fa-exchange-alt"></i>Transactions</a
|
class="fas fa-exchange-alt"></i>Transactions</a>
|
||||||
>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" onclick="showPage('translatePage')" class="nav-link"
|
<a href="#" onclick="showPage('translatePage')" class="nav-link"><i class="fas fa-sync-alt"></i>Translate</a>
|
||||||
><i class="fas fa-sync-alt"></i>Translate</a
|
|
||||||
>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" onclick="showPage('recoverPage')" class="nav-link"
|
<a href="#" onclick="showPage('recoverPage')" class="nav-link"><i class="fas fa-key"></i>Recover</a>
|
||||||
><i class="fas fa-key"></i>Recover</a
|
|
||||||
>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
@ -161,14 +140,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="generate-actions">
|
<div class="generate-actions">
|
||||||
<button
|
<button id="generateBtn" class="btn btn-primary btn-block" onclick="generateMultiChain()">
|
||||||
id="generateBtn"
|
<span class="btn-text"><i class="fas fa-wallet"></i> Generate</span>
|
||||||
class="btn btn-primary btn-block"
|
|
||||||
onclick="generateMultiChain()"
|
|
||||||
>
|
|
||||||
<span class="btn-text"
|
|
||||||
><i class="fas fa-wallet"></i> Generate</span
|
|
||||||
>
|
|
||||||
<span class="btn-loading" style="display: none">
|
<span class="btn-loading" style="display: none">
|
||||||
<i class="fas fa-spinner fa-spin"></i> Generating...
|
<i class="fas fa-spinner fa-spin"></i> Generating...
|
||||||
</span>
|
</span>
|
||||||
@ -192,31 +165,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label
|
<label><i class="fas fa-key"></i> Private Key
|
||||||
><i class="fas fa-key"></i> Private Key
|
(LTC/DOGE/BTC/FLO):</label>
|
||||||
(LTC/DOGE/BTC/FLO):</label
|
|
||||||
>
|
|
||||||
<div class="input-with-actions">
|
<div class="input-with-actions">
|
||||||
<input
|
<input id="translateWIF" class="form-input" type="password"
|
||||||
id="translateWIF"
|
placeholder="Enter LTC / DOGE / BTC / FLO private key" />
|
||||||
class="form-input"
|
<button type="button" class="input-action-btn password-toggle"
|
||||||
type="password"
|
onclick="togglePasswordVisibility('translateWIF')" title="Show/Hide Password">
|
||||||
placeholder="Enter LTC / DOGE / BTC / FLO private key"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="input-action-btn password-toggle"
|
|
||||||
onclick="togglePasswordVisibility('translateWIF')"
|
|
||||||
title="Show/Hide Password"
|
|
||||||
>
|
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button type="button" class="input-action-btn clear-btn" onclick="clearInput('translateWIF')"
|
||||||
type="button"
|
title="Clear">
|
||||||
class="input-action-btn clear-btn"
|
|
||||||
onclick="clearInput('translateWIF')"
|
|
||||||
title="Clear"
|
|
||||||
>
|
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -226,14 +185,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="recover-actions">
|
<div class="recover-actions">
|
||||||
<button
|
<button class="btn btn-primary btn-block" onclick="translateAddress()" id="recoverBtn">
|
||||||
class="btn btn-primary btn-block"
|
<span class="btn-text"><i class="fas fa-search"></i> Recover Addresses</span>
|
||||||
onclick="translateAddress()"
|
|
||||||
id="recoverBtn"
|
|
||||||
>
|
|
||||||
<span class="btn-text"
|
|
||||||
><i class="fas fa-search"></i> Recover Addresses</span
|
|
||||||
>
|
|
||||||
<span class="btn-loading" style="display: none">
|
<span class="btn-loading" style="display: none">
|
||||||
<i class="fas fa-spinner fa-spin"></i> Recovering...
|
<i class="fas fa-spinner fa-spin"></i> Recovering...
|
||||||
</span>
|
</span>
|
||||||
@ -252,39 +205,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label
|
<label><i class="fas fa-map-marker-alt"></i> Blockchain
|
||||||
><i class="fas fa-map-marker-alt"></i> Blockchain
|
Address:</label>
|
||||||
Address:</label
|
|
||||||
>
|
|
||||||
<div class="input-with-actions">
|
<div class="input-with-actions">
|
||||||
<input
|
<input id="addressToTranslate" class="form-input" placeholder="Enter LTC / DOGE / BTC / FLO address" />
|
||||||
id="addressToTranslate"
|
<button type="button" class="input-action-btn clear-btn" onclick="clearInput('addressToTranslate')"
|
||||||
class="form-input"
|
title="Clear">
|
||||||
placeholder="Enter LTC / DOGE / BTC / FLO address"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="input-action-btn clear-btn"
|
|
||||||
onclick="clearInput('addressToTranslate')"
|
|
||||||
title="Clear"
|
|
||||||
>
|
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<small class="form-text"
|
<small class="form-text">Enter any blockchain address to see equivalent addresses on
|
||||||
>Enter any blockchain address to see equivalent addresses on
|
other chains</small>
|
||||||
other chains</small
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button class="btn btn-primary btn-block" onclick="translateDirectAddress()" id="translateBtn">
|
||||||
class="btn btn-primary btn-block"
|
<span class="btn-text"><i class="fas fa-exchange-alt"></i> Translate Address</span>
|
||||||
onclick="translateDirectAddress()"
|
|
||||||
id="translateBtn"
|
|
||||||
>
|
|
||||||
<span class="btn-text"
|
|
||||||
><i class="fas fa-exchange-alt"></i> Translate Address</span
|
|
||||||
>
|
|
||||||
<span class="btn-loading" style="display: none">
|
<span class="btn-loading" style="display: none">
|
||||||
<i class="fas fa-spinner fa-spin"></i> Translating...
|
<i class="fas fa-spinner fa-spin"></i> Translating...
|
||||||
</span>
|
</span>
|
||||||
@ -308,33 +243,16 @@
|
|||||||
<div class="search-type-selector">
|
<div class="search-type-selector">
|
||||||
<p class="search-type-label">Search Type:</p>
|
<p class="search-type-label">Search Type:</p>
|
||||||
<div class="search-type-options">
|
<div class="search-type-options">
|
||||||
<label
|
<label class="radio-button-container active" id="balanceHistoryRadio">
|
||||||
class="radio-button-container active"
|
<input type="radio" name="searchType" value="balance" checked
|
||||||
id="balanceHistoryRadio"
|
onclick="toggleSearchType('balance'); return true;" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="searchType"
|
|
||||||
value="balance"
|
|
||||||
checked
|
|
||||||
onclick="toggleSearchType('balance'); return true;"
|
|
||||||
/>
|
|
||||||
<span class="radio-icon"><i class="fas fa-coins"></i></span>
|
<span class="radio-icon"><i class="fas fa-coins"></i></span>
|
||||||
<span>Balance & History</span>
|
<span>Balance & History</span>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="radio-button-container" id="transactionDetailsRadio">
|
||||||
class="radio-button-container"
|
<input type="radio" name="searchType" value="transaction"
|
||||||
id="transactionDetailsRadio"
|
onclick=" toggleSearchType('transaction'); return true;" />
|
||||||
>
|
<span class="radio-icon"><i class="fas fa-exchange-alt"></i></span>
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="searchType"
|
|
||||||
value="transaction"
|
|
||||||
onclick=" toggleSearchType('transaction'); return true;"
|
|
||||||
/>
|
|
||||||
<span class="radio-icon"
|
|
||||||
><i class="fas fa-exchange-alt"></i
|
|
||||||
></span>
|
|
||||||
<span>Transaction Details</span>
|
<span>Transaction Details</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -343,34 +261,18 @@
|
|||||||
<!-- Balance & History Search Form -->
|
<!-- Balance & History Search Form -->
|
||||||
<div id="balanceHistorySearch">
|
<div id="balanceHistorySearch">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label
|
<label><i class="fas fa-map-marker-alt"></i> LTC/DOGE/BTC/FLO
|
||||||
><i class="fas fa-map-marker-alt"></i> LTC/DOGE/BTC/FLO
|
address:</label>
|
||||||
address:</label
|
|
||||||
>
|
|
||||||
<div class="input-with-actions">
|
<div class="input-with-actions">
|
||||||
<input
|
<input id="transactionAddr" class="form-input" placeholder="Enter LTC/DOGE/BTC/FLO address" />
|
||||||
id="transactionAddr"
|
<button type="button" class="input-action-btn clear-btn" onclick="clearInput('transactionAddr')"
|
||||||
class="form-input"
|
title="Clear">
|
||||||
placeholder="Enter LTC/DOGE/BTC/FLO address"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="input-action-btn clear-btn"
|
|
||||||
onclick="clearInput('transactionAddr')"
|
|
||||||
title="Clear"
|
|
||||||
>
|
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button class="btn btn-primary btn-block" id="loadTransactions" onclick="loadTransactions()">
|
||||||
class="btn btn-primary btn-block"
|
<span class="btn-text"><i class="fas fa-search"></i> Check Balance</span>
|
||||||
id="loadTransactions"
|
|
||||||
onclick="loadTransactions()"
|
|
||||||
>
|
|
||||||
<span class="btn-text"
|
|
||||||
><i class="fas fa-search"></i> Check Balance</span
|
|
||||||
>
|
|
||||||
<span class="btn-loading" style="display: none">
|
<span class="btn-loading" style="display: none">
|
||||||
<i class="fas fa-spinner fa-spin"></i> Loading...
|
<i class="fas fa-spinner fa-spin"></i> Loading...
|
||||||
</span>
|
</span>
|
||||||
@ -382,32 +284,15 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label><i class="fas fa-hashtag"></i> Transaction Hash:</label>
|
<label><i class="fas fa-hashtag"></i> Transaction Hash:</label>
|
||||||
<div class="input-with-actions">
|
<div class="input-with-actions">
|
||||||
<input
|
<input id="txHash" class="form-input" placeholder="Enter transaction hash" />
|
||||||
id="txHash"
|
<button type="button" class="input-action-btn clear-btn" onclick="clearInput('txHash')" title="Clear">
|
||||||
class="form-input"
|
|
||||||
placeholder="Enter transaction hash"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="input-action-btn clear-btn"
|
|
||||||
onclick="clearInput('txHash')"
|
|
||||||
title="Clear"
|
|
||||||
>
|
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<small class="form-text"
|
<small class="form-text">Enter a transaction hash to see details</small>
|
||||||
>Enter a transaction hash to see details</small
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button class="btn btn-primary btn-block" id="txSearchBtn" onclick="searchTransactionDetails();">
|
||||||
class="btn btn-primary btn-block"
|
<span class="btn-text"><i class="fas fa-search"></i> Search Transaction</span>
|
||||||
id="txSearchBtn"
|
|
||||||
onclick="searchTransactionDetails();"
|
|
||||||
>
|
|
||||||
<span class="btn-text"
|
|
||||||
><i class="fas fa-search"></i> Search Transaction</span
|
|
||||||
>
|
|
||||||
<span class="btn-loading" style="display: none">
|
<span class="btn-loading" style="display: none">
|
||||||
<i class="fas fa-spinner fa-spin"></i> Searching...
|
<i class="fas fa-spinner fa-spin"></i> Searching...
|
||||||
</span>
|
</span>
|
||||||
@ -418,18 +303,10 @@
|
|||||||
<!-- Balance & History Results -->
|
<!-- Balance & History Results -->
|
||||||
<div id="balanceHistoryResults">
|
<div id="balanceHistoryResults">
|
||||||
<!-- Account Balance section -->
|
<!-- Account Balance section -->
|
||||||
<div
|
<div id="balanceSection" class="card balance-info" style="display: none">
|
||||||
id="balanceSection"
|
|
||||||
class="card balance-info"
|
|
||||||
style="display: none"
|
|
||||||
>
|
|
||||||
<div class="balance-header">
|
<div class="balance-header">
|
||||||
<h3><i class="fas fa-coins"></i> Account Balance</h3>
|
<h3><i class="fas fa-coins"></i> Account Balance</h3>
|
||||||
<button
|
<button onclick="shareAddress()" class="btn-icon share-btn" title="Copy Address Link">
|
||||||
onclick="shareAddress()"
|
|
||||||
class="btn-icon share-btn"
|
|
||||||
title="Copy Address Link"
|
|
||||||
>
|
|
||||||
<i class="fas fa-share-alt"></i>
|
<i class="fas fa-share-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -453,25 +330,13 @@
|
|||||||
<div class="transaction-header">
|
<div class="transaction-header">
|
||||||
<h3>Transactions</h3>
|
<h3>Transactions</h3>
|
||||||
<div class="filter-buttons">
|
<div class="filter-buttons">
|
||||||
<button
|
<button class="filter-btn active" data-filter="all" onclick="filterTransactions('all')">
|
||||||
class="filter-btn active"
|
|
||||||
data-filter="all"
|
|
||||||
onclick="filterTransactions('all')"
|
|
||||||
>
|
|
||||||
All
|
All
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="filter-btn" data-filter="received" onclick="filterTransactions('received')">
|
||||||
class="filter-btn"
|
|
||||||
data-filter="received"
|
|
||||||
onclick="filterTransactions('received')"
|
|
||||||
>
|
|
||||||
Received
|
Received
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="filter-btn" data-filter="sent" onclick="filterTransactions('sent')">
|
||||||
class="filter-btn"
|
|
||||||
data-filter="sent"
|
|
||||||
onclick="filterTransactions('sent')"
|
|
||||||
>
|
|
||||||
Sent
|
Sent
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -483,21 +348,11 @@
|
|||||||
Showing 0 - 0 of 0 transactions
|
Showing 0 - 0 of 0 transactions
|
||||||
</div>
|
</div>
|
||||||
<div class="pagination-controls" id="paginationControls">
|
<div class="pagination-controls" id="paginationControls">
|
||||||
<button
|
<button class="pagination-btn" id="prevBtn" onclick="loadPreviousTransactions()" disabled>
|
||||||
class="pagination-btn"
|
|
||||||
id="prevBtn"
|
|
||||||
onclick="loadPreviousTransactions()"
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
<i class="fas fa-chevron-left"></i> Previous
|
<i class="fas fa-chevron-left"></i> Previous
|
||||||
</button>
|
</button>
|
||||||
<div class="page-numbers" id="pageNumbers"></div>
|
<div class="page-numbers" id="pageNumbers"></div>
|
||||||
<button
|
<button class="pagination-btn" id="nextBtn" onclick="loadNextTransactions()" disabled>
|
||||||
class="pagination-btn"
|
|
||||||
id="nextBtn"
|
|
||||||
onclick="loadNextTransactions()"
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
Next <i class="fas fa-chevron-right"></i>
|
Next <i class="fas fa-chevron-right"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -509,18 +364,10 @@
|
|||||||
<div id="txOutput" class="transaction-result"></div>
|
<div id="txOutput" class="transaction-result"></div>
|
||||||
|
|
||||||
<!-- Searched Addresses History -->
|
<!-- Searched Addresses History -->
|
||||||
<div
|
<div id="searchedAddressesContainer" class="card searched-addresses-card" style="display: none">
|
||||||
id="searchedAddressesContainer"
|
|
||||||
class="card searched-addresses-card"
|
|
||||||
style="display: none"
|
|
||||||
>
|
|
||||||
<div class="searched-addresses-header">
|
<div class="searched-addresses-header">
|
||||||
<h3><i class="fas fa-history"></i> Recent Addresses</h3>
|
<h3><i class="fas fa-history"></i> Recent Addresses</h3>
|
||||||
<button
|
<button onclick="clearAllSearchedAddresses()" class="btn-clear-all" title="Clear all">
|
||||||
onclick="clearAllSearchedAddresses()"
|
|
||||||
class="btn-clear-all"
|
|
||||||
title="Clear all"
|
|
||||||
>
|
|
||||||
<i class="fas fa-trash"></i> Clear All
|
<i class="fas fa-trash"></i> Clear All
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -542,55 +389,48 @@
|
|||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label
|
<label><i class="fas fa-key"></i> Sender's Private Key
|
||||||
><i class="fas fa-key"></i> Sender's Private Key
|
(LTC/DOGE/BTC/FLO):</label>
|
||||||
(LTC/DOGE/BTC/FLO):</label
|
|
||||||
>
|
|
||||||
<div class="input-with-actions">
|
<div class="input-with-actions">
|
||||||
<input
|
<input id="privateKey" class="form-input" type="password"
|
||||||
id="privateKey"
|
placeholder="Sender's Private Key (LTC/DOGE/BTC/FLO)" onchange="updateSenderAddress()"
|
||||||
class="form-input"
|
onpaste="setTimeout(updateSenderAddress, 10)" oninput="setTimeout(updateSenderAddress, 10)" />
|
||||||
type="password"
|
<button type="button" class="input-action-btn password-toggle"
|
||||||
placeholder="Sender's Private Key (LTC/DOGE/BTC/FLO)"
|
onclick="togglePasswordVisibility('privateKey')" title="Show/Hide Password">
|
||||||
onchange="updateSenderAddress()"
|
|
||||||
onpaste="setTimeout(updateSenderAddress, 10)"
|
|
||||||
oninput="setTimeout(updateSenderAddress, 10)"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="input-action-btn password-toggle"
|
|
||||||
onclick="togglePasswordVisibility('privateKey')"
|
|
||||||
title="Show/Hide Password"
|
|
||||||
>
|
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button type="button" class="input-action-btn clear-btn"
|
||||||
type="button"
|
onclick="clearInput('privateKey'); hideSenderBalanceCard()" title="Clear">
|
||||||
class="input-action-btn clear-btn"
|
|
||||||
onclick="clearInput('privateKey'); document.getElementById('senderAddress').value = ''"
|
|
||||||
title="Clear"
|
|
||||||
>
|
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Sender Balance Card (shown when private key is entered) -->
|
||||||
|
<div id="senderBalanceCard" class="card balance-info" style="display: none; margin-bottom: 1rem;">
|
||||||
|
<div class="balance-header">
|
||||||
|
<h3><i class="fas fa-wallet"></i> Sender's Wallet</h3>
|
||||||
|
</div>
|
||||||
|
<div class="balance-display">
|
||||||
|
<span class="balance-amount" id="senderBalanceValue">0</span>
|
||||||
|
<span class="currency">LTC</span>
|
||||||
|
</div>
|
||||||
|
<div class="account-details">
|
||||||
|
<div class="detail-row">
|
||||||
|
<label>Address:</label>
|
||||||
|
<div class="address-container">
|
||||||
|
<span id="senderAddress" class="address-text"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label
|
<label><i class="fas fa-user"></i> Recipient's LTC Address:</label>
|
||||||
><i class="fas fa-user"></i> Recipient's LTC Address:</label
|
|
||||||
>
|
|
||||||
<div class="input-with-actions">
|
<div class="input-with-actions">
|
||||||
<input
|
<input id="receiverAddress" class="form-input" placeholder="Recipient's LTC address (L, M, or ltc1)" />
|
||||||
id="receiverAddress"
|
<button type="button" class="input-action-btn clear-btn" onclick="clearInput('receiverAddress')"
|
||||||
class="form-input"
|
title="Clear">
|
||||||
placeholder="Recipient's LTC address (L, M, or ltc1)"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="input-action-btn clear-btn"
|
|
||||||
onclick="clearInput('receiverAddress')"
|
|
||||||
title="Clear"
|
|
||||||
>
|
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -599,36 +439,21 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label><i class="fas fa-coins"></i> Amount (LTC):</label>
|
<label><i class="fas fa-coins"></i> Amount (LTC):</label>
|
||||||
<div class="input-with-actions">
|
<div class="input-with-actions">
|
||||||
<input
|
<input id="sendAmount" class="form-input" type="number" step="0.00000001" min="0.0001"
|
||||||
id="sendAmount"
|
placeholder="Amount to send (LTC)" oninput="validateSendAmount()" />
|
||||||
class="form-input"
|
<button type="button" class="input-action-btn clear-btn"
|
||||||
type="number"
|
onclick="clearInput('sendAmount'); validateSendAmount()" title="Clear">
|
||||||
step="0.00000001"
|
|
||||||
min="1"
|
|
||||||
placeholder="Amount to send (LTC)"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="input-action-btn clear-btn"
|
|
||||||
onclick="clearInput('sendAmount')"
|
|
||||||
title="Clear"
|
|
||||||
>
|
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<small class="form-text"
|
<small class="form-text">Minimum: 0.0001 LTC | Fee: ~0.00002 LTC (calculated based on tx size)</small>
|
||||||
>Transaction fee (0.001 LTC) will be automatically added</small
|
<small id="amountError" class="form-text" style="color: var(--error-color, #e74c3c); display: none;">
|
||||||
>
|
<i class="fas fa-exclamation-circle"></i> <span id="amountErrorText"></span>
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button class="btn btn-primary btn-block" onclick="sendLtcRPC()" id="sendBtn">
|
||||||
class="btn btn-primary btn-block"
|
<span class="btn-text"><i class="fas fa-paper-plane"></i> Send LTC</span>
|
||||||
onclick="sendLtcRPC()"
|
|
||||||
id="sendBtn"
|
|
||||||
>
|
|
||||||
<span class="btn-text"
|
|
||||||
><i class="fas fa-paper-plane"></i> Send LTC</span
|
|
||||||
>
|
|
||||||
<span class="btn-loading" style="display: none">
|
<span class="btn-loading" style="display: none">
|
||||||
<i class="fas fa-spinner fa-spin"></i> Sending...
|
<i class="fas fa-spinner fa-spin"></i> Sending...
|
||||||
</span>
|
</span>
|
||||||
@ -642,39 +467,19 @@
|
|||||||
|
|
||||||
<!-- Bottom Navigation (Mobile) -->
|
<!-- Bottom Navigation (Mobile) -->
|
||||||
<div class="nav-box">
|
<div class="nav-box">
|
||||||
<button
|
<button onclick="showPage('walletPage')" class="nav-btn active" data-page="walletPage">
|
||||||
onclick="showPage('walletPage')"
|
|
||||||
class="nav-btn active"
|
|
||||||
data-page="walletPage"
|
|
||||||
>
|
|
||||||
<i class="fas fa-wallet"></i><span>Generate</span>
|
<i class="fas fa-wallet"></i><span>Generate</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onclick="showPage('sendPage')" class="nav-btn" data-page="sendPage">
|
||||||
onclick="showPage('sendPage')"
|
|
||||||
class="nav-btn"
|
|
||||||
data-page="sendPage"
|
|
||||||
>
|
|
||||||
<i class="fas fa-paper-plane"></i><span>Send</span>
|
<i class="fas fa-paper-plane"></i><span>Send</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onclick="showPage('transactionsPage')" class="nav-btn" data-page="transactionsPage">
|
||||||
onclick="showPage('transactionsPage')"
|
|
||||||
class="nav-btn"
|
|
||||||
data-page="transactionsPage"
|
|
||||||
>
|
|
||||||
<i class="fas fa-exchange-alt"></i><span>Transaction</span>
|
<i class="fas fa-exchange-alt"></i><span>Transaction</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onclick="showPage('translatePage')" class="nav-btn" data-page="translatePage">
|
||||||
onclick="showPage('translatePage')"
|
|
||||||
class="nav-btn"
|
|
||||||
data-page="translatePage"
|
|
||||||
>
|
|
||||||
<i class="fas fa-sync-alt"></i><span>Translate</span>
|
<i class="fas fa-sync-alt"></i><span>Translate</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onclick="showPage('recoverPage')" class="nav-btn" data-page="recoverPage">
|
||||||
onclick="showPage('recoverPage')"
|
|
||||||
class="nav-btn"
|
|
||||||
data-page="recoverPage"
|
|
||||||
>
|
|
||||||
<i class="fas fa-key"></i><span>Recover</span>
|
<i class="fas fa-key"></i><span>Recover</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -1283,8 +1088,7 @@
|
|||||||
for (const chain of ["LTC", "DOGE", "FLO", "BTC"]) {
|
for (const chain of ["LTC", "DOGE", "FLO", "BTC"]) {
|
||||||
if (result[chain]) {
|
if (result[chain]) {
|
||||||
formattedResult += `
|
formattedResult += `
|
||||||
<div class="blockchain-section " style="animation-delay: ${
|
<div class="blockchain-section " style="animation-delay: ${["DOGE", "FLO", "BTC", "LTC"].indexOf(chain) * 0.1
|
||||||
["DOGE", "FLO", "BTC", "LTC"].indexOf(chain) * 0.1
|
|
||||||
}s">
|
}s">
|
||||||
<div class="blockchain-header">
|
<div class="blockchain-header">
|
||||||
<h4><i class="fas fa-coins"></i> ${chain}</h4>
|
<h4><i class="fas fa-coins"></i> ${chain}</h4>
|
||||||
@ -1294,8 +1098,7 @@
|
|||||||
<label><i class="fas fa-map-marker-alt"></i> ${chain} Address</label>
|
<label><i class="fas fa-map-marker-alt"></i> ${chain} Address</label>
|
||||||
<div class="value-container">
|
<div class="value-container">
|
||||||
<code>${result[chain]}</code>
|
<code>${result[chain]}</code>
|
||||||
<button class="btn-icon" onclick="navigator.clipboard.writeText('${
|
<button class="btn-icon" onclick="navigator.clipboard.writeText('${result[chain]
|
||||||
result[chain]
|
|
||||||
}').then(()=>notify('${chain} address copied','success'))" title="Copy ${chain} Address">
|
}').then(()=>notify('${chain} address copied','success'))" title="Copy ${chain} Address">
|
||||||
<i class="fas fa-copy"></i>
|
<i class="fas fa-copy"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -1492,8 +1295,7 @@
|
|||||||
"none";
|
"none";
|
||||||
document.getElementById("txList").innerHTML = createErrorUI(
|
document.getElementById("txList").innerHTML = createErrorUI(
|
||||||
"Address Translation Failed",
|
"Address Translation Failed",
|
||||||
`Error: ${
|
`Error: ${error.message || "Unable to translate address to LTC equivalent"
|
||||||
error.message || "Unable to translate address to LTC equivalent"
|
|
||||||
}`,
|
}`,
|
||||||
"loadTransactions()"
|
"loadTransactions()"
|
||||||
);
|
);
|
||||||
@ -1654,8 +1456,7 @@
|
|||||||
<div class="transaction-card ${txClass}" data-tx-type="${txType.toLowerCase()}">
|
<div class="transaction-card ${txClass}" data-tx-type="${txType.toLowerCase()}">
|
||||||
<div class="tx-main">
|
<div class="tx-main">
|
||||||
<div class="tx-icon">
|
<div class="tx-icon">
|
||||||
<i class="fas ${
|
<i class="fas ${txClass === "incoming" ? "fa-arrow-down" : "fa-arrow-up"
|
||||||
txClass === "incoming" ? "fa-arrow-down" : "fa-arrow-up"
|
|
||||||
}"></i>
|
}"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="tx-info">
|
<div class="tx-info">
|
||||||
@ -1665,8 +1466,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="tx-right-header">
|
<div class="tx-right-header">
|
||||||
<span class="tx-date">${txDate}</span>
|
<span class="tx-date">${txDate}</span>
|
||||||
<span class="tx-status ${
|
<span class="tx-status ${tx.confirmations > 0 ? "success" : "pending"
|
||||||
tx.confirmations > 0 ? "success" : "pending"
|
|
||||||
}">
|
}">
|
||||||
${tx.confirmations > 0 ? "CONFIRMED" : "PENDING"}
|
${tx.confirmations > 0 ? "CONFIRMED" : "PENDING"}
|
||||||
</span>
|
</span>
|
||||||
@ -1679,8 +1479,7 @@
|
|||||||
<div class="tx-field">
|
<div class="tx-field">
|
||||||
<div class="tx-label">From:</div>
|
<div class="tx-label">From:</div>
|
||||||
<div class="tx-value">
|
<div class="tx-value">
|
||||||
${
|
${tx.fromAddresses && tx.fromAddresses.length > 0
|
||||||
tx.fromAddresses && tx.fromAddresses.length > 0
|
|
||||||
? tx.fromAddresses[0]
|
? tx.fromAddresses[0]
|
||||||
: "Unknown sender"
|
: "Unknown sender"
|
||||||
}
|
}
|
||||||
@ -1689,8 +1488,7 @@
|
|||||||
<div class="tx-field">
|
<div class="tx-field">
|
||||||
<div class="tx-label">To:</div>
|
<div class="tx-label">To:</div>
|
||||||
<div class="tx-value">
|
<div class="tx-value">
|
||||||
${
|
${tx.toAddresses && tx.toAddresses.length > 0
|
||||||
tx.toAddresses && tx.toAddresses.length > 0
|
|
||||||
? (() => {
|
? (() => {
|
||||||
// Filter out duplicate addresses
|
// Filter out duplicate addresses
|
||||||
const filteredAddresses = tx.toAddresses.filter(
|
const filteredAddresses = tx.toAddresses.filter(
|
||||||
@ -1717,8 +1515,7 @@
|
|||||||
<div class="tx-field">
|
<div class="tx-field">
|
||||||
<div class="tx-label">Tx:</div>
|
<div class="tx-label">Tx:</div>
|
||||||
<div class="tx-value tx-hash-value">
|
<div class="tx-value tx-hash-value">
|
||||||
<a href="#" class="txid-link" onclick="viewTransactionDetails('${
|
<a href="#" class="txid-link" onclick="viewTransactionDetails('${tx.txid
|
||||||
tx.txid
|
|
||||||
}'); return false;">${tx.txid.substring(
|
}'); return false;">${tx.txid.substring(
|
||||||
0,
|
0,
|
||||||
12
|
12
|
||||||
@ -1727,8 +1524,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="confirmation-count">
|
<div class="confirmation-count">
|
||||||
${tx.confirmations} confirmation${
|
${tx.confirmations} confirmation${tx.confirmations !== 1 ? "s" : ""
|
||||||
tx.confirmations !== 1 ? "s" : ""
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1737,8 +1533,7 @@
|
|||||||
|
|
||||||
document.getElementById("txList").innerHTML = transactionsHTML;
|
document.getElementById("txList").innerHTML = transactionsHTML;
|
||||||
|
|
||||||
document.getElementById("paginationInfo").innerHTML = `Showing ${
|
document.getElementById("paginationInfo").innerHTML = `Showing ${currentTxOffset + 1
|
||||||
currentTxOffset + 1
|
|
||||||
}-${Math.min(
|
}-${Math.min(
|
||||||
currentTxOffset + result.transactions.length,
|
currentTxOffset + result.transactions.length,
|
||||||
totalTxCount
|
totalTxCount
|
||||||
@ -1857,13 +1652,10 @@
|
|||||||
addr.sourceInfo && addr.sourceInfo.originalAddress !== addr.address;
|
addr.sourceInfo && addr.sourceInfo.originalAddress !== addr.address;
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
<div class="searched-address-item ${
|
<div class="searched-address-item ${hasSourceInfo ? "has-source-info" : ""
|
||||||
hasSourceInfo ? "has-source-info" : ""
|
}" data-index="${index}" data-current-type="${hasSourceInfo ? addr.sourceInfo.blockchain.toLowerCase() : "ltc"
|
||||||
}" data-index="${index}" data-current-type="${
|
|
||||||
hasSourceInfo ? addr.sourceInfo.blockchain.toLowerCase() : "ltc"
|
|
||||||
}">
|
}">
|
||||||
${
|
${hasSourceInfo
|
||||||
hasSourceInfo
|
|
||||||
? `
|
? `
|
||||||
<div class="address-toggle-section">
|
<div class="address-toggle-section">
|
||||||
<div class="address-toggle-group">
|
<div class="address-toggle-group">
|
||||||
@ -1884,8 +1676,7 @@
|
|||||||
<div class="address-content-wrapper">
|
<div class="address-content-wrapper">
|
||||||
<div class="address-info">
|
<div class="address-info">
|
||||||
<div class="address-display">
|
<div class="address-display">
|
||||||
<div class="address-text" id="address-display-${index}" title="${
|
<div class="address-text" id="address-display-${index}" title="${addr.sourceInfo.originalAddress
|
||||||
addr.sourceInfo.originalAddress
|
|
||||||
}">
|
}">
|
||||||
${addr.sourceInfo.originalAddress}
|
${addr.sourceInfo.originalAddress}
|
||||||
</div>
|
</div>
|
||||||
@ -1895,13 +1686,11 @@
|
|||||||
<button onclick="copyCurrentAddress(${index})" class="btn-copy-current" title="Copy Selected Address">
|
<button onclick="copyCurrentAddress(${index})" class="btn-copy-current" title="Copy Selected Address">
|
||||||
<i class="fas fa-copy"></i>
|
<i class="fas fa-copy"></i>
|
||||||
</button>
|
</button>
|
||||||
<button onclick="deleteSearchedAddress('${
|
<button onclick="deleteSearchedAddress('${addr.address
|
||||||
addr.address
|
|
||||||
}')" class="btn-delete" title="Delete">
|
}')" class="btn-delete" title="Delete">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
<button onclick="recheckBalance('${
|
<button onclick="recheckBalance('${addr.address
|
||||||
addr.address
|
|
||||||
}')" class="btn-check" title="Check balance">
|
}')" class="btn-check" title="Check balance">
|
||||||
<i class="fas fa-search"></i>
|
<i class="fas fa-search"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -2004,8 +1793,7 @@
|
|||||||
} else {
|
} else {
|
||||||
addressToCopy =
|
addressToCopy =
|
||||||
addressItem.sourceInfo?.originalAddress || addressItem.address;
|
addressItem.sourceInfo?.originalAddress || addressItem.address;
|
||||||
addressLabel = `${
|
addressLabel = `${addressItem.sourceInfo?.blockchain || "Original"
|
||||||
addressItem.sourceInfo?.blockchain || "Original"
|
|
||||||
} address`;
|
} address`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2108,7 +1896,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3>Transaction Successful!</h3>
|
<h3>Transaction Successful!</h3>
|
||||||
<p class="success-message">Your DOGE have been successfully sent.</p>
|
<p class="success-message">Your LTC have been successfully sent.</p>
|
||||||
<div class="tx-details-card">
|
<div class="tx-details-card">
|
||||||
<div class="tx-detail-item">
|
<div class="tx-detail-item">
|
||||||
<div class="tx-detail-label"><i class="fas fa-coins"></i> Amount</div>
|
<div class="tx-detail-label"><i class="fas fa-coins"></i> Amount</div>
|
||||||
@ -2143,6 +1931,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tx-detail-item" style="margin-top: 1rem;">
|
||||||
|
<a href="https://live.blockcypher.com/ltc/tx/${txid}/" target="_blank" rel="noopener noreferrer" class="btn btn-secondary btn-block" style="text-decoration: none; display: flex; align-items: center; justify-content: center; gap: 8px;">
|
||||||
|
<i class="fas fa-external-link-alt"></i> View on Explorer
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -2156,9 +1949,8 @@
|
|||||||
document.getElementById("sendResult").innerHTML =
|
document.getElementById("sendResult").innerHTML =
|
||||||
createTransactionErrorUI(
|
createTransactionErrorUI(
|
||||||
"Transaction Failed",
|
"Transaction Failed",
|
||||||
"Your DOGE transaction could not be processed.",
|
"Your LTC transaction could not be processed.",
|
||||||
`Error Details: ${
|
`Error Details: ${error.message || "Unknown error"
|
||||||
error.message || "Unknown error"
|
|
||||||
} (${new Date().toLocaleString()})`,
|
} (${new Date().toLocaleString()})`,
|
||||||
"sendLtcRPC()"
|
"sendLtcRPC()"
|
||||||
);
|
);
|
||||||
@ -2255,8 +2047,7 @@
|
|||||||
|
|
||||||
if (endPage < totalPages) {
|
if (endPage < totalPages) {
|
||||||
pageNumbersHTML += `
|
pageNumbersHTML += `
|
||||||
${
|
${endPage < totalPages - 1
|
||||||
endPage < totalPages - 1
|
|
||||||
? '<span class="page-ellipsis">...</span>'
|
? '<span class="page-ellipsis">...</span>'
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
@ -2380,8 +2171,7 @@
|
|||||||
<div class="card transaction-details">
|
<div class="card transaction-details">
|
||||||
<div class="transaction-header">
|
<div class="transaction-header">
|
||||||
<h3><i class="fas fa-exchange-alt"></i> Transaction Details</h3>
|
<h3><i class="fas fa-exchange-alt"></i> Transaction Details</h3>
|
||||||
<button onclick="shareTransactionLink('${
|
<button onclick="shareTransactionLink('${tx.txid
|
||||||
tx.txid
|
|
||||||
}')" class="btn-icon share-btn" title="Copy Transaction Link">
|
}')" class="btn-icon share-btn" title="Copy Transaction Link">
|
||||||
<i class="fas fa-share-alt"></i>
|
<i class="fas fa-share-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -2391,8 +2181,7 @@
|
|||||||
<label>Transaction ID:</label>
|
<label>Transaction ID:</label>
|
||||||
<div class="tx-detail-value">
|
<div class="tx-detail-value">
|
||||||
<span>${tx.txid}</span>
|
<span>${tx.txid}</span>
|
||||||
<button onclick="navigator.clipboard.writeText('${
|
<button onclick="navigator.clipboard.writeText('${tx.txid
|
||||||
tx.txid
|
|
||||||
}').then(()=>notify('Transaction ID copied','success'))" class="copy-btn" title="Copy TX ID">
|
}').then(()=>notify('Transaction ID copied','success'))" class="copy-btn" title="Copy TX ID">
|
||||||
<i class="fas fa-copy"></i>
|
<i class="fas fa-copy"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -2403,8 +2192,7 @@
|
|||||||
<label>Status:</label>
|
<label>Status:</label>
|
||||||
<div class="tx-detail-value">
|
<div class="tx-detail-value">
|
||||||
<span class="tx-status ${txStatusClass}">${txStatus}</span>
|
<span class="tx-status ${txStatusClass}">${txStatus}</span>
|
||||||
<span class="confirmations">${
|
<span class="confirmations">${tx.confirmations
|
||||||
tx.confirmations
|
|
||||||
} confirmation${tx.confirmations !== 1 ? "s" : ""}</span>
|
} confirmation${tx.confirmations !== 1 ? "s" : ""}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -2418,8 +2206,7 @@
|
|||||||
<label>Block:</label>
|
<label>Block:</label>
|
||||||
<div class="tx-detail-value">
|
<div class="tx-detail-value">
|
||||||
${tx.blockHeight || "Unconfirmed"}
|
${tx.blockHeight || "Unconfirmed"}
|
||||||
${
|
${tx.blockHash
|
||||||
tx.blockHash
|
|
||||||
? `<span class="tx-blockhash">(${tx.blockHash.substring(
|
? `<span class="tx-blockhash">(${tx.blockHash.substring(
|
||||||
0,
|
0,
|
||||||
10
|
10
|
||||||
@ -2436,27 +2223,27 @@
|
|||||||
|
|
||||||
<div class="tx-detail-row">
|
<div class="tx-detail-row">
|
||||||
<label>Fee:</label>
|
<label>Fee:</label>
|
||||||
<div class="tx-detail-value">${fee} DOGE</div>
|
<div class="tx-detail-value">${fee} LTC</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tx-detail-row">
|
<div class="tx-detail-row">
|
||||||
<label>Total Input:</label>
|
<label>Total Input:</label>
|
||||||
<div class="tx-detail-value">${tx.totalInput.toFixed(
|
<div class="tx-detail-value">${tx.totalInput.toFixed(
|
||||||
8
|
8
|
||||||
)} DOGE</div>
|
)} LTC</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tx-detail-row">
|
<div class="tx-detail-row">
|
||||||
<label>Total Output:</label>
|
<label>Total Output:</label>
|
||||||
<div class="tx-detail-value">${tx.totalOutput.toFixed(
|
<div class="tx-detail-value">${tx.totalOutput.toFixed(
|
||||||
8
|
8
|
||||||
)} DOGE</div>
|
)} LTC</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tx-detail-row highlight">
|
<div class="tx-detail-row highlight">
|
||||||
<label>Amount Sent:</label>
|
<label>Amount Sent:</label>
|
||||||
<div class="tx-detail-value">${calculateAmountSent(
|
<div class="tx-detail-value">${calculateAmountSent(
|
||||||
tx
|
tx
|
||||||
).toFixed(8)} DOGE</div>
|
).toFixed(8)} LTC</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -2473,7 +2260,7 @@
|
|||||||
<span class="tx-io-index">#${index}</span>
|
<span class="tx-io-index">#${index}</span>
|
||||||
<span class="tx-io-value">${input.value.toFixed(
|
<span class="tx-io-value">${input.value.toFixed(
|
||||||
8
|
8
|
||||||
)} DOGE</span>
|
)} LTC</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tx-io-addresses">`;
|
<div class="tx-io-addresses">`;
|
||||||
|
|
||||||
@ -2513,7 +2300,7 @@
|
|||||||
<span class="tx-io-index">#${output.n}</span>
|
<span class="tx-io-index">#${output.n}</span>
|
||||||
<span class="tx-io-value">${output.value.toFixed(
|
<span class="tx-io-value">${output.value.toFixed(
|
||||||
8
|
8
|
||||||
)} DOGE</span>
|
)} LTC</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tx-io-addresses">`;
|
<div class="tx-io-addresses">`;
|
||||||
@ -2541,7 +2328,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Transaction actions section removed as the share button is now in the header -->
|
<!-- View on Explorer button -->
|
||||||
|
<div class="tx-section" style="margin-top: 1rem;">
|
||||||
|
<a href="https://live.blockcypher.com/ltc/tx/${tx.txid}/" target="_blank" rel="noopener noreferrer" class="btn btn-secondary btn-block" style="text-decoration: none; display: flex; align-items: center; justify-content: center; gap: 8px;">
|
||||||
|
<i class="fas fa-external-link-alt"></i> View on Explorer
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -2729,26 +2521,93 @@
|
|||||||
|
|
||||||
function updateSenderAddress() {
|
function updateSenderAddress() {
|
||||||
const privateKey = document.getElementById("privateKey").value.trim();
|
const privateKey = document.getElementById("privateKey").value.trim();
|
||||||
|
const balanceCard = document.getElementById("senderBalanceCard");
|
||||||
const senderAddressField = document.getElementById("senderAddress");
|
const senderAddressField = document.getElementById("senderAddress");
|
||||||
|
const senderBalanceValue = document.getElementById("senderBalanceValue");
|
||||||
|
|
||||||
if (!privateKey) {
|
if (!privateKey) {
|
||||||
senderAddressField.value = "";
|
hideSenderBalanceCard();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const derivedAddress =
|
const derivedAddress =
|
||||||
ltcCrypto.generateMultiChain(privateKey).LTC.address;
|
ltcCrypto.generateMultiChain(privateKey).LTC.address;
|
||||||
senderAddressField.value = derivedAddress;
|
|
||||||
|
// Show the balance card and set address
|
||||||
|
balanceCard.style.display = "block";
|
||||||
|
senderAddressField.textContent = derivedAddress;
|
||||||
|
senderBalanceValue.textContent = "...";
|
||||||
|
|
||||||
|
// Fetch balance for the derived address
|
||||||
|
ltcBlockchainAPI.getBalance(derivedAddress)
|
||||||
|
.then((balance) => {
|
||||||
|
senderBalanceValue.textContent = balance.toFixed(8);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("[WALLET] Error fetching balance:", error);
|
||||||
|
senderBalanceValue.textContent = "unavailable";
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
"[WALLET] Error deriving address from private key:",
|
"[WALLET] Error deriving address from private key:",
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
senderAddressField.value = "Invalid private key format";
|
hideSenderBalanceCard();
|
||||||
|
notify("Invalid private key format", "error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hideSenderBalanceCard() {
|
||||||
|
const balanceCard = document.getElementById("senderBalanceCard");
|
||||||
|
const senderAddressField = document.getElementById("senderAddress");
|
||||||
|
const senderBalanceValue = document.getElementById("senderBalanceValue");
|
||||||
|
|
||||||
|
balanceCard.style.display = "none";
|
||||||
|
senderAddressField.textContent = "";
|
||||||
|
senderBalanceValue.textContent = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSendAmount() {
|
||||||
|
const sendAmountInput = document.getElementById("sendAmount");
|
||||||
|
const amountError = document.getElementById("amountError");
|
||||||
|
const amountErrorText = document.getElementById("amountErrorText");
|
||||||
|
const senderBalanceValue = document.getElementById("senderBalanceValue");
|
||||||
|
|
||||||
|
const amount = parseFloat(sendAmountInput.value);
|
||||||
|
const MIN_AMOUNT = 0.0001;
|
||||||
|
const FEE = 0.00005; // Estimated max fee for validation (actual fee calculated based on tx size)
|
||||||
|
|
||||||
|
// Hide error by default
|
||||||
|
amountError.style.display = "none";
|
||||||
|
sendAmountInput.style.borderColor = "";
|
||||||
|
|
||||||
|
if (!sendAmountInput.value || isNaN(amount)) {
|
||||||
|
return true; // No input yet, no error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount < MIN_AMOUNT) {
|
||||||
|
amountErrorText.textContent = `Amount too small. Minimum is ${MIN_AMOUNT} LTC.`;
|
||||||
|
amountError.style.display = "block";
|
||||||
|
sendAmountInput.style.borderColor = "var(--error-color, #e74c3c)";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if balance is available and sufficient
|
||||||
|
const balance = parseFloat(senderBalanceValue.textContent);
|
||||||
|
if (!isNaN(balance) && balance > 0) {
|
||||||
|
const totalNeeded = amount + FEE;
|
||||||
|
if (totalNeeded > balance) {
|
||||||
|
amountErrorText.textContent = `Insufficient balance. Need ${totalNeeded.toFixed(8)} LTC (${amount} + ${FEE} fee), but only have ${balance.toFixed(8)} LTC.`;
|
||||||
|
amountError.style.display = "block";
|
||||||
|
sendAmountInput.style.borderColor = "var(--error-color, #e74c3c)";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const confirmationPopup = document.getElementById("confirmationPopup");
|
const confirmationPopup = document.getElementById("confirmationPopup");
|
||||||
if (confirmationPopup) {
|
if (confirmationPopup) {
|
||||||
@ -2825,5 +2684,6 @@
|
|||||||
searchTransactionDetails();
|
searchTransactionDetails();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@ -3,9 +3,32 @@
|
|||||||
const ltcBlockchainAPI = EXPORTS;
|
const ltcBlockchainAPI = EXPORTS;
|
||||||
|
|
||||||
const DEFAULT = {
|
const DEFAULT = {
|
||||||
fee: 0.001,
|
// Fee rate in satoshis per byte (10 sat/byte is safe for Litecoin)
|
||||||
|
feeRateSatPerByte: 10,
|
||||||
|
// Fallback fixed fee in LTC (only used if calculation fails)
|
||||||
|
fallbackFee: 0.00001,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate transaction fee based on number of inputs and outputs
|
||||||
|
* Formula: (10 + inputs*148 + outputs*34) * satPerByte
|
||||||
|
* @param {number} numInputs - Number of transaction inputs
|
||||||
|
* @param {number} numOutputs - Number of transaction outputs
|
||||||
|
* @param {number} satPerByte - Fee rate in satoshis per byte (default 10)
|
||||||
|
* @returns {number} Fee in LTC
|
||||||
|
*/
|
||||||
|
function calculateFee(numInputs, numOutputs, satPerByte = DEFAULT.feeRateSatPerByte) {
|
||||||
|
// P2PKH transaction size estimation:
|
||||||
|
// - Overhead: ~10 bytes
|
||||||
|
// - Per input: ~148 bytes (for compressed pubkey signatures)
|
||||||
|
// - Per output: ~34 bytes
|
||||||
|
const estimatedSize = 10 + (numInputs * 148) + (numOutputs * 34);
|
||||||
|
const feeInSatoshis = estimatedSize * satPerByte;
|
||||||
|
const feeInLTC = feeInSatoshis / 100000000;
|
||||||
|
console.log(`Estimated tx size: ${estimatedSize} bytes, Fee: ${feeInLTC.toFixed(8)} LTC (${feeInSatoshis} satoshis)`);
|
||||||
|
return feeInLTC;
|
||||||
|
}
|
||||||
|
|
||||||
//Get balance for the given Address
|
//Get balance for the given Address
|
||||||
ltcBlockchainAPI.getBalance = function (addr) {
|
ltcBlockchainAPI.getBalance = function (addr) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -282,12 +305,12 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send Litecoin transaction using direct RPC calls to GetBlock.io
|
* Send Litecoin transaction using client-side signing with bitjs library
|
||||||
* This method implements the full RPC workflow: createrawtransaction -> signrawtransaction -> sendrawtransaction
|
* Transaction is constructed and signed locally, then broadcast via RPC
|
||||||
* @param {string} senderAddr - Sender's Litecoin address
|
* @param {string} senderAddr - Sender's Litecoin address
|
||||||
* @param {string} receiverAddr - Receiver's Litecoin address
|
* @param {string} receiverAddr - Receiver's Litecoin address
|
||||||
* @param {number} sendAmt - Amount to send in LTC
|
* @param {number} sendAmt - Amount to send in LTC
|
||||||
* @param {string} privKey - Private key of the sender
|
* @param {string} privKey - Private key of the sender (WIF format)
|
||||||
* @returns {Promise} Promise that resolves with the transaction ID
|
* @returns {Promise} Promise that resolves with the transaction ID
|
||||||
*/
|
*/
|
||||||
ltcBlockchainAPI.sendLitecoinRPC = function (
|
ltcBlockchainAPI.sendLitecoinRPC = function (
|
||||||
@ -303,10 +326,14 @@
|
|||||||
return reject(`Invalid receiver address: ${receiverAddr}`);
|
return reject(`Invalid receiver address: ${receiverAddr}`);
|
||||||
if (typeof sendAmt !== "number" || sendAmt <= 0)
|
if (typeof sendAmt !== "number" || sendAmt <= 0)
|
||||||
return reject(`Invalid send amount: ${sendAmt}`);
|
return reject(`Invalid send amount: ${sendAmt}`);
|
||||||
|
|
||||||
|
// Minimum amount to avoid dust errors (GetBlock requires ~10000 satoshis minimum)
|
||||||
|
const MIN_SEND_AMOUNT = 0.0001; // 10000 satoshis
|
||||||
|
if (sendAmt < MIN_SEND_AMOUNT)
|
||||||
|
return reject(`Amount too small. Minimum is ${MIN_SEND_AMOUNT} LTC to avoid dust rejection.`);
|
||||||
if (privKey.length < 1 || !ltcCrypto.verifyPrivKey(privKey, senderAddr))
|
if (privKey.length < 1 || !ltcCrypto.verifyPrivKey(privKey, senderAddr))
|
||||||
return reject("Invalid Private key!");
|
return reject("Invalid Private key!");
|
||||||
|
|
||||||
const fee = DEFAULT.fee;
|
|
||||||
const apiToken = "31ea37c3a0c44b368e879007af7a64c8";
|
const apiToken = "31ea37c3a0c44b368e879007af7a64c8";
|
||||||
const rpcEndpoint = `https://go.getblock.io/${apiToken}/`;
|
const rpcEndpoint = `https://go.getblock.io/${apiToken}/`;
|
||||||
|
|
||||||
@ -319,9 +346,15 @@
|
|||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(text);
|
const data = JSON.parse(text);
|
||||||
if (data.error) throw new Error(JSON.stringify(data.error));
|
if (data.error) {
|
||||||
|
// Extract meaningful error message from RPC response
|
||||||
|
const errMsg = data.error.message || JSON.stringify(data.error);
|
||||||
|
throw new Error(`RPC Error: ${errMsg}`);
|
||||||
|
}
|
||||||
return data.result;
|
return data.result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// Re-throw if it's already our formatted error
|
||||||
|
if (err.message.startsWith("RPC Error:")) throw err;
|
||||||
console.error("Raw RPC response:\n", text);
|
console.error("Raw RPC response:\n", text);
|
||||||
throw new Error("Failed to parse JSON-RPC response");
|
throw new Error("Failed to parse JSON-RPC response");
|
||||||
}
|
}
|
||||||
@ -336,61 +369,69 @@
|
|||||||
const utxoTotal = utxos.reduce((sum, utxo) => sum + utxo.value, 0);
|
const utxoTotal = utxos.reduce((sum, utxo) => sum + utxo.value, 0);
|
||||||
console.log("Total UTXO value:", utxoTotal);
|
console.log("Total UTXO value:", utxoTotal);
|
||||||
|
|
||||||
|
// Calculate fee based on transaction size
|
||||||
|
// Inputs = number of UTXOs, Outputs = 2 (receiver + change)
|
||||||
|
const numInputs = utxos.length;
|
||||||
|
const numOutputs = 2; // receiver + change output
|
||||||
|
const fee = calculateFee(numInputs, numOutputs);
|
||||||
|
console.log(`Dynamic fee calculated: ${fee.toFixed(8)} LTC for ${numInputs} inputs, ${numOutputs} outputs`);
|
||||||
|
|
||||||
if (utxoTotal < sendAmt + fee)
|
if (utxoTotal < sendAmt + fee)
|
||||||
return reject(
|
return reject(
|
||||||
`Insufficient funds: ${utxoTotal} < ${sendAmt + fee}`
|
`Insufficient funds: ${utxoTotal.toFixed(8)} LTC < ${(sendAmt + fee).toFixed(8)} LTC (${sendAmt} + ${fee.toFixed(8)} fee)`
|
||||||
);
|
);
|
||||||
|
|
||||||
const inputs = utxos.map((utxo) => ({
|
|
||||||
txid: utxo.txid,
|
|
||||||
vout: utxo.vout,
|
|
||||||
}));
|
|
||||||
|
|
||||||
console.log("inputs:", inputs);
|
|
||||||
|
|
||||||
// Calculate change amount
|
// Calculate change amount
|
||||||
const change = utxoTotal - sendAmt - fee;
|
const change = utxoTotal - sendAmt - fee;
|
||||||
|
|
||||||
const outputs = {
|
|
||||||
[senderAddr]: Number(change.toFixed(8)),
|
|
||||||
[receiverAddr]: Number(sendAmt.toFixed(8)),
|
|
||||||
};
|
|
||||||
console.log("outputs:", outputs);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create raw transaction
|
// Save original bitjs settings and set Litecoin version bytes
|
||||||
console.log("Creating raw transaction...");
|
const origPub = bitjs.pub;
|
||||||
const rawTx = await rpc("createrawtransaction", [inputs, outputs]);
|
const origPriv = bitjs.priv;
|
||||||
console.log("Raw transaction hex:", rawTx);
|
const origCompressed = bitjs.compressed;
|
||||||
// Sign raw transaction
|
|
||||||
console.log("Signing transaction...");
|
|
||||||
const signedTx = await rpc("signrawtransaction", [
|
|
||||||
rawTx,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
txid: utxos[0].txid,
|
|
||||||
vout: utxos[0].vout,
|
|
||||||
scriptPubKey: utxos[0].scriptPubKey,
|
|
||||||
amount: utxos[0].value.toFixed(8),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[privKey],
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!signedTx.complete) {
|
// Litecoin mainnet version bytes
|
||||||
return reject(
|
bitjs.pub = 0x30; // Litecoin P2PKH address prefix
|
||||||
`Failed to sign transaction: ${JSON.stringify(signedTx.errors)}`
|
bitjs.priv = 0xb0; // Litecoin WIF prefix
|
||||||
);
|
bitjs.compressed = true;
|
||||||
|
|
||||||
|
// Create transaction using bitjs
|
||||||
|
console.log("Creating transaction with bitjs...");
|
||||||
|
const tx = bitjs.transaction();
|
||||||
|
|
||||||
|
// Add all UTXOs as inputs
|
||||||
|
for (const utxo of utxos) {
|
||||||
|
tx.addinput(utxo.txid, utxo.vout, utxo.scriptPubKey);
|
||||||
|
console.log(`Added input: ${utxo.txid}:${utxo.vout}`);
|
||||||
}
|
}
|
||||||
console.log("Signed transaction hex:", signedTx.hex);
|
|
||||||
|
|
||||||
// Send raw transaction
|
// Add outputs: receiver first, then change back to sender
|
||||||
|
tx.addoutput(receiverAddr, sendAmt);
|
||||||
|
console.log(`Added output to receiver: ${receiverAddr} = ${sendAmt} LTC`);
|
||||||
|
|
||||||
|
if (change > 0.00000546) { // Only add change if above dust threshold
|
||||||
|
tx.addoutput(senderAddr, change);
|
||||||
|
console.log(`Added change output: ${senderAddr} = ${change} LTC`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the transaction with private key
|
||||||
|
console.log("Signing transaction locally...");
|
||||||
|
const signedTxHex = tx.sign(privKey);
|
||||||
|
console.log("Signed transaction hex:", signedTxHex);
|
||||||
|
|
||||||
|
// Restore original bitjs settings
|
||||||
|
bitjs.pub = origPub;
|
||||||
|
bitjs.priv = origPriv;
|
||||||
|
bitjs.compressed = origCompressed;
|
||||||
|
|
||||||
|
// Broadcast the signed transaction
|
||||||
console.log("Broadcasting transaction...");
|
console.log("Broadcasting transaction...");
|
||||||
const txid = await rpc("sendrawtransaction", [signedTx.hex]);
|
const txid = await rpc("sendrawtransaction", [signedTxHex]);
|
||||||
|
console.log("Transaction broadcast successful! TXID:", txid);
|
||||||
|
|
||||||
resolve(txid);
|
resolve(txid);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("RPC error:", error);
|
console.error("Transaction error:", error);
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user