From 1ce9974833f33c90497bd35f98301c9241748570 Mon Sep 17 00:00:00 2001 From: wmagev <74554762+wmagev@users.noreply.github.com> Date: Sat, 18 Apr 2026 22:14:05 +0900 Subject: [PATCH 1/2] feat: link addresses, tx hashes, and blocks to blockchain explorers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds getExplorerUrl(chain, type, value) mapping BTC → blockchain.com (address/tx) and TAO → taostats.io (account/block). CopyableAddress splits the single click-to-copy behavior into two affordances: the label becomes an explorer link when chain is provided, and a dedicated copy IconButton handles clipboard separately. Wires chain/type through EventFeed, MinerRatesTable, SwapTracker, and SwapDetailPage so hotkeys, source/dest tx hashes, and block numbers in the timeline are all jump-to-explorer links. --- src/components/CopyableAddress.tsx | 84 ++++++++++++--- src/components/dashboard/EventFeed.tsx | 12 ++- src/components/dashboard/MinerRatesTable.tsx | 2 +- src/components/dashboard/SwapTracker.tsx | 12 ++- src/pages/SwapDetailPage.tsx | 106 +++++++++++++++---- src/utils/explorer.ts | 26 +++++ src/utils/index.ts | 1 + 7 files changed, 204 insertions(+), 39 deletions(-) create mode 100644 src/utils/explorer.ts diff --git a/src/components/CopyableAddress.tsx b/src/components/CopyableAddress.tsx index eaee6e9..d05f590 100644 --- a/src/components/CopyableAddress.tsx +++ b/src/components/CopyableAddress.tsx @@ -1,44 +1,102 @@ import React, { useState } from 'react'; -import { Tooltip, Typography } from '@mui/material'; +import { IconButton, Link, Stack, Tooltip, Typography } from '@mui/material'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import CheckIcon from '@mui/icons-material/Check'; import { FONTS } from '../theme'; +import { getExplorerUrl, type ExplorerType } from '../utils/explorer'; const shortAddr = (addr: string) => addr.length > 10 ? `${addr.slice(0, 4)}..${addr.slice(-4)}` : addr; interface CopyableAddressProps { address: string; + chain?: string | null; + type?: ExplorerType; + display?: string; fontSize?: string; color?: string; + showCopy?: boolean; } const CopyableAddress: React.FC = ({ address, + chain, + type = 'address', + display, fontSize = '0.7rem', color = 'text.secondary', + showCopy = true, }) => { const [copied, setCopied] = useState(false); + const explorerUrl = getExplorerUrl(chain, type, address); + const text = display ?? shortAddr(address); - const handleClick = () => { + const handleCopy = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); navigator.clipboard.writeText(address); setCopied(true); setTimeout(() => setCopied(false), 1500); }; + const label = explorerUrl ? ( + e.stopPropagation()} + sx={{ + fontFamily: FONTS.mono, + fontSize, + color, + textDecoration: 'none', + '&:hover': { textDecoration: 'underline', color: 'primary.main' }, + }} + > + {text} + + ) : ( + + {text} + + ); + return ( - - {shortAddr(address)} - + {label} + {showCopy && ( + + {copied ? ( + + ) : ( + + )} + + )} + ); }; diff --git a/src/components/dashboard/EventFeed.tsx b/src/components/dashboard/EventFeed.tsx index 72d10b8..8ec6dcc 100644 --- a/src/components/dashboard/EventFeed.tsx +++ b/src/components/dashboard/EventFeed.tsx @@ -4,6 +4,7 @@ import { Box, Button, Chip, Stack, Typography, useTheme } from '@mui/material'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import { useLatestEvents } from '../../api'; import { FONTS } from '../../theme'; +import { getExplorerUrl } from '../../utils'; import CopyableAddress from '../CopyableAddress'; import { EventFeedSkeleton } from './Skeletons'; @@ -111,10 +112,19 @@ const EventFeed: React.FC = () => { }} /> #{event.blockNumber} @@ -155,7 +165,7 @@ const EventFeed: React.FC = () => { )} {event.minerHotkey && ( - + )} {event.taoAmount && ( { color: 'text.secondary', }} > - + ); diff --git a/src/components/dashboard/SwapTracker.tsx b/src/components/dashboard/SwapTracker.tsx index 54360c4..558028b 100644 --- a/src/components/dashboard/SwapTracker.tsx +++ b/src/components/dashboard/SwapTracker.tsx @@ -255,7 +255,11 @@ const SwapTracker: React.FC = () => { color: 'text.secondary', }} > - User: + User:{' '} + )} {swap.minerHotkey && ( @@ -268,7 +272,11 @@ const SwapTracker: React.FC = () => { color: 'text.secondary', }} > - Miner: + Miner:{' '} + )} diff --git a/src/pages/SwapDetailPage.tsx b/src/pages/SwapDetailPage.tsx index 1c648d0..43b9574 100644 --- a/src/pages/SwapDetailPage.tsx +++ b/src/pages/SwapDetailPage.tsx @@ -18,6 +18,7 @@ import { chainSymbol, formatBlockEstimate, } from '../utils/format'; +import { getExplorerUrl } from '../utils'; type TimelineStep = { label: string; @@ -215,15 +216,35 @@ const SwapDetailPage: React.FC = () => { > {step.label} - - {step.block ? `Block #${step.block}` : '\u2014'} - + {step.block ? ( + + Block #{step.block} + + ) : ( + + {'\u2014'} + + )} ); })} @@ -253,7 +274,24 @@ const SwapDetailPage: React.FC = () => { color: 'text.secondary', }} > - Block #{swap.timeoutBlock} + + Block #{swap.timeoutBlock} + {!isTimedOut && swap.status !== 'COMPLETED' && swap.initiatedBlock && ( @@ -282,11 +320,15 @@ const SwapDetailPage: React.FC = () => { label="Source TX" value={swap.sourceTxHash || '\u2014'} copyable={!!swap.sourceTxHash} + chain={swap.sourceChain} + type="tx" /> @@ -297,19 +339,31 @@ const SwapDetailPage: React.FC = () => { Participants {swap.userAddress && ( - + )} {swap.userSourceAddress && ( - + )} {swap.userDestAddress && ( - + )} {swap.minerHotkey && ( - + )} {swap.minerSourceAddress && ( - + )} @@ -434,7 +488,9 @@ const LabelValue: React.FC<{ label: string; value: string; copyable?: boolean; -}> = ({ label, value, copyable }) => ( + chain?: string | null; + type?: 'address' | 'tx' | 'block'; +}> = ({ label, value, copyable, chain, type }) => ( {copyable ? ( - + ) : ( ); -const LabelAddr: React.FC<{ label: string; address: string }> = ({ - label, - address, -}) => ( +const LabelAddr: React.FC<{ + label: string; + address: string; + chain?: string | null; +}> = ({ label, address, chain }) => ( = ({ > {label} - + ); diff --git a/src/utils/explorer.ts b/src/utils/explorer.ts new file mode 100644 index 0000000..6820ffa --- /dev/null +++ b/src/utils/explorer.ts @@ -0,0 +1,26 @@ +export type ExplorerChain = 'btc' | 'tao'; +export type ExplorerType = 'address' | 'tx' | 'block'; + +const builders: Record< + ExplorerChain, + Partial string>> +> = { + btc: { + address: (v) => `https://www.blockchain.com/btc/address/${v}`, + tx: (v) => `https://www.blockchain.com/btc/tx/${v}`, + }, + tao: { + address: (v) => `https://taostats.io/account/${v}`, + block: (v) => `https://taostats.io/block/${v}`, + }, +}; + +export const getExplorerUrl = ( + chain: string | undefined | null, + type: ExplorerType, + value: string | undefined | null, +): string | null => { + if (!chain || !value) return null; + const key = chain.toLowerCase() as ExplorerChain; + return builders[key]?.[type]?.(value) ?? null; +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index 16c5b2b..7874fd9 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1 +1,2 @@ export * from './format'; +export * from './explorer'; From 57f3ba119b70132ef2a2703cfea99c9230549f31 Mon Sep 17 00:00:00 2001 From: wmagev <74554762+wmagev@users.noreply.github.com> Date: Sat, 18 Apr 2026 22:40:18 +0900 Subject: [PATCH 2/2] fix: link remaining block numbers and unnest anchors on swap tracker - Link `reservedUntil` block and event-history block numbers to taostats - Drop explorer link on tracker card addresses to fix nested DOM warning --- src/components/dashboard/EventFeed.tsx | 12 ++++++++++++ src/components/dashboard/SwapTracker.tsx | 10 ++-------- src/pages/SwapDetailPage.tsx | 12 ++++++++++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/components/dashboard/EventFeed.tsx b/src/components/dashboard/EventFeed.tsx index 8ec6dcc..b61547f 100644 --- a/src/components/dashboard/EventFeed.tsx +++ b/src/components/dashboard/EventFeed.tsx @@ -180,10 +180,22 @@ const EventFeed: React.FC = () => { )} {event.reservedUntil && ( until #{event.reservedUntil} diff --git a/src/components/dashboard/SwapTracker.tsx b/src/components/dashboard/SwapTracker.tsx index 558028b..c81dc7f 100644 --- a/src/components/dashboard/SwapTracker.tsx +++ b/src/components/dashboard/SwapTracker.tsx @@ -256,10 +256,7 @@ const SwapTracker: React.FC = () => { }} > User:{' '} - + )} {swap.minerHotkey && ( @@ -273,10 +270,7 @@ const SwapTracker: React.FC = () => { }} > Miner:{' '} - + )} diff --git a/src/pages/SwapDetailPage.tsx b/src/pages/SwapDetailPage.tsx index 43b9574..2856e50 100644 --- a/src/pages/SwapDetailPage.tsx +++ b/src/pages/SwapDetailPage.tsx @@ -394,10 +394,22 @@ const SwapDetailPage: React.FC = () => { }} /> #{event.blockNumber}