2138 lines
69 KiB
JavaScript
2138 lines
69 KiB
JavaScript
import bs58check from "https://cdn.jsdelivr.net/npm/bs58check/+esm";
|
|
const uiGlobals = {};
|
|
|
|
// IndexedDB for storing searched addresses
|
|
class SearchedAddressDB {
|
|
constructor() {
|
|
this.dbName = "RippleWalletDB";
|
|
this.version = 1;
|
|
this.storeName = "searchedAddresses";
|
|
this.db = null;
|
|
}
|
|
|
|
async init() {
|
|
return new Promise((resolve, reject) => {
|
|
const request = indexedDB.open(this.dbName, this.version);
|
|
|
|
request.onerror = () => reject(request.error);
|
|
request.onsuccess = () => {
|
|
this.db = request.result;
|
|
resolve();
|
|
};
|
|
|
|
request.onupgradeneeded = (event) => {
|
|
const db = event.target.result;
|
|
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
const store = db.createObjectStore(this.storeName, {
|
|
keyPath: "address",
|
|
});
|
|
store.createIndex("timestamp", "timestamp", { unique: false });
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
async saveSearchedAddress(
|
|
address,
|
|
balance,
|
|
timestamp = Date.now(),
|
|
sourceInfo = null
|
|
) {
|
|
if (!this.db) await this.init();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = this.db.transaction([this.storeName], "readwrite");
|
|
const store = transaction.objectStore(this.storeName);
|
|
|
|
// First, check if this address already exists
|
|
const getRequest = store.get(address);
|
|
|
|
getRequest.onsuccess = () => {
|
|
const existingRecord = getRequest.result;
|
|
let finalSourceInfo = sourceInfo;
|
|
|
|
// If record exists and has sourceInfo, preserve it unless we're providing new sourceInfo
|
|
if (existingRecord && existingRecord.sourceInfo && !sourceInfo) {
|
|
finalSourceInfo = existingRecord.sourceInfo;
|
|
}
|
|
// If existing record has sourceInfo and new one doesn't, keep the existing one
|
|
else if (
|
|
existingRecord &&
|
|
existingRecord.sourceInfo &&
|
|
sourceInfo === null
|
|
) {
|
|
finalSourceInfo = existingRecord.sourceInfo;
|
|
}
|
|
|
|
const data = {
|
|
address, // This will be the XRP address
|
|
balance,
|
|
timestamp,
|
|
formattedBalance: `${balance} XRP`,
|
|
sourceInfo: finalSourceInfo, // Contains original blockchain info if converted from private key
|
|
};
|
|
|
|
const putRequest = store.put(data);
|
|
putRequest.onsuccess = () => resolve();
|
|
putRequest.onerror = () => reject(putRequest.error);
|
|
};
|
|
|
|
getRequest.onerror = () => reject(getRequest.error);
|
|
});
|
|
}
|
|
|
|
async getSearchedAddresses() {
|
|
if (!this.db) await this.init();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = this.db.transaction([this.storeName], "readonly");
|
|
const store = transaction.objectStore(this.storeName);
|
|
const index = store.index("timestamp");
|
|
|
|
// Get all records sorted by timestamp (newest first)
|
|
const request = index.getAll();
|
|
request.onsuccess = () => {
|
|
const results = request.result.sort(
|
|
(a, b) => b.timestamp - a.timestamp
|
|
);
|
|
resolve(results);
|
|
};
|
|
request.onerror = () => reject(request.error);
|
|
});
|
|
}
|
|
|
|
async deleteSearchedAddress(address) {
|
|
if (!this.db) await this.init();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = this.db.transaction([this.storeName], "readwrite");
|
|
const store = transaction.objectStore(this.storeName);
|
|
|
|
const request = store.delete(address);
|
|
request.onsuccess = () => resolve();
|
|
request.onerror = () => reject(request.error);
|
|
});
|
|
}
|
|
|
|
async clearAllSearchedAddresses() {
|
|
if (!this.db) await this.init();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = this.db.transaction([this.storeName], "readwrite");
|
|
const store = transaction.objectStore(this.storeName);
|
|
|
|
const request = store.clear();
|
|
request.onsuccess = () => resolve();
|
|
request.onerror = () => reject(request.error);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Initialize the database
|
|
const searchedAddressDB = new SearchedAddressDB();
|
|
|
|
const {
|
|
html,
|
|
svg,
|
|
render: renderElem,
|
|
} = typeof uhtml !== "undefined"
|
|
? uhtml
|
|
: {
|
|
html: (strings, ...values) => strings.join(""),
|
|
svg: () => "",
|
|
render: () => {},
|
|
};
|
|
const { signal, computed, effect } =
|
|
typeof preactSignalsCore !== "undefined"
|
|
? preactSignalsCore
|
|
: {
|
|
signal: (val) => ({ value: val }),
|
|
computed: () => ({}),
|
|
effect: () => {},
|
|
};
|
|
uiGlobals.connectionErrorNotification = [];
|
|
//Checks for internet connection status
|
|
if (!navigator.onLine)
|
|
uiGlobals.connectionErrorNotification.push(
|
|
notify(
|
|
"There seems to be a problem connecting to the internet, Please check you internet connection.",
|
|
"error"
|
|
)
|
|
);
|
|
window.addEventListener("offline", () => {
|
|
uiGlobals.connectionErrorNotification.push(
|
|
notify(
|
|
"There seems to be a problem connecting to the internet, Please check you internet connection.",
|
|
"error"
|
|
)
|
|
);
|
|
});
|
|
window.addEventListener("online", () => {
|
|
uiGlobals.connectionErrorNotification.forEach((notification) => {
|
|
getRef("notification_drawer").remove(notification);
|
|
});
|
|
notify("We are back online.", "success");
|
|
});
|
|
|
|
// Use instead of document.getElementById
|
|
function getRef(elementId) {
|
|
const element = document.getElementById(elementId);
|
|
if (!element) {
|
|
console.warn(`Element with ID '${elementId}' not found`);
|
|
}
|
|
return element;
|
|
}
|
|
|
|
// displays a popup for asking permission.
|
|
const getConfirmation = (title, options = {}) => {
|
|
return new Promise((resolve) => {
|
|
const {
|
|
message = "",
|
|
cancelText = "Cancel",
|
|
confirmText = "OK",
|
|
danger = false,
|
|
} = options;
|
|
openPopup("confirmation_popup", true);
|
|
getRef("confirm_title").innerText = title;
|
|
renderElem(getRef("confirm_message"), message);
|
|
const cancelButton =
|
|
getRef("confirmation_popup").querySelector(".cancel-button");
|
|
const confirmButton =
|
|
getRef("confirmation_popup").querySelector(".confirm-button");
|
|
confirmButton.textContent = confirmText;
|
|
cancelButton.textContent = cancelText;
|
|
if (danger) confirmButton.classList.add("button--danger");
|
|
else confirmButton.classList.remove("button--danger");
|
|
confirmButton.onclick = () => {
|
|
closePopup();
|
|
resolve(true);
|
|
};
|
|
cancelButton.onclick = () => {
|
|
closePopup();
|
|
resolve(false);
|
|
};
|
|
});
|
|
};
|
|
const debounce = (callback, wait) => {
|
|
let timeoutId = null;
|
|
return (...args) => {
|
|
window.clearTimeout(timeoutId);
|
|
timeoutId = window.setTimeout(() => {
|
|
callback.apply(null, args);
|
|
}, wait);
|
|
};
|
|
};
|
|
|
|
let zIndex = 50;
|
|
|
|
function openPopup(popupId, pinned) {
|
|
zIndex++;
|
|
const popup = getRef(popupId);
|
|
popup.setAttribute("style", `z-index: ${zIndex}`);
|
|
popup.show({ pinned });
|
|
|
|
if (typeof popupStack !== "undefined" && popupStack.push) {
|
|
popupStack.push({ popup, id: popupId });
|
|
}
|
|
return popup;
|
|
}
|
|
|
|
function closePopup() {
|
|
if (typeof popupStack !== "undefined" && popupStack.peek && popupStack.pop) {
|
|
if (popupStack.peek() === undefined) return;
|
|
const current = popupStack.pop();
|
|
current.popup.hide();
|
|
}
|
|
}
|
|
|
|
document.addEventListener("popupclosed", (e) => {
|
|
zIndex--;
|
|
});
|
|
|
|
//Function for displaying toast notifications.
|
|
function notify(message, mode, options = {}) {
|
|
const icon =
|
|
mode === "success"
|
|
? `<svg class="icon icon--success" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z"/></svg>`
|
|
: mode === "error"
|
|
? `<svg class="icon icon--error" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>`
|
|
: "";
|
|
|
|
const drawer = getRef("notification_drawer");
|
|
if (!drawer) return;
|
|
|
|
const notification = document.createElement("div");
|
|
notification.className = `notification ${mode}`;
|
|
notification.innerHTML = `${icon}<span>${message}</span>`;
|
|
|
|
drawer.appendChild(notification);
|
|
|
|
// Auto-remove after 3 seconds
|
|
setTimeout(() => {
|
|
notification.remove();
|
|
}, options.duration || 3000);
|
|
}
|
|
|
|
// Input field control functions
|
|
function togglePasswordVisibility(inputId) {
|
|
const input = document.getElementById(inputId);
|
|
const toggleBtn = input.parentElement.querySelector(".toggle-password");
|
|
|
|
if (input.type === "password") {
|
|
input.type = "text";
|
|
toggleBtn.innerHTML = '<i class="fas fa-eye-slash"></i>';
|
|
toggleBtn.title = "Hide";
|
|
} else {
|
|
input.type = "password";
|
|
toggleBtn.innerHTML = '<i class="fas fa-eye"></i>';
|
|
toggleBtn.title = "Show";
|
|
}
|
|
}
|
|
|
|
function clearInput(inputId) {
|
|
const input = document.getElementById(inputId);
|
|
input.value = "";
|
|
input.focus();
|
|
|
|
// If it's a password field that was shown, hide it again
|
|
if (input.type === "text" && input.classList.contains("password-field")) {
|
|
const toggleBtn = input.parentElement.querySelector(".toggle-password");
|
|
input.type = "password";
|
|
toggleBtn.innerHTML = '<i class="fas fa-eye"></i>';
|
|
toggleBtn.title = "Show";
|
|
}
|
|
|
|
notify("Input cleared", "success");
|
|
}
|
|
|
|
// Initialize input containers with controls
|
|
function initializeInputControls() {
|
|
// List of input IDs that need controls
|
|
const inputIds = [
|
|
"sendKey", // Send page - sender key
|
|
"recipient", // Send page - recipient
|
|
"amount", // Send page - amount
|
|
"recoverKey", // Retrieve page
|
|
"checkAddress", // Balance check
|
|
"generateKey", // Generate page
|
|
];
|
|
|
|
inputIds.forEach((inputId) => {
|
|
const input = document.getElementById(inputId);
|
|
if (!input) return;
|
|
|
|
// Skip if already wrapped
|
|
if (input.parentElement.classList.contains("input-container")) return;
|
|
|
|
// Create wrapper container
|
|
const container = document.createElement("div");
|
|
container.className = "input-container";
|
|
|
|
// Insert container before input
|
|
input.parentNode.insertBefore(container, input);
|
|
|
|
// Move input into container
|
|
container.appendChild(input);
|
|
|
|
// Determine if this is a sensitive field (private keys, seeds)
|
|
const isSensitiveField = ["sendKey", "recoverKey", "generateKey"].includes(
|
|
inputId
|
|
);
|
|
|
|
// Add password-field class for sensitive fields
|
|
if (isSensitiveField) {
|
|
input.classList.add("password-field");
|
|
input.type = "password";
|
|
}
|
|
|
|
// Create controls container
|
|
const controls = document.createElement("div");
|
|
controls.className = "input-controls";
|
|
|
|
// Add show/hide button for sensitive fields
|
|
if (isSensitiveField) {
|
|
const toggleBtn = document.createElement("button");
|
|
toggleBtn.className = "input-control-btn toggle-password";
|
|
toggleBtn.innerHTML = '<i class="fas fa-eye"></i>';
|
|
toggleBtn.title = "Show";
|
|
toggleBtn.type = "button";
|
|
toggleBtn.onclick = () => togglePasswordVisibility(inputId);
|
|
controls.appendChild(toggleBtn);
|
|
}
|
|
|
|
// Add clear button for all fields
|
|
const clearBtn = document.createElement("button");
|
|
clearBtn.className = "input-control-btn clear-input";
|
|
clearBtn.innerHTML = '<i class="fas fa-times"></i>';
|
|
clearBtn.title = "Clear";
|
|
clearBtn.type = "button";
|
|
clearBtn.onclick = () => clearInput(inputId);
|
|
controls.appendChild(clearBtn);
|
|
|
|
// Add controls to container
|
|
container.appendChild(controls);
|
|
});
|
|
}
|
|
|
|
function getFormattedTime(timestamp, format) {
|
|
try {
|
|
if (String(timestamp).length < 13) timestamp *= 1000;
|
|
let [day, month, date, year] = new Date(timestamp).toString().split(" "),
|
|
minutes = new Date(timestamp).getMinutes(),
|
|
hours = new Date(timestamp).getHours(),
|
|
currentTime = new Date().toString().split(" ");
|
|
|
|
minutes = minutes < 10 ? `0${minutes}` : minutes;
|
|
let finalHours = ``;
|
|
if (hours > 12) finalHours = `${hours - 12}:${minutes}`;
|
|
else if (hours === 0) finalHours = `12:${minutes}`;
|
|
else finalHours = `${hours}:${minutes}`;
|
|
|
|
finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`;
|
|
switch (format) {
|
|
case "date-only":
|
|
return `${month} ${date}, ${year}`;
|
|
break;
|
|
case "time-only":
|
|
return finalHours;
|
|
case "relative":
|
|
// check if timestamp is older than a day
|
|
if (Date.now() - new Date(timestamp) < 60 * 60 * 24 * 1000)
|
|
return `${finalHours}`;
|
|
else return relativeTime.from(timestamp);
|
|
default:
|
|
return `${month} ${date} ${year}, ${finalHours}`;
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
return timestamp;
|
|
}
|
|
}
|
|
// Simple state management for the wallet
|
|
let selectedCurrency = "xrp";
|
|
window.addEventListener("load", () => {
|
|
document.body.classList.remove("hidden");
|
|
document.addEventListener("keyup", (e) => {
|
|
if (e.key === "Escape") {
|
|
closePopup();
|
|
}
|
|
});
|
|
document.addEventListener("copy", () => {
|
|
notify("copied", "success");
|
|
});
|
|
document.addEventListener("pointerdown", (e) => {
|
|
if (
|
|
e.target.closest("button:not(:disabled), .interactive:not(:disabled)")
|
|
) {
|
|
// createRipple effect can be added later
|
|
}
|
|
});
|
|
|
|
// Initialize the wallet UI
|
|
setTimeout(() => {
|
|
const loadingPage = getRef("loading_page");
|
|
if (loadingPage) {
|
|
loadingPage.animate(
|
|
[{ transform: "translateY(0)" }, { transform: "translateY(-100%)" }],
|
|
{
|
|
duration: 300,
|
|
fill: "forwards",
|
|
easing: "ease",
|
|
}
|
|
).onfinish = () => {
|
|
loadingPage.remove();
|
|
};
|
|
}
|
|
}, 500);
|
|
});
|
|
|
|
function getRippleAddress(input) {
|
|
// This function should not accept addresses directly
|
|
if (input.startsWith("r")) {
|
|
throw new Error("Use private key or seed, not address");
|
|
}
|
|
if (input.startsWith("s")) return xrpl.Wallet.fromSeed(input).address;
|
|
try {
|
|
return convertWIFtoRippleWallet(input).address;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function convertWIFtoRippleWallet(wif) {
|
|
try {
|
|
const decoded = bs58check.decode(wif);
|
|
let keyBuffer = decoded.slice(1); // remove version byte
|
|
|
|
if (keyBuffer.length === 33 && keyBuffer[32] === 0x01) {
|
|
keyBuffer = keyBuffer.slice(0, -1); // remove compression flag
|
|
}
|
|
const data = xrpl.Wallet.fromEntropy(keyBuffer);
|
|
console.log(data);
|
|
|
|
return {
|
|
address: data.address,
|
|
seed: data.seed,
|
|
};
|
|
} catch (error) {
|
|
console.error("WIF conversion error:", error);
|
|
throw new Error("Invalid WIF private key format: " + error.message);
|
|
}
|
|
}
|
|
|
|
async function sendXRP() {
|
|
const senderKeyElement = getRef("sendKey");
|
|
const destinationElement = getRef("recipient");
|
|
const amountElement = getRef("amount");
|
|
|
|
if (!senderKeyElement || !destinationElement || !amountElement) {
|
|
notify("Form elements not found", "error");
|
|
return;
|
|
}
|
|
|
|
const senderKey = senderKeyElement.value;
|
|
const destination = destinationElement.value;
|
|
const amount = amountElement.value;
|
|
console.log("Sender Key:", senderKey);
|
|
console.log("Destination:", destination);
|
|
console.log("Amount:", amount);
|
|
// Validation
|
|
if (!senderKey) return notify("Please enter your private key", "error");
|
|
if (!destination) return notify("Please enter recipient address", "error");
|
|
if (!amount || amount <= 0)
|
|
return notify("Please enter valid amount", "error");
|
|
|
|
try {
|
|
let wallet;
|
|
if (senderKey.startsWith("s")) {
|
|
wallet = xrpl.Wallet.fromSeed(senderKey);
|
|
} else {
|
|
wallet = convertWIFtoRippleWallet(senderKey);
|
|
wallet = xrpl.Wallet.fromSeed(wallet.seed);
|
|
}
|
|
|
|
// Store transaction data globally for confirmation
|
|
window.pendingTransaction = {
|
|
senderKey,
|
|
destination,
|
|
amount,
|
|
wallet,
|
|
};
|
|
|
|
// Populate transaction details in confirmation popup
|
|
const detailsContainer = getRef("transactionDetails");
|
|
if (detailsContainer) {
|
|
detailsContainer.innerHTML = ` <div class="detail-row">
|
|
<span class="detail-label">From:</span>
|
|
<span class="detail-value">${
|
|
wallet.address || wallet.classicAddress
|
|
}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">To:</span>
|
|
<span class="detail-value">${destination}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Amount:</span>
|
|
<span class="detail-value">${amount} XRP</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Network Fee:</span>
|
|
<span class="detail-value">~0.00001 XRP</span>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Show confirmation popup
|
|
openPopup("sendConfirm");
|
|
} catch (err) {
|
|
console.error("Send XRP error:", err);
|
|
notify("Error processing transaction: ", "error");
|
|
}
|
|
}
|
|
|
|
async function confirmSend() {
|
|
if (!window.pendingTransaction) {
|
|
notify("No transaction to confirm", "error");
|
|
return;
|
|
}
|
|
|
|
const { wallet, destination, amount } = window.pendingTransaction;
|
|
const client = new xrpl.Client("wss://s.altnet.rippletest.net:51233");
|
|
|
|
// Show loading state on confirm button
|
|
const confirmBtn = document.querySelector('[onclick="confirmSend()"]');
|
|
const originalText = confirmBtn.innerHTML;
|
|
confirmBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sending...';
|
|
confirmBtn.disabled = true;
|
|
|
|
try {
|
|
await client.connect();
|
|
|
|
try {
|
|
// Get the correct address from wallet object
|
|
const walletAddress = wallet.classicAddress || wallet.address;
|
|
|
|
const accountInfo = await client.request({
|
|
command: "account_info",
|
|
account: walletAddress,
|
|
ledger_index: "validated",
|
|
});
|
|
|
|
// Check if account has sufficient balance
|
|
const balance = xrpl.dropsToXrp(accountInfo.result.account_data.Balance);
|
|
const requiredAmount = parseFloat(amount) + 0.000012; // Add typical fee
|
|
|
|
if (balance < requiredAmount) {
|
|
throw new Error(
|
|
`Insufficient balance. Available: ${balance} XRP, Required: ${requiredAmount} XRP (including fee)`
|
|
);
|
|
}
|
|
|
|
// Check if master key is disabled
|
|
if (
|
|
accountInfo.result.account_data.Flags &&
|
|
accountInfo.result.account_data.Flags & 0x00100000
|
|
) {
|
|
throw new Error(
|
|
"Account master key is disabled. Cannot send transactions with this key."
|
|
);
|
|
}
|
|
} catch (accountError) {
|
|
if (accountError.message.includes("Account not found")) {
|
|
throw new Error(
|
|
"Sender account does not exist or is not activated on the ledger."
|
|
);
|
|
}
|
|
throw accountError;
|
|
}
|
|
|
|
// Use your exact reference logic with improved LastLedgerSequence
|
|
const ledgerInfo = await client.request({
|
|
command: "ledger",
|
|
ledger_index: "validated",
|
|
});
|
|
|
|
const currentLedger = ledgerInfo.result.ledger_index;
|
|
|
|
const tx = {
|
|
TransactionType: "Payment",
|
|
Account: wallet.classicAddress || wallet.address,
|
|
Destination: destination,
|
|
Amount: xrpl.xrpToDrops(amount.toString()),
|
|
LastLedgerSequence: currentLedger + 20,
|
|
};
|
|
|
|
const prepared = await client.autofill(tx);
|
|
|
|
let signed;
|
|
try {
|
|
if (wallet.seed && wallet.seed.startsWith("s")) {
|
|
const seedWallet = xrpl.Wallet.fromSeed(wallet.seed);
|
|
signed = seedWallet.sign(prepared);
|
|
} else {
|
|
throw new Error("Invalid wallet object - no signing method available");
|
|
}
|
|
} catch (signError) {
|
|
console.error("Error signing transaction:", signError);
|
|
throw new Error(`Failed to sign transaction: ${signError.message}`);
|
|
}
|
|
|
|
const result = await client.submitAndWait(signed.tx_blob);
|
|
|
|
// Safe access to transaction date
|
|
let rippleDate = "N/A";
|
|
if (result.result) {
|
|
rippleDate = new Date(
|
|
(result.result.date + 946684800) * 1000
|
|
).toLocaleString("en-IN", {
|
|
timeZone: "Asia/Kolkata",
|
|
hour12: true,
|
|
});
|
|
}
|
|
|
|
const Ledger_Index = result.result.ledger_index || "N/A";
|
|
const fee = xrpl.dropsToXrp(result.result.Fee);
|
|
|
|
// Check if transaction was successful
|
|
if (result.result?.meta?.TransactionResult === "tesSUCCESS") {
|
|
const fee = xrpl.dropsToXrp(result.result.Fee);
|
|
|
|
// Close the confirmation popup first
|
|
closePopup();
|
|
|
|
// Populate detailed success popup
|
|
const successDetailsContainer = getRef("successTransactionDetails");
|
|
if (successDetailsContainer) {
|
|
successDetailsContainer.innerHTML = `
|
|
<div class="detail-row">
|
|
<span class="detail-label">Status:</span>
|
|
<span class="detail-value" style="color: var(--success-color); font-weight: bold;">✅ Confirmed</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Amount Sent:</span>
|
|
<span class="detail-value">${amount} XRP</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Network Fee:</span>
|
|
<span class="detail-value">${fee} XRP</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">From:</span>
|
|
<span class="detail-value">${
|
|
wallet.classicAddress || wallet.address
|
|
}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">To:</span>
|
|
<span class="detail-value">${destination}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Transaction Hash:</span>
|
|
<span class="detail-value">${signed.hash}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Ledger Index:</span>
|
|
<span class="detail-value">${Ledger_Index}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Transaction Date:</span>
|
|
<span class="detail-value">${rippleDate}</span>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Show success popup
|
|
openPopup("transactionSuccess");
|
|
|
|
// Clear form safely
|
|
const sendKeyElement = getRef("sendKey");
|
|
const recipientElement = getRef("recipient");
|
|
const amountElement = getRef("amount");
|
|
|
|
if (sendKeyElement) sendKeyElement.value = "";
|
|
if (recipientElement) recipientElement.value = "";
|
|
if (amountElement) amountElement.value = "";
|
|
} else {
|
|
console.error(
|
|
"Transaction Failed:",
|
|
result.result?.meta?.TransactionResult || "Unknown error"
|
|
);
|
|
throw new Error(
|
|
`Transaction failed: ${
|
|
result.result?.meta?.TransactionResult || "Unknown error"
|
|
}`
|
|
);
|
|
}
|
|
} catch (err) {
|
|
console.error("Transaction failed:", err.message);
|
|
|
|
let errorMessage = err.message;
|
|
|
|
// Provide user-friendly error messages for common XRPL errors
|
|
if (err.message.includes("tefMASTER_DISABLED")) {
|
|
errorMessage =
|
|
"The sender account's master key is disabled. Please use a different account or enable the master key.";
|
|
} else if (err.message.includes("tefPAST_SEQ")) {
|
|
errorMessage =
|
|
"Transaction sequence number is too old. Please try again.";
|
|
} else if (err.message.includes("terPRE_SEQ")) {
|
|
errorMessage =
|
|
"Transaction sequence number is too high. Please try again.";
|
|
} else if (err.message.includes("tecUNFUNDED_PAYMENT")) {
|
|
errorMessage = "Insufficient funds to complete the transaction.";
|
|
} else if (err.message.includes("tecNO_DST")) {
|
|
errorMessage = "Destination account does not exist on the ledger.";
|
|
} else if (err.message.includes("tecNO_DST_INSUF_XRP")) {
|
|
errorMessage =
|
|
"Destination requires a minimum XRP balance to receive this transaction.";
|
|
} else if (err.message.includes("LastLedgerSequence")) {
|
|
errorMessage =
|
|
"Transaction expired (took too long to process). Please try again.";
|
|
} else if (err.message.includes("Account not found")) {
|
|
errorMessage =
|
|
"Sender account does not exist or is not activated on the ledger.";
|
|
} else if (err.message.includes("master key is disabled")) {
|
|
errorMessage =
|
|
"This account's master key is disabled and cannot send transactions.";
|
|
} else if (err.message.includes("Insufficient balance")) {
|
|
// This is already user-friendly from our custom check
|
|
} else if (
|
|
err.message.includes("timeout") ||
|
|
err.message.includes("network")
|
|
) {
|
|
errorMessage =
|
|
"Network timeout. Please check your connection and try again.";
|
|
}
|
|
|
|
const formattedError = `
|
|
<div style="font-weight: 600; margin-bottom: 0.5rem;">
|
|
<i class="fas fa-exclamation-circle" style="color: var(--danger-color); margin-right: 0.5rem;"></i>
|
|
Transaction Failed
|
|
</div>
|
|
<div style="font-size: 0.875rem;">${errorMessage}</div>
|
|
`;
|
|
notify(formattedError, "error", { timeout: 8000 });
|
|
|
|
// Close the confirmation popup on error
|
|
closePopup();
|
|
} finally {
|
|
await client.disconnect();
|
|
|
|
// Restore button state
|
|
confirmBtn.innerHTML = originalText;
|
|
confirmBtn.disabled = false;
|
|
|
|
// Don't close popup here - it's handled in success/error cases
|
|
window.pendingTransaction = null;
|
|
}
|
|
}
|
|
|
|
// Searched Addresses Management
|
|
async function updateSearchedAddressesList() {
|
|
try {
|
|
const searchedAddresses = await searchedAddressDB.getSearchedAddresses();
|
|
displaySearchedAddresses(searchedAddresses);
|
|
} catch (error) {
|
|
console.error("Error loading searched addresses:", error);
|
|
}
|
|
}
|
|
|
|
function displaySearchedAddresses(addresses) {
|
|
// Check if we need to create the searched addresses container
|
|
let container = document.getElementById("searchedAddressesContainer");
|
|
|
|
if (!container && addresses.length > 0) {
|
|
// Create the container after the balance check card
|
|
const balanceCard = document.querySelector("#connectPage .card");
|
|
container = document.createElement("div");
|
|
container.id = "searchedAddressesContainer";
|
|
container.className = "card searched-addresses-card";
|
|
balanceCard.parentNode.insertBefore(container, balanceCard.nextSibling);
|
|
}
|
|
|
|
if (!container) return;
|
|
|
|
if (addresses.length === 0) {
|
|
container.style.display = "none";
|
|
return;
|
|
}
|
|
|
|
container.style.display = "block";
|
|
container.innerHTML = `
|
|
<div class="searched-addresses-header">
|
|
<h3><i class="fas fa-history"></i> Searched addresses</h3>
|
|
<button onclick="clearAllSearchedAddresses()" class="btn-clear-all" title="Clear all">
|
|
<i class="fas fa-trash"></i> Clear All
|
|
</button>
|
|
</div>
|
|
<div class="searched-addresses-list">
|
|
${addresses
|
|
.map((addr, index) => {
|
|
// Check if this was converted from a private key
|
|
const hasSourceInfo =
|
|
addr.sourceInfo && addr.sourceInfo.originalAddress !== addr.address;
|
|
|
|
return `
|
|
<div class="searched-address-item ${
|
|
hasSourceInfo ? "has-source-info" : ""
|
|
}" data-index="${index}" data-current-type="${
|
|
hasSourceInfo ? addr.sourceInfo.blockchain.toLowerCase() : "xrp"
|
|
}">
|
|
${
|
|
hasSourceInfo
|
|
? `
|
|
<div class="address-toggle-section">
|
|
<div class="address-toggle-group">
|
|
<button onclick="toggleAddressType(${index}, '${addr.sourceInfo.blockchain.toLowerCase()}')"
|
|
class="btn-toggle-address active"
|
|
data-type="${addr.sourceInfo.blockchain.toLowerCase()}"
|
|
title="Show ${addr.sourceInfo.blockchain} Address">
|
|
${addr.sourceInfo.blockchain}
|
|
</button>
|
|
<button onclick="toggleAddressType(${index}, 'xrp')"
|
|
class="btn-toggle-address"
|
|
data-type="xrp"
|
|
title="Show XRP Address">
|
|
XRP
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="address-content-wrapper">
|
|
<div class="address-info">
|
|
<div class="address-display">
|
|
<div class="address-text" id="address-display-${index}" title="${
|
|
addr.sourceInfo.originalAddress
|
|
}">
|
|
${addr.sourceInfo.originalAddress}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="address-actions">
|
|
<button onclick="copyCurrentAddress(${index})" class="btn-copy-current" title="Copy Selected Address">
|
|
<i class="fas fa-copy"></i> COPY
|
|
</button>
|
|
<button onclick="deleteSearchedAddress('${
|
|
addr.address
|
|
}')" class="btn-delete" title="Delete">
|
|
Delete
|
|
</button>
|
|
<button onclick="recheckBalance('${
|
|
addr.address
|
|
}')" class="btn-check" title="Check balance">
|
|
Check balance
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`
|
|
: `
|
|
<div class="address-info">
|
|
<div class="address-display">
|
|
<div class="address-text" id="address-display-${index}" title="${addr.address}">
|
|
${addr.address}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="address-actions">
|
|
<button onclick="copyAddressToClipboard('${addr.address}')" class="btn-copy" title="Copy XRP Address">
|
|
<i class="fas fa-copy"></i> COPY
|
|
</button>
|
|
<button onclick="deleteSearchedAddress('${addr.address}')" class="btn-delete" title="Delete">
|
|
Delete
|
|
</button>
|
|
<button onclick="recheckBalance('${addr.address}')" class="btn-check" title="Check balance">
|
|
Check balance
|
|
</button>
|
|
</div>
|
|
`
|
|
}
|
|
</div>
|
|
`;
|
|
})
|
|
.join("")}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
|
|
// toggle between address types in searched addresses
|
|
async function toggleAddressType(addressIndex, type) {
|
|
try {
|
|
// Get the searched addresses list
|
|
const addresses = await searchedAddressDB.getSearchedAddresses();
|
|
if (!addresses[addressIndex]) return;
|
|
|
|
const addressItem = addresses[addressIndex];
|
|
const container = document.querySelector(`[data-index="${addressIndex}"]`);
|
|
if (!container) return;
|
|
|
|
// Update toggle button states
|
|
const toggleButtons = container.querySelectorAll(".btn-toggle-address");
|
|
toggleButtons.forEach((btn) => btn.classList.remove("active"));
|
|
|
|
const activeButton = container.querySelector(`[data-type="${type}"]`);
|
|
if (activeButton) {
|
|
activeButton.classList.add("active");
|
|
}
|
|
|
|
// Store the current selection in the container data
|
|
container.setAttribute("data-current-type", type);
|
|
|
|
// Update the displayed address text based on selection
|
|
const addressDisplay = container.querySelector(
|
|
`#address-display-${addressIndex}`
|
|
);
|
|
if (addressDisplay) {
|
|
if (type === "xrp") {
|
|
// Show XRP address
|
|
addressDisplay.textContent = addressItem.address;
|
|
addressDisplay.title = addressItem.address;
|
|
} else {
|
|
// Show original blockchain address (FLO/BTC)
|
|
const originalAddress =
|
|
addressItem.sourceInfo?.originalAddress || addressItem.address;
|
|
addressDisplay.textContent = originalAddress;
|
|
addressDisplay.title = originalAddress;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Error toggling address type:", error);
|
|
}
|
|
}
|
|
|
|
// copy the currently selected address
|
|
async function copyCurrentAddress(addressIndex) {
|
|
try {
|
|
// Get the searched addresses list
|
|
const addresses = await searchedAddressDB.getSearchedAddresses();
|
|
if (!addresses[addressIndex]) return;
|
|
|
|
const addressItem = addresses[addressIndex];
|
|
const container = document.querySelector(`[data-index="${addressIndex}"]`);
|
|
if (!container) return;
|
|
|
|
// Get the current selection type
|
|
const currentType = container.getAttribute("data-current-type") || "flo"; // Default to original blockchain
|
|
|
|
let addressToCopy;
|
|
let addressLabel;
|
|
|
|
if (currentType === "xrp") {
|
|
addressToCopy = addressItem.address;
|
|
addressLabel = "XRP address";
|
|
} else {
|
|
addressToCopy =
|
|
addressItem.sourceInfo?.originalAddress || addressItem.address;
|
|
addressLabel = `${
|
|
addressItem.sourceInfo?.blockchain || "Original"
|
|
} address`;
|
|
}
|
|
|
|
await copyAddressToClipboard(addressToCopy, addressLabel);
|
|
} catch (error) {
|
|
console.error("Error copying current address:", error);
|
|
notify("Failed to copy address", "error");
|
|
}
|
|
}
|
|
|
|
async function deleteSearchedAddress(address) {
|
|
try {
|
|
await searchedAddressDB.deleteSearchedAddress(address);
|
|
await updateSearchedAddressesList();
|
|
notify("Address removed from history", "success");
|
|
} catch (error) {
|
|
console.error("Error deleting searched address:", error);
|
|
notify("Failed to remove address", "error");
|
|
}
|
|
}
|
|
|
|
async function clearAllSearchedAddresses() {
|
|
try {
|
|
await searchedAddressDB.clearAllSearchedAddresses();
|
|
await updateSearchedAddressesList();
|
|
notify("All searched addresses cleared", "success");
|
|
} catch (error) {
|
|
console.error("Error clearing searched addresses:", error);
|
|
notify("Failed to clear addresses", "error");
|
|
}
|
|
}
|
|
|
|
async function copyAddressToClipboard(address, label = "Address") {
|
|
try {
|
|
await navigator.clipboard.writeText(address);
|
|
notify(`${label} copied to clipboard`, "success");
|
|
} catch (error) {
|
|
console.error("Error copying to clipboard:", error);
|
|
notify("Failed to copy address", "error");
|
|
}
|
|
}
|
|
|
|
async function recheckBalance(xrpAddress) {
|
|
document.getElementById("checkAddress").value = xrpAddress;
|
|
await checkBalanceAndTransactions();
|
|
}
|
|
|
|
// Transaction pagination and filtering
|
|
let allTransactions = [];
|
|
let filteredTransactions = [];
|
|
let currentPage = 1;
|
|
const transactionsPerPage = 10;
|
|
let currentFilter = "all";
|
|
|
|
function setTransactionFilter(filter) {
|
|
currentFilter = filter;
|
|
currentPage = 1;
|
|
|
|
// Update filter button states
|
|
document.querySelectorAll(".filter-btn").forEach((btn) => {
|
|
btn.classList.remove("active");
|
|
if (btn.dataset.filter === filter) {
|
|
btn.classList.add("active");
|
|
}
|
|
});
|
|
|
|
// Filter transactions
|
|
filterAndDisplayTransactions();
|
|
}
|
|
|
|
function filterAndDisplayTransactions() {
|
|
// Try to get address from either input field
|
|
let address = "";
|
|
|
|
const checkInput = document.getElementById("checkAddress");
|
|
|
|
if (checkInput && checkInput.value.trim()) {
|
|
address = checkInput.value.trim();
|
|
} else {
|
|
filteredTransactions = [...allTransactions];
|
|
displayTransactionsPage();
|
|
updatePaginationControls();
|
|
return;
|
|
}
|
|
|
|
// Filter transactions based on current filter
|
|
switch (currentFilter) {
|
|
case "received":
|
|
filteredTransactions = allTransactions.filter(
|
|
(tx) => tx.tx.Destination === address
|
|
);
|
|
break;
|
|
case "sent":
|
|
filteredTransactions = allTransactions.filter(
|
|
(tx) => tx.tx.Account === address
|
|
);
|
|
break;
|
|
default:
|
|
filteredTransactions = [...allTransactions];
|
|
}
|
|
|
|
displayTransactionsPage();
|
|
updatePaginationControls();
|
|
}
|
|
|
|
function displayTransactionsPage() {
|
|
const startIndex = (currentPage - 1) * transactionsPerPage;
|
|
const endIndex = startIndex + transactionsPerPage;
|
|
const pageTransactions = filteredTransactions.slice(startIndex, endIndex);
|
|
|
|
const txList = document.getElementById("txList");
|
|
const address = document.getElementById("checkAddress").value.trim();
|
|
|
|
if (pageTransactions.length === 0) {
|
|
if (filteredTransactions.length === 0 && allTransactions.length > 0) {
|
|
txList.innerHTML = `
|
|
<div class="no-transactions">
|
|
<i class="fas fa-filter"></i>
|
|
<p>No ${
|
|
currentFilter === "all" ? "" : currentFilter
|
|
} transactions found for this filter.</p>
|
|
</div>
|
|
`;
|
|
} else {
|
|
txList.innerHTML = `
|
|
<div class="no-transactions">
|
|
<i class="fas fa-inbox"></i>
|
|
<p>No transactions found for this address.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
return;
|
|
}
|
|
|
|
txList.innerHTML = "";
|
|
|
|
pageTransactions.forEach((tx) => {
|
|
const t = tx.tx;
|
|
const meta = tx.meta;
|
|
const date = new Date((t.date + 946684800) * 1000); // Ripple epoch conversion
|
|
|
|
// Determine transaction direction and type
|
|
const isIncoming = t.Destination === address;
|
|
const direction = isIncoming ? "Received" : "Sent";
|
|
const directionIcon = isIncoming ? "fa-arrow-down" : "fa-arrow-up";
|
|
const directionClass = isIncoming ? "incoming" : "outgoing";
|
|
|
|
// Get amount - handle different formats
|
|
let amount = "0";
|
|
if (meta.delivered_amount) {
|
|
amount = xrpl.dropsToXrp(meta.delivered_amount);
|
|
} else if (t.Amount && typeof t.Amount === "string") {
|
|
amount = xrpl.dropsToXrp(t.Amount);
|
|
}
|
|
|
|
// Format date
|
|
const formattedDate = date.toLocaleDateString("en-US", {
|
|
day: "2-digit",
|
|
month: "short",
|
|
year: "numeric",
|
|
});
|
|
const formattedTime = date.toLocaleTimeString("en-US", {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
hour12: true,
|
|
});
|
|
|
|
// Transaction status
|
|
const isSuccess = meta.TransactionResult === "tesSUCCESS";
|
|
const statusText = isSuccess ? "Confirmed" : "Failed";
|
|
const statusClass = isSuccess ? "success" : "failed";
|
|
|
|
const div = document.createElement("div");
|
|
div.className = `transaction-card ${directionClass}`;
|
|
div.innerHTML = `
|
|
<div class="tx-main">
|
|
<div class="tx-icon">
|
|
<i class="fas ${directionIcon}"></i>
|
|
</div>
|
|
<div class="tx-info">
|
|
<div class="tx-header">
|
|
<span class="tx-direction">${direction}</span>
|
|
<span class="tx-date">${formattedDate}, ${formattedTime}</span>
|
|
</div>
|
|
<div class="tx-amount ${directionClass}">
|
|
${amount} XRP
|
|
</div>
|
|
<div class="tx-addresses">
|
|
<div class="tx-address-row">
|
|
<span class="address-label">From:</span>
|
|
<span class="address-value">${t.Account.substring(
|
|
0,
|
|
8
|
|
)}...${t.Account.substring(t.Account.length - 6)}</span>
|
|
</div>
|
|
<div class="tx-address-row">
|
|
<span class="address-label">To:</span>
|
|
<span class="address-value">${
|
|
t.Destination
|
|
? t.Destination.substring(0, 8) +
|
|
"..." +
|
|
t.Destination.substring(t.Destination.length - 6)
|
|
: "N/A"
|
|
}</span>
|
|
</div>
|
|
</div>
|
|
<div class="tx-hash">
|
|
<span class="hash-label">Tx:</span>
|
|
<span class="hash-value">${t.hash.substring(
|
|
0,
|
|
8
|
|
)}...${t.hash.substring(t.hash.length - 6)}</span>
|
|
</div>
|
|
</div>
|
|
<div class="tx-status ${statusClass}">
|
|
${statusText}
|
|
</div>
|
|
</div>
|
|
`;
|
|
txList.appendChild(div);
|
|
});
|
|
}
|
|
|
|
function updatePaginationControls() {
|
|
const totalPages = Math.ceil(
|
|
filteredTransactions.length / transactionsPerPage
|
|
);
|
|
const startIndex = (currentPage - 1) * transactionsPerPage + 1;
|
|
const endIndex = Math.min(
|
|
currentPage * transactionsPerPage,
|
|
filteredTransactions.length
|
|
);
|
|
|
|
// Update pagination info
|
|
document.getElementById(
|
|
"paginationInfo"
|
|
).textContent = `Showing ${startIndex} - ${endIndex} of ${filteredTransactions.length} transactions`;
|
|
|
|
// Update previous/next buttons
|
|
document.getElementById("prevBtn").disabled = currentPage === 1;
|
|
document.getElementById("nextBtn").disabled =
|
|
currentPage === totalPages || totalPages === 0;
|
|
|
|
// Update page numbers
|
|
generatePageNumbers(totalPages);
|
|
}
|
|
|
|
function generatePageNumbers(totalPages) {
|
|
const pageNumbers = document.getElementById("pageNumbers");
|
|
pageNumbers.innerHTML = "";
|
|
|
|
if (totalPages <= 1) return;
|
|
|
|
// Calculate which page numbers to show
|
|
let startPage = Math.max(1, currentPage - 2);
|
|
let endPage = Math.min(totalPages, startPage + 4);
|
|
|
|
if (endPage - startPage < 4) {
|
|
startPage = Math.max(1, endPage - 4);
|
|
}
|
|
|
|
// Add first page and ellipsis if needed
|
|
if (startPage > 1) {
|
|
addPageNumber(1);
|
|
if (startPage > 2) {
|
|
const ellipsis = document.createElement("span");
|
|
ellipsis.textContent = "...";
|
|
ellipsis.className = "page-ellipsis";
|
|
pageNumbers.appendChild(ellipsis);
|
|
}
|
|
}
|
|
|
|
// Add page numbers
|
|
for (let i = startPage; i <= endPage; i++) {
|
|
addPageNumber(i);
|
|
}
|
|
|
|
// Add last page and ellipsis if needed
|
|
if (endPage < totalPages) {
|
|
if (endPage < totalPages - 1) {
|
|
const ellipsis = document.createElement("span");
|
|
ellipsis.textContent = "...";
|
|
ellipsis.className = "page-ellipsis";
|
|
pageNumbers.appendChild(ellipsis);
|
|
}
|
|
addPageNumber(totalPages);
|
|
}
|
|
}
|
|
|
|
function addPageNumber(pageNum) {
|
|
const pageNumbers = document.getElementById("pageNumbers");
|
|
const pageBtn = document.createElement("button");
|
|
pageBtn.className = `page-number ${pageNum === currentPage ? "active" : ""}`;
|
|
pageBtn.textContent = pageNum;
|
|
pageBtn.onclick = () => goToPage(pageNum);
|
|
pageNumbers.appendChild(pageBtn);
|
|
}
|
|
|
|
function goToPage(page) {
|
|
currentPage = page;
|
|
displayTransactionsPage();
|
|
updatePaginationControls();
|
|
}
|
|
|
|
function goToPreviousPage() {
|
|
if (currentPage > 1) {
|
|
goToPage(currentPage - 1);
|
|
}
|
|
}
|
|
|
|
function goToNextPage() {
|
|
const totalPages = Math.ceil(
|
|
filteredTransactions.length / transactionsPerPage
|
|
);
|
|
if (currentPage < totalPages) {
|
|
goToPage(currentPage + 1);
|
|
}
|
|
}
|
|
|
|
//combines balance checking and transaction lookup
|
|
async function checkBalanceAndTransactions() {
|
|
const addressInput = document.getElementById("checkAddress");
|
|
const userInput = addressInput.value.trim();
|
|
let actualXRPAddress = userInput;
|
|
let sourceInfo = null;
|
|
|
|
try {
|
|
if (!userInput) {
|
|
notify(
|
|
"Please enter an XRP address, BTC private key, or FLO private key",
|
|
"error"
|
|
);
|
|
return;
|
|
}
|
|
|
|
// If it's already an XRP address, use it directly
|
|
if (
|
|
userInput.startsWith("r") &&
|
|
userInput.length >= 25 &&
|
|
userInput.length <= 34
|
|
) {
|
|
actualXRPAddress = userInput;
|
|
|
|
}
|
|
// Check if user is trying to enter BTC or FLO addresses (which we don't support)
|
|
else if (
|
|
userInput.startsWith("1") || // BTC Legacy address
|
|
userInput.startsWith("3") || // BTC Script address
|
|
userInput.startsWith("bc1") || // BTC Bech32 address
|
|
userInput.startsWith("F") // FLO address
|
|
) {
|
|
let addressType = "";
|
|
if (
|
|
userInput.startsWith("1") ||
|
|
userInput.startsWith("3") ||
|
|
userInput.startsWith("bc1")
|
|
) {
|
|
addressType = "Bitcoin address";
|
|
} else if (userInput.startsWith("F")) {
|
|
addressType = "FLO address";
|
|
}
|
|
|
|
notify(
|
|
`${addressType} detected. Please use private keys only, not addresses. Enter a Bitcoin private key (starting with 'L' or 'K'), FLO private key, or XRP address (starting with 'r').`,
|
|
"error"
|
|
);
|
|
return;
|
|
}
|
|
// Detect if input is a private key and convert to XRP address
|
|
else if (!userInput.startsWith("r")) {
|
|
try {
|
|
// Check if it's a Bitcoin WIF format (starts with "L" or "K")
|
|
if (userInput.startsWith("L") || userInput.startsWith("K")) {
|
|
notify(
|
|
"Detected Bitcoin private key - converting to XRP address...",
|
|
"info"
|
|
);
|
|
|
|
// Convert BTC WIF to XRP
|
|
const xrpResult = convertWIFtoRippleWallet(userInput);
|
|
actualXRPAddress = xrpResult.address;
|
|
|
|
// Get BTC address for source info
|
|
const btcResult = generateBTCFromPrivateKey(userInput);
|
|
|
|
sourceInfo = {
|
|
type: "Bitcoin Private Key",
|
|
originalKey: userInput,
|
|
originalAddress: btcResult ? btcResult.address : "N/A",
|
|
blockchain: "BTC",
|
|
};
|
|
|
|
notify(
|
|
`Converted Bitcoin key to XRP address: ${actualXRPAddress}`,
|
|
"success"
|
|
);
|
|
}
|
|
// FLO private key or other WIF format (but NOT XRP seeds)
|
|
else {
|
|
try {
|
|
notify(
|
|
"Detected FLO private key - converting to XRP address...",
|
|
"info"
|
|
);
|
|
|
|
// convert as FLO WIF
|
|
const xrpResult = convertWIFtoRippleWallet(userInput);
|
|
actualXRPAddress = xrpResult.address;
|
|
|
|
// get FLO address for source info
|
|
const floResult = generateFLOFromPrivateKey(userInput);
|
|
|
|
sourceInfo = {
|
|
type: "FLO Private Key",
|
|
originalKey: userInput,
|
|
originalAddress: floResult ? floResult.address : "N/A",
|
|
blockchain: "FLO",
|
|
};
|
|
|
|
notify(
|
|
`Converted FLO private key to XRP address: ${actualXRPAddress}`,
|
|
"success"
|
|
);
|
|
} catch (conversionError) {
|
|
throw new Error(
|
|
"Invalid input. Please enter a valid XRP address (starting with 'r'),private key(BTC/FLO)."
|
|
);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
notify(`Invalid input: ${error.message}`, "error");
|
|
return;
|
|
}
|
|
}
|
|
// Handle invalid XRP address format
|
|
else {
|
|
notify(
|
|
"Invalid XRP address format. XRP addresses should start with 'r' and be 25-34 characters long.",
|
|
"error"
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Validate the final XRP address format
|
|
if (
|
|
!actualXRPAddress.startsWith("r") ||
|
|
actualXRPAddress.length < 25 ||
|
|
actualXRPAddress.length > 34
|
|
) {
|
|
notify("Invalid or unconvertible address format", "error");
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const checkBtn = document.querySelector(
|
|
'[onclick="checkBalanceAndTransactions()"]'
|
|
);
|
|
const originalText = checkBtn.innerHTML;
|
|
checkBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Loading...';
|
|
checkBtn.disabled = true;
|
|
|
|
// Create XRPL client instance
|
|
const client = new xrpl.Client("wss://s.altnet.rippletest.net:51233");
|
|
|
|
try {
|
|
await client.connect();
|
|
|
|
// First, check balance
|
|
try {
|
|
const accountInfo = await client.request({
|
|
command: "account_info",
|
|
account: actualXRPAddress,
|
|
ledger_index: "current",
|
|
});
|
|
|
|
const balance =
|
|
parseFloat(accountInfo.result.account_data.Balance) / 1000000; // Convert drops to XRP
|
|
|
|
// Update balance display
|
|
document.getElementById(
|
|
"displayBalance"
|
|
).textContent = `${balance.toLocaleString()} XRP`;
|
|
document.getElementById("checkedAddress").textContent =
|
|
actualXRPAddress;
|
|
|
|
// Save to IndexedDB with source information
|
|
try {
|
|
await searchedAddressDB.saveSearchedAddress(
|
|
actualXRPAddress,
|
|
balance.toLocaleString(),
|
|
Date.now(),
|
|
sourceInfo
|
|
);
|
|
await updateSearchedAddressesList();
|
|
} catch (dbError) {
|
|
console.warn("Failed to save address to IndexedDB:", dbError);
|
|
}
|
|
|
|
// Show balance info
|
|
document.getElementById("balanceInfo").style.display = "block";
|
|
} catch (error) {
|
|
if (error.data && error.data.error === "actNotFound") {
|
|
// Account not found (not activated)
|
|
document.getElementById("displayBalance").textContent = "0 XRP";
|
|
document.getElementById("checkedAddress").textContent =
|
|
actualXRPAddress;
|
|
|
|
// Save to IndexedDB with source information
|
|
try {
|
|
await searchedAddressDB.saveSearchedAddress(
|
|
actualXRPAddress,
|
|
"0",
|
|
Date.now(),
|
|
sourceInfo
|
|
);
|
|
await updateSearchedAddressesList();
|
|
} catch (dbError) {
|
|
console.warn("Failed to save address to IndexedDB:", dbError);
|
|
}
|
|
|
|
// Show balance info
|
|
document.getElementById("balanceInfo").style.display = "block";
|
|
|
|
notify(
|
|
"Account not found - Address not activated (needs 10 XRP minimum)",
|
|
"warning"
|
|
);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Now lookup transactions
|
|
try {
|
|
const res = await client.request({
|
|
command: "account_tx",
|
|
account: actualXRPAddress,
|
|
ledger_index_min: -1,
|
|
ledger_index_max: -1,
|
|
limit: 1000,
|
|
});
|
|
|
|
// Store all transactions
|
|
allTransactions = res.result.transactions;
|
|
|
|
// Reset pagination state
|
|
currentPage = 1;
|
|
currentFilter = "all";
|
|
|
|
// Reset filter buttons
|
|
document.querySelectorAll(".filter-btn").forEach((btn) => {
|
|
btn.classList.remove("active");
|
|
if (btn.dataset.filter === "all") {
|
|
btn.classList.add("active");
|
|
}
|
|
});
|
|
|
|
if (allTransactions.length === 0) {
|
|
document.getElementById("txList").innerHTML =
|
|
'<div class="no-transactions"><i class="fas fa-inbox"></i><p>No transactions found for this address.</p></div>';
|
|
} else {
|
|
// Show controls and display transactions
|
|
document.getElementById("transactionControls").style.display =
|
|
"block";
|
|
filterAndDisplayTransactions();
|
|
}
|
|
|
|
// transaction lookup address field value for filtering compatibility
|
|
const lookupInput = document.getElementById("checkAddress");
|
|
if (lookupInput) {
|
|
lookupInput.value = actualXRPAddress;
|
|
}
|
|
|
|
// Show transaction section
|
|
document.getElementById("transactionSection").style.display = "block";
|
|
|
|
const sourceMessage = sourceInfo
|
|
? ` (converted from ${sourceInfo.blockchain} ${sourceInfo.type})`
|
|
: "";
|
|
notify(
|
|
`Balance loaded${sourceMessage}. Found ${allTransactions.length} transactions`,
|
|
"success"
|
|
);
|
|
} catch (transactionError) {
|
|
console.warn("Failed to fetch transactions:", transactionError);
|
|
document.getElementById("txList").innerHTML =
|
|
'<div class="error-state"><i class="fas fa-exclamation-triangle"></i><p>Failed to load transactions.</p></div>';
|
|
|
|
// Still show transaction section even if transactions failed
|
|
document.getElementById("transactionSection").style.display = "block";
|
|
|
|
notify("Balance loaded, but failed to fetch transactions", "warning");
|
|
}
|
|
} finally {
|
|
await client.disconnect();
|
|
// Restore button state
|
|
checkBtn.innerHTML = originalText;
|
|
checkBtn.disabled = false;
|
|
}
|
|
} catch (error) {
|
|
console.error("Error in checkBalanceAndTransactions:", error);
|
|
notify(`Error: ${error.message}`, "error");
|
|
// Restore button state on error
|
|
const checkBtn = document.querySelector(
|
|
'[onclick="checkBalanceAndTransactions()"]'
|
|
);
|
|
if (checkBtn) {
|
|
checkBtn.innerHTML = '<i class="fas fa-search-dollar"></i> Check Balance';
|
|
checkBtn.disabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate specific cryptocurrency addresses
|
|
async function generateXRPAddress() {
|
|
try {
|
|
// Get private key from the wallet generation form input
|
|
const keyInput = document.getElementById("generateKey");
|
|
if (!keyInput) {
|
|
notify("Private key input field not found", "error");
|
|
return;
|
|
}
|
|
|
|
const sourcePrivateKey = keyInput.value.trim();
|
|
if (!sourcePrivateKey) {
|
|
notify(
|
|
"Please enter a private key from any blockchain (BTC/FLO)",
|
|
"error"
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const generateBtn = document.querySelector(
|
|
'[onclick="generateXRPAddress()"]'
|
|
);
|
|
const originalText = generateBtn.innerHTML;
|
|
generateBtn.innerHTML =
|
|
'<i class="fas fa-spinner fa-spin"></i> Generating...';
|
|
generateBtn.disabled = true;
|
|
|
|
notify("Converting private key to multiple addresses...", "info");
|
|
|
|
// Convert the source private key using improved logic
|
|
let xrpResult;
|
|
let btcResult = null;
|
|
let floResult = null;
|
|
let sourceBlockchain = "Unknown";
|
|
|
|
try {
|
|
if (
|
|
sourcePrivateKey.startsWith("L") ||
|
|
sourcePrivateKey.startsWith("K")
|
|
) {
|
|
// Bitcoin WIF format
|
|
sourceBlockchain = "Bitcoin";
|
|
xrpResult = convertWIFtoRippleWallet(sourcePrivateKey);
|
|
|
|
// Generate FLO from BTC
|
|
floResult = generateFLOFromPrivateKey(sourcePrivateKey);
|
|
|
|
// Keep original BTC info
|
|
btcResult = generateBTCFromPrivateKey(sourcePrivateKey);
|
|
} else {
|
|
// Try to decode as WIF (FLO or other)
|
|
try {
|
|
sourceBlockchain = "FLO/Other";
|
|
xrpResult = convertWIFtoRippleWallet(sourcePrivateKey);
|
|
|
|
// Generate BTC from FLO
|
|
btcResult = generateBTCFromPrivateKey(sourcePrivateKey);
|
|
|
|
// Keep original FLO info (if floCrypto available)
|
|
floResult = generateFLOFromPrivateKey(sourcePrivateKey);
|
|
} catch (e) {
|
|
throw new Error(
|
|
"Unsupported private key format. Please use BTC WIF, FLO WIF private key."
|
|
);
|
|
}
|
|
}
|
|
|
|
// Display result with all blockchain information
|
|
const outputDiv = document.getElementById("walletOutput");
|
|
if (outputDiv) {
|
|
outputDiv.innerHTML = `
|
|
<div class="wallet-result">
|
|
<h3><i class="fas fa-coins"></i> Multi-Blockchain Addresses Generated</h3>
|
|
<div class="wallet-details">
|
|
|
|
<!-- XRP Section -->
|
|
<div class="blockchain-section">
|
|
<h4><i class="fas fa-coins" style="color: #23b469;"></i> Ripple (XRP)</h4>
|
|
<div class="detail-row">
|
|
<label>XRP Address:</label>
|
|
<div class="value-container">
|
|
<code>${xrpResult.address}</code>
|
|
<button onclick="copyToClipboard('${
|
|
xrpResult.address
|
|
}')" class="btn-copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>XRP Seed:</label>
|
|
<div class="value-container">
|
|
<code>${xrpResult.seed}</code>
|
|
<button onclick="copyToClipboard('${
|
|
xrpResult.seed
|
|
}')" class="btn-copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
${
|
|
btcResult
|
|
? `
|
|
<!-- BTC Section -->
|
|
<div class="blockchain-section">
|
|
<h4><i class="fab fa-bitcoin" style="color: #f2a900;"></i> Bitcoin (BTC)</h4>
|
|
<div class="detail-row">
|
|
<label>BTC Address:</label>
|
|
<div class="value-container">
|
|
<code>${btcResult.address}</code>
|
|
<button onclick="copyToClipboard('${btcResult.address}')" class="btn-copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>BTC Private Key:</label>
|
|
<div class="value-container">
|
|
<code>${btcResult.privateKey}</code>
|
|
<button onclick="copyToClipboard('${btcResult.privateKey}')" class="btn-copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
: ""
|
|
}
|
|
|
|
${
|
|
floResult
|
|
? `
|
|
<!-- FLO Section -->
|
|
<div class="blockchain-section">
|
|
<h4><i class="fas fa-leaf" style="color: #00d4aa;"></i> FLO Chain</h4>
|
|
<div class="detail-row">
|
|
<label>FLO Address:</label>
|
|
<div class="value-container">
|
|
<code>${floResult.address}</code>
|
|
<button onclick="copyToClipboard('${floResult.address}')" class="btn-copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>FLO Private Key:</label>
|
|
<div class="value-container">
|
|
<code>${floResult.privateKey}</code>
|
|
<button onclick="copyToClipboard('${floResult.privateKey}')" class="btn-copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
: ""
|
|
}
|
|
|
|
</div>
|
|
<div class="warning-message" style="margin-top: 1rem; padding: 0.75rem; background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 6px; color: #856404;">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<strong>Important:</strong> These addresses are mathematically derived from your ${sourceBlockchain} private key using proper elliptic curve cryptography. Keep all private keys secure.
|
|
</div>
|
|
</div>
|
|
`;
|
|
outputDiv.style.display = "block";
|
|
}
|
|
|
|
const blockchainCount = 1 + (btcResult ? 1 : 0) + (floResult ? 1 : 0);
|
|
notify(
|
|
`${blockchainCount} blockchain addresses generated successfully from ${sourceBlockchain} private key!`,
|
|
"success"
|
|
);
|
|
} catch (conversionError) {
|
|
console.error("Private key conversion error:", conversionError);
|
|
notify(
|
|
"Failed to convert private key: " + conversionError.message,
|
|
"error"
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error("XRP generation error:", error);
|
|
notify("Failed to generate XRP address: " + error.message, "error");
|
|
} finally {
|
|
// Restore button state
|
|
const generateBtn = document.querySelector(
|
|
'[onclick="generateXRPAddress()"]'
|
|
);
|
|
if (generateBtn) {
|
|
generateBtn.innerHTML =
|
|
'<i class="fas fa-coins"></i> Generate XRP Address';
|
|
generateBtn.disabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Retrieve specific cryptocurrency addresses from private key
|
|
async function retrieveXRPAddress() {
|
|
const keyInput = document.getElementById("recoverKey");
|
|
if (!keyInput || !keyInput.value.trim()) {
|
|
notify("Please enter a private key or seed", "error");
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const retrieveBtn = document.querySelector(
|
|
'[onclick="retrieveXRPAddress()"]'
|
|
);
|
|
const originalText = retrieveBtn.innerHTML;
|
|
retrieveBtn.innerHTML =
|
|
'<i class="fas fa-spinner fa-spin"></i> Retrieving...';
|
|
retrieveBtn.disabled = true;
|
|
|
|
try {
|
|
const sourceKey = keyInput.value.trim();
|
|
let walletResult;
|
|
let sourceType;
|
|
let sourceBlockchain = "XRP";
|
|
let btcResult = null;
|
|
let floResult = null;
|
|
// Check if it's an XRP seed first (starts with 's')
|
|
if (sourceKey.startsWith("s") && sourceKey.length >= 25) {
|
|
try {
|
|
sourceType = "XRP Seed";
|
|
sourceBlockchain = "XRP";
|
|
notify("Retrieving addresses from XRP seed...", "info");
|
|
const rippleWallet = xrpl.Wallet.fromSeed(sourceKey);
|
|
walletResult = {
|
|
address: rippleWallet.address,
|
|
publicKey: rippleWallet.publicKey,
|
|
privateKey: rippleWallet.privateKey,
|
|
seed: rippleWallet.seed,
|
|
};
|
|
} catch (seedError) {
|
|
throw new Error("Invalid XRP seed format");
|
|
}
|
|
}
|
|
// Check if it's a Bitcoin WIF format (starts with "L" or "K")
|
|
else if (sourceKey.startsWith("L") || sourceKey.startsWith("K")) {
|
|
if (typeof elliptic === "undefined") {
|
|
throw new Error(
|
|
"elliptic library not loaded. Please refresh the page."
|
|
);
|
|
}
|
|
if (typeof bs58check === "undefined") {
|
|
throw new Error(
|
|
"bs58check library not loaded. Please refresh the page."
|
|
);
|
|
}
|
|
|
|
sourceType = "Bitcoin WIF";
|
|
sourceBlockchain = "Bitcoin";
|
|
notify("Converting Bitcoin WIF to multi-blockchain addresses...", "info");
|
|
walletResult = convertWIFtoRippleWallet(sourceKey);
|
|
try {
|
|
floResult = generateFLOFromPrivateKey(sourceKey);
|
|
} catch (error) {
|
|
console.warn("Could not generate FLO address:", error.message);
|
|
}
|
|
try {
|
|
btcResult = generateBTCFromPrivateKey(sourceKey);
|
|
} catch (error) {
|
|
console.warn("Could not generate BTC address:", error.message);
|
|
}
|
|
} else {
|
|
try {
|
|
if (
|
|
typeof elliptic === "undefined" ||
|
|
typeof bs58check === "undefined"
|
|
) {
|
|
throw new Error(
|
|
"Required libraries not loaded. Please refresh the page."
|
|
);
|
|
}
|
|
|
|
sourceType = "FLO/Other WIF";
|
|
sourceBlockchain = "FLO";
|
|
notify("Converting WIF key to multi-blockchain addresses...", "info");
|
|
walletResult = convertWIFtoRippleWallet(sourceKey);
|
|
try {
|
|
floResult = generateFLOFromPrivateKey(sourceKey);
|
|
} catch (error) {
|
|
console.warn("Could not generate FLO address:", error.message);
|
|
}
|
|
try {
|
|
btcResult = generateBTCFromPrivateKey(sourceKey);
|
|
} catch (error) {
|
|
console.warn("Could not generate BTC address:", error.message);
|
|
}
|
|
} catch (e) {
|
|
throw new Error(
|
|
`Unsupported key/seed format. Supported formats:
|
|
• XRP Seed (s...)
|
|
• Bitcoin WIF (L.../K...)
|
|
• FLO WIF`
|
|
);
|
|
}
|
|
}
|
|
|
|
// Display the retrieved wallet information
|
|
const outputDiv = document.getElementById("recoveryOutput");
|
|
if (outputDiv) {
|
|
outputDiv.innerHTML = `
|
|
<div class="wallet-result">
|
|
<h3><i class="fas fa-key"></i> Multi-Blockchain Addresses Retrieved</h3>
|
|
<div class="wallet-details">
|
|
<div class="detail-row">
|
|
<label>Source:</label>
|
|
<span>${sourceType} (${sourceBlockchain})</span>
|
|
</div>
|
|
|
|
<!-- XRP Section -->
|
|
<div class="blockchain-section">
|
|
<h4><i class="fas fa-coins" style="color: #23b469;"></i> Ripple (XRP)</h4>
|
|
<div class="detail-row">
|
|
<label>XRP Address:</label>
|
|
<div class="value-container">
|
|
<code>${walletResult.address}</code>
|
|
<button onclick="copyToClipboard('${
|
|
walletResult.address
|
|
}')" class="btn-copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>XRP Seed:</label>
|
|
<div class="value-container">
|
|
<code>${walletResult.seed}</code>
|
|
<button onclick="copyToClipboard('${
|
|
walletResult.seed
|
|
}')" class="btn-copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
${
|
|
btcResult
|
|
? `
|
|
<!-- BTC Section -->
|
|
<div class="blockchain-section">
|
|
<h4><i class="fab fa-bitcoin" style="color: #f2a900;"></i> Bitcoin (BTC)</h4>
|
|
<div class="detail-row">
|
|
<label>BTC Address:</label>
|
|
<div class="value-container">
|
|
<code>${btcResult.address}</code>
|
|
<button onclick="copyToClipboard('${btcResult.address}')" class="btn-copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>BTC Private Key:</label>
|
|
<div class="value-container">
|
|
<code>${btcResult.privateKey}</code>
|
|
<button onclick="copyToClipboard('${btcResult.privateKey}')" class="btn-copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
: ""
|
|
}
|
|
|
|
${
|
|
floResult
|
|
? `
|
|
<!-- FLO Section -->
|
|
<div class="blockchain-section">
|
|
<h4><i class="fas fa-leaf" style="color: #00d4aa;"></i> FLO Chain</h4>
|
|
<div class="detail-row">
|
|
<label>FLO Address:</label>
|
|
<div class="value-container">
|
|
<code>${floResult.address}</code>
|
|
<button onclick="copyToClipboard('${floResult.address}')" class="btn-copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="detail-row">
|
|
<label>FLO Private Key:</label>
|
|
<div class="value-container">
|
|
<code>${floResult.privateKey}</code>
|
|
<button onclick="copyToClipboard('${floResult.privateKey}')" class="btn-copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
: ""
|
|
}
|
|
|
|
</div>
|
|
<div class="warning-message" style="margin-top: 1rem; padding: 0.75rem; background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 6px; color: #856404;">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<strong>Retrieved from ${sourceType}:</strong> ${
|
|
sourceType === "XRP Seed"
|
|
? "These addresses were generated from your original XRP seed."
|
|
: `These addresses are mathematically derived from your ${sourceType} using elliptic curve cryptography.`
|
|
} Keep all private keys secure.
|
|
</div>
|
|
</div>
|
|
`;
|
|
outputDiv.style.display = "block";
|
|
}
|
|
|
|
const blockchainCount = 1 + (btcResult ? 1 : 0) + (floResult ? 1 : 0);
|
|
notify(
|
|
`${blockchainCount} blockchain addresses retrieved successfully from ${sourceType}!`,
|
|
"success"
|
|
);
|
|
} catch (conversionError) {
|
|
console.error("Key/seed conversion error:", conversionError);
|
|
notify("Failed to retrieve address: " + conversionError.message, "error");
|
|
} finally {
|
|
// Restore button state
|
|
const retrieveBtn = document.querySelector(
|
|
'[onclick="retrieveXRPAddress()"]'
|
|
);
|
|
if (retrieveBtn) {
|
|
retrieveBtn.innerHTML = '<i class="fas fa-coins"></i> Retrieve Addresses';
|
|
retrieveBtn.disabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper function for copying to clipboard
|
|
function copyToClipboard(text) {
|
|
navigator.clipboard
|
|
.writeText(text)
|
|
.then(() => {
|
|
notify("Copied to clipboard!", "success");
|
|
})
|
|
.catch(() => {
|
|
// Fallback for older browsers
|
|
const textArea = document.createElement("textarea");
|
|
textArea.value = text;
|
|
document.body.appendChild(textArea);
|
|
textArea.select();
|
|
document.execCommand("copy");
|
|
document.body.removeChild(textArea);
|
|
notify("Copied to clipboard!", "success");
|
|
});
|
|
}
|
|
|
|
function generateBTCFromPrivateKey(privateKey) {
|
|
try {
|
|
if (typeof btcOperator === "undefined") {
|
|
throw new Error("btcOperator library not available");
|
|
}
|
|
|
|
// Convert private key to WIF format if it's hex
|
|
let wifKey = privateKey;
|
|
if (/^[0-9a-fA-F]{64}$/.test(privateKey)) {
|
|
wifKey = coinjs.privkey2wif(privateKey);
|
|
}
|
|
let btcPrivateKey = btcOperator.convert.wif(wifKey);
|
|
let btcAddress;
|
|
btcAddress = btcOperator.bech32Address(wifKey);
|
|
|
|
return {
|
|
address: btcAddress,
|
|
privateKey: btcPrivateKey,
|
|
};
|
|
} catch (error) {
|
|
console.warn("BTC generation error:", error.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// FLO address generation
|
|
function generateFLOFromPrivateKey(privateKey) {
|
|
try {
|
|
let flowif = privateKey;
|
|
|
|
if (/^[0-9a-fA-F]{64}$/.test(privateKey)) {
|
|
flowif = coinjs.privkey2wif(privateKey);
|
|
}
|
|
|
|
let floprivateKey = btcOperator.convert.wif(flowif, bitjs.priv);
|
|
let floAddress = floCrypto.getFloID(floprivateKey);
|
|
|
|
if (!floAddress) {
|
|
throw new Error("No working FLO address generation method found");
|
|
}
|
|
|
|
return {
|
|
address: floAddress,
|
|
privateKey: floprivateKey, // Returns the format that actually works
|
|
};
|
|
} catch (error) {
|
|
console.warn("FLO generation not available:", error.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
window.sendXRP = sendXRP;
|
|
|
|
window.generateXRPAddress = generateXRPAddress;
|
|
window.retrieveXRPAddress = retrieveXRPAddress;
|
|
window.copyToClipboard = copyToClipboard;
|
|
window.getRippleAddress = getRippleAddress;
|
|
window.confirmSend = confirmSend;
|
|
window.closePopup = closePopup;
|
|
window.checkBalanceAndTransactions = checkBalanceAndTransactions;
|
|
|
|
window.convertWIFtoRippleWallet = convertWIFtoRippleWallet;
|
|
|
|
// Multi-blockchain function exports
|
|
window.generateBTCFromPrivateKey = generateBTCFromPrivateKey;
|
|
window.generateFLOFromPrivateKey = generateFLOFromPrivateKey;
|
|
|
|
window.setTransactionFilter = setTransactionFilter;
|
|
window.goToPreviousPage = goToPreviousPage;
|
|
window.goToNextPage = goToNextPage;
|
|
// Input control functions
|
|
window.togglePasswordVisibility = togglePasswordVisibility;
|
|
window.clearInput = clearInput;
|
|
// Searched addresses functions
|
|
window.updateSearchedAddressesList = updateSearchedAddressesList;
|
|
window.deleteSearchedAddress = deleteSearchedAddress;
|
|
window.clearAllSearchedAddresses = clearAllSearchedAddresses;
|
|
window.copyAddressToClipboard = copyAddressToClipboard;
|
|
window.recheckBalance = recheckBalance;
|
|
window.toggleAddressType = toggleAddressType;
|
|
window.copyCurrentAddress = copyCurrentAddress;
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
initializeInputControls();
|
|
});
|