Workflow updating files of tonwallet
This commit is contained in:
parent
16e0a1cc94
commit
7fc4177989
3288
tonwallet/index.html
Normal file
3288
tonwallet/index.html
Normal file
File diff suppressed because it is too large
Load Diff
9993
tonwallet/lib.toncoin.js
Normal file
9993
tonwallet/lib.toncoin.js
Normal file
File diff suppressed because it is too large
Load Diff
2520
tonwallet/style.css
Normal file
2520
tonwallet/style.css
Normal file
File diff suppressed because it is too large
Load Diff
484
tonwallet/tonBlockchainAPI.js
Normal file
484
tonwallet/tonBlockchainAPI.js
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
(function (EXPORTS) {
|
||||||
|
"use strict";
|
||||||
|
const tonBlockchainAPI = EXPORTS;
|
||||||
|
|
||||||
|
const API = "https://toncenter.com/api/v2";
|
||||||
|
const API_KEY =
|
||||||
|
"62bbf0ea18f197520db44c23d961a4213f373c4c08bf5cb818b722b85192ca63";
|
||||||
|
const USDT_MASTER = "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs";
|
||||||
|
|
||||||
|
const addrCache = new Map();
|
||||||
|
|
||||||
|
// TonWeb initialization
|
||||||
|
let tonweb;
|
||||||
|
if (typeof TonWeb !== "undefined") {
|
||||||
|
tonweb = new TonWeb(
|
||||||
|
new TonWeb.HttpProvider("https://toncenter.com/api/v2/jsonRPC"),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"X-API-Key": API_KEY,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get TON balance for the given address
|
||||||
|
* @param {string} address - The TON address to check
|
||||||
|
* @returns {Promise} Promise object that resolves with balance in TON
|
||||||
|
*/
|
||||||
|
tonBlockchainAPI.getTonBalance = function (address) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fetch(`${API}/getAddressInformation?address=${address}`, {
|
||||||
|
headers: { "X-API-Key": API_KEY },
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok)
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
const balance = (data?.result?.balance || 0) / 1e9;
|
||||||
|
resolve(balance);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("TON balance error:", error);
|
||||||
|
resolve(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get USDT jetton balance for the given address
|
||||||
|
* @param {string} ownerAddress - The TON address to check for USDT balance
|
||||||
|
* @returns {Promise} Promise object that resolves with USDT balance
|
||||||
|
*/
|
||||||
|
tonBlockchainAPI.getUsdtBalance = function (ownerAddress) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log("Getting USDT balance for:", ownerAddress);
|
||||||
|
|
||||||
|
fetch(`https://tonapi.io/v2/accounts/${ownerAddress}/jettons`)
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) throw new Error(`TonAPI error: ${response.status}`);
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
console.log("TonAPI jettons response:", data);
|
||||||
|
|
||||||
|
const usdtJetton = data.balances?.find(
|
||||||
|
(jetton) =>
|
||||||
|
jetton.jetton?.address === USDT_MASTER ||
|
||||||
|
jetton.jetton?.symbol === "USDT" ||
|
||||||
|
jetton.jetton?.name?.includes("Tether")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (usdtJetton) {
|
||||||
|
const balance = parseInt(usdtJetton.balance) / 1e6;
|
||||||
|
console.log("USDT balance found:", balance);
|
||||||
|
resolve(balance);
|
||||||
|
} else {
|
||||||
|
console.log("No USDT balance found");
|
||||||
|
resolve(0);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("USDT balance error:", error);
|
||||||
|
resolve(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rate limiting for API calls
|
||||||
|
let requestQueue = [];
|
||||||
|
let isProcessingQueue = false;
|
||||||
|
const REQUEST_DELAY = 300;
|
||||||
|
let conversionEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process request queue with rate limiting
|
||||||
|
*/
|
||||||
|
function processRequestQueue() {
|
||||||
|
if (isProcessingQueue || requestQueue.length === 0) return;
|
||||||
|
|
||||||
|
isProcessingQueue = true;
|
||||||
|
|
||||||
|
const processNext = () => {
|
||||||
|
if (requestQueue.length === 0) {
|
||||||
|
isProcessingQueue = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { rawAddr, resolve } = requestQueue.shift();
|
||||||
|
|
||||||
|
// Check cache first
|
||||||
|
if (addrCache.has(rawAddr)) {
|
||||||
|
resolve(addrCache.get(rawAddr));
|
||||||
|
setTimeout(processNext, 50);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
`https://toncenter.com/api/v2/detectAddress?address=${encodeURIComponent(
|
||||||
|
rawAddr
|
||||||
|
)}`,
|
||||||
|
{
|
||||||
|
headers: { "X-API-Key": API_KEY },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 429) {
|
||||||
|
if (rawAddr.includes(":retry:")) {
|
||||||
|
console.warn(
|
||||||
|
"Rate limit exceeded, using original address:",
|
||||||
|
rawAddr.replace(":retry:", "")
|
||||||
|
);
|
||||||
|
const originalAddr = rawAddr.replace(":retry:", "");
|
||||||
|
resolve(originalAddr);
|
||||||
|
setTimeout(processNext, REQUEST_DELAY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestQueue.push({ rawAddr: rawAddr + ":retry:", resolve });
|
||||||
|
setTimeout(processNext, 2000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
if (data) {
|
||||||
|
const friendly =
|
||||||
|
data?.result?.bounceable?.b64url ||
|
||||||
|
rawAddr.replace(":retry:", "");
|
||||||
|
const cleanAddr = rawAddr.replace(":retry:", "");
|
||||||
|
addrCache.set(cleanAddr, friendly);
|
||||||
|
resolve(friendly);
|
||||||
|
} else {
|
||||||
|
resolve(rawAddr.replace(":retry:", ""));
|
||||||
|
}
|
||||||
|
setTimeout(processNext, REQUEST_DELAY);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn("Address conversion failed:", error);
|
||||||
|
resolve(rawAddr.replace(":retry:", "")); // Fallback to original address
|
||||||
|
setTimeout(processNext, REQUEST_DELAY);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
processNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert address to b64 format with rate limiting
|
||||||
|
* @param {string} rawAddr - The address to convert (raw, EQ, UQ formats)
|
||||||
|
* @returns {Promise} Promise object that resolves with user-friendly address
|
||||||
|
*/
|
||||||
|
tonBlockchainAPI.convertTob64 = function (rawAddr) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// if it doesn't look like an address, return as-is
|
||||||
|
if (!rawAddr || typeof rawAddr !== "string" || rawAddr === "Unknown") {
|
||||||
|
resolve(rawAddr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If conversion is disabled, return original address
|
||||||
|
if (!conversionEnabled) {
|
||||||
|
resolve(rawAddr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRawAddress =
|
||||||
|
rawAddr.includes(":") && rawAddr.match(/^-?\d+:[a-fA-F0-9]{64}$/);
|
||||||
|
|
||||||
|
const isFriendlyAddress = rawAddr.match(/^[EUk]Q[A-Za-z0-9_-]{46}$/);
|
||||||
|
|
||||||
|
if (!isRawAddress && !isFriendlyAddress) {
|
||||||
|
resolve(rawAddr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cache first
|
||||||
|
if (addrCache.has(rawAddr)) {
|
||||||
|
resolve(addrCache.get(rawAddr));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to queue for conversion (works for both raw and friendly addresses)
|
||||||
|
requestQueue.push({ rawAddr, resolve });
|
||||||
|
processRequestQueue();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or disable address conversion
|
||||||
|
* @param {boolean} enabled - Whether to enable address conversion
|
||||||
|
*/
|
||||||
|
tonBlockchainAPI.setConversionEnabled = function (enabled) {
|
||||||
|
conversionEnabled = enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch transaction history for an address
|
||||||
|
* @param {string} address - The TON address to check
|
||||||
|
* @param {Object} options - Optional parameters
|
||||||
|
* @param {number} options.limit - Number of transactions to retrieve (default: 100)
|
||||||
|
* @param {string} options.beforeLt - Last transaction LT for pagination
|
||||||
|
* @returns {Promise} Promise object that resolves with transaction data
|
||||||
|
*/
|
||||||
|
tonBlockchainAPI.fetchTransactions = function (address, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const limit = options.limit || 100;
|
||||||
|
const beforeLt = options.beforeLt || null;
|
||||||
|
|
||||||
|
const url = `https://tonapi.io/v2/blockchain/accounts/${address}/transactions?limit=${limit}${
|
||||||
|
beforeLt ? "&before_lt=" + beforeLt : ""
|
||||||
|
}`;
|
||||||
|
|
||||||
|
console.log(`Fetching transactions for: ${address}`);
|
||||||
|
|
||||||
|
fetch(url)
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) throw new Error(`API Error ${response.status}`);
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
const transactions = data.transactions || [];
|
||||||
|
resolve({
|
||||||
|
transactions,
|
||||||
|
hasMore: transactions.length === limit,
|
||||||
|
nextBeforeLt:
|
||||||
|
transactions.length > 0
|
||||||
|
? transactions[transactions.length - 1].lt
|
||||||
|
: null,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error fetching transactions:", error);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get balance for an address
|
||||||
|
* @param {string} address - The TON address to check
|
||||||
|
* @returns {Promise} Promise object that resolves with balance in TON
|
||||||
|
*/
|
||||||
|
tonBlockchainAPI.getMainnetBalance = function (address) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fetch(
|
||||||
|
`https://toncenter.com/api/v2/getAddressBalance?address=${address}`,
|
||||||
|
{
|
||||||
|
headers: { "X-API-Key": API_KEY },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok)
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
const balance = parseFloat(data.result) / 1e9;
|
||||||
|
resolve(balance);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Balance check error:", error);
|
||||||
|
resolve(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
tonBlockchainAPI.getUQAddress = function (address) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (!address || typeof address !== "string") {
|
||||||
|
resolve(address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidTonAddress = address.match(/^[EUk]Q[A-Za-z0-9_-]{46}$/);
|
||||||
|
if (!isValidTonAddress) {
|
||||||
|
resolve(address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (address.startsWith("UQ")) {
|
||||||
|
resolve(address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
`https://toncenter.com/api/v2/detectAddress?address=${encodeURIComponent(
|
||||||
|
address
|
||||||
|
)}`,
|
||||||
|
{
|
||||||
|
headers: { "X-API-Key": API_KEY },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.result) {
|
||||||
|
|
||||||
|
const uqAddress =
|
||||||
|
data.result.non_bounceable?.b64url ||
|
||||||
|
data.result.non_bounceable?.b64 ||
|
||||||
|
address;
|
||||||
|
resolve(uqAddress);
|
||||||
|
} else {
|
||||||
|
resolve(address);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn("Failed to convert address using API:", error);
|
||||||
|
resolve(address);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in getUQAddress:", error);
|
||||||
|
resolve(address);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create wallet from private key
|
||||||
|
* @param {string} privHex - Private key in hexadecimal format
|
||||||
|
* @returns {Promise} Promise object that resolves with wallet, address, and keyPair
|
||||||
|
*/
|
||||||
|
tonBlockchainAPI.getSenderWallet = function (privHex) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!tonweb) {
|
||||||
|
reject(new Error("TonWeb not initialized"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const seed = TonWeb.utils.hexToBytes(privHex.slice(0, 64));
|
||||||
|
const keyPair = TonWeb.utils.keyPairFromSeed(seed.slice(0, 32));
|
||||||
|
|
||||||
|
// v4R2 wallet
|
||||||
|
const WalletClass = tonweb.wallet.all.v4R2;
|
||||||
|
const wallet = new WalletClass(tonweb.provider, {
|
||||||
|
publicKey: keyPair.publicKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.getAddress()
|
||||||
|
.then((address) => {
|
||||||
|
resolve({ wallet, address, keyPair });
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send TON transaction
|
||||||
|
* @param {string} privHex - Private key in hexadecimal format
|
||||||
|
* @param {string} toAddress - Recipient's TON address
|
||||||
|
* @param {string|number} amount - Amount to send in TON
|
||||||
|
* @returns {Promise} Promise object that resolves with wallet, seqno, and sender address
|
||||||
|
*/
|
||||||
|
tonBlockchainAPI.sendTonTransaction = function (privHex, toAddress, amount) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const { wallet, address, keyPair } =
|
||||||
|
await tonBlockchainAPI.getSenderWallet(privHex);
|
||||||
|
const seqno = await wallet.methods.seqno().call();
|
||||||
|
const senderAddr = address.toString(true, true, true);
|
||||||
|
toAddress=await tonBlockchainAPI.getUQAddress(toAddress);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Sending ${amount} TON from ${senderAddr} to ${toAddress}, seqno: ${seqno}`
|
||||||
|
);
|
||||||
|
await wallet.methods
|
||||||
|
.transfer({
|
||||||
|
secretKey: keyPair.secretKey,
|
||||||
|
toAddress: toAddress,
|
||||||
|
amount: TonWeb.utils.toNano(amount),
|
||||||
|
seqno: seqno || 0,
|
||||||
|
payload: null,
|
||||||
|
sendMode: 3,
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
|
||||||
|
resolve({ wallet, seqno, senderAddr });
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for transaction confirmation and get hash
|
||||||
|
* @param {Object} wallet - The TON wallet object
|
||||||
|
* @param {number} originalSeqno - The original sequence number before transaction
|
||||||
|
* @param {string} senderAddr - The sender's address
|
||||||
|
* @returns {Promise} Promise object that resolves with transaction hash and explorer URL
|
||||||
|
*/
|
||||||
|
tonBlockchainAPI.waitForTransactionConfirmation = function (
|
||||||
|
wallet,
|
||||||
|
originalSeqno,
|
||||||
|
senderAddr
|
||||||
|
) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let seqAfter = originalSeqno;
|
||||||
|
for (let i = 0; i < 30; i++) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
seqAfter = await wallet.methods.seqno().call();
|
||||||
|
if (Number(seqAfter) > Number(originalSeqno)) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seqAfter === originalSeqno) {
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
"Seqno not increased — transaction might not be confirmed yet."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait and fetch transaction hash
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
|
const txRes = await fetch(
|
||||||
|
`https://toncenter.com/api/v2/getTransactions?address=${senderAddr}&limit=5`,
|
||||||
|
{
|
||||||
|
headers: { "X-API-Key": API_KEY },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const txData = await txRes.json();
|
||||||
|
const txs = txData.result || [];
|
||||||
|
|
||||||
|
if (txs.length === 0) {
|
||||||
|
reject(new Error("No transactions found."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestTx = txs[0];
|
||||||
|
const hash = latestTx.transaction_id?.hash || "Unknown";
|
||||||
|
const urlHash = hash.replace(/\+/g, "-").replace(/\//g, "_");
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
urlHash,
|
||||||
|
explorerUrl: `https://tonviewer.com/transaction/${urlHash}`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})(
|
||||||
|
"object" === typeof module ? module.exports : (window.tonBlockchainAPI = {})
|
||||||
|
);
|
||||||
204
tonwallet/tonCrypto.js
Normal file
204
tonwallet/tonCrypto.js
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
(function (EXPORTS) {
|
||||||
|
"use strict";
|
||||||
|
const tonCrypto = EXPORTS;
|
||||||
|
|
||||||
|
const nacl = window.nacl;
|
||||||
|
const TonWeb = window.TonWeb;
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
const generateNewID = (tonCrypto.generateNewID = function () {
|
||||||
|
var key = new Bitcoin.ECKey(false);
|
||||||
|
key.setCompressed(true);
|
||||||
|
return {
|
||||||
|
floID: key.getBitcoinAddress(),
|
||||||
|
pubKey: key.getPubKeyHex(),
|
||||||
|
privKey: key.getBitcoinWalletImportFormat(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperties(tonCrypto, {
|
||||||
|
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));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
function hexToBytes(hex) {
|
||||||
|
if (hex.startsWith("0x")) hex = hex.slice(2);
|
||||||
|
return new Uint8Array(hex.match(/.{1,2}/g).map((b) => parseInt(b, 16)));
|
||||||
|
}
|
||||||
|
function bytesToHex(bytes) {
|
||||||
|
return Array.from(bytes)
|
||||||
|
.map((b) => b.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
function sha256Hex(hexString) {
|
||||||
|
return Crypto.SHA256(Crypto.util.hexToBytes(hexString));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Multi-chain (FLO, BTC, TON) ----
|
||||||
|
tonCrypto.generateMultiChain = async function (inputWif) {
|
||||||
|
const origBitjsPub = bitjs.pub;
|
||||||
|
const origBitjsPriv = bitjs.priv;
|
||||||
|
const origBitjsCompressed = bitjs.compressed;
|
||||||
|
const origCoinJsCompressed = coinjs.compressed;
|
||||||
|
|
||||||
|
bitjs.compressed = true;
|
||||||
|
coinjs.compressed = true;
|
||||||
|
|
||||||
|
const versions = {
|
||||||
|
BTC: { pub: 0x00, priv: 0x80 },
|
||||||
|
FLO: { pub: 0x23, priv: 0xa3 },
|
||||||
|
};
|
||||||
|
|
||||||
|
let privKeyHex;
|
||||||
|
let compressed = true;
|
||||||
|
|
||||||
|
if (typeof inputWif === "string" && inputWif.length > 0) {
|
||||||
|
const hexOnly = /^[0-9a-fA-F]+$/.test(inputWif.trim());
|
||||||
|
|
||||||
|
if (hexOnly && (inputWif.length === 64 || inputWif.length === 128)) {
|
||||||
|
// Raw hex private key input
|
||||||
|
if (inputWif.length === 128) {
|
||||||
|
privKeyHex = inputWif.substring(0, 64);
|
||||||
|
} else {
|
||||||
|
privKeyHex = inputWif;
|
||||||
|
}
|
||||||
|
compressed = true;
|
||||||
|
} else {
|
||||||
|
// WIF format input
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Invalid WIF format, treating as seed:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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 = {
|
||||||
|
BTC: { address: "", privateKey: "" },
|
||||||
|
FLO: { address: "", privateKey: "" },
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
bitjs.pub = origBitjsPub;
|
||||||
|
bitjs.priv = origBitjsPriv;
|
||||||
|
bitjs.compressed = origBitjsCompressed;
|
||||||
|
coinjs.compressed = origCoinJsCompressed;
|
||||||
|
// For TON
|
||||||
|
let tonSeed;
|
||||||
|
if (privKeyHex.length === 64) {
|
||||||
|
tonSeed = Crypto.util.hexToBytes(privKeyHex);
|
||||||
|
} else {
|
||||||
|
const padded = privKeyHex.padEnd(64, "0").substring(0, 64);
|
||||||
|
tonSeed = Crypto.util.hexToBytes(padded);
|
||||||
|
}
|
||||||
|
const kp = nacl.sign.keyPair.fromSeed(new Uint8Array(tonSeed));
|
||||||
|
|
||||||
|
let tonAddr;
|
||||||
|
try {
|
||||||
|
let WalletClass = null;
|
||||||
|
let tonweb = null;
|
||||||
|
|
||||||
|
tonweb = new TonWeb();
|
||||||
|
|
||||||
|
if (TonWeb.Wallets.all.v4R2) {
|
||||||
|
WalletClass = TonWeb.Wallets.all.v4R2;
|
||||||
|
console.log("Using TonWeb.Wallets.all.v4R2");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WalletClass && tonweb) {
|
||||||
|
const wallet = new WalletClass(tonweb.provider, {
|
||||||
|
publicKey: kp.publicKey,
|
||||||
|
});
|
||||||
|
const realAddr = await wallet.getAddress();
|
||||||
|
tonAddr = realAddr.toString(true, true, false);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("TonWeb error, using fallback:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.TON = {
|
||||||
|
address: tonAddr,
|
||||||
|
privateKey: bytesToHex(kp.secretKey),
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Recover ----
|
||||||
|
tonCrypto.recoverFromInput = async function (input) {
|
||||||
|
const trimmed = input.trim();
|
||||||
|
return await tonCrypto.generateMultiChain(trimmed);
|
||||||
|
};
|
||||||
|
})("object" === typeof module ? module.exports : (window.tonCrypto = {}));
|
||||||
106
tonwallet/tonSearchDB.js
Normal file
106
tonwallet/tonSearchDB.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
class SearchedAddressDB {
|
||||||
|
constructor() {
|
||||||
|
this.dbName = "TonWalletDB";
|
||||||
|
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(
|
||||||
|
tonAddress,
|
||||||
|
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);
|
||||||
|
const getRequest = store.get(tonAddress);
|
||||||
|
getRequest.onsuccess = () => {
|
||||||
|
const existingRecord = getRequest.result;
|
||||||
|
let finalSourceInfo = sourceInfo;
|
||||||
|
if (existingRecord && existingRecord.sourceInfo && !sourceInfo) {
|
||||||
|
finalSourceInfo = existingRecord.sourceInfo;
|
||||||
|
} else if (
|
||||||
|
existingRecord &&
|
||||||
|
existingRecord.sourceInfo &&
|
||||||
|
sourceInfo === null
|
||||||
|
) {
|
||||||
|
finalSourceInfo = existingRecord.sourceInfo;
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
address: tonAddress,
|
||||||
|
balance,
|
||||||
|
timestamp,
|
||||||
|
formattedBalance: `${balance} TON`,
|
||||||
|
sourceInfo: finalSourceInfo,
|
||||||
|
};
|
||||||
|
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(tonAddress) {
|
||||||
|
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(tonAddress);
|
||||||
|
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