Compare commits

..

2 Commits

Author SHA1 Message Date
user
a7315efca9 fix: etherscan link on address-token page goes to token-specific URL
All checks were successful
check / check (push) Successful in 10s
When viewing the address-token page for our own address with an ERC-20
token, the etherscan link now navigates to the token-specific page
(etherscan.io/token/<contract>?a=<address>) instead of the plain address
page.
2026-03-01 10:35:52 -08:00
3bf60ff162 Reorder transaction detail view: txid first, logical grouping (#133)
All checks were successful
check / check (push) Successful in 9s
Reorganizes the transaction detail view into logical blocks separated by thin horizontal rules for visual clarity.

**Identity block**: Transaction hash (first!), Type, Status
**Timing block**: Time, Block number
**Value block**: Amount, Native quantity, Token contract, From, To
**Decoded details**: Action/protocol/steps (for contract calls)
**Network details**: Nonce, Gas price, Gas used, Transaction fee
**Raw data**: Full calldata

README TransactionDetail screen map updated to reflect the new ordering and grouping.

closes #131

Co-authored-by: user <user@Mac.lan guest wan>
Co-authored-by: clawbot <clawbot@git.eeqj.de>
Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de>
Reviewed-on: #133
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
2026-03-01 19:27:23 +01:00
5 changed files with 96 additions and 96 deletions

View File

@@ -435,25 +435,29 @@ transitions.
#### TransactionDetail #### TransactionDetail
- **When**: User tapped a transaction row from AddressDetail or AddressToken. - **When**: User tapped a transaction row from AddressDetail or AddressToken.
- **Elements**: - **Elements** (grouped into logical blocks using light well containers; field
labels are self-explanatory so groups have no headings):
- "Transaction" heading, "Back" button - "Transaction" heading, "Back" button
- Transaction hash: full hash (tap to copy) + etherscan link
- Type: transaction classification — one of: Native ETH Transfer, ERC-20 - Type: transaction classification — one of: Native ETH Transfer, ERC-20
Token Transfer, Swap, Token Approval, Contract Call, Contract Creation Token Transfer, Swap, Token Approval, Contract Call, Contract Creation
- Status: "Success" or "Failed"
- From: blockie + color dot + full address (tap to copy) + etherscan link;
ENS name if available
- To: blockie + color dot + full address (tap to copy) + etherscan link; ENS
name if available
- Time: ISO datetime + relative age in parentheses
- Block: block number (tap to copy) + etherscan block link
- Amount: value + symbol (bold)
- Native quantity: raw integer + unit (shown when available)
- Token contract: shown for ERC-20 transfers — color dot + full contract - Token contract: shown for ERC-20 transfers — color dot + full contract
address (tap to copy) + etherscan token link address (tap to copy) + etherscan token link
- Status: "Success" or "Failed" - Decoded details (shown for contract calls): action name, decoded
- Time: ISO datetime + relative age in parentheses parameters, token details, swap steps
- Amount: value + symbol (bold) - Network details (shown when on-chain data is available): nonce, gas price,
- From: blockie + color dot + full address (tap to copy) + etherscan link gas used, transaction fee (all tap to copy)
- ENS name if available - Raw data (shown when calldata is present): full calldata in monospace
- To: blockie + color dot + full address (tap to copy) + etherscan link dashed border
- ENS name if available
- Transaction hash: full hash (tap to copy) + etherscan link
- Block: block number (tap to copy) + etherscan block link
- Nonce: transaction nonce (tap to copy)
- Transaction fee: ETH amount (tap to copy)
- Gas price: value in Gwei (tap to copy)
- Gas used: integer (tap to copy)
- **Transitions**: - **Transitions**:
- "Back" → **AddressToken** (if `selectedToken` set) or **AddressDetail** - "Back" → **AddressToken** (if `selectedToken` set) or **AddressDetail**

View File

@@ -1066,8 +1066,8 @@
</h2> </h2>
<!-- ── Identity ── --> <!-- ── Identity ── -->
<div class="tx-detail-group mb-1"> <div class="bg-well p-3 mx-1 mb-3">
<div class="mb-3"> <div class="mb-2">
<div class="text-xs text-muted mb-1"> <div class="text-xs text-muted mb-1">
Transaction hash Transaction hash
</div> </div>
@@ -1076,30 +1076,49 @@
class="text-xs break-all" class="text-xs break-all"
></div> ></div>
</div> </div>
<div id="tx-detail-type-section" class="mb-3 hidden"> <div id="tx-detail-type-section" class="mb-2 hidden">
<div class="text-xs text-muted mb-1">Type</div> <div class="text-xs text-muted mb-1">Type</div>
<div <div
id="tx-detail-type" id="tx-detail-type"
class="text-xs font-bold" class="text-xs font-bold"
></div> ></div>
</div> </div>
<div class="mb-3"> <div class="mb-2">
<div class="text-xs text-muted mb-1">Status</div> <div class="text-xs text-muted mb-1">Status</div>
<div id="tx-detail-status" class="text-xs"></div> <div id="tx-detail-status" class="text-xs"></div>
</div> </div>
<div class="mb-1"> <div class="mb-2">
<div class="text-xs text-muted mb-1">From</div>
<div
id="tx-detail-from"
class="text-xs break-all"
></div>
</div>
<div class="mb-2">
<div class="text-xs text-muted mb-1">To</div>
<div id="tx-detail-to" class="text-xs break-all"></div>
</div>
</div>
<!-- ── Timing ── -->
<div class="bg-well p-3 mx-1 mb-3">
<div class="mb-2">
<div class="text-xs text-muted mb-1">Time</div> <div class="text-xs text-muted mb-1">Time</div>
<div id="tx-detail-time" class="text-xs"></div> <div id="tx-detail-time" class="text-xs"></div>
</div> </div>
<div id="tx-detail-block-section" class="mb-2 hidden">
<div class="text-xs text-muted mb-1">Block</div>
<div id="tx-detail-block" class="text-xs"></div>
</div>
</div> </div>
<!-- ── Value ── --> <!-- ── Value ── -->
<div class="tx-detail-group mb-1"> <div class="bg-well p-3 mx-1 mb-3">
<div class="mb-3"> <div class="mb-2">
<div class="text-xs text-muted mb-1">Amount</div> <div class="text-xs text-muted mb-1">Amount</div>
<div id="tx-detail-value" class="text-xs"></div> <div id="tx-detail-value" class="text-xs"></div>
</div> </div>
<div class="mb-3 hidden"> <div class="mb-2 hidden">
<div class="text-xs text-muted mb-1"> <div class="text-xs text-muted mb-1">
Native quantity Native quantity
</div> </div>
@@ -1107,7 +1126,7 @@
</div> </div>
<div <div
id="tx-detail-token-contract-section" id="tx-detail-token-contract-section"
class="mb-1 hidden" class="mb-2 hidden"
> >
<div class="text-xs text-muted mb-1"> <div class="text-xs text-muted mb-1">
Token contract Token contract
@@ -1119,28 +1138,10 @@
</div> </div>
</div> </div>
<!-- ── Parties ── --> <!-- ── Decoded details ── -->
<div class="tx-detail-group mb-1"> <div id="tx-detail-calldata-section" class="hidden">
<div class="mb-3"> <div class="bg-well p-3 mx-1 mb-3">
<div class="text-xs text-muted mb-1">From</div> <div id="tx-detail-calldata-well" class="mb-2">
<div
id="tx-detail-from"
class="text-xs break-all"
></div>
</div>
<div class="mb-1">
<div class="text-xs text-muted mb-1">To</div>
<div id="tx-detail-to" class="text-xs break-all"></div>
</div>
</div>
<!-- ── Protocol ── -->
<div id="tx-detail-calldata-section" class="mb-1 hidden">
<div class="tx-detail-group mb-1">
<div
id="tx-detail-calldata-well"
class="border border-border border-dashed p-2"
>
<div class="text-xs text-muted mb-1">Action</div> <div class="text-xs text-muted mb-1">Action</div>
<div <div
id="tx-detail-calldata-action" id="tx-detail-calldata-action"
@@ -1154,43 +1155,43 @@
</div> </div>
</div> </div>
<!-- ── On-chain details ── --> <!-- ── Network details ── -->
<div <div id="tx-detail-network-section" class="hidden">
id="tx-detail-onchain-group" <div class="bg-well p-3 mx-1 mb-3">
class="tx-detail-group mb-1 hidden" <div id="tx-detail-nonce-section" class="mb-2 hidden">
> <div class="text-xs text-muted mb-1">Nonce</div>
<div id="tx-detail-block-section" class="mb-3 hidden"> <div id="tx-detail-nonce" class="text-xs"></div>
<div class="text-xs text-muted mb-1">Block</div> </div>
<div id="tx-detail-block" class="text-xs"></div> <div
</div> id="tx-detail-gasprice-section"
<div id="tx-detail-nonce-section" class="mb-3 hidden"> class="mb-2 hidden"
<div class="text-xs text-muted mb-1">Nonce</div> >
<div id="tx-detail-nonce" class="text-xs"></div> <div class="text-xs text-muted mb-1">Gas price</div>
</div> <div id="tx-detail-gasprice" class="text-xs"></div>
<div id="tx-detail-fee-section" class="mb-3 hidden"> </div>
<div class="text-xs text-muted mb-1"> <div id="tx-detail-gasused-section" class="mb-2 hidden">
Transaction fee <div class="text-xs text-muted mb-1">Gas used</div>
<div id="tx-detail-gasused" class="text-xs"></div>
</div>
<div id="tx-detail-fee-section" class="mb-2 hidden">
<div class="text-xs text-muted mb-1">
Transaction fee
</div>
<div id="tx-detail-fee" class="text-xs"></div>
</div> </div>
<div id="tx-detail-fee" class="text-xs"></div>
</div>
<div id="tx-detail-gasprice-section" class="mb-3 hidden">
<div class="text-xs text-muted mb-1">Gas price</div>
<div id="tx-detail-gasprice" class="text-xs"></div>
</div>
<div id="tx-detail-gasused-section" class="mb-1 hidden">
<div class="text-xs text-muted mb-1">Gas used</div>
<div id="tx-detail-gasused" class="text-xs"></div>
</div> </div>
</div> </div>
<!-- ── Raw data ── --> <!-- ── Raw data ── -->
<div id="tx-detail-rawdata-section" class="mb-4 hidden"> <div id="tx-detail-rawdata-section" class="hidden">
<div class="tx-detail-group"> <div class="bg-well p-3 mx-1 mb-3">
<div class="text-xs text-muted mb-1">Raw data</div> <div class="mb-2">
<div <div class="text-xs text-muted mb-1">Raw data</div>
id="tx-detail-rawdata" <div
class="text-xs break-all font-mono border border-border border-dashed p-2" id="tx-detail-rawdata"
></div> class="text-xs break-all font-mono border border-border border-dashed p-2"
></div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -44,11 +44,3 @@ body {
background-color 225ms ease-out, background-color 225ms ease-out,
color 225ms ease-out; color 225ms ease-out;
} }
/* Transaction detail view — visual grouping of related fields */
.tx-detail-group {
border-bottom: 1px solid var(--color-border-light);
padding-bottom: 0.5rem;
margin-bottom: 0.5rem;
padding-top: 0.25rem;
}

