first commit litecoin wallet
This commit is contained in:
commit
7326885bb6
2829
index.html
Normal file
2829
index.html
Normal file
File diff suppressed because it is too large
Load Diff
9993
litecoin/lib.litecoin.js
Normal file
9993
litecoin/lib.litecoin.js
Normal file
File diff suppressed because it is too large
Load Diff
507
litecoin/ltcBlockchainAPI.js
Normal file
507
litecoin/ltcBlockchainAPI.js
Normal file
@ -0,0 +1,507 @@
|
||||
(function (EXPORTS) {
|
||||
"use strict";
|
||||
const ltcBlockchainAPI = EXPORTS;
|
||||
|
||||
const DEFAULT = {
|
||||
fee: 0.001,
|
||||
};
|
||||
|
||||
//Get balance for the given Address
|
||||
ltcBlockchainAPI.getBalance = function (addr) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(
|
||||
`https://go.getblock.io/cfa5c9eb49c944a7aa4856e4e9a516a2/api/address/${addr}`
|
||||
)
|
||||
.then((response) => {
|
||||
if (!response.ok)
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
console.log("Balance data:", data);
|
||||
if (data && typeof data.balance !== "undefined")
|
||||
resolve(parseFloat(data.balance));
|
||||
else reject("Balance not found in response");
|
||||
})
|
||||
.catch((error) => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function to get UTXOs for an address
|
||||
const getUTXOs = async (addr) => {
|
||||
const url = `https://go.getblock.io/cfa5c9eb49c944a7aa4856e4e9a516a2/api/address/${addr}?details=txs`;
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
if (!data.txs) throw new Error("No transactions found for address");
|
||||
|
||||
const utxos = [];
|
||||
data.txs.forEach((tx) => {
|
||||
tx.vout.forEach((vout) => {
|
||||
const addresses =
|
||||
vout.addresses ||
|
||||
(vout.scriptPubKey ? vout.scriptPubKey.addresses : []);
|
||||
if (
|
||||
!vout.spent &&
|
||||
vout.scriptPubKey &&
|
||||
vout.scriptPubKey.hex &&
|
||||
addresses &&
|
||||
addresses.some((a) => a.toLowerCase() === addr.toLowerCase())
|
||||
) {
|
||||
console.log("Found UTXO:", {
|
||||
txid: tx.txid,
|
||||
vout: vout.n,
|
||||
value: parseFloat(vout.value),
|
||||
});
|
||||
|
||||
utxos.push({
|
||||
txid: tx.txid,
|
||||
vout: vout.n,
|
||||
value: parseFloat(vout.value),
|
||||
scriptPubKey: vout.scriptPubKey.hex,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
return utxos;
|
||||
};
|
||||
|
||||
function toLTC(val) {
|
||||
if (typeof val === "string" && val.includes("LTC")) {
|
||||
return parseFloat(val.replace("LTC", "").trim());
|
||||
}
|
||||
|
||||
const num = parseFloat(val || "0");
|
||||
|
||||
return isNaN(num) ? 0 : num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction history for a Litecoin address
|
||||
* @param {string} address - The Litecoin address to check
|
||||
* @param {Object} options - Optional parameters
|
||||
* @param {number} options.limit - Number of transactions to retrieve (default: 10)
|
||||
* @param {number} options.offset - Offset for pagination
|
||||
* @returns {Promise} Promise object that resolves with transaction list
|
||||
*/
|
||||
ltcBlockchainAPI.getLtcTransactions = function (address, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`Fetching transaction history for: ${address}`);
|
||||
fetch(
|
||||
`https://go.getblock.io/cfa5c9eb49c944a7aa4856e4e9a516a2/api/address/${address}?details=txs`
|
||||
)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
if (response.status === 429) {
|
||||
throw new Error(
|
||||
"API rate limit exceeded. Please try again later."
|
||||
);
|
||||
}
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(async (data) => {
|
||||
console.log("Raw API response data:", data);
|
||||
const txs = data.txs || [];
|
||||
const txids = txs.map((tx) => tx.txid) || [];
|
||||
console.log(
|
||||
`Found ${txids.length} transactions for address ${address}`
|
||||
);
|
||||
const limit = options.limit || 10;
|
||||
const offset = options.offset || 0;
|
||||
|
||||
const maxTxToProcess = Math.min(10, limit);
|
||||
const txsToProcess = txs.slice(offset, offset + maxTxToProcess);
|
||||
|
||||
if (txsToProcess.length === 0) {
|
||||
console.log("No transactions to process based on offset/limit");
|
||||
resolve({
|
||||
transactions: [],
|
||||
total: txs.length,
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Processing ${txsToProcess.length} transactions`);
|
||||
|
||||
const transactions = txsToProcess;
|
||||
console.log("Transactions to process:", transactions);
|
||||
|
||||
try {
|
||||
const processedTransactions = transactions.map((tx) => {
|
||||
const inputs = tx.vin || [];
|
||||
const outputs = tx.vout || [];
|
||||
|
||||
// Check if address is sender (in vin)
|
||||
const isSender = inputs.some((i) =>
|
||||
i.addresses?.includes(address)
|
||||
);
|
||||
|
||||
// Check if address is receiver (in vout)
|
||||
const isReceiver = outputs.some(
|
||||
(o) =>
|
||||
(o.addresses && o.addresses.includes(address)) ||
|
||||
(o.scriptPubKey?.addresses &&
|
||||
o.scriptPubKey.addresses.includes(address))
|
||||
);
|
||||
|
||||
let type = "unknown";
|
||||
let value = 0;
|
||||
let fromAddresses = [];
|
||||
let toAddresses = [];
|
||||
|
||||
// Extract sender addresses (from)
|
||||
inputs.forEach((input) => {
|
||||
if (input.addresses && input.addresses.length > 0) {
|
||||
input.addresses.forEach((addr) => {
|
||||
if (!fromAddresses.includes(addr)) {
|
||||
fromAddresses.push(addr);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Extract recipient addresses (to)
|
||||
outputs.forEach((output) => {
|
||||
const outAddresses =
|
||||
output.addresses ||
|
||||
(output.scriptPubKey ? output.scriptPubKey.addresses : []);
|
||||
|
||||
if (outAddresses && outAddresses.length > 0) {
|
||||
outAddresses.forEach((addr) => {
|
||||
if (!toAddresses.includes(addr)) {
|
||||
toAddresses.push(addr);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (isSender && isReceiver) {
|
||||
type = "self";
|
||||
|
||||
const totalInput = inputs
|
||||
.filter((i) => i.addresses?.includes(address))
|
||||
.reduce((sum, i) => sum + toLTC(i.value), 0);
|
||||
|
||||
const totalOutput = outputs
|
||||
.filter(
|
||||
(o) =>
|
||||
(o.addresses && o.addresses.includes(address)) ||
|
||||
(o.scriptPubKey?.addresses &&
|
||||
o.scriptPubKey.addresses.includes(address))
|
||||
)
|
||||
.reduce((sum, o) => sum + toLTC(o.value), 0);
|
||||
|
||||
value = totalOutput - totalInput;
|
||||
} else if (isSender) {
|
||||
type = "sent";
|
||||
|
||||
const totalInput = inputs
|
||||
.filter((i) => i.addresses?.includes(address))
|
||||
.reduce((sum, i) => sum + toLTC(i.value), 0);
|
||||
|
||||
const changeBack = outputs
|
||||
.filter(
|
||||
(o) =>
|
||||
(o.addresses && o.addresses.includes(address)) ||
|
||||
(o.scriptPubKey?.addresses &&
|
||||
o.scriptPubKey.addresses.includes(address))
|
||||
)
|
||||
.reduce((sum, o) => sum + toLTC(o.value), 0);
|
||||
|
||||
value = -(totalInput - changeBack);
|
||||
} else if (isReceiver) {
|
||||
type = "received";
|
||||
|
||||
value = outputs
|
||||
.filter(
|
||||
(o) =>
|
||||
(o.addresses && o.addresses.includes(address)) ||
|
||||
(o.scriptPubKey?.addresses &&
|
||||
o.scriptPubKey.addresses.includes(address))
|
||||
)
|
||||
.reduce((sum, o) => sum + toLTC(o.value), 0);
|
||||
}
|
||||
|
||||
console.log(`Transaction ${tx.txid} time data:`, {
|
||||
blockTime: tx.blocktime,
|
||||
blockheight: tx.blockheight,
|
||||
time: tx.time,
|
||||
});
|
||||
|
||||
const timestamp =
|
||||
tx.time ||
|
||||
tx.blockTime ||
|
||||
(tx.confirmations
|
||||
? Math.floor(Date.now() / 1000) - tx.confirmations * 600
|
||||
: Math.floor(Date.now() / 1000));
|
||||
|
||||
return {
|
||||
txid: tx.txid,
|
||||
type,
|
||||
value: value.toFixed(8),
|
||||
time: timestamp,
|
||||
blockHeight: tx.blockheight,
|
||||
formattedTime: new Date(timestamp * 1000).toLocaleString(),
|
||||
confirmations: tx.confirmations || 0,
|
||||
rawTx: tx.hex,
|
||||
fromAddresses: fromAddresses,
|
||||
toAddresses: toAddresses,
|
||||
};
|
||||
});
|
||||
|
||||
if (processedTransactions.length > 0) {
|
||||
console.log(
|
||||
"Sample transaction processed:",
|
||||
processedTransactions[0]
|
||||
);
|
||||
|
||||
console.log("Raw transaction data:", transactions[0]);
|
||||
} else {
|
||||
console.log("No transactions were processed successfully");
|
||||
console.log("Original txids found:", txids);
|
||||
}
|
||||
resolve({
|
||||
transactions: processedTransactions,
|
||||
total: txids.length,
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error processing transactions:", error);
|
||||
reject(error);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("API Error:", error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Send Litecoin transaction using direct RPC calls to GetBlock.io
|
||||
* This method implements the full RPC workflow: createrawtransaction -> signrawtransaction -> sendrawtransaction
|
||||
* @param {string} senderAddr - Sender's Litecoin address
|
||||
* @param {string} receiverAddr - Receiver's Litecoin address
|
||||
* @param {number} sendAmt - Amount to send in LTC
|
||||
* @param {string} privKey - Private key of the sender
|
||||
* @returns {Promise} Promise that resolves with the transaction ID
|
||||
*/
|
||||
ltcBlockchainAPI.sendLitecoinRPC = function (
|
||||
senderAddr,
|
||||
receiverAddr,
|
||||
sendAmt,
|
||||
privKey
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!ltcCrypto.validateLtcID(senderAddr, true))
|
||||
return reject(`Invalid sender address: ${senderAddr}`);
|
||||
if (!ltcCrypto.validateLtcID(receiverAddr))
|
||||
return reject(`Invalid receiver address: ${receiverAddr}`);
|
||||
if (typeof sendAmt !== "number" || sendAmt <= 0)
|
||||
return reject(`Invalid send amount: ${sendAmt}`);
|
||||
if (privKey.length < 1 || !ltcCrypto.verifyPrivKey(privKey, senderAddr))
|
||||
return reject("Invalid Private key!");
|
||||
|
||||
const fee = DEFAULT.fee;
|
||||
const apiToken = "31ea37c3a0c44b368e879007af7a64c8";
|
||||
const rpcEndpoint = `https://go.getblock.io/${apiToken}/`;
|
||||
|
||||
async function rpc(method, params = []) {
|
||||
const res = await fetch(rpcEndpoint, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jsonrpc: "2.0", id: "1", method, params }),
|
||||
});
|
||||
const text = await res.text();
|
||||
try {
|
||||
const data = JSON.parse(text);
|
||||
if (data.error) throw new Error(JSON.stringify(data.error));
|
||||
return data.result;
|
||||
} catch (err) {
|
||||
console.error("Raw RPC response:\n", text);
|
||||
throw new Error("Failed to parse JSON-RPC response");
|
||||
}
|
||||
}
|
||||
|
||||
// Get UTXOs for the address
|
||||
getUTXOs(senderAddr)
|
||||
.then(async (utxos) => {
|
||||
if (utxos.length === 0) return reject("No valid UTXOs found");
|
||||
console.log("Found UTXOs:", utxos);
|
||||
|
||||
const utxoTotal = utxos.reduce((sum, utxo) => sum + utxo.value, 0);
|
||||
console.log("Total UTXO value:", utxoTotal);
|
||||
|
||||
if (utxoTotal < sendAmt + fee)
|
||||
return reject(
|
||||
`Insufficient funds: ${utxoTotal} < ${sendAmt + fee}`
|
||||
);
|
||||
|
||||
const inputs = utxos.map((utxo) => ({
|
||||
txid: utxo.txid,
|
||||
vout: utxo.vout,
|
||||
}));
|
||||
|
||||
console.log("inputs:", inputs);
|
||||
|
||||
// Calculate change amount
|
||||
const change = utxoTotal - sendAmt - fee;
|
||||
|
||||
const outputs = {
|
||||
[senderAddr]: Number(change.toFixed(8)),
|
||||
[receiverAddr]: Number(sendAmt.toFixed(8)),
|
||||
};
|
||||
console.log("outputs:", outputs);
|
||||
|
||||
try {
|
||||
// Create raw transaction
|
||||
console.log("Creating raw transaction...");
|
||||
const rawTx = await rpc("createrawtransaction", [inputs, outputs]);
|
||||
console.log("Raw transaction hex:", rawTx);
|
||||
// Sign raw transaction
|
||||
console.log("Signing transaction...");
|
||||
const signedTx = await rpc("signrawtransaction", [
|
||||
rawTx,
|
||||
[
|
||||
{
|
||||
txid: utxos[0].txid,
|
||||
vout: utxos[0].vout,
|
||||
scriptPubKey: utxos[0].scriptPubKey,
|
||||
amount: utxos[0].value.toFixed(8),
|
||||
},
|
||||
],
|
||||
[privKey],
|
||||
]);
|
||||
|
||||
if (!signedTx.complete) {
|
||||
return reject(
|
||||
`Failed to sign transaction: ${JSON.stringify(signedTx.errors)}`
|
||||
);
|
||||
}
|
||||
console.log("Signed transaction hex:", signedTx.hex);
|
||||
|
||||
// Send raw transaction
|
||||
console.log("Broadcasting transaction...");
|
||||
const txid = await rpc("sendrawtransaction", [signedTx.hex]);
|
||||
|
||||
resolve(txid);
|
||||
} catch (error) {
|
||||
console.error("RPC error:", error);
|
||||
reject(error);
|
||||
}
|
||||
})
|
||||
.catch((error) => reject(error));
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Get transaction details by transaction ID
|
||||
* @param {string} txid - The transaction ID to look up
|
||||
* @returns {Promise} Promise object that resolves with transaction details
|
||||
*/
|
||||
ltcBlockchainAPI.getTransactionDetails = function (txid) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!txid || typeof txid !== "string" || txid.length !== 64) {
|
||||
reject(new Error("Invalid transaction ID format"));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Fetching transaction details for txid: ${txid}`);
|
||||
|
||||
fetch(
|
||||
`https://go.getblock.io/cfa5c9eb49c944a7aa4856e4e9a516a2/api/tx/${txid}`
|
||||
)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw new Error("Transaction not found");
|
||||
} else if (response.status === 429) {
|
||||
throw new Error(
|
||||
"API rate limit exceeded. Please try again later."
|
||||
);
|
||||
}
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
console.log("Transaction details data:", data);
|
||||
|
||||
if (!data || !data.txid) {
|
||||
throw new Error("Invalid transaction data returned");
|
||||
}
|
||||
|
||||
const processedData = {
|
||||
txid: data.txid,
|
||||
blockHeight: data.blockheight,
|
||||
blockHash: data.blockhash,
|
||||
blockTime: data.blocktime
|
||||
? new Date(data.blocktime * 1000).toLocaleString()
|
||||
: "Pending",
|
||||
confirmations: data.confirmations || 0,
|
||||
fees: data.fees,
|
||||
size: data.hex.length / 2, // Size in bytes
|
||||
inputsCount: data.vin ? data.vin.length : 0,
|
||||
outputsCount: data.vout ? data.vout.length : 0,
|
||||
totalInput: 0,
|
||||
totalOutput: 0,
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
};
|
||||
|
||||
// Process inputs
|
||||
if (data.vin && Array.isArray(data.vin)) {
|
||||
data.vin.forEach((input) => {
|
||||
const inputValue = parseFloat(input.value || 0);
|
||||
processedData.totalInput += inputValue;
|
||||
|
||||
const inputData = {
|
||||
txid: input.txid,
|
||||
vout: input.vout,
|
||||
addresses: input.addresses || [],
|
||||
value: inputValue,
|
||||
};
|
||||
|
||||
processedData.inputs.push(inputData);
|
||||
});
|
||||
}
|
||||
|
||||
// Process outputs
|
||||
if (data.vout && Array.isArray(data.vout)) {
|
||||
data.vout.forEach((output) => {
|
||||
const outputValue = parseFloat(output.value || 0);
|
||||
processedData.totalOutput += outputValue;
|
||||
|
||||
const addresses =
|
||||
output.scriptPubKey && output.scriptPubKey.addresses
|
||||
? output.scriptPubKey.addresses
|
||||
: [];
|
||||
|
||||
const outputData = {
|
||||
n: output.n,
|
||||
addresses: addresses,
|
||||
value: outputValue,
|
||||
spent: output.spent || false,
|
||||
scriptPubKey: output.scriptPubKey
|
||||
? output.scriptPubKey.hex
|
||||
: "",
|
||||
};
|
||||
|
||||
processedData.outputs.push(outputData);
|
||||
});
|
||||
}
|
||||
|
||||
resolve(processedData);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error fetching transaction details:", error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
})(
|
||||
"object" === typeof module ? module.exports : (window.ltcBlockchainAPI = {})
|
||||
);
|
||||
292
litecoin/ltcCrypto.js
Normal file
292
litecoin/ltcCrypto.js
Normal file
@ -0,0 +1,292 @@
|
||||
(function (EXPORTS) {
|
||||
"use strict";
|
||||
const ltcCrypto = EXPORTS;
|
||||
|
||||
const generateNewID = (ltcCrypto.generateNewID = function () {
|
||||
var key = new Bitcoin.ECKey(false);
|
||||
key.setCompressed(true);
|
||||
return {
|
||||
floID: key.getBitcoinAddress(),
|
||||
pubKey: key.getPubKeyHex(),
|
||||
privKey: key.getBitcoinWalletImportFormat(),
|
||||
};
|
||||
});
|
||||
|
||||
Object.defineProperties(ltcCrypto, {
|
||||
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));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Verify the private-key for the given public-key or ltc-ID
|
||||
ltcCrypto.verifyPrivKey = function (privateKeyWIF, ltcAddress) {
|
||||
if (!privateKeyWIF || !ltcAddress) return false;
|
||||
try {
|
||||
var derivedAddress =
|
||||
ltcCrypto.generateMultiChain(privateKeyWIF).LTC.address;
|
||||
return derivedAddress === ltcAddress;
|
||||
} catch (e) {
|
||||
console.error("verifyPrivKey error:", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//Check if the given ltc-id is valid or not
|
||||
ltcCrypto.validateLtcID = function (ltcID) {
|
||||
if (!ltcID) return false;
|
||||
|
||||
// Check for SegWit addresses (ltc1...)
|
||||
if (ltcID.toLowerCase().startsWith("ltc1")) {
|
||||
try {
|
||||
// Basic SegWit validation
|
||||
if (ltcID.length < 42 || ltcID.length > 62) return false;
|
||||
|
||||
// Check if it contains only valid bech32 characters
|
||||
const bech32Regex = /^ltc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]+$/i;
|
||||
return bech32Regex.test(ltcID);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy address validation (L, M prefixes)
|
||||
try {
|
||||
// Decode Base58Check
|
||||
let bytes = bitjs.Base58.decode(ltcID);
|
||||
if (!bytes || bytes.length < 25) return false;
|
||||
let version = bytes[0];
|
||||
|
||||
return version === 0x30 || version === 0x32 || version === 0x05; // Litecoin legacy (L), P2SH (M), or segwit compatible
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//Generates multi-chain addresses (LTC, BTC, FLO, DOGE) from the given WIF or new WIF
|
||||
ltcCrypto.generateMultiChain = function (inputWif) {
|
||||
try {
|
||||
const origBitjsPub = bitjs.pub;
|
||||
const origBitjsPriv = bitjs.priv;
|
||||
const origBitjsCompressed = bitjs.compressed;
|
||||
const origCoinJsCompressed = coinjs.compressed;
|
||||
|
||||
bitjs.compressed = true;
|
||||
coinjs.compressed = true;
|
||||
|
||||
const versions = {
|
||||
LTC: { pub: 0x30, priv: 0xb0 },
|
||||
BTC: { pub: 0x00, priv: 0x80 },
|
||||
FLO: { pub: 0x23, priv: 0xa3 },
|
||||
DOGE: { pub: 0x1e, priv: 0x9e },
|
||||
};
|
||||
|
||||
let privKeyHex;
|
||||
let compressed = true;
|
||||
|
||||
if (typeof inputWif === "string" && inputWif.length > 0) {
|
||||
const decode = Bitcoin.Base58.decode(inputWif);
|
||||
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||
let key = keyWithVersion.slice(1);
|
||||
|
||||
if (key.length >= 33 && key[key.length - 1] === 0x01) {
|
||||
key = key.slice(0, key.length - 1);
|
||||
compressed = true;
|
||||
} else {
|
||||
compressed = false;
|
||||
}
|
||||
|
||||
privKeyHex = Crypto.util.bytesToHex(key);
|
||||
} else {
|
||||
const newKey = generateNewID();
|
||||
const decode = Bitcoin.Base58.decode(newKey.privKey);
|
||||
const keyWithVersion = decode.slice(0, decode.length - 4);
|
||||
let key = keyWithVersion.slice(1);
|
||||
|
||||
if (key.length >= 33 && key[key.length - 1] === 0x01) {
|
||||
key = key.slice(0, key.length - 1);
|
||||
}
|
||||
|
||||
privKeyHex = Crypto.util.bytesToHex(key);
|
||||
}
|
||||
|
||||
bitjs.compressed = compressed;
|
||||
coinjs.compressed = compressed;
|
||||
|
||||
// Generate public key
|
||||
const pubKey = bitjs.newPubkey(privKeyHex);
|
||||
|
||||
const result = {
|
||||
LTC: { address: "", privateKey: "" },
|
||||
BTC: { address: "", privateKey: "" },
|
||||
FLO: { address: "", privateKey: "" },
|
||||
DOGE: { address: "", privateKey: "" },
|
||||
};
|
||||
|
||||
// For LTC
|
||||
bitjs.pub = versions.LTC.pub;
|
||||
bitjs.priv = versions.LTC.priv;
|
||||
result.LTC.address = bitjs.pubkey2address(pubKey);
|
||||
result.LTC.privateKey = bitjs.privkey2wif(privKeyHex);
|
||||
|
||||
// For BTC
|
||||
bitjs.pub = versions.BTC.pub;
|
||||
bitjs.priv = versions.BTC.priv;
|
||||
result.BTC.address = coinjs.bech32Address(pubKey).address;
|
||||
result.BTC.privateKey = bitjs.privkey2wif(privKeyHex);
|
||||
|
||||
// For FLO
|
||||
bitjs.pub = versions.FLO.pub;
|
||||
bitjs.priv = versions.FLO.priv;
|
||||
result.FLO.address = bitjs.pubkey2address(pubKey);
|
||||
result.FLO.privateKey = bitjs.privkey2wif(privKeyHex);
|
||||
|
||||
// For DOGE
|
||||
bitjs.pub = versions.DOGE.pub;
|
||||
bitjs.priv = versions.DOGE.priv;
|
||||
result.DOGE.address = bitjs.pubkey2address(pubKey);
|
||||
result.DOGE.privateKey = bitjs.privkey2wif(privKeyHex);
|
||||
|
||||
bitjs.pub = origBitjsPub;
|
||||
bitjs.priv = origBitjsPriv;
|
||||
bitjs.compressed = origBitjsCompressed;
|
||||
coinjs.compressed = origCoinJsCompressed;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error in generateMultiChain:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Translates an address from one blockchain to equivalent addresses on other chains
|
||||
* Works by extracting the public key hash from the address and recreating addresses with different version bytes
|
||||
*/
|
||||
ltcCrypto.translateAddress = function (address) {
|
||||
try {
|
||||
let sourceChain = null;
|
||||
|
||||
if (address.startsWith("bc1")) {
|
||||
sourceChain = "BTC";
|
||||
} else if (address.startsWith("D")) {
|
||||
sourceChain = "DOGE";
|
||||
} else if (address.startsWith("F")) {
|
||||
sourceChain = "FLO";
|
||||
} else if (address.startsWith("L") || address.startsWith("M")) {
|
||||
sourceChain = "LTC";
|
||||
} else {
|
||||
throw new Error("Unsupported address format");
|
||||
}
|
||||
|
||||
let decoded, hash160;
|
||||
|
||||
if (sourceChain === "BTC") {
|
||||
decoded = coinjs.bech32_decode(address);
|
||||
if (!decoded) throw new Error("Invalid bech32 address");
|
||||
|
||||
// For segwit addresses, convert from 5-bit to 8-bit
|
||||
const data = coinjs.bech32_convert(decoded.data.slice(1), 5, 8, false);
|
||||
hash160 = Crypto.util.bytesToHex(data);
|
||||
} else {
|
||||
// Handle LTC, DOGE and FLO addresses (Base58)
|
||||
const decodedBytes = Bitcoin.Base58.decode(address);
|
||||
if (!decodedBytes || decodedBytes.length < 25)
|
||||
throw new Error("Invalid address");
|
||||
|
||||
// Remove version byte (first byte) and checksum (last 4 bytes)
|
||||
const bytes = decodedBytes.slice(1, decodedBytes.length - 4);
|
||||
hash160 = Crypto.util.bytesToHex(bytes);
|
||||
}
|
||||
|
||||
if (!hash160) throw new Error("Could not extract hash160 from address");
|
||||
|
||||
const versions = {
|
||||
LTC: 0x30,
|
||||
DOGE: 0x1e,
|
||||
FLO: 0x23,
|
||||
BTC: 0x00,
|
||||
};
|
||||
|
||||
const result = {};
|
||||
|
||||
// Generate address for LTC
|
||||
const ltcBytes = Crypto.util.hexToBytes(hash160);
|
||||
ltcBytes.unshift(versions.LTC);
|
||||
const ltcChecksum = Crypto.SHA256(
|
||||
Crypto.SHA256(ltcBytes, { asBytes: true }),
|
||||
{ asBytes: true }
|
||||
).slice(0, 4);
|
||||
result.LTC = Bitcoin.Base58.encode(ltcBytes.concat(ltcChecksum));
|
||||
|
||||
// Generate address for DOGE
|
||||
const dogeBytes = Crypto.util.hexToBytes(hash160);
|
||||
dogeBytes.unshift(versions.DOGE);
|
||||
const dogeChecksum = Crypto.SHA256(
|
||||
Crypto.SHA256(dogeBytes, { asBytes: true }),
|
||||
{ asBytes: true }
|
||||
).slice(0, 4);
|
||||
result.DOGE = Bitcoin.Base58.encode(dogeBytes.concat(dogeChecksum));
|
||||
|
||||
// Generate address for FLO
|
||||
const floBytes = Crypto.util.hexToBytes(hash160);
|
||||
floBytes.unshift(versions.FLO);
|
||||
const floChecksum = Crypto.SHA256(
|
||||
Crypto.SHA256(floBytes, { asBytes: true }),
|
||||
{ asBytes: true }
|
||||
).slice(0, 4);
|
||||
result.FLO = Bitcoin.Base58.encode(floBytes.concat(floChecksum));
|
||||
|
||||
// Generate address for BTC
|
||||
try {
|
||||
const words = coinjs.bech32_convert(
|
||||
Crypto.util.hexToBytes(hash160),
|
||||
8,
|
||||
5,
|
||||
true
|
||||
);
|
||||
result.BTC = coinjs.bech32_encode("bc", [0].concat(words));
|
||||
} catch (e) {
|
||||
console.log("Could not generate segwit address:", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error("Address translation error:", err);
|
||||
throw new Error("Address translation failed: " + err.message);
|
||||
}
|
||||
};
|
||||
})("object" === typeof module ? module.exports : (window.ltcCrypto = {}));
|
||||
124
litecoin/ltcSearchDB.js
Normal file
124
litecoin/ltcSearchDB.js
Normal file
@ -0,0 +1,124 @@
|
||||
class SearchedAddressDB {
|
||||
constructor() {
|
||||
this.dbName = "LtcWalletDB";
|
||||
this.version = 1;
|
||||
this.storeName = "searchedAddresses";
|
||||
this.db = null;
|
||||
}
|
||||
|
||||
async init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(this.dbName, this.version);
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => {
|
||||
this.db = request.result;
|
||||
resolve();
|
||||
};
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = event.target.result;
|
||||
if (!db.objectStoreNames.contains(this.storeName)) {
|
||||
const store = db.createObjectStore(this.storeName, {
|
||||
keyPath: "address",
|
||||
});
|
||||
store.createIndex("timestamp", "timestamp", { unique: false });
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async saveSearchedAddress(
|
||||
address,
|
||||
balance,
|
||||
timestamp = Date.now(),
|
||||
sourceInfo = null
|
||||
) {
|
||||
if (!this.db) await this.init();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db.transaction([this.storeName], "readwrite");
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
|
||||
// First, check if this address already exists
|
||||
const getRequest = store.get(address);
|
||||
|
||||
getRequest.onsuccess = () => {
|
||||
const existingRecord = getRequest.result;
|
||||
let finalSourceInfo = sourceInfo;
|
||||
|
||||
// If record exists and has sourceInfo, preserve it unless we're providing new sourceInfo
|
||||
if (existingRecord && existingRecord.sourceInfo && !sourceInfo) {
|
||||
finalSourceInfo = existingRecord.sourceInfo;
|
||||
}
|
||||
// If existing record has sourceInfo and new one doesn't, keep the existing one
|
||||
else if (
|
||||
existingRecord &&
|
||||
existingRecord.sourceInfo &&
|
||||
sourceInfo === null
|
||||
) {
|
||||
finalSourceInfo = existingRecord.sourceInfo;
|
||||
}
|
||||
|
||||
const data = {
|
||||
address, // This will be the LTC address
|
||||
balance,
|
||||
timestamp,
|
||||
formattedBalance: `${balance} LTC`,
|
||||
sourceInfo: finalSourceInfo, // Contains original blockchain info if translated from another chain
|
||||
};
|
||||
|
||||
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");
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user