feat: Implement initial Hedera wallet application with styling and core crypto functionality.
This commit is contained in:
parent
a9025e6345
commit
a33ff71bc3
181
hederaCrypto.js
Normal file
181
hederaCrypto.js
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
(function (EXPORTS) {
|
||||||
|
"use strict";
|
||||||
|
const hederaCrypto = EXPORTS;
|
||||||
|
|
||||||
|
function hexToBytes(hex) {
|
||||||
|
const bytes = [];
|
||||||
|
for (let i = 0; i < hex.length; i += 2) {
|
||||||
|
bytes.push(parseInt(hex.substr(i, 2), 16));
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToHex(bytes) {
|
||||||
|
return Array.from(bytes)
|
||||||
|
.map(b => b.toString(16).padStart(2, '0'))
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new random key
|
||||||
|
function generateNewID() {
|
||||||
|
var key = new Bitcoin.ECKey(false);
|
||||||
|
key.setCompressed(true);
|
||||||
|
return {
|
||||||
|
floID: key.getBitcoinAddress(),
|
||||||
|
pubKey: key.getPubKeyHex(),
|
||||||
|
privKey: key.getBitcoinWalletImportFormat(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperties(hederaCrypto, {
|
||||||
|
newID: {
|
||||||
|
get: () => generateNewID(),
|
||||||
|
},
|
||||||
|
hashID: {
|
||||||
|
value: (str) => {
|
||||||
|
let bytes = ripemd160(Crypto.SHA256(str, { asBytes: true }), {
|
||||||
|
asBytes: true,
|
||||||
|
});
|
||||||
|
bytes.unshift(bitjs.pub);
|
||||||
|
var hash = Crypto.SHA256(Crypto.SHA256(bytes, { asBytes: true }), {
|
||||||
|
asBytes: true,
|
||||||
|
});
|
||||||
|
var checksum = hash.slice(0, 4);
|
||||||
|
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tmpID: {
|
||||||
|
get: () => {
|
||||||
|
let bytes = Crypto.util.randomBytes(20);
|
||||||
|
bytes.unshift(bitjs.pub);
|
||||||
|
var hash = Crypto.SHA256(Crypto.SHA256(bytes, { asBytes: true }), {
|
||||||
|
asBytes: true,
|
||||||
|
});
|
||||||
|
var checksum = hash.slice(0, 4);
|
||||||
|
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Multi-chain Generator (BTC, FLO, HBAR) ---
|
||||||
|
hederaCrypto.generateMultiChain = async function (inputWif) {
|
||||||
|
const versions = {
|
||||||
|
BTC: { pub: 0x00, priv: 0x80 },
|
||||||
|
FLO: { pub: 0x23, priv: 0xa3 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const origBitjsPub = bitjs.pub;
|
||||||
|
const origBitjsPriv = bitjs.priv;
|
||||||
|
const origBitjsCompressed = bitjs.compressed;
|
||||||
|
const origCoinJsCompressed = coinjs.compressed;
|
||||||
|
|
||||||
|
bitjs.compressed = true;
|
||||||
|
coinjs.compressed = true;
|
||||||
|
|
||||||
|
let privKeyHex;
|
||||||
|
let compressed = true;
|
||||||
|
|
||||||
|
// --- Decode input or generate new ---
|
||||||
|
if (typeof inputWif === "string" && inputWif.trim().length > 0) {
|
||||||
|
const trimmedInput = inputWif.trim();
|
||||||
|
const hexOnly = /^[0-9a-fA-F]+$/.test(trimmedInput);
|
||||||
|
|
||||||
|
if (hexOnly && (trimmedInput.length === 64 || trimmedInput.length === 128)) {
|
||||||
|
privKeyHex =
|
||||||
|
trimmedInput.length === 128 ? trimmedInput.substring(0, 64) : trimmedInput;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const decode = Bitcoin.Base58.decode(trimmedInput);
|
||||||
|
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||||
|
let key = keyWithVersion.slice(1);
|
||||||
|
if (key.length >= 33 && key[key.length - 1] === 0x01) {
|
||||||
|
key = key.slice(0, key.length - 1);
|
||||||
|
compressed = true;
|
||||||
|
}
|
||||||
|
privKeyHex = bytesToHex(key);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Invalid WIF, generating new key:", e);
|
||||||
|
const newKey = generateNewID();
|
||||||
|
const decode = Bitcoin.Base58.decode(newKey.privKey);
|
||||||
|
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||||
|
let key = keyWithVersion.slice(1);
|
||||||
|
if (key.length >= 33 && key[key.length - 1] === 0x01)
|
||||||
|
key = key.slice(0, key.length - 1);
|
||||||
|
privKeyHex = bytesToHex(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Generate new key if no input
|
||||||
|
const newKey = generateNewID();
|
||||||
|
const decode = Bitcoin.Base58.decode(newKey.privKey);
|
||||||
|
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||||
|
let key = keyWithVersion.slice(1);
|
||||||
|
if (key.length >= 33 && key[key.length - 1] === 0x01)
|
||||||
|
key = key.slice(0, key.length - 1);
|
||||||
|
privKeyHex = bytesToHex(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Derive addresses for each chain ---
|
||||||
|
const result = { BTC: {}, FLO: {}, HBAR: {} };
|
||||||
|
|
||||||
|
// BTC
|
||||||
|
bitjs.pub = versions.BTC.pub;
|
||||||
|
bitjs.priv = versions.BTC.priv;
|
||||||
|
const pubKeyBTC = bitjs.newPubkey(privKeyHex);
|
||||||
|
result.BTC.address = coinjs.bech32Address(pubKeyBTC).address;
|
||||||
|
result.BTC.privateKey = bitjs.privkey2wif(privKeyHex);
|
||||||
|
|
||||||
|
// FLO
|
||||||
|
bitjs.pub = versions.FLO.pub;
|
||||||
|
bitjs.priv = versions.FLO.priv;
|
||||||
|
const pubKeyFLO = bitjs.newPubkey(privKeyHex);
|
||||||
|
result.FLO.address = bitjs.pubkey2address(pubKeyFLO);
|
||||||
|
result.FLO.privateKey = bitjs.privkey2wif(privKeyHex);
|
||||||
|
|
||||||
|
// HBAR (Hedera)
|
||||||
|
try {
|
||||||
|
const privBytes = hexToBytes(privKeyHex.substring(0, 64));
|
||||||
|
|
||||||
|
// Create ECDSA key from private key bytes
|
||||||
|
const ecKey = new Bitcoin.ECKey(privBytes);
|
||||||
|
ecKey.setCompressed(false); // Uncompressed for EVM address derivation
|
||||||
|
|
||||||
|
// Get uncompressed public key (65 bytes: 04 + 32 bytes X + 32 bytes Y)
|
||||||
|
const pubKeyHex = ecKey.getPubKeyHex();
|
||||||
|
|
||||||
|
|
||||||
|
// Derive EVM address from public key using Keccak-256
|
||||||
|
// Remove '04' prefix and hash the remaining 64 bytes
|
||||||
|
const pubKeyBytes = pubKeyHex.substring(2);
|
||||||
|
|
||||||
|
// Use web3.js for proper Keccak-256 hash (Ethereum standard)
|
||||||
|
const hash = Web3.utils.keccak256('0x' + pubKeyBytes);
|
||||||
|
// hash is '0x...' format, take last 20 bytes (40 hex chars)
|
||||||
|
const evmAddress = '0x' + hash.substring(26);
|
||||||
|
|
||||||
|
// Compressed public key for display
|
||||||
|
ecKey.setCompressed(true);
|
||||||
|
const compressedPubKey = ecKey.getPubKeyHex();
|
||||||
|
|
||||||
|
result.HBAR.evmAddress = evmAddress;
|
||||||
|
result.HBAR.publicKey = compressedPubKey;
|
||||||
|
result.HBAR.privateKey = privKeyHex.substring(0, 64);
|
||||||
|
result.HBAR.address = evmAddress; // EVM address
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error generating HBAR keys:", error);
|
||||||
|
result.HBAR.evmAddress = "Error generating address";
|
||||||
|
result.HBAR.publicKey = "Error";
|
||||||
|
result.HBAR.privateKey = privKeyHex;
|
||||||
|
result.HBAR.address = "Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
bitjs.pub = origBitjsPub;
|
||||||
|
bitjs.priv = origBitjsPriv;
|
||||||
|
bitjs.compressed = origBitjsCompressed;
|
||||||
|
coinjs.compressed = origCoinJsCompressed;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
})(typeof module === "object" ? module.exports : (window.hederaCrypto = {}));
|
||||||
BIN
hedera_favicon.png
Normal file
BIN
hedera_favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 386 KiB |
577
index.html
Normal file
577
index.html
Normal file
@ -0,0 +1,577 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Hedera Wallet - RanchiMall</title>
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="hedera_favicon.png?v=1">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Header -->
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-content">
|
||||||
|
<div id="logo" class="app-brand">
|
||||||
|
<svg id="main_logo" class="icon" viewBox="0 0 27.25 32">
|
||||||
|
<title>RanchiMall</title>
|
||||||
|
<path
|
||||||
|
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.80,2.33q-.18.34-.39.69Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div class="app-name">
|
||||||
|
<div class="app-name__company">RanchiMall</div>
|
||||||
|
<h4 class="app-name__title">Hedera 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 disabled" data-page="transactions" style="opacity: 0.5; pointer-events: none;">
|
||||||
|
<i class="fas fa-history"></i>
|
||||||
|
<span>Transactions (Coming Soon)</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#" class="nav-link disabled" data-page="send" style="opacity: 0.5; pointer-events: none;">
|
||||||
|
<i class="fas fa-paper-plane"></i>
|
||||||
|
<span>Send (Coming Soon)</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 Address</h2>
|
||||||
|
<p>Generate addresses for BTC, FLO, and HBAR 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, and HBAR 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;">
|
||||||
|
<!-- HBAR Section -->
|
||||||
|
<div class="blockchain-section algo-primary">
|
||||||
|
<div class="blockchain-header">
|
||||||
|
<h4><svg class="chain-icon-svg" viewBox="700 650 1100 1200" fill="currentColor"><path d="M1758.12,1790.62H1599.38V1453.13H900.62v337.49H741.87V696.25H900.62v329.37h698.76V696.25h158.75Zm-850-463.75h698.75V1152.5H908.12Z"/></svg> HBAR</h4>
|
||||||
|
<span class="blockchain-badge primary">Primary</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<label>EVM Address</label>
|
||||||
|
<div class="detail-value-wrapper">
|
||||||
|
<code id="hbar-evmAddress" class="detail-value">-</code>
|
||||||
|
<button class="input-action-btn clear-btn" onclick="copyToClipboard('hbar-evmAddress')">
|
||||||
|
<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="hbar-privateKey" class="form-input" value="-" readonly />
|
||||||
|
<button type="button" class="input-action-btn password-toggle" onclick="toggleVisibility('hbar-privateKey')">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="input-action-btn clear-btn" onclick="copyPrivateKey('hbar-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>
|
||||||
|
|
||||||
|
<!-- 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 Address</h2>
|
||||||
|
<p>Recover all blockchain addresses (BTC, FLO, HBAR) 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/HBAR)</label>
|
||||||
|
<div class="input-with-actions">
|
||||||
|
<input type="password" id="privateKeyInput" class="form-input" placeholder="Enter HBAR/FLO/BTC 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;">
|
||||||
|
<!-- HBAR Section -->
|
||||||
|
<div class="blockchain-section algo-primary">
|
||||||
|
<div class="blockchain-header">
|
||||||
|
<h4><svg class="chain-icon-svg" viewBox="700 650 1100 1200" fill="currentColor"><path d="M1758.12,1790.62H1599.38V1453.13H900.62v337.49H741.87V696.25H900.62v329.37h698.76V696.25h158.75Zm-850-463.75h698.75V1152.5H908.12Z"/></svg> HBAR (Hedera)</h4>
|
||||||
|
<span class="blockchain-badge primary">Primary</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<label>EVM Address (for auto account creation)</label>
|
||||||
|
<div class="detail-value-wrapper">
|
||||||
|
<code id="recover-hbar-evmAddress" class="detail-value">-</code>
|
||||||
|
<button class="input-action-btn clear-btn" onclick="copyToClipboard('recover-hbar-evmAddress')">
|
||||||
|
<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-hbar-privateKey" class="form-input" value="-" readonly />
|
||||||
|
<button type="button" class="input-action-btn password-toggle" onclick="toggleVisibility('recover-hbar-privateKey')">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="input-action-btn clear-btn" onclick="copyPrivateKey('recover-hbar-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>
|
||||||
|
|
||||||
|
<!-- Required libraries -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/web3@1.10.0/dist/web3.min.js"></script>
|
||||||
|
<script src="lib.hedera.js"></script>
|
||||||
|
<script src="hederaCrypto.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Clear input helper
|
||||||
|
function clearInput(inputId) {
|
||||||
|
const input = document.getElementById(inputId);
|
||||||
|
if (input) {
|
||||||
|
input.value = '';
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateWallet() {
|
||||||
|
try {
|
||||||
|
showLoading(true);
|
||||||
|
const result = await hederaCrypto.generateMultiChain();
|
||||||
|
|
||||||
|
// Display BTC
|
||||||
|
document.getElementById('btc-address').textContent = result.BTC.address;
|
||||||
|
document.getElementById('btc-privateKey').value = result.BTC.privateKey;
|
||||||
|
|
||||||
|
// Display FLO
|
||||||
|
document.getElementById('flo-address').textContent = result.FLO.address;
|
||||||
|
document.getElementById('flo-privateKey').value = result.FLO.privateKey;
|
||||||
|
|
||||||
|
// Display HBAR
|
||||||
|
document.getElementById('hbar-evmAddress').textContent = result.HBAR.evmAddress || result.HBAR.address;
|
||||||
|
document.getElementById('hbar-privateKey').value = result.HBAR.privateKey;
|
||||||
|
|
||||||
|
document.getElementById('generate-results').style.display = 'block';
|
||||||
|
showNotification('✅ New address generated!', 'success');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating address:', 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's hex format or WIF format
|
||||||
|
const hexOnly = /^[0-9a-fA-F]+$/.test(privateKey);
|
||||||
|
const isValidHex = hexOnly && (privateKey.length === 64 || privateKey.length === 128);
|
||||||
|
const isValidWIF = !hexOnly && privateKey.length >= 50; // BTC/FLO WIF format
|
||||||
|
|
||||||
|
if (!isValidHex && !isValidWIF) {
|
||||||
|
showNotification('⚠️ Invalid private key format. Please enter either:\n- 64-character hex key (HBAR)\n- WIF format (BTC/FLO)', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
showLoading(true);
|
||||||
|
const result = await hederaCrypto.generateMultiChain(privateKey);
|
||||||
|
|
||||||
|
// Display BTC
|
||||||
|
document.getElementById('recover-btc-address').textContent = result.BTC.address;
|
||||||
|
document.getElementById('recover-btc-privateKey').value = result.BTC.privateKey;
|
||||||
|
|
||||||
|
// Display FLO
|
||||||
|
document.getElementById('recover-flo-address').textContent = result.FLO.address;
|
||||||
|
document.getElementById('recover-flo-privateKey').value = result.FLO.privateKey;
|
||||||
|
|
||||||
|
// Display HBAR
|
||||||
|
document.getElementById('recover-hbar-evmAddress').textContent = result.HBAR.evmAddress || result.HBAR.address;
|
||||||
|
document.getElementById('recover-hbar-privateKey').value = result.HBAR.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 toggleRecoverKeyVisibility() {
|
||||||
|
const input = document.getElementById('privateKeyInput');
|
||||||
|
const icon = event.target.closest('.password-toggle')?.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 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);
|
||||||
|
const icon = input.closest('.input-with-actions')?.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('.btn-primary').forEach(btn => {
|
||||||
|
btn.disabled = show;
|
||||||
|
if (show) {
|
||||||
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
|
||||||
|
} else {
|
||||||
|
if (btn.id === 'generateBtn') {
|
||||||
|
btn.innerHTML = '<i class="fas fa-wallet"></i> Generate';
|
||||||
|
} else if (btn.id === 'recoverBtn') {
|
||||||
|
btn.innerHTML = '<i class="fas fa-sync-alt"></i> Recover';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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:not(.disabled), .nav-btn:not(.disabled)');
|
||||||
|
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');
|
||||||
|
|
||||||
|
switchTabByPage(page);
|
||||||
|
|
||||||
|
// Update active states
|
||||||
|
document.querySelectorAll('.nav-link, .nav-btn').forEach(l => l.classList.remove('active'));
|
||||||
|
document.querySelectorAll(`[data-page="${page}"]`).forEach(l => l.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();
|
||||||
|
});
|
||||||
|
</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 disabled" data-page="transactions" style="opacity: 0.5; pointer-events: none;">
|
||||||
|
<i class="fas fa-history"></i>
|
||||||
|
<span>Transactions</span>
|
||||||
|
</button>
|
||||||
|
<button class="nav-btn disabled" data-page="send" style="opacity: 0.5; pointer-events: none;">
|
||||||
|
<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>
|
||||||
11473
lib.hedera.js
Normal file
11473
lib.hedera.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user