526 lines
16 KiB
HTML
526 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>TON Wallet</title>
|
|
<style>
|
|
body {
|
|
font-family: monospace;
|
|
padding: 20px;
|
|
background: #f7f8fa;
|
|
}
|
|
textarea {
|
|
width: 100%;
|
|
height: 80px;
|
|
margin-top: 10px;
|
|
}
|
|
button {
|
|
margin: 5px;
|
|
padding: 8px 12px;
|
|
font-size: 14px;
|
|
background-color: #007bff;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
pre {
|
|
background: #f4f4f4;
|
|
padding: 10px;
|
|
margin-top: 10px;
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
}
|
|
#inputKey,
|
|
#addressInput {
|
|
width: 100%;
|
|
padding: 8px;
|
|
font-size: 14px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
margin-right: 8px;
|
|
}
|
|
#walletSection,
|
|
#explorerSection,
|
|
#sendSection,
|
|
#balances,
|
|
#transactions {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
margin-top: 20px;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
#sendPrivKey,
|
|
#sendToAddress,
|
|
#sendAmount {
|
|
width: 100%;
|
|
padding: 8px;
|
|
font-size: 14px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
margin: 5px 0 10px 0;
|
|
}
|
|
#senderBalanceDisplay {
|
|
background: #e8f5e8;
|
|
border: 1px solid #28a745;
|
|
border-radius: 6px;
|
|
padding: 12px;
|
|
margin: 10px 0 15px 0;
|
|
font-family: monospace;
|
|
}
|
|
#senderBalanceDisplay p {
|
|
margin: 5px 0;
|
|
font-size: 14px;
|
|
}
|
|
#senderBalance,
|
|
#senderAddress {
|
|
color: #155724;
|
|
font-weight: bold;
|
|
}
|
|
#sendLog {
|
|
background: #f4f4f4;
|
|
border: 1px solid #ddd;
|
|
padding: 10px;
|
|
border-radius: 6px;
|
|
margin-top: 15px;
|
|
white-space: pre-wrap;
|
|
font-family: monospace;
|
|
min-height: 100px;
|
|
}
|
|
.tx {
|
|
border-bottom: 1px solid #eee;
|
|
padding: 10px 0;
|
|
}
|
|
.tx:last-child {
|
|
border-bottom: none;
|
|
}
|
|
.tx small {
|
|
color: #555;
|
|
display: block;
|
|
margin-top: 3px;
|
|
}
|
|
#pagination {
|
|
margin-top: 15px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>TON Wallet Test</h1>
|
|
|
|
<!-- Generate and Recover -->
|
|
<div id="walletSection">
|
|
<h2>Generate and Recover TON Address</h2>
|
|
<button id="generateBtn">Generate</button>
|
|
<button id="recoverBtn">Recover</button>
|
|
<br /><br />
|
|
|
|
<label for="inputKey">Enter TON/FLO/BTC private key:</label>
|
|
<input type="text" id="inputKey" />
|
|
|
|
<pre id="output"></pre>
|
|
</div>
|
|
|
|
<!-- Balance and History -->
|
|
<div id="explorerSection">
|
|
<h2>Transactions</h2>
|
|
<input
|
|
id="addressInput"
|
|
placeholder="Enter TON Address (EQ... or UQ...)"
|
|
/>
|
|
<button id="loadWallet">Search</button>
|
|
|
|
<div id="balances">
|
|
<h3>Balances</h3>
|
|
<p><b>TON:</b> <span id="tonBalance">-</span></p>
|
|
<p><b>USDT:</b> <span id="usdtBalance">-</span></p>
|
|
</div>
|
|
|
|
<div id="transactions" style="display: none">
|
|
<h3>Transaction History</h3>
|
|
<p id="status">Status: idle</p>
|
|
<div id="txList"></div>
|
|
<div id="pagination">
|
|
<button id="prevBtn" disabled>⬅ Previous</button>
|
|
<button id="nextBtn" disabled>Next ➡</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Send TON Section -->
|
|
<div id="sendSection">
|
|
<h2>Send TON (Testnet)</h2>
|
|
|
|
<div>
|
|
<label for="sendPrivKey"><b>Private Key (Hex):</b></label
|
|
><br />
|
|
<input id="sendPrivKey" placeholder="Enter private key hex " />
|
|
</div>
|
|
|
|
<!-- Balance Display -->
|
|
<div id="senderBalanceDisplay" style="display: none">
|
|
<p><b>Balance:</b> <span id="senderBalance">-</span> TON</p>
|
|
<p><b> Address:</b> <span id="senderAddress">-</span></p>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="sendToAddress"><b>Recipient Address:</b></label
|
|
><br />
|
|
<input id="sendToAddress" placeholder="EQ... or UQ..." />
|
|
</div>
|
|
|
|
<div>
|
|
<label for="sendAmount"><b>Amount (TON):</b></label
|
|
><br />
|
|
<input id="sendAmount" type="number" step="0.001" placeholder="0.1" />
|
|
</div>
|
|
|
|
<button id="sendTON">Send TON</button>
|
|
|
|
<div id="sendLog"></div>
|
|
</div>
|
|
|
|
<script src="lib.toncoin.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/tweetnacl/1.0.3/nacl.min.js"></script>
|
|
<script src="https://unpkg.com/tonweb/dist/tonweb.js"></script>
|
|
<script src="tonCrypto.js"></script>
|
|
<script src="tonBlockchainAPI.js"></script>
|
|
|
|
<script>
|
|
const output = document.getElementById("output");
|
|
|
|
document.getElementById("generateBtn").onclick = async () => {
|
|
try {
|
|
const res = await tonCrypto.generateMultiChain();
|
|
output.textContent = JSON.stringify(res, null, 2);
|
|
} catch (e) {
|
|
output.textContent = "Error: " + e.message;
|
|
}
|
|
};
|
|
|
|
document.getElementById("recoverBtn").onclick = async () => {
|
|
const inp = document.getElementById("inputKey").value.trim();
|
|
if (!inp) {
|
|
output.textContent = "Please enter a private key (TON/BTC/FLO)";
|
|
return;
|
|
}
|
|
try {
|
|
const res = await tonCrypto.recoverFromInput(inp);
|
|
output.textContent = JSON.stringify(res, null, 2);
|
|
} catch (e) {
|
|
output.textContent = "Error: " + e.message;
|
|
}
|
|
};
|
|
|
|
let currentAddress = "";
|
|
|
|
let transactions = [];
|
|
let beforeLt = null;
|
|
let allFetched = false;
|
|
let currentPage = 0;
|
|
const pageSize = 10;
|
|
|
|
document
|
|
.getElementById("loadWallet")
|
|
.addEventListener("click", async () => {
|
|
const addr = document.getElementById("addressInput").value.trim();
|
|
if (!addr) return alert("Enter a valid TON address");
|
|
await loadWallet(addr);
|
|
});
|
|
|
|
async function loadWallet(address) {
|
|
document.getElementById("tonBalance").innerText = "Loading...";
|
|
document.getElementById("usdtBalance").innerText = "Loading...";
|
|
|
|
currentAddress = address;
|
|
|
|
beforeLt = null;
|
|
allFetched = false;
|
|
transactions = [];
|
|
currentPage = 0;
|
|
document.getElementById("transactions").style.display = "block";
|
|
document.getElementById("txList").innerHTML =
|
|
"<p>Loading transactions...</p>";
|
|
|
|
await Promise.all([
|
|
getTonBalance(address),
|
|
getUsdtBalance(address),
|
|
fetchTransactions(address),
|
|
]);
|
|
|
|
await renderPage();
|
|
}
|
|
|
|
// Get TON balance
|
|
async function getTonBalance(address) {
|
|
try {
|
|
const balance = await tonBlockchainAPI.getTonBalance(address);
|
|
document.getElementById("tonBalance").innerText = balance.toFixed(6);
|
|
} catch (err) {
|
|
console.error("TON balance error:", err);
|
|
document.getElementById("tonBalance").innerText = "0.000000";
|
|
}
|
|
}
|
|
|
|
// Get USDT balance
|
|
async function getUsdtBalance(ownerAddress) {
|
|
try {
|
|
const balance = await tonBlockchainAPI.getUsdtBalance(ownerAddress);
|
|
document.getElementById("usdtBalance").innerText = balance.toFixed(6);
|
|
} catch (err) {
|
|
console.error("USDT balance error:", err);
|
|
document.getElementById("usdtBalance").innerText = "0.000000";
|
|
}
|
|
}
|
|
|
|
async function convertTob64(rawAddr) {
|
|
return await tonBlockchainAPI.convertTob64(rawAddr);
|
|
}
|
|
|
|
// Fetch transaction history
|
|
async function fetchTransactions(address, append = false) {
|
|
if (allFetched) return;
|
|
|
|
document.getElementById("status").textContent =
|
|
"Fetching transactions...";
|
|
|
|
try {
|
|
const result = await tonBlockchainAPI.fetchTransactions(address, {
|
|
limit: 100,
|
|
beforeLt: beforeLt,
|
|
});
|
|
|
|
const newTxs = result.transactions || [];
|
|
if (newTxs.length === 0) {
|
|
allFetched = true;
|
|
document.getElementById("status").textContent =
|
|
"✅ All transactions fetched.";
|
|
return;
|
|
}
|
|
|
|
if (append) transactions = transactions.concat(newTxs);
|
|
else transactions = newTxs;
|
|
|
|
beforeLt = result.nextBeforeLt;
|
|
allFetched = !result.hasMore;
|
|
|
|
document.getElementById(
|
|
"status"
|
|
).textContent = `Fetched total: ${transactions.length}`;
|
|
} catch (error) {
|
|
console.error("Error fetching transactions:", error);
|
|
document.getElementById("status").textContent =
|
|
"❌ Error fetching transactions";
|
|
}
|
|
}
|
|
|
|
async function renderPage() {
|
|
const start = currentPage * pageSize;
|
|
const end = start + pageSize;
|
|
const pageTxs = transactions.slice(start, end);
|
|
const txList = document.getElementById("txList");
|
|
txList.innerHTML = "";
|
|
|
|
if (pageTxs.length === 0) {
|
|
txList.innerHTML = "<p>No transactions found.</p>";
|
|
document.getElementById("nextBtn").disabled = true;
|
|
document.getElementById("prevBtn").disabled = currentPage === 0;
|
|
return;
|
|
}
|
|
|
|
for (const tx of pageTxs) {
|
|
const time = new Date(tx.utime * 1000).toLocaleString();
|
|
const hash = tx.hash;
|
|
const lt = tx.lt;
|
|
const gas = tx.compute_phase?.gas_fees / 1e9 || 0;
|
|
const success = tx.success ? "✅ Success" : "❌ Failed";
|
|
|
|
// --- IN MESSAGE ---
|
|
if (tx.in_msg && tx.in_msg.source) {
|
|
const inValue = (parseFloat(tx.in_msg.value || 0) / 1e9).toFixed(6);
|
|
const fromRaw = tx.in_msg.source.address || "N/A";
|
|
const toRaw = tx.in_msg.destination?.address || "N/A";
|
|
const [from, to] = await Promise.all([
|
|
convertTob64(fromRaw),
|
|
convertTob64(toRaw),
|
|
]);
|
|
|
|
const div = document.createElement("div");
|
|
div.className = "tx";
|
|
div.innerHTML = `
|
|
<b>${success}</b> — <b>${inValue} TON (IN)</b><br>
|
|
<small>🕒 ${time}</small>
|
|
<small>🔗 Hash: ${hash}</small>
|
|
<small>🔢 LT: ${lt}</small>
|
|
<small>📤 From: ${from}</small>
|
|
<small>📥 To: ${to}</small>
|
|
<small>⚙️ Gas Used: ${gas}</small>
|
|
<small>💬 Operation: ${tx.in_msg.decoded_op_name || "—"}</small>
|
|
`;
|
|
txList.appendChild(div);
|
|
}
|
|
|
|
// --- OUT MESSAGES ---
|
|
if (tx.out_msgs && tx.out_msgs.length > 0) {
|
|
for (const out of tx.out_msgs) {
|
|
const outValue = (parseFloat(out.value || 0) / 1e9).toFixed(6);
|
|
const fromRaw = out.source?.address || "N/A";
|
|
const toRaw = out.destination?.address || "N/A";
|
|
const [from, to] = await Promise.all([
|
|
convertTob64(fromRaw),
|
|
convertTob64(toRaw),
|
|
]);
|
|
|
|
const div = document.createElement("div");
|
|
div.className = "tx";
|
|
div.innerHTML = `
|
|
<b>${success}</b> — <b>${outValue} TON (OUT)</b><br>
|
|
<small>🕒 ${time}</small>
|
|
<small>🔗 Hash: ${hash}</small>
|
|
<small>🔢 LT: ${lt}</small>
|
|
<small>📤 From: ${from}</small>
|
|
<small>📥 To: ${to}</small>
|
|
<small>⚙️ Gas Used: ${gas}</small>
|
|
<small>💬 Operation: ${out.decoded_op_name || "—"}</small>
|
|
`;
|
|
txList.appendChild(div);
|
|
}
|
|
}
|
|
}
|
|
|
|
document.getElementById("prevBtn").disabled = currentPage === 0;
|
|
document.getElementById("nextBtn").disabled =
|
|
end >= transactions.length && allFetched;
|
|
}
|
|
|
|
document.getElementById("nextBtn").addEventListener("click", async () => {
|
|
const nextStart = (currentPage + 1) * pageSize;
|
|
if (nextStart >= transactions.length && !allFetched) {
|
|
await fetchTransactions(currentAddress, true);
|
|
}
|
|
if ((currentPage + 1) * pageSize < transactions.length) {
|
|
currentPage++;
|
|
await renderPage();
|
|
}
|
|
});
|
|
|
|
document.getElementById("prevBtn").addEventListener("click", async () => {
|
|
if (currentPage > 0) {
|
|
currentPage--;
|
|
await renderPage();
|
|
}
|
|
});
|
|
|
|
// Send TON functionality
|
|
|
|
const sendLog = (msg, isHtml = false) => {
|
|
console.log(msg);
|
|
const logDiv = document.getElementById("sendLog");
|
|
|
|
if (isHtml) {
|
|
const newLine = document.createElement("div");
|
|
newLine.innerHTML = msg;
|
|
logDiv.appendChild(newLine);
|
|
} else {
|
|
logDiv.textContent += msg + "\n";
|
|
}
|
|
};
|
|
|
|
const clearSendLog = () => {
|
|
document.getElementById("sendLog").textContent = "";
|
|
};
|
|
|
|
async function getSenderWallet(privHex) {
|
|
return await tonBlockchainAPI.getSenderWallet(privHex);
|
|
}
|
|
|
|
// Auto-check balance when private key is entered
|
|
async function updateSenderBalance() {
|
|
const privHex = document.getElementById("sendPrivKey").value.trim();
|
|
const balanceDisplay = document.getElementById("senderBalanceDisplay");
|
|
|
|
if (!privHex || (privHex.length !== 64 && privHex.length !== 128)) {
|
|
balanceDisplay.style.display = "none";
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const { address } = await getSenderWallet(privHex);
|
|
const addr = address.toString(true, true, true);
|
|
|
|
const balance = await tonBlockchainAPI.getTestnetBalance(addr);
|
|
|
|
document.getElementById("senderBalance").textContent =
|
|
balance.toFixed(6);
|
|
document.getElementById("senderAddress").textContent = addr;
|
|
balanceDisplay.style.display = "block";
|
|
} catch (err) {
|
|
balanceDisplay.style.display = "none";
|
|
console.error("Balance check error:", err);
|
|
}
|
|
}
|
|
|
|
document
|
|
.getElementById("sendPrivKey")
|
|
.addEventListener("input", updateSenderBalance);
|
|
|
|
document.getElementById("sendTON").addEventListener("click", async () => {
|
|
const privHex = document.getElementById("sendPrivKey").value.trim();
|
|
const toAddress = document.getElementById("sendToAddress").value.trim();
|
|
const amount = document.getElementById("sendAmount").value.trim();
|
|
|
|
clearSendLog();
|
|
|
|
if (!privHex || !toAddress || !amount) {
|
|
sendLog("⚠️ Please fill all fields first!");
|
|
return;
|
|
}
|
|
|
|
sendLog(" Processing transaction...");
|
|
|
|
try {
|
|
sendLog(` Sending ${amount} TON...`);
|
|
|
|
const { wallet, seqno, senderAddr } =
|
|
await tonBlockchainAPI.sendTonTransaction(
|
|
privHex,
|
|
toAddress,
|
|
amount
|
|
);
|
|
|
|
sendLog(" Transaction Successful!");
|
|
sendLog(` Sent: ${amount} TON`);
|
|
sendLog(`To: ${toAddress}`);
|
|
|
|
sendLog("✅ Transaction sent. Waiting for confirmation...");
|
|
|
|
const result = await tonBlockchainAPI.waitForTransactionConfirmation(
|
|
wallet,
|
|
seqno,
|
|
senderAddr
|
|
);
|
|
|
|
sendLog("✅ Transaction confirmed!");
|
|
sendLog(`📦 Transaction Hash: ${result.urlHash}`);
|
|
sendLog(
|
|
`🔗 View on explorer: <a href="${result.explorerUrl}" target="_blank" style="color: #007bff; text-decoration: underline;">Click here to view transaction</a>`,
|
|
true
|
|
);
|
|
|
|
setTimeout(updateSenderBalance, 2000);
|
|
document.getElementById("sendAmount").value = "";
|
|
} catch (err) {
|
|
sendLog(`Send error: ${err.message || err}`);
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|