Compare commits
6 Commits
fix/59-tra
...
8379ab4685
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8379ab4685 | ||
| e09904147b | |||
|
|
b02a1d3a55 | ||
| 9788db95f2 | |||
|
|
9981be6986 | ||
| bbf5945ff1 |
@@ -422,6 +422,11 @@
|
||||
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
|
||||
placeholder="Address (0x...) or ENS name"
|
||||
/>
|
||||
<div
|
||||
id="send-to-error"
|
||||
class="text-xs"
|
||||
style="min-height: 1.25rem; color: #cc0000"
|
||||
></div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex justify-between mb-1">
|
||||
@@ -531,6 +536,7 @@
|
||||
<!-- ============ TX SUCCESS ============ -->
|
||||
<div id="view-success-tx" class="view hidden">
|
||||
<h2 class="font-bold mb-2">Transaction Confirmed</h2>
|
||||
<div id="success-tx-decoded" class="mb-3 hidden text-xs"></div>
|
||||
<div class="mb-3">
|
||||
<div class="text-xs text-muted mb-1">Amount</div>
|
||||
<div id="success-tx-summary" class="font-bold"></div>
|
||||
|
||||
@@ -184,6 +184,15 @@ function showTxApproval(details) {
|
||||
}
|
||||
}
|
||||
|
||||
// Carry decoded calldata info through to success/error views
|
||||
if (decoded) {
|
||||
pendingTxDetails.decoded = {
|
||||
name: decoded.name,
|
||||
description: decoded.description,
|
||||
details: decoded.details,
|
||||
};
|
||||
}
|
||||
|
||||
$("approve-tx-hostname").textContent = details.hostname;
|
||||
$("approve-tx-from").innerHTML = approvalAddressHtml(state.activeAddress);
|
||||
|
||||
|
||||
@@ -11,6 +11,107 @@ const { state, currentAddress } = require("../../shared/state");
|
||||
let ctx;
|
||||
const { getProvider } = require("../../shared/balances");
|
||||
const { KNOWN_SYMBOLS, TOKEN_BY_ADDRESS } = require("../../shared/tokenList");
|
||||
const { getAddress } = require("ethers");
|
||||
|
||||
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
||||
|
||||
/**
|
||||
* Validate a destination address string.
|
||||
* Returns { valid: true } or { valid: false, error: "..." }.
|
||||
*/
|
||||
function validateToAddress(value) {
|
||||
const v = value.trim();
|
||||
if (!v) return { valid: false, error: "" };
|
||||
|
||||
// ENS names: contains a dot and doesn't start with 0x
|
||||
if (v.includes(".") && !v.startsWith("0x")) {
|
||||
// Basic ENS format check: at least one label before and after dot
|
||||
if (/^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$/.test(v)) {
|
||||
return { valid: true };
|
||||
}
|
||||
return {
|
||||
valid: false,
|
||||
error: "Please enter a valid ENS name.",
|
||||
};
|
||||
}
|
||||
|
||||
// Must look like an Ethereum address
|
||||
if (!/^0x[0-9a-fA-F]{40}$/.test(v)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: "Please enter a valid Ethereum address.",
|
||||
};
|
||||
}
|
||||
|
||||
// Reject zero address
|
||||
if (v.toLowerCase() === ZERO_ADDRESS) {
|
||||
return {
|
||||
valid: false,
|
||||
error: "Sending to the zero address is not allowed.",
|
||||
};
|
||||
}
|
||||
|
||||
// EIP-55 checksum validation: all-lowercase is ok, otherwise must match checksum
|
||||
if (v !== v.toLowerCase()) {
|
||||
try {
|
||||
const checksummed = getAddress(v);
|
||||
if (checksummed !== v) {
|
||||
return {
|
||||
valid: false,
|
||||
error: "Address checksum is invalid. Please double-check the address.",
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
valid: false,
|
||||
error: "Address checksum is invalid. Please double-check the address.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Warn if sending to own address
|
||||
const addr = currentAddress();
|
||||
if (addr && v.toLowerCase() === addr.address.toLowerCase()) {
|
||||
// Allow but will warn — we return valid with a warning
|
||||
return {
|
||||
valid: true,
|
||||
warning: "This is your own address. Are you sure?",
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
function updateToValidation() {
|
||||
const input = $("send-to");
|
||||
const errorEl = $("send-to-error");
|
||||
const btn = $("btn-send-review");
|
||||
const value = input.value.trim();
|
||||
|
||||
if (!value) {
|
||||
errorEl.textContent = "";
|
||||
btn.disabled = true;
|
||||
btn.classList.add("opacity-50");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = validateToAddress(value);
|
||||
if (!result.valid) {
|
||||
errorEl.textContent = result.error;
|
||||
errorEl.style.color = "#cc0000";
|
||||
btn.disabled = true;
|
||||
btn.classList.add("opacity-50");
|
||||
} else if (result.warning) {
|
||||
errorEl.textContent = result.warning;
|
||||
errorEl.style.color = "#b8860b";
|
||||
btn.disabled = false;
|
||||
btn.classList.remove("opacity-50");
|
||||
} else {
|
||||
errorEl.textContent = "";
|
||||
btn.disabled = false;
|
||||
btn.classList.remove("opacity-50");
|
||||
}
|
||||
}
|
||||
|
||||
const EXT_ICON =
|
||||
`<span style="display:inline-block;width:10px;height:10px;margin-left:4px;vertical-align:middle">` +
|
||||
@@ -92,6 +193,13 @@ function init(_ctx) {
|
||||
ctx = _ctx;
|
||||
$("send-token").addEventListener("change", updateSendBalance);
|
||||
|
||||
// Initial state: disable review button until address is entered
|
||||
$("btn-send-review").disabled = true;
|
||||
$("btn-send-review").classList.add("opacity-50");
|
||||
|
||||
// Validate address on input
|
||||
$("send-to").addEventListener("input", updateToValidation);
|
||||
|
||||
$("btn-send-review").addEventListener("click", async () => {
|
||||
const to = $("send-to").value.trim();
|
||||
const amount = $("send-amount").value.trim();
|
||||
@@ -99,6 +207,15 @@ function init(_ctx) {
|
||||
showFlash("Please enter a recipient address.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-validate before proceeding
|
||||
const validation = validateToAddress(to);
|
||||
if (!validation.valid) {
|
||||
showFlash(
|
||||
validation.error || "Please enter a valid Ethereum address.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!amount || isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
|
||||
showFlash("Please enter a valid amount.");
|
||||
return;
|
||||
|
||||
@@ -143,11 +143,10 @@ function render() {
|
||||
typeEl.textContent = tx.directionLabel;
|
||||
typeSection.classList.remove("hidden");
|
||||
}
|
||||
if (headingEl) headingEl.textContent = tx.directionLabel;
|
||||
} else {
|
||||
if (typeSection) typeSection.classList.add("hidden");
|
||||
if (headingEl) headingEl.textContent = "Transaction";
|
||||
}
|
||||
if (headingEl) headingEl.textContent = "Transaction";
|
||||
|
||||
// Hide calldata and raw data sections; re-fetch if this is a contract call
|
||||
const calldataSection = $("tx-detail-calldata-section");
|
||||
|
||||
@@ -8,6 +8,7 @@ const {
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
} = require("./helpers");
|
||||
const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList");
|
||||
const { state, saveState } = require("../../shared/state");
|
||||
const { getProvider } = require("../../shared/balances");
|
||||
const { log } = require("../../shared/log");
|
||||
@@ -121,11 +122,51 @@ function showSuccess(txInfo, txHash, blockNumber) {
|
||||
to: txInfo.to,
|
||||
hash: txHash,
|
||||
blockNumber: blockNumber,
|
||||
decoded: txInfo.decoded || null,
|
||||
};
|
||||
renderSuccess();
|
||||
ctx.doRefreshAndRender();
|
||||
}
|
||||
|
||||
function tokenLabel(address) {
|
||||
const t = TOKEN_BY_ADDRESS.get(address.toLowerCase());
|
||||
return t ? t.symbol : null;
|
||||
}
|
||||
|
||||
function etherscanTokenLink(address) {
|
||||
return `https://etherscan.io/token/${address}`;
|
||||
}
|
||||
|
||||
function decodedDetailsHtml(decoded) {
|
||||
if (!decoded || !decoded.details) return "";
|
||||
let html = "";
|
||||
if (decoded.name) {
|
||||
html += `<div class="mb-2"><div class="text-xs text-muted mb-1">Action</div>`;
|
||||
html += `<div class="font-bold">${escapeHtml(decoded.name)}</div></div>`;
|
||||
}
|
||||
if (decoded.description) {
|
||||
html += `<div class="mb-2"><div class="text-xs text-muted mb-1">Description</div>`;
|
||||
html += `<div>${escapeHtml(decoded.description)}</div></div>`;
|
||||
}
|
||||
for (const d of decoded.details) {
|
||||
html += `<div class="mb-2">`;
|
||||
html += `<div class="text-xs text-muted mb-1">${escapeHtml(d.label)}</div>`;
|
||||
if (d.address) {
|
||||
if (d.isToken) {
|
||||
const sym = tokenLabel(d.address) || "Unknown token";
|
||||
html += `<div class="font-bold">${escapeHtml(sym)}</div>`;
|
||||
html += toAddressHtml(d.address);
|
||||
} else {
|
||||
html += toAddressHtml(d.address);
|
||||
}
|
||||
} else {
|
||||
html += `<div class="font-bold">${escapeHtml(d.value)}</div>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderSuccess() {
|
||||
const d = state.viewData;
|
||||
if (!d || !d.hash) return;
|
||||
@@ -133,6 +174,16 @@ function renderSuccess() {
|
||||
$("success-tx-to").innerHTML = toAddressHtml(d.to);
|
||||
$("success-tx-block").textContent = String(d.blockNumber);
|
||||
$("success-tx-hash").innerHTML = txHashHtml(d.hash);
|
||||
|
||||
// Show decoded calldata details if present
|
||||
const decodedEl = $("success-tx-decoded");
|
||||
if (decodedEl && d.decoded) {
|
||||
decodedEl.innerHTML = decodedDetailsHtml(d.decoded);
|
||||
decodedEl.classList.remove("hidden");
|
||||
} else if (decodedEl) {
|
||||
decodedEl.classList.add("hidden");
|
||||
}
|
||||
|
||||
attachCopyHandlers("view-success-tx");
|
||||
showView("success-tx");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user