diff --git a/frontend/public/explorer-spa.js b/frontend/public/explorer-spa.js index 8e85fa6..53bb6b7 100644 --- a/frontend/public/explorer-spa.js +++ b/frontend/public/explorer-spa.js @@ -366,7 +366,7 @@ if (list.length === 0) { container.innerHTML = filterBar + '

No addresses in watchlist. Open an address and click "Add to watchlist".

'; return; } if (filtered.length === 0) { container.innerHTML = filterBar + '

No watchlist entries match the current filter.

'; return; } var html = filterBar + ''; - filtered.forEach(function(addr){ var label = getAddressLabel(addr) || ''; html += ''; }); + filtered.forEach(function(addr){ var label = getAddressLabel(addr) || ''; html += ''; }); html += '
AddressLabel
' + escapeHtml(shortenHash(addr)) + '' + escapeHtml(label) + '
' + explorerAddressLink(addr, escapeHtml(shortenHash(addr)), 'color: inherit; text-decoration: none;') + '' + escapeHtml(label) + '
'; container.innerHTML = html; }; @@ -3743,7 +3743,7 @@ html += '' + escapeHtml(row.category) + ''; html += '' + escapeHtml(row.poolPair) + ''; html += '' + escapeHtml(row.poolType) + ''; - html += '' + (safeAddress(addr) ? '' + escapeHtml(shortenHash(addr)) + '' : '') + ''; + html += '' + (safeAddress(addr) ? explorerAddressLink(addr, escapeHtml(shortenHash(addr)), 'color: inherit; text-decoration: none;') : '') + ''; html += '' + escapeHtml(row.status) + ''; html += '' + escapeHtml(row.notes) + ''; html += ''; @@ -4150,19 +4150,19 @@
CCIPWETH9Bridge
- ${WETH9_BRIDGE_138} + ${explorerAddressLink(WETH9_BRIDGE_138, escapeHtml(WETH9_BRIDGE_138), 'color: inherit; text-decoration: none; font-size: 0.9rem;')}
- Token: WETH9 + Token: ${explorerAddressLink('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 'WETH9', 'color: inherit; text-decoration: none;')}
CCIPWETH10Bridge
- ${WETH10_BRIDGE_138} + ${explorerAddressLink(WETH10_BRIDGE_138, escapeHtml(WETH10_BRIDGE_138), 'color: inherit; text-decoration: none; font-size: 0.9rem;')}
- Token: WETH10 + Token: ${explorerAddressLink('0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f', 'WETH10', 'color: inherit; text-decoration: none;')}
@@ -4315,8 +4315,8 @@

CCIP Infrastructure:

@@ -4336,6 +4336,24 @@ if (/^0x0{40}$/i.test(s)) return null; return s; } + function escapeJsSingleQuoted(value) { + return String(value == null ? '' : value).replace(/\\/g, '\\\\').replace(/'/g, "\\'"); + } + function explorerAddressLink(address, content, style) { + var safe = safeAddress(address); + if (!safe) return content || 'N/A'; + return '' + (content || escapeHtml(shortenHash(safe))) + ''; + } + function explorerTransactionLink(txHash, content, style) { + var safe = safeTxHash(txHash); + if (!safe) return content || 'N/A'; + return '' + (content || escapeHtml(shortenHash(safe))) + ''; + } + function explorerBlockLink(blockNumber, content, style) { + var safe = safeBlockNumber(blockNumber); + if (!safe) return content || 'N/A'; + return '' + (content || escapeHtml(String(safe))) + ''; + } async function renderBlockDetail(blockNumber) { const bn = safeBlockNumber(blockNumber); if (!bn) { showToast('Invalid block number', 'error'); return; } @@ -4396,7 +4414,7 @@
Parent Hash
-
${escapeHtml(b.parent_hash || '')}
+
${explorerBlockLink(String(parseInt(b.number) - 1), escapeHtml(b.parent_hash || ''), 'color: inherit; text-decoration: none;')}
Timestamp
@@ -4404,7 +4422,7 @@
Miner
-
${formatAddressWithLabel(b.miner || '') || 'N/A'}
+
${explorerAddressLink(b.miner || '', formatAddressWithLabel(b.miner || '') || 'N/A', 'color: inherit; text-decoration: none;')}
Transaction Count
@@ -4501,7 +4519,7 @@ const revertReason = t.revert_reason || (rawTx && (rawTx.revert_reason || rawTx.error || rawTx.result)); const inputHex = (t.input && t.input !== '0x') ? t.input : null; const decodedInput = t.decoded_input || (rawTx && rawTx.decoded_input); - const toCellContent = t.to ? formatAddressWithLabel(t.to) + ' ' : 'N/A'; + const toCellContent = t.to ? explorerAddressLink(t.to, formatAddressWithLabel(t.to), 'color: inherit; text-decoration: none;') + ' ' : 'N/A'; let mainHtml = `
@@ -4533,7 +4551,7 @@
Block Number
-
${escapeHtml(String(t.block_number || 'N/A'))}
+
${explorerBlockLink(String(t.block_number || ''), escapeHtml(String(t.block_number || 'N/A')), 'color: inherit; text-decoration: none;')}
Block Hash
@@ -4541,11 +4559,11 @@
From
-
${formatAddressWithLabel(t.from || '')}
+
${explorerAddressLink(t.from || '', formatAddressWithLabel(t.from || ''), 'color: inherit; text-decoration: none;')}
To
-
${toCellContent}
+
${toCellContent}
Value
@@ -4569,7 +4587,7 @@ ${t.tx_burnt_fee && parseInt(t.tx_burnt_fee) > 0 ? `
Burnt Fee
${burntFeeEth} ETH
` : ''}
Nonce
${t.nonce || 'N/A'}
Timestamp
${timestamp}
- ${t.contract_address ? `
Contract Address
${escapeHtml(t.contract_address)}
` : ''} + ${t.contract_address ? `
Contract Address
${explorerAddressLink(t.contract_address, escapeHtml(t.contract_address), 'color: inherit; text-decoration: none;')}
` : ''} `; if (revertReason && t.status !== 1) { @@ -4650,7 +4668,7 @@ const to = it.to?.hash || it.to || 'N/A'; const val = it.value ? formatEther(it.value) : '0'; const type = it.type || it.call_type || 'call'; - tbl += '' + escapeHtml(type) + '' + escapeHtml(shortenHash(from)) + '' + escapeHtml(shortenHash(to)) + '' + escapeHtml(val) + ' ETH'; + tbl += '' + escapeHtml(type) + '' + explorerAddressLink(from, escapeHtml(shortenHash(from)), 'color: inherit; text-decoration: none;') + '' + explorerAddressLink(to, escapeHtml(shortenHash(to)), 'color: inherit; text-decoration: none;') + '' + escapeHtml(val) + ' ETH'; }); if (filteredInternals.length === 0) { tbl += 'No internal transactions match the current filter.'; @@ -4682,7 +4700,7 @@ const topics = (log.topics && Array.isArray(log.topics)) ? log.topics : (log.topic0 ? [log.topic0] : []); const topicsStr = topics.join(', '); const data = log.data || log.raw_data || '0x'; - tbl += '' + escapeHtml(shortenHash(addr)) + '' + escapeHtml(String(topicsStr).substring(0, 80)) + (String(topicsStr).length > 80 ? '...' : '') + '' + escapeHtml(String(data).substring(0, 66)) + (String(data).length > 66 ? '...' : '') + '—'; + tbl += '' + explorerAddressLink(addr, escapeHtml(shortenHash(addr)), 'color: inherit; text-decoration: none;') + '' + escapeHtml(String(topicsStr).substring(0, 80)) + (String(topicsStr).length > 80 ? '...' : '') + '' + escapeHtml(String(data).substring(0, 66)) + (String(data).length > 66 ? '...' : '') + '—'; }); if (filteredLogs.length === 0) { tbl += 'No event logs match the current filter.'; @@ -4912,7 +4930,7 @@
Type
${a.is_contract ? 'Contract' + verifiedBadge + (contractLink ? '
' + contractLink : '') : 'EOA'}
- ${a.creation_tx_hash ? `
Contract created in
${escapeHtml(shortenHash(a.creation_tx_hash))}
` : ''} + ${a.creation_tx_hash ? `
Contract created in
${explorerTransactionLink(a.creation_tx_hash, escapeHtml(shortenHash(a.creation_tx_hash)), 'color: inherit; text-decoration: none;')}
` : ''} ${a.first_seen_at ? `
First seen
${escapeHtml(typeof a.first_seen_at === 'string' ? a.first_seen_at : new Date(a.first_seen_at).toISOString())}
` : ''} ${a.last_seen_at ? `
Last seen
${escapeHtml(typeof a.last_seen_at === 'string' ? a.last_seen_at : new Date(a.last_seen_at).toISOString())}
` : ''}
@@ -5013,7 +5031,7 @@ const divisor = Math.pow(10, parseInt(decimals, 10)); const displayBalance = (Number(balance) / divisor).toLocaleString(undefined, { maximumFractionDigits: 6 }); const type = token.type || b.token_type || 'ERC-20'; - tbl += '' + escapeHtml(symbol) + '' + escapeHtml(shortenHash(contract)) + '' + escapeHtml(displayBalance) + '' + escapeHtml(type) + ''; + tbl += '' + escapeHtml(symbol) + '' + explorerAddressLink(contract, escapeHtml(shortenHash(contract)), 'color: inherit; text-decoration: none;') + '' + escapeHtml(displayBalance) + '' + escapeHtml(type) + ''; }); if (filteredItems.length === 0) { tbl += 'No token balances match the current filter.'; @@ -5063,7 +5081,7 @@ var tokenId = b.token_id != null ? b.token_id : (b.tokenId != null ? b.tokenId : (b.id != null ? b.id : '-')); var name = token.name || token.symbol || '-'; var balance = b.value != null ? b.value : (b.balance != null ? b.balance : '1'); - tbl += '' + escapeHtml(shortenHash(contract)) + ''; + tbl += '' + explorerAddressLink(contract, escapeHtml(shortenHash(contract)), 'color: inherit; text-decoration: none;') + ''; tbl += '' + (tokenId !== '-' ? '' + escapeHtml(String(tokenId)) + '' : '-') + ''; tbl += '' + escapeHtml(name) + '' + escapeHtml(String(balance)) + ''; }); @@ -5108,7 +5126,7 @@ const val = it.value ? formatEther(it.value) : '0'; const block = it.block_number || it.block || '-'; const txHash = it.transaction_hash || it.tx_hash || '-'; - tbl += '' + escapeHtml(block) + '' + escapeHtml(shortenHash(from)) + '' + escapeHtml(shortenHash(to)) + '' + escapeHtml(val) + ' ETH' + (txHash !== '-' ? escapeHtml(shortenHash(txHash)) : '-') + ''; + tbl += '' + explorerBlockLink(block, escapeHtml(block), 'color: inherit; text-decoration: none;') + '' + explorerAddressLink(from, escapeHtml(shortenHash(from)), 'color: inherit; text-decoration: none;') + '' + explorerAddressLink(to, escapeHtml(shortenHash(to)), 'color: inherit; text-decoration: none;') + '' + escapeHtml(val) + ' ETH' + (txHash !== '-' ? explorerTransactionLink(txHash, escapeHtml(shortenHash(txHash)), 'color: inherit; text-decoration: none;') : '-') + ''; }); if (filteredItems.length === 0) { tbl += 'No internal transactions match the current filter.'; @@ -5312,7 +5330,7 @@ }) : txs.data; let txHtml = filterBar + ''; filteredTxs.forEach(function(tx) { - txHtml += ''; + txHtml += ''; }); if (filteredTxs.length === 0) { txHtml += ''; @@ -5380,7 +5398,7 @@ var addrEsc = tokenAddress.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); var symbolEsc = String(symbol).replace(/\\/g, '\\\\').replace(/'/g, "\\'"); var html = '
'; - html += '
Contract
' + escapeHtml(tokenAddress) + '
'; + html += '
Contract
' + explorerAddressLink(tokenAddress, escapeHtml(tokenAddress), 'color: inherit; text-decoration: none;') + '
'; html += '
Name
' + escapeHtml(name) + '
'; html += '
Symbol
' + escapeHtml(symbol) + '
'; html += '
Decimals
' + decimals + '
'; @@ -5409,7 +5427,7 @@ var dec = tr.token?.decimals != null ? tr.token.decimals : decimals; var v = Number(val) / Math.pow(10, parseInt(dec, 10)); var txHash = tr.transaction_hash || tr.tx_hash || ''; - html += ''; + html += ''; }); if (filteredTransfers.length === 0) { html += ''; @@ -5442,7 +5460,7 @@ if (r) { data = r; break; } } catch (e) {} } - var html = '
Contract
' + escapeHtml(contractAddress) + '
'; + var html = '
Contract
' + explorerAddressLink(contractAddress, escapeHtml(contractAddress), 'color: inherit; text-decoration: none;') + '
'; html += '
Token ID
' + escapeHtml(String(tokenId)) + '
'; if (data) { if (data.metadata && data.metadata.image) { @@ -5450,7 +5468,7 @@ } if (data.name) html += '
Name
' + escapeHtml(data.name) + '
'; if (data.description) html += '
Description
' + escapeHtml(data.description) + '
'; - if (data.owner) { var ownerAddr = (data.owner.hash || data.owner); html += '
Owner
' + escapeHtml(ownerAddr) + '
'; } + if (data.owner) { var ownerAddr = (data.owner.hash || data.owner); html += '
Owner
' + explorerAddressLink(ownerAddr, escapeHtml(ownerAddr), 'color: inherit; text-decoration: none;') + '
'; } if (data.metadata && data.metadata.attributes && Array.isArray(data.metadata.attributes)) { html += '
Traits
'; data.metadata.attributes.forEach(function(attr) { @@ -5488,20 +5506,20 @@ if (item.address_hash || item.hash) { addr = item.address_hash || item.hash; if (/^0x[a-f0-9]{40}$/i.test(addr)) { - html += '
'; + html += ''; return; } } if (item.tx_hash || (item.hash && item.hash.length === 66)) { txHash = item.tx_hash || item.hash; if (/^0x[a-f0-9]{64}$/i.test(txHash)) { - html += ''; + html += ''; return; } } if (item.block_number != null) { blockNum = String(item.block_number); - html += ''; + html += ''; return; } html += ''; diff --git a/frontend/public/index.html b/frontend/public/index.html index aeee489..be3f053 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -1200,7 +1200,7 @@
WETH9 Token
- Contract: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + Contract: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
@@ -1250,7 +1250,7 @@
WETH10 Token
- Contract: 0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f + Contract: 0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f
@@ -1307,8 +1307,8 @@

Contract Addresses

How to Use

HashBlockFromToValue
' + escapeHtml(shortenHash(tx.hash)) + '' + escapeHtml(String(tx.block_number)) + '' + formatAddressWithLabel(tx.from) + '' + (tx.to ? formatAddressWithLabel(tx.to) : 'N/A') + '' + escapeHtml(formatEther(tx.value || '0')) + ' ETH
' + explorerTransactionLink(tx.hash, escapeHtml(shortenHash(tx.hash)), 'color: inherit; text-decoration: none;') + '' + explorerBlockLink(String(tx.block_number), escapeHtml(String(tx.block_number)), 'color: inherit; text-decoration: none;') + '' + explorerAddressLink(tx.from, formatAddressWithLabel(tx.from), 'color: inherit; text-decoration: none;') + '' + (tx.to ? explorerAddressLink(tx.to, formatAddressWithLabel(tx.to), 'color: inherit; text-decoration: none;') : 'N/A') + '' + escapeHtml(formatEther(tx.value || '0')) + ' ETH
No transactions match the current filter.
' + escapeHtml(shortenHash(from)) + '' + escapeHtml(shortenHash(to)) + '' + escapeHtml(v.toLocaleString(undefined, { maximumFractionDigits: 6 })) + '' + (txHash ? escapeHtml(shortenHash(txHash)) : '-') + '
' + explorerAddressLink(from, escapeHtml(shortenHash(from)), 'color: inherit; text-decoration: none;') + '' + explorerAddressLink(to, escapeHtml(shortenHash(to)), 'color: inherit; text-decoration: none;') + '' + escapeHtml(v.toLocaleString(undefined, { maximumFractionDigits: 6 })) + '' + (txHash ? explorerTransactionLink(txHash, escapeHtml(shortenHash(txHash)), 'color: inherit; text-decoration: none;') : '-') + '
No transfers match the current filter.
Address' + escapeHtml(shortenHash(addr)) + '
Address' + explorerAddressLink(addr, escapeHtml(shortenHash(addr)), 'color: inherit; text-decoration: none;') + '
Transaction' + escapeHtml(shortenHash(txHash)) + '
Transaction' + explorerTransactionLink(txHash, escapeHtml(shortenHash(txHash)), 'color: inherit; text-decoration: none;') + '
Block#' + escapeHtml(blockNum) + '
Block' + explorerBlockLink(blockNum, '#' + escapeHtml(blockNum), 'color: inherit; text-decoration: none;') + '
' + escapeHtml(type || 'Unknown') + '' + escapeHtml(String(label).substring(0, 80)) + '