feat: Add multi-chain wallet generation and address recovery
- Implemented `genMultichainAddress.js` for generating addresses for FLO, BTC, and TRON from private keys. - Created `recoverAddress.js` to recover addresses from a given private key, supporting multiple blockchains. - Added `sendTRX.js` for sending TRX transactions, including WIF to hex conversion and transaction details display. - Developed `transactionHistory.js` to fetch and display transaction history for TRON addresses with pagination and filtering options.
This commit is contained in:
parent
05ae9a27e8
commit
ef65ac5886
BIN
RanchiMall_TRON_Wallet_Tasks.pdf
Normal file
BIN
RanchiMall_TRON_Wallet_Tasks.pdf
Normal file
Binary file not shown.
4746
css/style.css
Normal file
4746
css/style.css
Normal file
File diff suppressed because it is too large
Load Diff
1029
index.html
Normal file
1029
index.html
Normal file
File diff suppressed because it is too large
Load Diff
102
scripts/balance.js
Normal file
102
scripts/balance.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
// 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 out = document.getElementById("balanceOutput");
|
||||||
|
|
||||||
|
// Set loading state
|
||||||
|
if (typeof setButtonLoading === "function") {
|
||||||
|
setButtonLoading("balanceBtn", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (inputVal.startsWith("T")) {
|
||||||
|
// Direct Tron address
|
||||||
|
const balance = await getBalanceByAddress(inputVal);
|
||||||
|
out.innerHTML = `
|
||||||
|
<div class="card balance-info">
|
||||||
|
<div class="balance-header">
|
||||||
|
<h3><i class="fas fa-coins"></i> Account Balance</h3>
|
||||||
|
</div>
|
||||||
|
<div class="balance-display"><span class="balance-amount">${balance} TRX</span></div>
|
||||||
|
<div class="account-details">
|
||||||
|
<div class="detail-row">
|
||||||
|
<label>Address:</label>
|
||||||
|
<div class="address-container">
|
||||||
|
<span class="address-text">${inputVal}</span>
|
||||||
|
<button class="btn-icon" onclick="navigator.clipboard.writeText('${inputVal}').then(()=>notify && notify('Copied','success'))" title="Copy"><i class="fas fa-copy"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
if (typeof notify === "function") notify("Balance loaded", "success");
|
||||||
|
} else {
|
||||||
|
// Treat as private key (WIF or HEX)
|
||||||
|
const { tronAddress, balance } = await getBalanceByPrivKey(inputVal);
|
||||||
|
out.innerHTML = `
|
||||||
|
<div class="card balance-info">
|
||||||
|
<div class="balance-header">
|
||||||
|
<h3><i class="fas fa-coins"></i> Account Balance</h3>
|
||||||
|
</div>
|
||||||
|
<div class="balance-display"><span class="balance-amount">${balance} TRX</span></div>
|
||||||
|
<div class="account-details">
|
||||||
|
<div class="detail-row">
|
||||||
|
<label>Derived Address:</label>
|
||||||
|
<div class="address-container">
|
||||||
|
<span class="address-text">${tronAddress}</span>
|
||||||
|
<button class="btn-icon" onclick="navigator.clipboard.writeText('${tronAddress}').then(()=>notify && notify('Copied','success'))" title="Copy"><i class="fas fa-copy"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
if (typeof notify === "function") notify("Balance loaded", "success");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
out.innerHTML = `<div class="error-state"><i class="fas fa-triangle-exclamation"></i>${err.message}</div>`;
|
||||||
|
if (typeof notify === "function") notify(err.message, "error");
|
||||||
|
} finally {
|
||||||
|
// Clear loading state
|
||||||
|
if (typeof setButtonLoading === "function") {
|
||||||
|
setButtonLoading("balanceBtn", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1499
scripts/btcOperator.js
Normal file
1499
scripts/btcOperator.js
Normal file
File diff suppressed because it is too large
Load Diff
10247
scripts/btcwallet_scripts_lib.js
Normal file
10247
scripts/btcwallet_scripts_lib.js
Normal file
File diff suppressed because it is too large
Load Diff
1
scripts/components.min.js
vendored
Normal file
1
scripts/components.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
606
scripts/floCrypto.js
Normal file
606
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
scripts/genMultichainAddress.js
Normal file
78
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.shasta.trongrid.io";
|
||||||
|
const solidityNode = "https://api.shasta.trongrid.io";
|
||||||
|
const eventServer = "https://api.shasta.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
scripts/recoverAddress.js
Normal file
199
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.shasta.trongrid.io",
|
||||||
|
"https://api.shasta.trongrid.io",
|
||||||
|
"https://api.shasta.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
144
scripts/sendTRX.js
Normal file
144
scripts/sendTRX.js
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
const fullNode = "https://api.shasta.trongrid.io";
|
||||||
|
const solidityNode = "https://api.shasta.trongrid.io";
|
||||||
|
const eventServer = "https://api.shasta.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);
|
||||||
|
|
||||||
|
|
||||||
|
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-key"></i> Private Key Used</h4>
|
||||||
|
<div class="blockchain-badge primary">${source}</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<label><i class="fas fa-key"></i> Private Key</label>
|
||||||
|
<div class="value-container">
|
||||||
|
<code>${document.getElementById("privKey").value.trim()}</code>
|
||||||
|
<button class="btn-icon" onclick="navigator.clipboard.writeText('${document
|
||||||
|
.getElementById("privKey")
|
||||||
|
.value.trim()}').then(()=>notify && notify('Private key copied','success'))" title="Copy Private Key">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</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>${fromAddress}</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>${toAddress}</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>${txid}</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) {
|
||||||
|
outputDiv.innerHTML = `<div class="error-state"><i class="fas fa-triangle-exclamation"></i>Error: ${err.message}</div>`;
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
373
scripts/transactionHistory.js
Normal file
373
scripts/transactionHistory.js
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
const options = { method: "GET", headers: { accept: "application/json" } };
|
||||||
|
let nextUrl = null;
|
||||||
|
|
||||||
|
async function transactionHistory(url, address) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
const historyDiv = document.getElementById("historyOutput");
|
||||||
|
historyDiv.innerHTML = "";
|
||||||
|
|
||||||
|
if (data && data.data) {
|
||||||
|
console.log(data.data);
|
||||||
|
|
||||||
|
data.data.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 amount = "";
|
||||||
|
let extraContractLine = "";
|
||||||
|
|
||||||
|
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);
|
||||||
|
amount = v.amount / 1e6 + " TRX";
|
||||||
|
} else if (type === "TriggerSmartContract") {
|
||||||
|
const v = tx.raw_data.contract[0].parameter.value;
|
||||||
|
|
||||||
|
from = tronWeb.address.fromHex(v.owner_address);
|
||||||
|
|
||||||
|
|
||||||
|
const contractBase58 = tronWeb.address.fromHex(v.contract_address);
|
||||||
|
extraContractLine = `
|
||||||
|
<p><b>Contract:</b> ${contractBase58}
|
||||||
|
<button onclick="copyToClipboard('${contractBase58}')"><i class="fas fa-copy"></i></button>
|
||||||
|
</p>`;
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
amount = Number(raw) / 1e6 + " USDT";
|
||||||
|
} else {
|
||||||
|
to = "—";
|
||||||
|
amount = "—";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = tx.ret?.[0]?.contractRet || "UNKNOWN";
|
||||||
|
const statusColor = result === "SUCCESS" ? "green" : "red";
|
||||||
|
|
||||||
|
// create card
|
||||||
|
const card = document.createElement("div");
|
||||||
|
card.className = "tx-card";
|
||||||
|
|
||||||
|
card.innerHTML = `
|
||||||
|
<p><b>Hash:</b> ${truncate(hash)}
|
||||||
|
<button onclick="copyToClipboard('${hash}')"><i class="fas fa-copy"></i></button></p>
|
||||||
|
<p><b>Block:</b> ${block}</p>
|
||||||
|
<p><b>Age:</b> ${age}</p>
|
||||||
|
<p><b>Type:</b> ${type}</p>
|
||||||
|
<p><b>From:</b> ${from}
|
||||||
|
<button onclick="copyToClipboard('${from}')"><i class="fas fa-copy"></i></button></p>
|
||||||
|
<p><b>To:</b> ${to}
|
||||||
|
<button onclick="copyToClipboard('${to}')"><i class="fas fa-copy"></i></button></p>
|
||||||
|
${extraContractLine}
|
||||||
|
<p><b>Amount:</b> <span style="color:#0f0;font-weight:bold">${amount}</span></p>
|
||||||
|
<p><b>Status:</b> <span style="color:${statusColor}">${result}</span></p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
historyDiv.appendChild(card);
|
||||||
|
});
|
||||||
|
|
||||||
|
// save nextUrl for pagination
|
||||||
|
if (data.meta && data.meta.fingerprint) {
|
||||||
|
nextUrl = `https://api.shasta.trongrid.io/v1/accounts/${address}/transactions?limit=10&fingerprint=${encodeURIComponent(
|
||||||
|
data.meta.fingerprint
|
||||||
|
)}`;
|
||||||
|
} else {
|
||||||
|
nextUrl = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
const __origTransactionHistory = transactionHistory;
|
||||||
|
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) {
|
||||||
|
__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.shasta.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 __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";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set direction and icon based on transaction direction
|
||||||
|
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 === "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">${from}</span></div>
|
||||||
|
<div class="tx-address-row"><span class="address-label">To</span><span class="address-value">${
|
||||||
|
to || "—"
|
||||||
|
}</span></div>
|
||||||
|
<div class="tx-hash"><span class="hash-label">Hash</span><span class="hash-value">${hash}</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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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" : ""}">${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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user