Workflow updating files of tronwallet
This commit is contained in:
parent
602075ab8e
commit
f9801ca065
37
tronwallet/README.md
Normal file
37
tronwallet/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# RanchiMall Tron Web Wallet
|
||||
|
||||
## Introduction
|
||||
|
||||
This project implements the RanchiMall Tron Web Wallet, providing multi-chain wallet functionality and seamless integration between Tron, FLO, and Bitcoin blockchains.
|
||||
|
||||
## Functions required for RanchiMall Tron Web Wallet
|
||||
|
||||
1. **Address Lookup**
|
||||
|
||||
- Allow search for any Tron blockchain address and display full transaction history.
|
||||
|
||||
2. **FLO Private Key Integration**
|
||||
|
||||
- Enable sending of TRX using a valid FLO blockchain private key or using TRON blockchain address private key of the sender.
|
||||
|
||||
3. **Multi-Chain Address Generation**
|
||||
|
||||
- On creating a new Tron address, automatically generate and display:
|
||||
- Equivalent FLO address
|
||||
- Equivalent Bitcoin address
|
||||
- Associated private keys for all three
|
||||
|
||||
4. **Private Key-Based Address Recovery**
|
||||
|
||||
- Derive the original Tron address from a valid FLO, Bitcoin, or Tron private key.
|
||||
|
||||
5. **Balance Retrieval**
|
||||
|
||||
- Show TRX balance for any address, using:
|
||||
- Tron blockchain address, or
|
||||
- Corresponding FLO / Bitcoin private keys
|
||||
|
||||
6. **Token Transfer**
|
||||
- Enable sending of TRX using:
|
||||
- Tron private key, or
|
||||
- Its corresponding/equivalent FLO and Bitcoin private keys
|
||||
BIN
tronwallet/RanchiMall_TRON_Wallet_Tasks.pdf
Normal file
BIN
tronwallet/RanchiMall_TRON_Wallet_Tasks.pdf
Normal file
Binary file not shown.
5093
tronwallet/css/style.css
Normal file
5093
tronwallet/css/style.css
Normal file
File diff suppressed because it is too large
Load Diff
11
tronwallet/favicon.svg
Normal file
11
tronwallet/favicon.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 2680.2 2915.7" style="enable-background:new 0 0 2680.2 2915.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#EB0029;}
|
||||
</style>
|
||||
<path class="st0" d="M1929.1,757.7L332,463.8l840.5,2114.9l1171.1-1426.8L1929.1,757.7z M1903.4,887.2l244.3,232.2l-668.2,121
|
||||
L1903.4,887.2z M1334.4,1216.2L630.1,632.1l1151.1,211.8L1334.4,1216.2z M1284.2,1319.5l-114.8,949.4L550.2,710.7L1284.2,1319.5z
|
||||
M1390.5,1369.9l739.9-134l-848.7,1034L1390.5,1369.9z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 729 B |
1170
tronwallet/index.html
Normal file
1170
tronwallet/index.html
Normal file
File diff suppressed because it is too large
Load Diff
BIN
tronwallet/ranchimall-logo.png
Normal file
BIN
tronwallet/ranchimall-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
470
tronwallet/scripts/balance.js
Normal file
470
tronwallet/scripts/balance.js
Normal file
@ -0,0 +1,470 @@
|
||||
function updateURLForPage(page, value) {
|
||||
let params = new URLSearchParams();
|
||||
if (page === "transaction") {
|
||||
params.set("page", "transactions");
|
||||
params.set("tx", value);
|
||||
} else if (page === "balance" || page === "history") {
|
||||
params.set("page", "transactions");
|
||||
params.set("address", value);
|
||||
}
|
||||
window.history.replaceState(
|
||||
{},
|
||||
"",
|
||||
`${location.pathname}?${params.toString()}`
|
||||
);
|
||||
}
|
||||
|
||||
function shareBalanceLink(address) {
|
||||
const addr =
|
||||
address || (document.getElementById("balanceAddr").value || "").trim();
|
||||
if (!addr) return;
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("page", "transactions");
|
||||
url.searchParams.set("address", addr);
|
||||
navigator.clipboard.writeText(url.toString()).then(() => {
|
||||
if (typeof notify === "function")
|
||||
notify("Shareable balance link copied", "success");
|
||||
});
|
||||
}
|
||||
function shareTxLink(txid) {
|
||||
const id = txid || (document.getElementById("txHash").value || "").trim();
|
||||
if (!id) return;
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("page", "transactions");
|
||||
url.searchParams.set("tx", id);
|
||||
navigator.clipboard.writeText(url.toString()).then(() => {
|
||||
if (typeof notify === "function")
|
||||
notify("Shareable tx link copied", "success");
|
||||
});
|
||||
}
|
||||
async function getTransactionDetails(txHash) {
|
||||
const url = "https://api.trongrid.io/wallet/gettransactionbyid";
|
||||
const headers = {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
const body = JSON.stringify({
|
||||
value: txHash,
|
||||
visible: true,
|
||||
});
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("Transaction details:", data);
|
||||
return data;
|
||||
}
|
||||
|
||||
async function getTransactionInfoById(txHash) {
|
||||
const url = "https://api.trongrid.io/wallet/gettransactioninfobyid";
|
||||
const headers = {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
const body = JSON.stringify({
|
||||
value: txHash,
|
||||
});
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("Transaction info:", data);
|
||||
return data;
|
||||
}
|
||||
async function getBalanceByAddress(address) {
|
||||
try {
|
||||
const balance = await tronWeb.trx.getBalance(address);
|
||||
return balance / 1e6;
|
||||
} catch (err) {
|
||||
throw new Error("Failed to fetch balance: " + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function getBalanceByPrivKey(privKey) {
|
||||
try {
|
||||
let rawHexKey;
|
||||
|
||||
// Detect WIF (BTC/FLO style)
|
||||
if (/^[5KLc9RQ][1-9A-HJ-NP-Za-km-z]{50,}$/.test(privKey)) {
|
||||
const decoded = coinjs.wif2privkey(privKey);
|
||||
if (!decoded || !decoded.privkey) {
|
||||
throw new Error("Invalid WIF private key");
|
||||
}
|
||||
rawHexKey = decoded.privkey;
|
||||
console.log("Detected WIF private key:", rawHexKey);
|
||||
|
||||
// Detect 64-char raw hex private key
|
||||
} else if (/^[0-9a-fA-F]{64}$/.test(privKey)) {
|
||||
rawHexKey = privKey;
|
||||
} else {
|
||||
throw new Error("Unsupported private key format");
|
||||
}
|
||||
|
||||
// Derive Tron address from private key
|
||||
const tronAddress = tronWeb.address.fromPrivateKey(rawHexKey);
|
||||
const balance = await getBalanceByAddress(tronAddress);
|
||||
|
||||
return { tronAddress, balance };
|
||||
} catch (err) {
|
||||
throw new Error("Invalid private key: " + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function runBalanceCheck() {
|
||||
const inputVal = document.getElementById("balanceAddr").value.trim();
|
||||
const output = document.getElementById("balanceOutput");
|
||||
|
||||
// Set loading state
|
||||
if (typeof setButtonLoading === "function") {
|
||||
setButtonLoading("balanceBtn", true);
|
||||
}
|
||||
|
||||
try {
|
||||
if (inputVal.startsWith("T")) {
|
||||
// Direct Tron address
|
||||
const tronAddress = inputVal;
|
||||
const balance = await getBalanceByAddress(inputVal);
|
||||
output.innerHTML = `
|
||||
<div class="card balance-info">
|
||||
<div class="balance-header">
|
||||
<h3><i class="fas fa-wallet"></i> Account Balance</h3>
|
||||
<button class="btn-icon share-btn" onclick="shareBalanceLink('${tronAddress}')" title="Copy shareable balance link">
|
||||
<i class="fas fa-share-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="balance-display">
|
||||
<div class="balance-amount">
|
||||
<span class="amount-number">${balance.toLocaleString()} TRX</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-details">
|
||||
<div class="detail-row">
|
||||
<label><i class="fas fa-map-marker-alt"></i> Address</label>
|
||||
<div class="value-container">
|
||||
<code>${tronAddress}</code>
|
||||
<button class="btn-icon" onclick="navigator.clipboard.writeText('${tronAddress}').then(()=>notify && notify('Address copied','success'))" title="Copy Address">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
if (typeof notify === "function") notify("Balance loaded", "success");
|
||||
loadHistoryFor(tronAddress);
|
||||
|
||||
// Save searched address to IndexedDB
|
||||
if (typeof searchedAddressDB !== "undefined") {
|
||||
try {
|
||||
await searchedAddressDB.saveSearchedAddress(
|
||||
tronAddress,
|
||||
balance.toLocaleString()
|
||||
);
|
||||
await updateSearchedAddressesList();
|
||||
} catch (dbError) {
|
||||
console.warn("Failed to save address to IndexedDB:", dbError);
|
||||
}
|
||||
}
|
||||
|
||||
updateURLForPage("balance", tronAddress);
|
||||
} else {
|
||||
// Treat as private key (WIF or HEX)
|
||||
const { tronAddress, balance } = await getBalanceByPrivKey(inputVal);
|
||||
|
||||
let sourceInfo = null;
|
||||
if (/^[5KLc9RQ][1-9A-HJ-NP-Za-km-z]{50,}$/.test(inputVal)) {
|
||||
// This is a BTC/FLO WIF key
|
||||
sourceInfo = {
|
||||
type: "Private Key",
|
||||
originalKey: inputVal,
|
||||
originalAddress: inputVal, // Store the original private key for toggling
|
||||
blockchain: /^[KL]/.test(inputVal) ? "BTC" : "FLO",
|
||||
};
|
||||
}
|
||||
|
||||
output.innerHTML = `
|
||||
<div class="card balance-info">
|
||||
<div class="balance-header">
|
||||
<h3><i class="fas fa-wallet"></i> Account Balance</h3>
|
||||
<button class="btn-icon share-btn" onclick="shareBalanceLink('${tronAddress}')" title="Copy shareable balance link">
|
||||
<i class="fas fa-share-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="balance-display">
|
||||
<div class="balance-amount">
|
||||
<span class="amount-number">${balance.toLocaleString()} TRX</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-details">
|
||||
<div class="detail-row">
|
||||
<label><i class="fas fa-map-marker-alt"></i> Address</label>
|
||||
<div class="value-container">
|
||||
<code>${tronAddress}</code>
|
||||
<button class="btn-icon" onclick="navigator.clipboard.writeText('${tronAddress}').then(()=>notify && notify('Address copied','success'))" title="Copy Address">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
if (typeof notify === "function") notify("Balance loaded", "success");
|
||||
loadHistoryFor(tronAddress);
|
||||
|
||||
// Save searched address to IndexedDB
|
||||
if (typeof searchedAddressDB !== "undefined") {
|
||||
try {
|
||||
await searchedAddressDB.saveSearchedAddress(
|
||||
tronAddress,
|
||||
balance.toLocaleString(),
|
||||
Date.now(),
|
||||
sourceInfo
|
||||
);
|
||||
await updateSearchedAddressesList();
|
||||
} catch (dbError) {
|
||||
console.warn("Failed to save address to IndexedDB:", dbError);
|
||||
}
|
||||
}
|
||||
|
||||
updateURLForPage("balance", tronAddress);
|
||||
}
|
||||
} catch (err) {
|
||||
output.innerHTML = `<div class="error-state"><div class="error-icon"><i class=\"fas fa-exclamation-triangle\"></i></div><h3>Failed</h3><p>${err.message}</p></div>`;
|
||||
if (typeof notify === "function") notify(err.message, "error");
|
||||
} finally {
|
||||
setButtonLoading("balanceBtn", false);
|
||||
}
|
||||
}
|
||||
|
||||
async function runTxSearch() {
|
||||
const input = document.getElementById("txHash");
|
||||
const output = document.getElementById("txOutput");
|
||||
const txid = (input.value || "").trim();
|
||||
if (!txid) {
|
||||
alert("Please enter a transaction hash");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validation for Tron transaction hash
|
||||
if (!/^[a-fA-F0-9]{64}$/.test(txid)) {
|
||||
if (typeof notify === "function") {
|
||||
notify("Invalid transaction hash format.", "error");
|
||||
} else {
|
||||
alert("Invalid transaction hash format.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setButtonLoading("txSearchBtn", true);
|
||||
try {
|
||||
if (typeof notify === "function")
|
||||
notify("Searching transaction...", "success", 1200);
|
||||
|
||||
const [tx, txInfo] = await Promise.all([
|
||||
getTransactionDetails(txid),
|
||||
getTransactionInfoById(txid),
|
||||
]);
|
||||
|
||||
// Extract transaction details from the response
|
||||
const id = tx.txID || txid;
|
||||
const ret = (tx.ret && tx.ret[0] && tx.ret[0].contractRet) || "SUCCESS";
|
||||
const contract =
|
||||
(tx.raw_data && tx.raw_data.contract && tx.raw_data.contract[0]) || {};
|
||||
const type = contract.type || "TransferContract";
|
||||
const parameter = contract.parameter && contract.parameter.value;
|
||||
const timestamp =
|
||||
tx.raw_data && tx.raw_data.timestamp
|
||||
? new Date(tx.raw_data.timestamp).toLocaleString()
|
||||
: "-";
|
||||
const blockNumber = txInfo.blockNumber || "-";
|
||||
|
||||
// Extract resources and fees from both transaction objects
|
||||
|
||||
let bandwidth = undefined;
|
||||
if (txInfo.receipt && txInfo.receipt.net_usage) {
|
||||
bandwidth = txInfo.receipt.net_usage;
|
||||
} else if (tx.net_usage) {
|
||||
bandwidth = tx.net_usage;
|
||||
}
|
||||
|
||||
// Check both transaction info and receipt for energy usage
|
||||
let energy = undefined;
|
||||
if (txInfo.receipt && txInfo.receipt.energy_usage) {
|
||||
energy = txInfo.receipt.energy_usage;
|
||||
} else if (tx.energy_usage) {
|
||||
energy = tx.energy_usage;
|
||||
}
|
||||
|
||||
// Check for fees from multiple sources
|
||||
let fee = "-";
|
||||
if (txInfo.receipt && txInfo.receipt.net_fee) {
|
||||
fee = txInfo.receipt.net_fee / 1000000;
|
||||
} else if (txInfo.fee) {
|
||||
fee = txInfo.fee / 1000000;
|
||||
} else if (tx.fee) {
|
||||
fee = tx.fee / 1000000;
|
||||
}
|
||||
|
||||
// Format the resource consumption and fee HTML
|
||||
let resourcesFeeHtml = "";
|
||||
let feeParts = [];
|
||||
if (bandwidth && bandwidth !== "-")
|
||||
feeParts.push(`<div class="resource-item">${bandwidth} Bandwidth</div>`);
|
||||
if (energy && energy !== "-")
|
||||
feeParts.push(`<div class="resource-item">${energy} Energy</div>`);
|
||||
if (fee && fee !== "-")
|
||||
feeParts.push(`<div class="resource-item">${fee} TRX</div>`);
|
||||
|
||||
if (feeParts.length) {
|
||||
resourcesFeeHtml = `
|
||||
<div class='tx-detail-row resource-row'>
|
||||
<span class='tx-detail-label'><i class='fas fa-cogs'></i> Resources Consumed & Fee:</span>
|
||||
<span class='tx-detail-value resources-list'>${feeParts.join(
|
||||
""
|
||||
)}</span>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
resourcesFeeHtml = `
|
||||
<div class='tx-detail-row resource-row'>
|
||||
<span class='tx-detail-label'><i class='fas fa-cogs'></i> Resources Consumed & Fee:</span>
|
||||
<span class='tx-detail-value resources-list'><span class="resource-item">- No resources consumed</span></span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
let detailsHtml = "";
|
||||
let owner = (parameter && parameter.owner_address) || "-";
|
||||
let to = (parameter && parameter.to_address) || "-";
|
||||
let amount =
|
||||
parameter && parameter.amount ? parameter.amount / 1000000 : "-";
|
||||
let tokenInfo = "";
|
||||
let resourceInfo = "";
|
||||
|
||||
if (type === "TriggerSmartContract") {
|
||||
let contractAddr =
|
||||
parameter && parameter.contract_address
|
||||
? parameter.contract_address
|
||||
: "-";
|
||||
let tokenAmount = "-";
|
||||
let tokenSymbol = "USDT";
|
||||
let tokenTo = "-";
|
||||
if (txInfo.log && txInfo.log.length > 0) {
|
||||
const log = txInfo.log[0];
|
||||
if (log.topics && log.topics.length >= 3) {
|
||||
tokenTo = tronWeb.address.fromHex("41" + log.topics[2].slice(-40));
|
||||
tokenAmount = parseInt(log.data, 16) / 1e6;
|
||||
}
|
||||
}
|
||||
tokenInfo = `<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-coins'></i> Amount:</span><span class='tx-detail-value amount'>${tokenAmount} USDT</span></div>`;
|
||||
detailsHtml = `
|
||||
<div class='tx-detail-card'>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-check-circle'></i> Status:</span><span class='tx-detail-value success'>${ret}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-exchange-alt'></i> Type:</span><span class='tx-detail-value'>${type}</span></div>
|
||||
${tokenInfo}
|
||||
</div>
|
||||
<div class='tx-detail-card'>
|
||||
${resourcesFeeHtml}
|
||||
</div>
|
||||
<div class='tx-detail-card'>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-user-minus'></i> From:</span><span class='tx-detail-value'>${owner}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-user-plus'></i> To:</span><span class='tx-detail-value'>${tokenTo}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-hashtag'></i> Hash:</span><span class='tx-detail-value'>${id}</span></div>
|
||||
</div>
|
||||
<div class='tx-detail-card'>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-layer-group'></i> Block:</span><span class='tx-detail-value'>${blockNumber}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-clock'></i> Date:</span><span class='tx-detail-value'>${timestamp}</span></div>
|
||||
</div>
|
||||
`;
|
||||
} else if (
|
||||
type === "DelegateResourceContract" ||
|
||||
type === "UnDelegateResourceContract"
|
||||
) {
|
||||
// Resource delegation/undelegation
|
||||
let resourceType =
|
||||
parameter && parameter.resource ? parameter.resource : "BANDWIDTH";
|
||||
let stakedAmount =
|
||||
amount !== "-" ? `${amount} TRX` : `${parameter.balance / 1e6} TRX`;
|
||||
let resourceTakenFrom =
|
||||
parameter && parameter.receiver_address
|
||||
? parameter.receiver_address
|
||||
: "-";
|
||||
let stakedAssetHtml = "";
|
||||
let delegatedHtml = "";
|
||||
let reclaimedHtml = "";
|
||||
if (type === "DelegateResourceContract") {
|
||||
stakedAssetHtml = `<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-coins'></i> Staked Asset Withheld:</span><span class='tx-detail-value amount'>${stakedAmount} </span></div>`;
|
||||
delegatedHtml = `<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-leaf'></i> Delegated Resources:</span><span class='tx-detail-value'>${resourceType}</span></div>`;
|
||||
} else if (type === "UnDelegateResourceContract") {
|
||||
stakedAssetHtml = `<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-coins'></i> Staked Asset Released:</span><span class='tx-detail-value amount'>${stakedAmount}</span></div>`;
|
||||
reclaimedHtml = `<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-leaf'></i> Reclaimed Resources:</span><span class='tx-detail-value'>${resourceType}</span></div>`;
|
||||
}
|
||||
detailsHtml = `
|
||||
<div class='tx-detail-card'>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-check-circle'></i> Status:</span><span class='tx-detail-value success'>${ret}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-exchange-alt'></i> Type:</span><span class='tx-detail-value'>${type}</span></div>
|
||||
${stakedAssetHtml}
|
||||
${delegatedHtml}
|
||||
${reclaimedHtml}
|
||||
</div>
|
||||
<div class='tx-detail-card'>
|
||||
${resourcesFeeHtml}
|
||||
</div>
|
||||
<div class='tx-detail-card'>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-user-minus'></i> Owner Address:</span><span class='tx-detail-value'>${owner}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-user-plus'></i> Resource Taken From:</span><span class='tx-detail-value'>${resourceTakenFrom}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-hashtag'></i> Hash:</span><span class='tx-detail-value'>${id}</span></div>
|
||||
</div>
|
||||
<div class='tx-detail-card'>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-layer-group'></i> Block:</span><span class='tx-detail-value'>${blockNumber}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-clock'></i> Date:</span><span class='tx-detail-value'>${timestamp}</span></div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// Default rendering (TransferContract, etc)
|
||||
detailsHtml = `
|
||||
<div class='tx-detail-card'>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-check-circle'></i> Status:</span><span class='tx-detail-value success'>${ret}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-exchange-alt'></i> Type:</span><span class='tx-detail-value'>${type}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-coins'></i> Amount:</span><span class='tx-detail-value amount'>${amount} TRX</span></div>
|
||||
${resourcesFeeHtml}
|
||||
</div>
|
||||
<div class='tx-detail-card'>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-user-minus'></i> From:</span><span class='tx-detail-value'>${owner}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-user-plus'></i> To:</span><span class='tx-detail-value'>${to}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-hashtag'></i> Hash:</span><span class='tx-detail-value'>${id}</span></div>
|
||||
</div>
|
||||
<div class='tx-detail-card'>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-layer-group'></i> Block:</span><span class='tx-detail-value'>${blockNumber}</span></div>
|
||||
<div class='tx-detail-row'><span class='tx-detail-label'><i class='fas fa-clock'></i> Date:</span><span class='tx-detail-value'>${timestamp}</span></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
output.innerHTML = `<div class='card transaction-details'><div class='transaction-details-header'><h3><i class='fas fa-receipt'></i> Transaction Details</h3><button onclick="shareTxLink('${id}')" class='btn-icon share-btn' title='Copy Shareable Link'><i class='fas fa-share-alt'></i></button></div><div class='transaction-details-content'>${detailsHtml}</div></div>`;
|
||||
|
||||
if (typeof notify === "function") notify("Transaction found", "success");
|
||||
} catch (err) {
|
||||
output.innerHTML = `<div class="error-state"><div class="error-icon"><i class="fas fa-exclamation-triangle"></i></div><h3>Failed</h3><p>${err.message}</p></div>`;
|
||||
if (typeof notify === "function") notify(err.message, "error");
|
||||
} finally {
|
||||
setButtonLoading("txSearchBtn", false);
|
||||
}
|
||||
updateURLForPage("transaction", txid);
|
||||
}
|
||||
1499
tronwallet/scripts/btcOperator.js
Normal file
1499
tronwallet/scripts/btcOperator.js
Normal file
File diff suppressed because it is too large
Load Diff
10247
tronwallet/scripts/btcwallet_scripts_lib.js
Normal file
10247
tronwallet/scripts/btcwallet_scripts_lib.js
Normal file
File diff suppressed because it is too large
Load Diff
1
tronwallet/scripts/components.min.js
vendored
Normal file
1
tronwallet/scripts/components.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
606
tronwallet/scripts/floCrypto.js
Normal file
606
tronwallet/scripts/floCrypto.js
Normal file
@ -0,0 +1,606 @@
|
||||
(function (EXPORTS) {
|
||||
//floCrypto v2.3.6a
|
||||
/* FLO Crypto Operators */
|
||||
"use strict";
|
||||
const floCrypto = EXPORTS;
|
||||
|
||||
const p = BigInteger(
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F",
|
||||
16
|
||||
);
|
||||
const ecparams = EllipticCurve.getSECCurveByName("secp256k1");
|
||||
const ascii_alternatives = `‘ '\n’ '\n“ "\n” "\n– --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>`;
|
||||
const exponent1 = () => p.add(BigInteger.ONE).divide(BigInteger("4"));
|
||||
coinjs.compressed = true; //defaulting coinjs compressed to true;
|
||||
|
||||
function calculateY(x) {
|
||||
let exp = exponent1();
|
||||
// x is x value of public key in BigInteger format without 02 or 03 or 04 prefix
|
||||
return x
|
||||
.modPow(BigInteger("3"), p)
|
||||
.add(BigInteger("7"))
|
||||
.mod(p)
|
||||
.modPow(exp, p);
|
||||
}
|
||||
|
||||
function getUncompressedPublicKey(compressedPublicKey) {
|
||||
// Fetch x from compressedPublicKey
|
||||
let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey);
|
||||
const prefix = pubKeyBytes.shift(); // remove prefix
|
||||
let prefix_modulus = prefix % 2;
|
||||
pubKeyBytes.unshift(0); // add prefix 0
|
||||
let x = new BigInteger(pubKeyBytes);
|
||||
let xDecimalValue = x.toString();
|
||||
// Fetch y
|
||||
let y = calculateY(x);
|
||||
let yDecimalValue = y.toString();
|
||||
// verify y value
|
||||
let resultBigInt = y.mod(BigInteger("2"));
|
||||
let check = resultBigInt.toString() % 2;
|
||||
if (prefix_modulus !== check) yDecimalValue = y.negate().mod(p).toString();
|
||||
return {
|
||||
x: xDecimalValue,
|
||||
y: yDecimalValue,
|
||||
};
|
||||
}
|
||||
|
||||
function getSenderPublicKeyString() {
|
||||
let privateKey = ellipticCurveEncryption.senderRandom();
|
||||
var senderPublicKeyString =
|
||||
ellipticCurveEncryption.senderPublicString(privateKey);
|
||||
return {
|
||||
privateKey: privateKey,
|
||||
senderPublicKeyString: senderPublicKeyString,
|
||||
};
|
||||
}
|
||||
|
||||
function deriveSharedKeySender(receiverPublicKeyHex, senderPrivateKey) {
|
||||
let receiverPublicKeyString =
|
||||
getUncompressedPublicKey(receiverPublicKeyHex);
|
||||
var senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation(
|
||||
receiverPublicKeyString.x,
|
||||
receiverPublicKeyString.y,
|
||||
senderPrivateKey
|
||||
);
|
||||
return senderDerivedKey;
|
||||
}
|
||||
|
||||
function deriveSharedKeyReceiver(senderPublicKeyString, receiverPrivateKey) {
|
||||
return ellipticCurveEncryption.receiverSharedKeyDerivation(
|
||||
senderPublicKeyString.XValuePublicString,
|
||||
senderPublicKeyString.YValuePublicString,
|
||||
receiverPrivateKey
|
||||
);
|
||||
}
|
||||
|
||||
function getReceiverPublicKeyString(privateKey) {
|
||||
return ellipticCurveEncryption.receiverPublicString(privateKey);
|
||||
}
|
||||
|
||||
function wifToDecimal(pk_wif, isPubKeyCompressed = false) {
|
||||
let pk = Bitcoin.Base58.decode(pk_wif);
|
||||
pk.shift();
|
||||
pk.splice(-4, 4);
|
||||
//If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01).
|
||||
if (isPubKeyCompressed == true) pk.pop();
|
||||
pk.unshift(0);
|
||||
let privateKeyDecimal = BigInteger(pk).toString();
|
||||
let privateKeyHex = Crypto.util.bytesToHex(pk);
|
||||
return {
|
||||
privateKeyDecimal: privateKeyDecimal,
|
||||
privateKeyHex: privateKeyHex,
|
||||
};
|
||||
}
|
||||
|
||||
//generate a random Interger within range
|
||||
floCrypto.randInt = function (min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(securedMathRandom() * (max - min + 1)) + min;
|
||||
};
|
||||
|
||||
//generate a random String within length (options : alphaNumeric chars only)
|
||||
floCrypto.randString = function (length, alphaNumeric = true) {
|
||||
var result = "";
|
||||
var characters = alphaNumeric
|
||||
? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():";
|
||||
for (var i = 0; i < length; i++)
|
||||
result += characters.charAt(
|
||||
Math.floor(securedMathRandom() * characters.length)
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
//Encrypt Data using public-key
|
||||
floCrypto.encryptData = function (data, receiverPublicKeyHex) {
|
||||
var senderECKeyData = getSenderPublicKeyString();
|
||||
var senderDerivedKey = deriveSharedKeySender(
|
||||
receiverPublicKeyHex,
|
||||
senderECKeyData.privateKey
|
||||
);
|
||||
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
|
||||
let secret = Crypto.AES.encrypt(data, senderKey);
|
||||
return {
|
||||
secret: secret,
|
||||
senderPublicKeyString: senderECKeyData.senderPublicKeyString,
|
||||
};
|
||||
};
|
||||
|
||||
//Decrypt Data using private-key
|
||||
floCrypto.decryptData = function (data, privateKeyHex) {
|
||||
var receiverECKeyData = {};
|
||||
if (typeof privateKeyHex !== "string")
|
||||
throw new Error("No private key found.");
|
||||
let privateKey = wifToDecimal(privateKeyHex, true);
|
||||
if (typeof privateKey.privateKeyDecimal !== "string")
|
||||
throw new Error("Failed to detremine your private key.");
|
||||
receiverECKeyData.privateKey = privateKey.privateKeyDecimal;
|
||||
var receiverDerivedKey = deriveSharedKeyReceiver(
|
||||
data.senderPublicKeyString,
|
||||
receiverECKeyData.privateKey
|
||||
);
|
||||
let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue;
|
||||
let decryptMsg = Crypto.AES.decrypt(data.secret, receiverKey);
|
||||
return decryptMsg;
|
||||
};
|
||||
|
||||
//Sign data using private-key
|
||||
floCrypto.signData = function (data, privateKeyHex) {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
var messageHash = Crypto.SHA256(data);
|
||||
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
|
||||
var sighex = Crypto.util.bytesToHex(messageSign);
|
||||
return sighex;
|
||||
};
|
||||
|
||||
//Verify signatue of the data using public-key
|
||||
floCrypto.verifySign = function (data, signatureHex, publicKeyHex) {
|
||||
var msgHash = Crypto.SHA256(data);
|
||||
var sigBytes = Crypto.util.hexToBytes(signatureHex);
|
||||
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
|
||||
var verify = Bitcoin.ECDSA.verify(msgHash, sigBytes, publicKeyPoint);
|
||||
return verify;
|
||||
};
|
||||
|
||||
//Generates a new flo ID and returns private-key, public-key and floID
|
||||
const generateNewID = (floCrypto.generateNewID = function () {
|
||||
var key = new Bitcoin.ECKey(false);
|
||||
key.setCompressed(true);
|
||||
return {
|
||||
floID: key.getBitcoinAddress(),
|
||||
pubKey: key.getPubKeyHex(),
|
||||
privKey: key.getBitcoinWalletImportFormat(),
|
||||
};
|
||||
});
|
||||
|
||||
Object.defineProperties(floCrypto, {
|
||||
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));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Returns public-key from private-key
|
||||
floCrypto.getPubKeyHex = function (privateKeyHex) {
|
||||
if (!privateKeyHex) return null;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null) return null;
|
||||
key.setCompressed(true);
|
||||
return key.getPubKeyHex();
|
||||
};
|
||||
|
||||
//Returns flo-ID from public-key or private-key
|
||||
floCrypto.getFloID = function (keyHex) {
|
||||
if (!keyHex) return null;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(keyHex);
|
||||
if (key.priv == null) key.setPub(keyHex);
|
||||
return key.getBitcoinAddress();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
floCrypto.getAddress = function (privateKeyHex, strict = false) {
|
||||
if (!privateKeyHex) return;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null) return null;
|
||||
key.setCompressed(true);
|
||||
let pubKey = key.getPubKeyHex(),
|
||||
version = bitjs.Base58.decode(privateKeyHex)[0];
|
||||
switch (version) {
|
||||
case coinjs.priv: //BTC
|
||||
return coinjs.bech32Address(pubKey).address;
|
||||
case bitjs.priv: //FLO
|
||||
return bitjs.pubkey2address(pubKey);
|
||||
default:
|
||||
return strict ? false : bitjs.pubkey2address(pubKey); //default to FLO address (if strict=false)
|
||||
}
|
||||
};
|
||||
|
||||
//Verify the private-key for the given public-key or flo-ID
|
||||
floCrypto.verifyPrivKey = function (
|
||||
privateKeyHex,
|
||||
pubKey_floID,
|
||||
isfloID = true
|
||||
) {
|
||||
if (!privateKeyHex || !pubKey_floID) return false;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null) return false;
|
||||
key.setCompressed(true);
|
||||
if (isfloID && pubKey_floID == key.getBitcoinAddress()) return true;
|
||||
else if (
|
||||
!isfloID &&
|
||||
pubKey_floID.toUpperCase() == key.getPubKeyHex().toUpperCase()
|
||||
)
|
||||
return true;
|
||||
else return false;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) {
|
||||
if (!Array.isArray(publicKeyList) || !publicKeyList.length) return null;
|
||||
if (
|
||||
!Number.isInteger(requiredSignatures) ||
|
||||
requiredSignatures < 1 ||
|
||||
requiredSignatures > publicKeyList.length
|
||||
)
|
||||
return null;
|
||||
try {
|
||||
var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures);
|
||||
return multisig;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
floCrypto.decodeRedeemScript = function (redeemScript) {
|
||||
try {
|
||||
var decoded = bitjs.transaction().decodeRedeemScript(redeemScript);
|
||||
return decoded;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
//Check if the given flo-id is valid or not
|
||||
floCrypto.validateFloID = function (floID, regularOnly = false) {
|
||||
if (!floID) return false;
|
||||
try {
|
||||
let addr = new Bitcoin.Address(floID);
|
||||
if (regularOnly && addr.version != Bitcoin.Address.standardVersion)
|
||||
return false;
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//Check if the given address (any blockchain) is valid or not
|
||||
floCrypto.validateAddr = function (address, std = true, bech = true) {
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw) return false;
|
||||
if (typeof raw.version !== "undefined") {
|
||||
//legacy or segwit
|
||||
if (std == false) return false;
|
||||
else if (
|
||||
std === true ||
|
||||
(!Array.isArray(std) && std === raw.version) ||
|
||||
(Array.isArray(std) && std.includes(raw.version))
|
||||
)
|
||||
return true;
|
||||
else return false;
|
||||
} else if (typeof raw.bech_version !== "undefined") {
|
||||
//bech32
|
||||
if (bech === false) return false;
|
||||
else if (
|
||||
bech === true ||
|
||||
(!Array.isArray(bech) && bech === raw.bech_version) ||
|
||||
(Array.isArray(bech) && bech.includes(raw.bech_version))
|
||||
)
|
||||
return true;
|
||||
else return false;
|
||||
} //unknown
|
||||
else return false;
|
||||
};
|
||||
|
||||
//Check the public-key (or redeem-script) for the address (any blockchain)
|
||||
floCrypto.verifyPubKey = function (pubKeyHex, address) {
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw) return;
|
||||
let pub_hash = Crypto.util.bytesToHex(
|
||||
ripemd160(
|
||||
Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { asBytes: true })
|
||||
)
|
||||
);
|
||||
if (typeof raw.bech_version !== "undefined" && raw.bytes.length == 32)
|
||||
//bech32-multisig
|
||||
raw.hex = Crypto.util.bytesToHex(ripemd160(raw.bytes, { asBytes: true }));
|
||||
return pub_hash === raw.hex;
|
||||
};
|
||||
|
||||
//Convert the given address (any blockchain) to equivalent floID
|
||||
floCrypto.toFloID = function (address, options = null) {
|
||||
if (!address) return;
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw) return;
|
||||
else if (options) {
|
||||
//if (optional) version check is passed
|
||||
if (
|
||||
typeof raw.version !== "undefined" &&
|
||||
(!options.std || !options.std.includes(raw.version))
|
||||
)
|
||||
return;
|
||||
if (
|
||||
typeof raw.bech_version !== "undefined" &&
|
||||
(!options.bech || !options.bech.includes(raw.bech_version))
|
||||
)
|
||||
return;
|
||||
}
|
||||
raw.bytes.unshift(bitjs.pub);
|
||||
let hash = Crypto.SHA256(
|
||||
Crypto.SHA256(raw.bytes, {
|
||||
asBytes: true,
|
||||
}),
|
||||
{
|
||||
asBytes: true,
|
||||
}
|
||||
);
|
||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
||||
};
|
||||
|
||||
//Convert raw address bytes to floID
|
||||
floCrypto.rawToFloID = function (raw_bytes) {
|
||||
if (typeof raw_bytes === "string")
|
||||
raw_bytes = Crypto.util.hexToBytes(raw_bytes);
|
||||
if (raw_bytes.length != 20) return null;
|
||||
raw_bytes.unshift(bitjs.pub);
|
||||
let hash = Crypto.SHA256(
|
||||
Crypto.SHA256(raw_bytes, {
|
||||
asBytes: true,
|
||||
}),
|
||||
{
|
||||
asBytes: true,
|
||||
}
|
||||
);
|
||||
return bitjs.Base58.encode(raw_bytes.concat(hash.slice(0, 4)));
|
||||
};
|
||||
|
||||
//Convert the given multisig address (any blockchain) to equivalent multisig floID
|
||||
floCrypto.toMultisigFloID = function (address, options = null) {
|
||||
if (!address) return;
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw) return;
|
||||
else if (options) {
|
||||
//if (optional) version check is passed
|
||||
if (
|
||||
typeof raw.version !== "undefined" &&
|
||||
(!options.std || !options.std.includes(raw.version))
|
||||
)
|
||||
return;
|
||||
if (
|
||||
typeof raw.bech_version !== "undefined" &&
|
||||
(!options.bech || !options.bech.includes(raw.bech_version))
|
||||
)
|
||||
return;
|
||||
}
|
||||
if (typeof raw.bech_version !== "undefined") {
|
||||
if (raw.bytes.length != 32) return; //multisig bech address have 32 bytes
|
||||
//multisig-bech:hash=SHA256 whereas multisig:hash=r160(SHA265), thus ripemd160 the bytes from multisig-bech
|
||||
raw.bytes = ripemd160(raw.bytes, {
|
||||
asBytes: true,
|
||||
});
|
||||
}
|
||||
raw.bytes.unshift(bitjs.multisig);
|
||||
let hash = Crypto.SHA256(
|
||||
Crypto.SHA256(raw.bytes, {
|
||||
asBytes: true,
|
||||
}),
|
||||
{
|
||||
asBytes: true,
|
||||
}
|
||||
);
|
||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
||||
};
|
||||
|
||||
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
|
||||
floCrypto.isSameAddr = function (addr1, addr2) {
|
||||
if (!addr1 || !addr2) return;
|
||||
let raw1 = decodeAddress(addr1),
|
||||
raw2 = decodeAddress(addr2);
|
||||
if (!raw1 || !raw2) return false;
|
||||
else {
|
||||
if (typeof raw1.bech_version !== "undefined" && raw1.bytes.length == 32)
|
||||
//bech32-multisig
|
||||
raw1.hex = Crypto.util.bytesToHex(
|
||||
ripemd160(raw1.bytes, { asBytes: true })
|
||||
);
|
||||
if (typeof raw2.bech_version !== "undefined" && raw2.bytes.length == 32)
|
||||
//bech32-multisig
|
||||
raw2.hex = Crypto.util.bytesToHex(
|
||||
ripemd160(raw2.bytes, { asBytes: true })
|
||||
);
|
||||
return raw1.hex === raw2.hex;
|
||||
}
|
||||
};
|
||||
|
||||
const decodeAddress = (floCrypto.decodeAddr = function (address) {
|
||||
if (!address) return;
|
||||
else if (address.length == 33 || address.length == 34) {
|
||||
//legacy encoding
|
||||
let decode = bitjs.Base58.decode(address);
|
||||
let bytes = decode.slice(0, decode.length - 4);
|
||||
let checksum = decode.slice(decode.length - 4),
|
||||
hash = Crypto.SHA256(
|
||||
Crypto.SHA256(bytes, {
|
||||
asBytes: true,
|
||||
}),
|
||||
{
|
||||
asBytes: true,
|
||||
}
|
||||
);
|
||||
return hash[0] != checksum[0] ||
|
||||
hash[1] != checksum[1] ||
|
||||
hash[2] != checksum[2] ||
|
||||
hash[3] != checksum[3]
|
||||
? null
|
||||
: {
|
||||
version: bytes.shift(),
|
||||
hex: Crypto.util.bytesToHex(bytes),
|
||||
bytes,
|
||||
};
|
||||
} else if (address.length == 42 || address.length == 62) {
|
||||
//bech encoding
|
||||
let decode = coinjs.bech32_decode(address);
|
||||
if (decode) {
|
||||
let bytes = decode.data;
|
||||
let bech_version = bytes.shift();
|
||||
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
|
||||
return {
|
||||
bech_version,
|
||||
hrp: decode.hrp,
|
||||
hex: Crypto.util.bytesToHex(bytes),
|
||||
bytes,
|
||||
};
|
||||
} else return null;
|
||||
}
|
||||
});
|
||||
|
||||
//Split the str using shamir's Secret and Returns the shares
|
||||
floCrypto.createShamirsSecretShares = function (
|
||||
str,
|
||||
total_shares,
|
||||
threshold_limit
|
||||
) {
|
||||
try {
|
||||
if (str.length > 0) {
|
||||
var strHex = shamirSecretShare.str2hex(str);
|
||||
var shares = shamirSecretShare.share(
|
||||
strHex,
|
||||
total_shares,
|
||||
threshold_limit
|
||||
);
|
||||
return shares;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//Returns the retrived secret by combining the shamirs shares
|
||||
const retrieveShamirSecret = (floCrypto.retrieveShamirSecret = function (
|
||||
sharesArray
|
||||
) {
|
||||
try {
|
||||
if (sharesArray.length > 0) {
|
||||
var comb = shamirSecretShare.combine(
|
||||
sharesArray.slice(0, sharesArray.length)
|
||||
);
|
||||
comb = shamirSecretShare.hex2str(comb);
|
||||
return comb;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
//Verifies the shares and str
|
||||
floCrypto.verifyShamirsSecret = function (sharesArray, str) {
|
||||
if (!str) return null;
|
||||
else if (retrieveShamirSecret(sharesArray) === str) return true;
|
||||
else return false;
|
||||
};
|
||||
|
||||
const validateASCII = (floCrypto.validateASCII = function (
|
||||
string,
|
||||
bool = true
|
||||
) {
|
||||
if (typeof string !== "string") return null;
|
||||
if (bool) {
|
||||
let x;
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127) return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
let x,
|
||||
invalids = {};
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
if (x in invalids) invalids[string[i]].push(i);
|
||||
else invalids[string[i]] = [i];
|
||||
}
|
||||
if (Object.keys(invalids).length) return invalids;
|
||||
else return true;
|
||||
}
|
||||
});
|
||||
|
||||
floCrypto.convertToASCII = function (string, mode = "soft-remove") {
|
||||
let chars = validateASCII(string, false);
|
||||
if (chars === true) return string;
|
||||
else if (chars === null) return null;
|
||||
let convertor,
|
||||
result = string,
|
||||
refAlt = {};
|
||||
ascii_alternatives.split("\n").forEach((a) => (refAlt[a[0]] = a.slice(2)));
|
||||
mode = mode.toLowerCase();
|
||||
if (mode === "hard-unicode")
|
||||
convertor = (c) =>
|
||||
`\\u${("000" + c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "soft-unicode")
|
||||
convertor = (c) =>
|
||||
refAlt[c] || `\\u${("000" + c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "hard-remove") convertor = (c) => "";
|
||||
else if (mode === "soft-remove") convertor = (c) => refAlt[c] || "";
|
||||
else return null;
|
||||
for (let c in chars) result = result.replaceAll(c, convertor(c));
|
||||
return result;
|
||||
};
|
||||
|
||||
floCrypto.revertUnicode = function (string) {
|
||||
return string.replace(/\\u[\dA-F]{4}/gi, (m) =>
|
||||
String.fromCharCode(parseInt(m.replace(/\\u/g, ""), 16))
|
||||
);
|
||||
};
|
||||
})("object" === typeof module ? module.exports : (window.floCrypto = {}));
|
||||
78
tronwallet/scripts/genMultichainAddress.js
Normal file
78
tronwallet/scripts/genMultichainAddress.js
Normal file
@ -0,0 +1,78 @@
|
||||
function getRandomPrivateKey() {
|
||||
const array = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(array);
|
||||
return Array.from(array)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
async function generateTronWallet() {
|
||||
const fullNode = "https://api.trongrid.io";
|
||||
const solidityNode = "https://api.trongrid.io";
|
||||
const eventServer = "https://api.trongrid.io";
|
||||
|
||||
const tronWeb = new TronWeb(
|
||||
fullNode,
|
||||
solidityNode,
|
||||
eventServer,
|
||||
getRandomPrivateKey()
|
||||
);
|
||||
|
||||
const wallet = await tronWeb.createAccount();
|
||||
return {
|
||||
address: wallet.address.base58,
|
||||
privateKey: wallet.privateKey,
|
||||
};
|
||||
}
|
||||
window.generateTronWallet = generateTronWallet;
|
||||
window.generateBTCFromPrivateKey = generateBTCFromPrivateKey;
|
||||
window.generateFLOFromPrivateKey = generateFLOFromPrivateKey;
|
||||
window.getRandomPrivateKey = getRandomPrivateKey;
|
||||
199
tronwallet/scripts/recoverAddress.js
Normal file
199
tronwallet/scripts/recoverAddress.js
Normal file
@ -0,0 +1,199 @@
|
||||
function isHex64(str) {
|
||||
return /^[0-9a-fA-F]{64}$/.test(str);
|
||||
}
|
||||
function isWif(str) {
|
||||
return /^[5KLc9RQ][1-9A-HJ-NP-Za-km-z]{50,}$/.test(str); // BTC/FLO WIF regex
|
||||
}
|
||||
|
||||
async function recoverAllAddressesFromPrivKey(privKey) {
|
||||
const tronWeb = new TronWeb(
|
||||
"https://api.trongrid.io",
|
||||
"https://api.trongrid.io",
|
||||
"https://api.trongrid.io"
|
||||
);
|
||||
|
||||
try {
|
||||
let hexPrivateKey = privKey;
|
||||
let source = "Tron";
|
||||
|
||||
// Convert WIF to hex if needed
|
||||
if (isWif(privKey)) {
|
||||
const decoded = coinjs.wif2privkey(privKey);
|
||||
if (!decoded || !decoded["privkey"]) {
|
||||
return { error: "Invalid WIF private key" };
|
||||
}
|
||||
hexPrivateKey = decoded["privkey"];
|
||||
source = "BTC/FLO";
|
||||
} else if (!isHex64(privKey)) {
|
||||
return {
|
||||
error:
|
||||
"Unsupported private key format. Please use Tron hex (64 characters) or BTC/FLO WIF format.",
|
||||
};
|
||||
}
|
||||
|
||||
// Generate TRON address
|
||||
const tronAddress = tronWeb.address.fromPrivateKey(hexPrivateKey);
|
||||
|
||||
// Generate FLO address
|
||||
const floWallet = generateFLOFromPrivateKey(hexPrivateKey);
|
||||
|
||||
// Generate BTC address
|
||||
const btcWallet = generateBTCFromPrivateKey(hexPrivateKey);
|
||||
|
||||
return {
|
||||
source,
|
||||
hexPrivateKey,
|
||||
tronAddress,
|
||||
floWallet,
|
||||
btcWallet,
|
||||
};
|
||||
} catch (err) {
|
||||
return { error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function runAddressRecovery() {
|
||||
const privKey = document.getElementById("recoveryPrivKey").value.trim();
|
||||
const output = document.getElementById("recoveryOutput");
|
||||
|
||||
if (!privKey) {
|
||||
output.innerHTML = `<div class="error-state"><i class="fas fa-triangle-exclamation"></i>Enter a private key</div>`;
|
||||
if (typeof notify === "function") notify("Enter a private key", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set loading state
|
||||
if (typeof setButtonLoading === "function") {
|
||||
setButtonLoading("recoverBtn", true);
|
||||
}
|
||||
|
||||
// Show notification
|
||||
if (typeof notify === "function") {
|
||||
notify("Recovering address...", "success", 1500);
|
||||
}
|
||||
|
||||
const recovered = await recoverAllAddressesFromPrivKey(privKey);
|
||||
|
||||
if (recovered.error) {
|
||||
output.innerHTML = `<div class="error-state"><i class="fas fa-triangle-exclamation"></i>${recovered.error}</div>`;
|
||||
if (typeof notify === "function") notify(recovered.error, "error");
|
||||
} else {
|
||||
output.innerHTML = `
|
||||
<div class="wallet-generated-success">
|
||||
<div class="success-header">
|
||||
<div class="success-icon">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
<h3>Addresses Recovered Successfully!</h3>
|
||||
<p>Your multi-blockchain addresses have been recovered. All addresses are derived from the same private key.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="blockchain-section">
|
||||
<div class="blockchain-header">
|
||||
<h4><i class="fas fa-coins"></i> TRON (TRX)</h4>
|
||||
<div class="blockchain-badge primary">Primary</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<label><i class="fas fa-map-marker-alt"></i> TRON Address</label>
|
||||
<div class="value-container">
|
||||
<code>${recovered.tronAddress}</code>
|
||||
<button class="btn-icon" onclick="navigator.clipboard.writeText('${
|
||||
recovered.tronAddress
|
||||
}').then(()=>notify && notify('TRON address copied','success'))" title="Copy TRON Address">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<label><i class="fas fa-key"></i> TRON Private Key</label>
|
||||
<div class="value-container">
|
||||
<code>${recovered.hexPrivateKey}</code>
|
||||
<button class="btn-icon" onclick="navigator.clipboard.writeText('${
|
||||
recovered.hexPrivateKey
|
||||
}').then(()=>notify && notify('Private key copied','success'))" title="Copy Private Key">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${
|
||||
recovered.floWallet
|
||||
? `
|
||||
<div class="blockchain-section">
|
||||
<div class="blockchain-header">
|
||||
<h4><i class="fas fa-coins"></i> FLO</h4>
|
||||
<div class="blockchain-badge secondary">Secondary</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<label><i class="fas fa-map-marker-alt"></i> FLO Address</label>
|
||||
<div class="value-container">
|
||||
<code>${recovered.floWallet.address}</code>
|
||||
<button class="btn-icon" onclick="navigator.clipboard.writeText('${recovered.floWallet.address}').then(()=>notify && notify('FLO address copied','success'))" title="Copy FLO Address">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<label><i class="fas fa-key"></i> FLO Private Key</label>
|
||||
<div class="value-container">
|
||||
<code>${recovered.floWallet.privateKey}</code>
|
||||
<button class="btn-icon" onclick="navigator.clipboard.writeText('${recovered.floWallet.privateKey}').then(()=>notify && notify('Private key copied','success'))" title="Copy Private Key">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
: ""
|
||||
}
|
||||
|
||||
${
|
||||
recovered.btcWallet
|
||||
? `
|
||||
<div class="blockchain-section">
|
||||
<div class="blockchain-header">
|
||||
<h4><i class="fab fa-btc"></i> Bitcoin (BTC)</h4>
|
||||
<div class="blockchain-badge secondary">Secondary</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<label><i class="fas fa-map-marker-alt"></i> BTC Address</label>
|
||||
<div class="value-container">
|
||||
<code>${recovered.btcWallet.address}</code>
|
||||
<button class="btn-icon" onclick="navigator.clipboard.writeText('${recovered.btcWallet.address}').then(()=>notify && notify('BTC address copied','success'))" title="Copy BTC Address">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<label><i class="fas fa-key"></i> BTC Private Key</label>
|
||||
<div class="value-container">
|
||||
<code>${recovered.btcWallet.privateKey}</code>
|
||||
<button class="btn-icon" onclick="navigator.clipboard.writeText('${recovered.btcWallet.privateKey}').then(()=>notify && notify('Private key copied','success'))" title="Copy Private Key">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</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 Reminder</h4>
|
||||
<p>Keep your private key safe and secure. Never share it with anyone. Consider backing it up in a secure location.</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
if (typeof notify === "function")
|
||||
notify("All addresses recovered", "success");
|
||||
}
|
||||
|
||||
// Clear loading state
|
||||
if (typeof setButtonLoading === "function") {
|
||||
setButtonLoading("recoverBtn", false);
|
||||
}
|
||||
}
|
||||
387
tronwallet/scripts/saveSearchedAddress.js
Normal file
387
tronwallet/scripts/saveSearchedAddress.js
Normal file
@ -0,0 +1,387 @@
|
||||
// IndexedDB for storing searched addresses
|
||||
class SearchedAddressDB {
|
||||
constructor() {
|
||||
this.dbName = "TronWalletDB";
|
||||
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);
|
||||
|
||||
// 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, // Tron address
|
||||
balance,
|
||||
timestamp,
|
||||
formattedBalance: `${balance} TRX`,
|
||||
sourceInfo: finalSourceInfo, //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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const searchedAddressDB = new SearchedAddressDB();
|
||||
|
||||
async function displaySearchedAddresses(addresses) {
|
||||
let container = document.getElementById("searchedAddressesContainer");
|
||||
const transactionSection = document.getElementById("transactionSection");
|
||||
|
||||
if (!container && addresses.length > 0) {
|
||||
container = document.createElement("div");
|
||||
container.id = "searchedAddressesContainer";
|
||||
container.className = "card searched-addresses-card";
|
||||
|
||||
if (transactionSection && transactionSection.parentNode) {
|
||||
const nextSibling = transactionSection.nextSibling;
|
||||
transactionSection.parentNode.insertBefore(container, nextSibling);
|
||||
} else {
|
||||
const transactionsPage = document.getElementById("transactionsPage");
|
||||
if (transactionsPage) {
|
||||
transactionsPage.appendChild(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 from another blockchain (BTC/FLO)
|
||||
const hasSourceInfo = addr.sourceInfo && addr.sourceInfo.originalKey;
|
||||
|
||||
return `
|
||||
<div class="searched-address-item ${
|
||||
hasSourceInfo ? "has-source-info" : ""
|
||||
}" data-index="${index}" data-current-type="${
|
||||
hasSourceInfo ? addr.sourceInfo.blockchain.toLowerCase() : "tron"
|
||||
}">
|
||||
${
|
||||
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} Private Key">
|
||||
${addr.sourceInfo.blockchain}
|
||||
</button>
|
||||
<button onclick="toggleAddressType(${index}, 'tron')"
|
||||
class="btn-toggle-address"
|
||||
data-type="tron"
|
||||
title="Show Tron Address">
|
||||
TRON
|
||||
</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.originalKey
|
||||
}">
|
||||
${addr.sourceInfo.originalKey}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="address-actions">
|
||||
<button onclick="copyCurrentAddress(${index})" class="btn-copy-current" title="Copy Selected Value">
|
||||
<i class="fas fa-copy"></i> COPY
|
||||
</button>
|
||||
<button onclick="deleteSearchedAddress('${
|
||||
addr.address
|
||||
}')" class="btn-delete" title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
<button onclick="checkBalanceForAddress('${
|
||||
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 Tron Address">
|
||||
<i class="fas fa-copy"></i> COPY
|
||||
</button>
|
||||
<button onclick="deleteSearchedAddress('${addr.address}')" class="btn-delete" title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
<button onclick="checkBalanceForAddress('${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 === "tron") {
|
||||
// Tron address
|
||||
addressDisplay.textContent = addressItem.address;
|
||||
addressDisplay.title = addressItem.address;
|
||||
} else {
|
||||
// Show original blockchain private key (FLO/BTC)
|
||||
const originalKey =
|
||||
addressItem.sourceInfo?.originalKey || addressItem.address;
|
||||
addressDisplay.textContent = originalKey;
|
||||
addressDisplay.title = originalKey;
|
||||
}
|
||||
}
|
||||
} 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") || "tron";
|
||||
|
||||
let valueToCopy;
|
||||
let valueLabel;
|
||||
|
||||
if (currentType === "tron") {
|
||||
valueToCopy = addressItem.address;
|
||||
valueLabel = "Tron address";
|
||||
} else {
|
||||
// Copy the private key for non-Tron selection
|
||||
valueToCopy = addressItem.sourceInfo?.originalKey || addressItem.address;
|
||||
valueLabel = `${
|
||||
addressItem.sourceInfo?.blockchain || "Original"
|
||||
} private key`;
|
||||
}
|
||||
|
||||
await copyAddressToClipboard(valueToCopy, valueLabel);
|
||||
} catch (error) {
|
||||
console.error("Error copying current value:", error);
|
||||
notify("Failed to copy value", "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");
|
||||
}
|
||||
}
|
||||
|
||||
function checkBalanceForAddress(address) {
|
||||
document.getElementById("balanceAddr").value = address;
|
||||
setSearchType("balance");
|
||||
runBalanceCheck();
|
||||
}
|
||||
|
||||
async function updateSearchedAddressesList() {
|
||||
try {
|
||||
const searchedAddresses = await searchedAddressDB.getSearchedAddresses();
|
||||
displaySearchedAddresses(searchedAddresses);
|
||||
} catch (error) {
|
||||
console.error("Error loading searched addresses:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the searched addresses list when the script loads
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
try {
|
||||
await updateSearchedAddressesList();
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize searched addresses:", error);
|
||||
}
|
||||
});
|
||||
172
tronwallet/scripts/sendTRX.js
Normal file
172
tronwallet/scripts/sendTRX.js
Normal file
@ -0,0 +1,172 @@
|
||||
const fullNode = "https://api.trongrid.io";
|
||||
const solidityNode = "https://api.trongrid.io";
|
||||
const eventServer = "https://api.trongrid.io";
|
||||
const tronWeb = new TronWeb(fullNode, solidityNode, eventServer);
|
||||
|
||||
async function sendTrx() {
|
||||
let privateKey = document.getElementById("privKey").value.trim();
|
||||
const toAddress = document.getElementById("toAddr").value.trim();
|
||||
const amount = parseFloat(document.getElementById("amount").value) * 1e6;
|
||||
|
||||
const outputDiv = document.getElementById("sendOutput");
|
||||
outputDiv.innerHTML = "⏳ Sending transaction...";
|
||||
|
||||
try {
|
||||
// Derive fromAddress from private key
|
||||
let fromAddress;
|
||||
let source = "Tron";
|
||||
|
||||
// (WIF → hex if needed)
|
||||
if (/^[5KLc9RQ][1-9A-HJ-NP-Za-km-z]{50,}$/.test(privateKey)) {
|
||||
// Looks like WIF (BTC / FLO style)
|
||||
const decoded = coinjs.wif2privkey(privateKey);
|
||||
if (!decoded || !decoded.privkey) {
|
||||
throw new Error("Invalid WIF private key");
|
||||
}
|
||||
privateKey = decoded.privkey; // hex format now
|
||||
source = "BTC/FLO";
|
||||
} else if (!/^[0-9a-fA-F]{64}$/.test(privateKey)) {
|
||||
throw new Error("Private key must be Tron hex or valid WIF");
|
||||
}
|
||||
|
||||
// Derive Tron address from private key
|
||||
fromAddress = tronWeb.address.fromPrivateKey(privateKey);
|
||||
|
||||
// Build transaction
|
||||
const tradeobj = await tronWeb.transactionBuilder.sendTrx(
|
||||
toAddress,
|
||||
amount,
|
||||
fromAddress
|
||||
);
|
||||
|
||||
// Sign transaction
|
||||
const signedtxn = await tronWeb.trx.sign(tradeobj, privateKey);
|
||||
|
||||
// Broadcast transaction
|
||||
const receipt = await tronWeb.trx.sendRawTransaction(signedtxn);
|
||||
console.log(receipt);
|
||||
if (
|
||||
receipt &&
|
||||
receipt.result &&
|
||||
receipt.result.code === "INSUFFICIENT_BALANCE"
|
||||
) {
|
||||
throw new Error("Insufficient balance to send transaction.");
|
||||
}
|
||||
if (receipt && receipt.code && receipt.code === "CONTRACT_VALIDATE_ERROR") {
|
||||
throw new Error("Insufficient balance to send transaction.");
|
||||
}
|
||||
if (receipt && !receipt.result) {
|
||||
throw new Error(
|
||||
"Transaction failed: " + (receipt.result.message || "Unknown error")
|
||||
);
|
||||
}
|
||||
const status = receipt.result ? "✅ Success" : "❌ Failed";
|
||||
const statusColor = receipt.result ? "green" : "red";
|
||||
const txid = receipt.txid ? truncate(receipt.txid) : "N/A";
|
||||
|
||||
outputDiv.innerHTML = `
|
||||
<div class="transaction-success">
|
||||
<div class="success-header">
|
||||
<div class="success-icon">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
<h3>Transaction Sent Successfully!</h3>
|
||||
<p>Your TRX transaction has been broadcasted to the network.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="blockchain-section">
|
||||
<div class="blockchain-header">
|
||||
<h4><i class="fas fa-exchange-alt"></i> Transaction Details</h4>
|
||||
<div class="blockchain-badge secondary">TRON</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<label><i class="fas fa-map-marker-alt"></i> From Address</label>
|
||||
<div class="value-container">
|
||||
<code>
|
||||
<a
|
||||
class="detail-link"
|
||||
href="index.html?page=transactions&address=${fromAddress}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
title="View address details"
|
||||
>${fromAddress}</a>
|
||||
</code>
|
||||
<button class="btn-icon" onclick="navigator.clipboard.writeText('${fromAddress}').then(()=>notify && notify('From address copied','success'))" title="Copy From Address">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<label><i class="fas fa-map-marker-alt"></i> To Address</label>
|
||||
<div class="value-container">
|
||||
<code>
|
||||
<a
|
||||
class="detail-link"
|
||||
href="index.html?page=transactions&address=${toAddress}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
title="View address details"
|
||||
>${toAddress}</a>
|
||||
</code>
|
||||
<button class="btn-icon" onclick="navigator.clipboard.writeText('${toAddress}').then(()=>notify && notify('To address copied','success'))" title="Copy To Address">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<label><i class="fas fa-coins"></i> Amount</label>
|
||||
<div class="value-container">
|
||||
<code style="color: #10b981; font-weight: bold;">${
|
||||
amount / 1e6
|
||||
} TRX</code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<label><i class="fas fa-hashtag"></i> Transaction Hash</label>
|
||||
<div class="value-container">
|
||||
<code>
|
||||
<a
|
||||
class="detail-link"
|
||||
href="index.html?page=transactions&tx=${receipt.txid}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
title="View transaction details"
|
||||
>${txid}</a>
|
||||
</code>
|
||||
${
|
||||
receipt.txid
|
||||
? `<button class="btn-icon" onclick="navigator.clipboard.writeText('${receipt.txid}').then(()=>notify && notify('Transaction hash copied','success'))" title="Copy Transaction Hash">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return receipt;
|
||||
} catch (err) {
|
||||
const outputDiv = document.getElementById("sendOutput");
|
||||
outputDiv.innerHTML = `<div class="error-state">
|
||||
<div class="error-icon"><i class="fas fa-exclamation-triangle"></i></div>
|
||||
<h3>Transaction Failed</h3>
|
||||
<p>${err.message}</p>
|
||||
</div>`;
|
||||
if (typeof notify === "function") notify(err.message, "error");
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function truncate(str, len = 12) {
|
||||
if (!str) return "";
|
||||
return str.length > len ? str.slice(0, 6) + "..." + str.slice(-6) : str;
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert("Copied: " + text);
|
||||
});
|
||||
}
|
||||
374
tronwallet/scripts/transactionHistory.js
Normal file
374
tronwallet/scripts/transactionHistory.js
Normal file
@ -0,0 +1,374 @@
|
||||
const options = { method: "GET", headers: { accept: "application/json" } };
|
||||
let nextUrl = null;
|
||||
|
||||
const transactionHistory = async function (url, address) {
|
||||
try {
|
||||
if (typeof notify === "function")
|
||||
notify("Loading transactions...", "success", 1500);
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: { accept: "application/json" },
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
const section = document.getElementById("transactionSection");
|
||||
if (section) section.style.display = "block";
|
||||
|
||||
__currentAddress = address;
|
||||
__currentUrl = url;
|
||||
window.lastUsedUrl = url;
|
||||
|
||||
if (data && data.data) {
|
||||
console.log(data.data);
|
||||
|
||||
__currentTxs = data.data;
|
||||
// track current per-page from url
|
||||
const m = url.match(/limit=(\d+)/);
|
||||
if (m) __perPage = parseInt(m[1], 10) || __perPage;
|
||||
__renderTransactions();
|
||||
|
||||
if (data.meta && data.meta.fingerprint) {
|
||||
__nextUrl = `https://api.trongrid.io/v1/accounts/${address}/transactions?limit=${__perPage}&fingerprint=${encodeURIComponent(
|
||||
data.meta.fingerprint
|
||||
)}`;
|
||||
} else {
|
||||
__nextUrl = null;
|
||||
}
|
||||
__updatePagination();
|
||||
}
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (typeof __origTransactionHistory === "function") {
|
||||
__origTransactionHistory(url, address);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
function fetchNext(address) {
|
||||
if (nextUrl) {
|
||||
transactionHistory(nextUrl, address);
|
||||
}
|
||||
}
|
||||
|
||||
function truncate(str, len = 12) {
|
||||
if (!str) return "";
|
||||
return str.length > len ? str.slice(0, 6) + "..." + str.slice(-6) : str;
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert("Copied: " + text);
|
||||
});
|
||||
}
|
||||
|
||||
// State for filtering and pagination
|
||||
let __nextUrl = null;
|
||||
let __prevUrls = [];
|
||||
let __currentAddress = "";
|
||||
let __currentTxs = [];
|
||||
let __currentFilter = "all"; // all | received | sent
|
||||
let __currentPage = 1;
|
||||
let __currentUrl = null;
|
||||
let __perPage = 10;
|
||||
|
||||
function __renderTransactions() {
|
||||
const list = document.getElementById("txList");
|
||||
const legacy = document.getElementById("historyOutput");
|
||||
if (!list) {
|
||||
if (legacy) legacy.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
list.innerHTML = "";
|
||||
|
||||
const filtered = __currentTxs.filter((tx) => {
|
||||
const type = tx.raw_data?.contract?.[0]?.type || "";
|
||||
let from = "";
|
||||
let to = "";
|
||||
if (type === "TransferContract") {
|
||||
const v = tx.raw_data.contract[0].parameter.value;
|
||||
from = tronWeb.address.fromHex(v.owner_address);
|
||||
to = tronWeb.address.fromHex(v.to_address);
|
||||
} else if (type === "TriggerSmartContract") {
|
||||
const v = tx.raw_data.contract[0].parameter.value;
|
||||
from = tronWeb.address.fromHex(v.owner_address);
|
||||
const input = (v.data || "").startsWith("0x")
|
||||
? v.data.slice(2)
|
||||
: v.data || "";
|
||||
const method = input.slice(0, 8).toLowerCase();
|
||||
if (method === "a9059cbb" && input.length >= 8 + 64 + 64) {
|
||||
const addrSlot = input.slice(8, 8 + 64);
|
||||
const evmAddrHex = addrSlot.slice(24);
|
||||
const tronHex = "41" + evmAddrHex.toLowerCase();
|
||||
to = tronWeb.address.fromHex(tronHex);
|
||||
}
|
||||
}
|
||||
if (__currentFilter === "sent") return from === __currentAddress;
|
||||
if (__currentFilter === "received") return to === __currentAddress;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (filtered.length === 0) {
|
||||
list.innerHTML =
|
||||
'<div class="no-transactions"><i class="fas fa-inbox"></i>No transactions found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
filtered.forEach((tx) => {
|
||||
const hash = tx.txID;
|
||||
const block = tx.blockNumber;
|
||||
const age = new Date(tx.block_timestamp).toLocaleString();
|
||||
const type = tx.raw_data.contract[0].type;
|
||||
|
||||
let from = "";
|
||||
let to = "";
|
||||
let amountText = "";
|
||||
let directionClass = "";
|
||||
let icon = "fa-arrow-up";
|
||||
|
||||
if (type === "TransferContract") {
|
||||
const v = tx.raw_data.contract[0].parameter.value;
|
||||
from = tronWeb.address.fromHex(v.owner_address);
|
||||
to = tronWeb.address.fromHex(v.to_address);
|
||||
const amount = v.amount / 1e6;
|
||||
amountText = amount + " TRX";
|
||||
} else if (type === "TriggerSmartContract") {
|
||||
const v = tx.raw_data.contract[0].parameter.value;
|
||||
from = tronWeb.address.fromHex(v.owner_address);
|
||||
const input = (v.data || "").startsWith("0x")
|
||||
? v.data.slice(2)
|
||||
: v.data || "";
|
||||
const method = input.slice(0, 8).toLowerCase();
|
||||
if (method === "a9059cbb" && input.length >= 8 + 64 + 64) {
|
||||
const addrSlot = input.slice(8, 8 + 64);
|
||||
const amountSlot = input.slice(8 + 64, 8 + 64 + 64);
|
||||
const evmAddrHex = addrSlot.slice(24);
|
||||
const tronHex = "41" + evmAddrHex.toLowerCase();
|
||||
to = tronWeb.address.fromHex(tronHex);
|
||||
const raw = BigInt("0x" + amountSlot);
|
||||
amountText = Number(raw) / 1e6 + " USDT";
|
||||
}
|
||||
icon = "fa-file-signature";
|
||||
} else if (
|
||||
type === "DelegateResourceContract" ||
|
||||
type === "UnDelegateResourceContract"
|
||||
) {
|
||||
// Handle resource delegation/undelegation
|
||||
const v = tx.raw_data.contract[0].parameter.value;
|
||||
from = tronWeb.address.fromHex(v.owner_address);
|
||||
to = v.receiver_address
|
||||
? tronWeb.address.fromHex(v.receiver_address)
|
||||
: "";
|
||||
amountText =
|
||||
v.balance / 1e6 +
|
||||
" TRX (" +
|
||||
(v.resource ? v.resource : "Bandwidth") +
|
||||
")";
|
||||
directionClass = "resource";
|
||||
}
|
||||
|
||||
// Set direction and icon based on transaction direction
|
||||
if (type === "DelegateResourceContract") {
|
||||
directionClass = "delegate-resource";
|
||||
icon = "fa-exchange-alt"; // custom icon for delegate
|
||||
} else if (type === "UnDelegateResourceContract") {
|
||||
directionClass = "reclaim-resource";
|
||||
icon = "fa-exchange-alt"; // custom icon for undelegate
|
||||
} else if (from === __currentAddress) {
|
||||
directionClass = "outgoing";
|
||||
icon = "fa-arrow-up"; // upward arrow for sent
|
||||
} else if (to === __currentAddress) {
|
||||
directionClass = "incoming";
|
||||
icon = "fa-arrow-down"; // downward arrow for received
|
||||
} else {
|
||||
directionClass = "";
|
||||
icon = "fa-exchange-alt"; // default for other transactions
|
||||
}
|
||||
const result = tx.ret?.[0]?.contractRet || "UNKNOWN";
|
||||
const statusClass = result === "SUCCESS" ? "success" : "failed";
|
||||
|
||||
const card = document.createElement("div");
|
||||
card.className = `transaction-card ${directionClass}`;
|
||||
card.innerHTML = `
|
||||
<div class="tx-main">
|
||||
<div class="tx-icon"><i class="fas ${icon}"></i></div>
|
||||
<div class="tx-info">
|
||||
<div class="tx-header">
|
||||
<div>
|
||||
<div class="tx-direction">${
|
||||
directionClass === "delegate-resource"
|
||||
? "Delegate Resources"
|
||||
: directionClass === "reclaim-resource"
|
||||
? "Reclaim Resources"
|
||||
: directionClass === "incoming"
|
||||
? "Received"
|
||||
: directionClass === "outgoing"
|
||||
? "Sent"
|
||||
: type
|
||||
}</div>
|
||||
<div class="tx-date">${age}</div>
|
||||
</div>
|
||||
<div class="tx-right-info">
|
||||
<div class="tx-amount ${
|
||||
directionClass === "incoming" ? "incoming" : "outgoing"
|
||||
}">${amountText}</div>
|
||||
<div class="tx-status ${statusClass}">${result}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tx-addresses">
|
||||
<div class="tx-address-row"><span class="address-label">From</span><span class="address-value"
|
||||
onclick="window.open('index.html?page=transactions&address=${from}','_blank')"
|
||||
title="View address details">${from}</span></div>
|
||||
<div class="tx-address-row"><span class="address-label">To</span><span class="address-value"
|
||||
onclick="window.open('index.html?page=transactions&address=${to}','_blank')"
|
||||
title="View address details">${to}</span></div>
|
||||
<div class="tx-hash"><span class="hash-label">Hash</span><span class="hash-value"><span class="detail-link"
|
||||
onclick="window.open('index.html?page=transactions&tx=${hash}','_blank')"
|
||||
title="View transaction details">${hash}</span></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
list.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function __updatePagination() {
|
||||
const nextBtn = document.getElementById("nextBtn");
|
||||
const prevBtn = document.getElementById("prevBtn");
|
||||
const info = document.getElementById("paginationInfo");
|
||||
const pageNumbers = document.getElementById("pageNumbers");
|
||||
|
||||
if (nextBtn) nextBtn.disabled = !__nextUrl;
|
||||
if (prevBtn) prevBtn.disabled = __prevUrls.length === 0;
|
||||
if (info) info.textContent = `Page ${__currentPage} • ${__perPage} / page`;
|
||||
|
||||
if (pageNumbers) {
|
||||
pageNumbers.innerHTML = __renderPageNumbers();
|
||||
|
||||
document.querySelectorAll(".page-number").forEach((button) => {
|
||||
const pageNum = parseInt(button.textContent);
|
||||
if (!isNaN(pageNum)) {
|
||||
button.style.cursor = "pointer";
|
||||
button.addEventListener("click", () => goToPage(pageNum));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setTransactionFilter(filter) {
|
||||
__currentFilter = filter;
|
||||
document.querySelectorAll(".filter-btn").forEach((btn) => {
|
||||
btn.classList.toggle("active", btn.getAttribute("data-filter") === filter);
|
||||
});
|
||||
__renderTransactions();
|
||||
}
|
||||
|
||||
function goToNextPage() {
|
||||
if (__nextUrl) {
|
||||
if (__currentUrl) {
|
||||
__prevUrls.push({ url: __currentUrl, page: __currentPage });
|
||||
}
|
||||
__currentPage += 1;
|
||||
transactionHistory(__nextUrl, __currentAddress);
|
||||
}
|
||||
}
|
||||
|
||||
function goToPreviousPage() {
|
||||
if (__prevUrls.length === 0) return;
|
||||
const prev = __prevUrls.pop();
|
||||
const prevUrl = prev.url;
|
||||
__currentPage = Math.max(1, prev.page || __currentPage - 1);
|
||||
if (prevUrl) {
|
||||
window.lastUsedUrl = prevUrl;
|
||||
transactionHistory(prevUrl, __currentAddress);
|
||||
}
|
||||
}
|
||||
|
||||
// simple numeric pagination with ellipses
|
||||
function __renderPageNumbers() {
|
||||
const parts = [];
|
||||
const push = (n, active) =>
|
||||
`<div class="page-number ${
|
||||
active ? "active" : ""
|
||||
}" onclick="goToPage(${n})">${n}</div>`;
|
||||
// Always show 1
|
||||
if (__currentPage === 1) parts.push(push(1, true));
|
||||
else parts.push(push(1, false));
|
||||
// Ellipsis if we're beyond page 3
|
||||
if (__currentPage > 3) parts.push('<div class="page-ellipsis">…</div>');
|
||||
// Middle window
|
||||
const start = Math.max(2, __currentPage - 1);
|
||||
const end = __nextUrl ? __currentPage + 1 : __currentPage; // if has next, show one ahead
|
||||
for (let n = start; n <= end; n++) {
|
||||
parts.push(push(n, n === __currentPage));
|
||||
}
|
||||
|
||||
if (__nextUrl) parts.push('<div class="page-ellipsis">…</div>');
|
||||
return parts.join("");
|
||||
}
|
||||
|
||||
function resetHistoryState(perPage) {
|
||||
__prevUrls = [];
|
||||
__currentPage = 1;
|
||||
__currentUrl = null;
|
||||
__nextUrl = null;
|
||||
__perPage = perPage || 10;
|
||||
}
|
||||
|
||||
// Function to go to a specific page number
|
||||
function goToPage(pageNumber) {
|
||||
if (pageNumber === __currentPage) {
|
||||
return; // Already on the page
|
||||
}
|
||||
|
||||
// If going to page 1, just reset and load initial data
|
||||
if (pageNumber === 1) {
|
||||
__prevUrls = [];
|
||||
__currentPage = 1;
|
||||
const baseUrl = `https://api.trongrid.io/v1/accounts/${__currentAddress}/transactions?limit=${__perPage}`;
|
||||
transactionHistory(baseUrl, __currentAddress);
|
||||
return;
|
||||
}
|
||||
|
||||
// If trying to go forward
|
||||
if (pageNumber > __currentPage) {
|
||||
// We can only go one page forward at a time due to API pagination limitations
|
||||
if (pageNumber === __currentPage + 1 && __nextUrl) {
|
||||
goToNextPage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If trying to go backward
|
||||
if (pageNumber < __currentPage) {
|
||||
// Check if we have the page in our history
|
||||
const targetPrevUrl = __prevUrls.find((prev) => prev.page === pageNumber);
|
||||
if (targetPrevUrl) {
|
||||
// We found the exact page in history
|
||||
while (
|
||||
__prevUrls.length > 0 &&
|
||||
__prevUrls[__prevUrls.length - 1].page >= pageNumber
|
||||
) {
|
||||
__prevUrls.pop();
|
||||
}
|
||||
__currentPage = pageNumber;
|
||||
transactionHistory(targetPrevUrl.url, __currentAddress);
|
||||
return;
|
||||
} else {
|
||||
// We need to go back to page 1 and build our way up
|
||||
const baseUrl = `https://api.trongrid.io/v1/accounts/${__currentAddress}/transactions?limit=${__perPage}`;
|
||||
__prevUrls = [];
|
||||
__currentPage = 1;
|
||||
transactionHistory(baseUrl, __currentAddress);
|
||||
|
||||
if (typeof notify === "function") {
|
||||
notify(
|
||||
"Navigating to page 1 due to API pagination limitations",
|
||||
"info",
|
||||
3000
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user