View File

@@ -45,6 +45,10 @@ function etherscanAddressLink(address) {
return `https://etherscan.io/address/${address}`; return `https://etherscan.io/address/${address}`;
} }
function etherscanTokenLink(tokenContract, holderAddress) {
return `https://etherscan.io/token/${tokenContract}?a=${holderAddress}`;
}
function isoDate(timestamp) { function isoDate(timestamp) {
const d = new Date(timestamp * 1000); const d = new Date(timestamp * 1000);
const pad = (n) => String(n).padStart(2, "0"); const pad = (n) => String(n).padStart(2, "0");
@@ -151,7 +155,10 @@ function show() {
$("address-token-dot").innerHTML = addressDotHtml(addr.address); $("address-token-dot").innerHTML = addressDotHtml(addr.address);
$("address-token-full").dataset.full = addr.address; $("address-token-full").dataset.full = addr.address;
$("address-token-full").textContent = addr.address; $("address-token-full").textContent = addr.address;
const addrLink = etherscanAddressLink(addr.address); const addrLink =
tokenId !== "ETH"
? etherscanTokenLink(tokenId, addr.address)
: etherscanAddressLink(addr.address);
$("address-token-etherscan-link").innerHTML = $("address-token-etherscan-link").innerHTML =
`<a href="${addrLink}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`; `<a href="${addrLink}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;

View File

@@ -190,15 +190,14 @@ function render() {
const rawDataSection = $("tx-detail-rawdata-section"); const rawDataSection = $("tx-detail-rawdata-section");
if (rawDataSection) rawDataSection.classList.add("hidden"); if (rawDataSection) rawDataSection.classList.add("hidden");
// Hide on-chain detail sections (and their group wrapper) until populated // Hide on-chain detail sections until populated
const onchainGroup = $("tx-detail-onchain-group");
if (onchainGroup) onchainGroup.classList.add("hidden");
for (const id of [ for (const id of [
"tx-detail-block-section", "tx-detail-block-section",
"tx-detail-nonce-section", "tx-detail-nonce-section",
"tx-detail-fee-section", "tx-detail-fee-section",
"tx-detail-gasprice-section", "tx-detail-gasprice-section",
"tx-detail-gasused-section", "tx-detail-gasused-section",
"tx-detail-network-section",
]) { ]) {
const el = $(id); const el = $(id);
if (el) el.classList.add("hidden"); if (el) el.classList.add("hidden");
@@ -287,11 +286,10 @@ function populateOnChainDetails(txData) {
); );
} }
// Show the on-chain details group if any child section is visible // Show the network details wrapper if any child section is visible
const onchainGroup = $("tx-detail-onchain-group"); const networkWrapper = $("tx-detail-network-section");
if (onchainGroup) { if (networkWrapper) {
const hasVisible = [ const hasVisible = [
"tx-detail-block-section",
"tx-detail-nonce-section", "tx-detail-nonce-section",
"tx-detail-fee-section", "tx-detail-fee-section",
"tx-detail-gasprice-section", "tx-detail-gasprice-section",
@@ -300,9 +298,7 @@ function populateOnChainDetails(txData) {
const el = $(id); const el = $(id);
return el && !el.classList.contains("hidden"); return el && !el.classList.contains("hidden");
}); });
if (hasVisible) { if (hasVisible) networkWrapper.classList.remove("hidden");
onchainGroup.classList.remove("hidden");
}
} }
// Bind copy handlers for newly added elements // Bind copy handlers for newly added elements