@rubanrubi/hardhat-dashboard 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@rubanrubi/hardhat-dashboard` are documented here.
4
+
5
+ ---
6
+
7
+ ## [0.2.0] — 2026-03-29
8
+
9
+ ### Added
10
+
11
+ #### Network names
12
+ - Expanded chain ID → name mapping from 7 to 35+ EVM networks and testnets.
13
+ - Now covers: Ethereum, Sepolia, Goerli, Polygon Mainnet, **Polygon Amoy Testnet**, Polygon Mumbai, Polygon zkEVM, BNB Smart Chain, Avalanche C-Chain, Avalanche Fuji, Arbitrum One, Arbitrum Sepolia, Optimism Mainnet, Optimism Sepolia, Base Mainnet, Base Sepolia, zkSync Era, Linea, Scroll, Fantom Opera, Gnosis Chain, Celo, Blast, Mantle, Moonbeam, Moonriver, Moonbase Alpha, Metis Andromeda, Cronos, Hardhat Local, Anvil Local, and more.
14
+ - Each chain displays its correct **native currency symbol** (ETH, POL, BNB, AVAX, FTM, xDAI, CELO, GLMR, MOVR, CRO) so balance values are labelled accurately.
15
+
16
+ #### Wallet details panel
17
+ - The wallet badge now shows: network name with a live green indicator dot, shortened address (`0x1234…5678`), a **Copy** button that writes the full address to the clipboard (shows `✓` for 2 s as feedback), and the current native token balance.
18
+ - Balance is fetched live via `eth_getBalance` on connect and re-fetched whenever the account or chain changes.
19
+ - The **Balance** metric in the status strip replaces the redundant "Wallet" name field.
20
+
21
+ #### Human-readable transaction display
22
+ - `eth_sendTransaction` cards now show a structured summary: **From**, **To** (shortened), and **Value** in ETH rather than raw hex.
23
+ - Transactions with no `to` address display a **Contract Deployment** badge and read *Deploy `<ContractName>`* when the ABI is available.
24
+ - Decoded call section shows `ContractName.functionName(…)` with the full signature and each argument on its own line.
25
+ - When a function selector is recognised but no ABI match is found, an "Unknown function — selector: `0x…`" warning is shown instead of silently hiding the call.
26
+ - **`personal_sign`** cards decode the hex message to UTF-8 and display it as plain text.
27
+ - **`eth_sign`** cards show the raw message hash.
28
+ - **`eth_signTypedData*`** cards parse the EIP-712 payload and display the **Domain** and **Message** fields as formatted JSON, so users can read what they are signing before confirming.
29
+ - Raw hex calldata is no longer shown in pending request cards.
30
+
31
+ #### Activity / Tx History filtering
32
+ - The **Tx History** panel now shows only signing and deployment events (`eth_sendTransaction`, `eth_sign`, `personal_sign`, `eth_signTypedData*`), filtering out read-only `eth_call` passthroughs that were adding noise.
33
+ - Confirmed transactions display the truncated **tx hash** when available.
34
+ - Failed/rejected entries show the human-readable **error message** instead of a raw JSON error object.
35
+
36
+ ### Changed
37
+ - `WalletBadge` component refactored to a multi-line detail panel; the old single-line pill is preserved for disconnected/unavailable states.
38
+ - Status strip "Wallet" metric renamed to **Balance** and now reflects live on-chain balance.
39
+ - History entries show `→` between queued and completed timestamps for clarity.
40
+ - Method names in cards use friendly labels (*Send Transaction*, *Sign Message*, *Sign Typed Data*) instead of raw RPC method strings.
41
+
42
+ ---
43
+
44
+ ## [0.1.1] — 2025-03-28
45
+
46
+ - Fix: updated README documentation and resolved TypeScript type-stub lint errors.
47
+
48
+ ## [0.1.0] — 2025-03-28
49
+
50
+ - Initial release: Hardhat plugin that routes JSON-RPC signing through a local browser dashboard backed by MetaMask.
@@ -59,6 +59,8 @@ export interface WalletConnectionState {
59
59
  account?: string;
60
60
  chainId?: string;
61
61
  networkName?: string;
62
+ balance?: string;
63
+ nativeSymbol?: string;
62
64
  }
63
65
  export interface DashboardSnapshot {
64
66
  pending: PendingRpcRequest[];
@@ -1 +1 @@
1
- {"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../src/types/rpc.ts"],"names":[],"mappings":";;;AAyGa,QAAA,kBAAkB,GAA0B;IACvD,SAAS,EAAE,KAAK;IAChB,SAAS,EAAE,KAAK;IAChB,MAAM,EAAE,KAAK;IACb,UAAU,EAAE,UAAU;CACvB,CAAC"}
1
+ {"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../src/types/rpc.ts"],"names":[],"mappings":";;;AA2Ga,QAAA,kBAAkB,GAA0B;IACvD,SAAS,EAAE,KAAK;IAChB,SAAS,EAAE,KAAK;IAChB,MAAM,EAAE,KAAK;IACb,UAAU,EAAE,UAAU;CACvB,CAAC"}
package/dist/ui/app.js CHANGED
@@ -1083,7 +1083,7 @@ var require_react_development = __commonJS({
1083
1083
  }
1084
1084
  return dispatcher.useContext(Context);
1085
1085
  }
1086
- function useState2(initialState) {
1086
+ function useState3(initialState) {
1087
1087
  var dispatcher = resolveDispatcher();
1088
1088
  return dispatcher.useState(initialState);
1089
1089
  }
@@ -1886,7 +1886,7 @@ var require_react_development = __commonJS({
1886
1886
  exports.useMemo = useMemo;
1887
1887
  exports.useReducer = useReducer;
1888
1888
  exports.useRef = useRef2;
1889
- exports.useState = useState2;
1889
+ exports.useState = useState3;
1890
1890
  exports.useSyncExternalStore = useSyncExternalStore;
1891
1891
  exports.useTransition = useTransition;
1892
1892
  exports.version = ReactVersion;
@@ -24486,7 +24486,7 @@ var require_jsx_runtime = __commonJS({
24486
24486
  });
24487
24487
 
24488
24488
  // src/ui/app.tsx
24489
- var import_react = __toESM(require_react());
24489
+ var import_react2 = __toESM(require_react());
24490
24490
  var import_client = __toESM(require_client());
24491
24491
 
24492
24492
  // src/types/rpc.ts
@@ -24506,8 +24506,98 @@ function formatTimestamp(value) {
24506
24506
  second: "2-digit"
24507
24507
  });
24508
24508
  }
24509
- function formatParams(params) {
24510
- return JSON.stringify(params ?? [], null, 2);
24509
+ function shortenAddress(addr) {
24510
+ return `${addr.slice(0, 8)}...${addr.slice(-6)}`;
24511
+ }
24512
+ function formatWeiValue(hex) {
24513
+ try {
24514
+ const wei = BigInt(hex);
24515
+ if (wei === 0n) return "";
24516
+ const eth = Number(wei) / 1e18;
24517
+ return `${eth.toFixed(6)} ETH`;
24518
+ } catch {
24519
+ return hex;
24520
+ }
24521
+ }
24522
+ function hexToUtf8(hex) {
24523
+ try {
24524
+ const bytes = (hex.startsWith("0x") ? hex.slice(2) : hex).match(/.{2}/g) ?? [];
24525
+ const decoded = bytes.map((b) => String.fromCharCode(parseInt(b, 16))).join("");
24526
+ return decoded.trim() || hex;
24527
+ } catch {
24528
+ return hex;
24529
+ }
24530
+ }
24531
+ function extractTxFields(request) {
24532
+ if (request.method !== "eth_sendTransaction") return null;
24533
+ const params = request.params;
24534
+ const first = Array.isArray(params) ? params[0] : params;
24535
+ if (!first || typeof first !== "object") return null;
24536
+ const tx = first;
24537
+ const to = typeof tx.to === "string" && tx.to.length > 2 ? tx.to : void 0;
24538
+ const from = typeof tx.from === "string" ? tx.from : void 0;
24539
+ const rawValue = typeof tx.value === "string" ? tx.value : void 0;
24540
+ const value = rawValue && rawValue !== "0x0" && rawValue !== "0x00" && rawValue !== "0x" ? formatWeiValue(rawValue) : void 0;
24541
+ return { from, to, value: value || void 0, isDeployment: !to };
24542
+ }
24543
+ function getMethodLabel(method) {
24544
+ switch (method) {
24545
+ case "eth_sendTransaction":
24546
+ return "Send Transaction";
24547
+ case "eth_sign":
24548
+ return "Sign Message";
24549
+ case "personal_sign":
24550
+ return "Sign Message";
24551
+ case "eth_signTypedData":
24552
+ case "eth_signTypedData_v1":
24553
+ case "eth_signTypedData_v3":
24554
+ case "eth_signTypedData_v4":
24555
+ return "Sign Typed Data";
24556
+ default:
24557
+ return method;
24558
+ }
24559
+ }
24560
+ function SigningMessageView({ request }) {
24561
+ const params = Array.isArray(request.params) ? request.params : [];
24562
+ if (request.method === "personal_sign") {
24563
+ const raw = typeof params[0] === "string" ? params[0] : "";
24564
+ const message = raw.startsWith("0x") ? hexToUtf8(raw) : raw;
24565
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tx-message-box", children: [
24566
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tx-field-label", children: "Message" }),
24567
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tx-message-content", children: message || raw })
24568
+ ] });
24569
+ }
24570
+ if (request.method === "eth_sign") {
24571
+ const raw = typeof params[1] === "string" ? params[1] : "";
24572
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tx-message-box", children: [
24573
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tx-field-label", children: "Message hash" }),
24574
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tx-message-content mono", children: raw })
24575
+ ] });
24576
+ }
24577
+ if (request.method.startsWith("eth_signTypedData")) {
24578
+ const rawData = typeof params[1] === "string" ? params[1] : JSON.stringify(params[1]);
24579
+ try {
24580
+ const parsed = typeof rawData === "string" ? JSON.parse(rawData) : rawData;
24581
+ const domain = parsed?.domain;
24582
+ const message = parsed?.message;
24583
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tx-message-box", children: [
24584
+ domain ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tx-typed-section", children: [
24585
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tx-field-label", children: "Domain" }),
24586
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "tx-typed-pre", children: JSON.stringify(domain, null, 2) })
24587
+ ] }) : null,
24588
+ message ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tx-typed-section", children: [
24589
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tx-field-label", children: "Message" }),
24590
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "tx-typed-pre", children: JSON.stringify(message, null, 2) })
24591
+ ] }) : null
24592
+ ] });
24593
+ } catch {
24594
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tx-message-box", children: [
24595
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tx-field-label", children: "Typed data" }),
24596
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tx-message-content mono", children: rawData })
24597
+ ] });
24598
+ }
24599
+ }
24600
+ return null;
24511
24601
  }
24512
24602
  function PendingTxCard({
24513
24603
  request,
@@ -24516,26 +24606,63 @@ function PendingTxCard({
24516
24606
  onReject
24517
24607
  }) {
24518
24608
  const actionLabel = request.requiresApproval ? "Confirm" : "Run";
24609
+ const txFields = extractTxFields(request);
24610
+ const isSigningMethod = [
24611
+ "eth_sign",
24612
+ "personal_sign",
24613
+ "eth_signTypedData",
24614
+ "eth_signTypedData_v1",
24615
+ "eth_signTypedData_v3",
24616
+ "eth_signTypedData_v4"
24617
+ ].includes(request.method);
24519
24618
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("article", { className: `request-card ${request.status === "processing" ? "processing" : ""}`, children: [
24520
24619
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "request-top", children: [
24521
24620
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
24522
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "method", children: request.method }),
24621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "method", children: getMethodLabel(request.method) }),
24523
24622
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "timestamp", children: [
24524
24623
  "Queued at ",
24525
24624
  formatTimestamp(request.createdAt)
24526
24625
  ] })
24527
24626
  ] }),
24528
24627
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tag-row", children: [
24628
+ txFields?.isDeployment ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tag deploy", children: "Contract Deployment" }) : null,
24529
24629
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tag", children: request.requiresApproval ? "Approval required" : "Read-only passthrough" }),
24530
24630
  request.status === "processing" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tag success", children: "Processing" }) : null
24531
24631
  ] })
24532
24632
  ] }),
24533
- request.decodedCall ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "decoded-call", children: [
24534
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: request.decodedCall.matched ? `${request.decodedCall.contractName ?? "Contract"}.${request.decodedCall.functionName ?? "call"}` : "No ABI decode available" }),
24535
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: request.decodedCall.signature ?? request.decodedCall.selector ?? "Raw transaction payload" }),
24536
- request.decodedCall.args.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: request.decodedCall.args.map((argument, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: argument }, `${request.queueId}-arg-${index}`)) }) : null
24633
+ txFields ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tx-summary", children: [
24634
+ txFields.isDeployment ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tx-summary-row", children: [
24635
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tx-field-label", children: "Action" }),
24636
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tx-field-value", children: request.decodedCall?.contractName ? `Deploy ${request.decodedCall.contractName}` : "Deploy new contract" })
24637
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tx-summary-row", children: [
24638
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tx-field-label", children: "To" }),
24639
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tx-field-value mono", children: txFields.to ? shortenAddress(txFields.to) : "\u2014" })
24640
+ ] }),
24641
+ txFields.from ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tx-summary-row", children: [
24642
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tx-field-label", children: "From" }),
24643
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tx-field-value mono", children: shortenAddress(txFields.from) })
24644
+ ] }) : null,
24645
+ txFields.value ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tx-summary-row", children: [
24646
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tx-field-label", children: "Value" }),
24647
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tx-field-value value-accent", children: txFields.value })
24648
+ ] }) : null
24537
24649
  ] }) : null,
24538
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { children: formatParams(request.params) }),
24650
+ request.decodedCall?.matched ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "decoded-call", children: [
24651
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
24652
+ request.decodedCall.contractName ?? "Contract",
24653
+ ".",
24654
+ request.decodedCall.functionName ?? "call"
24655
+ ] }),
24656
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "decoded-sig", children: request.decodedCall.signature }),
24657
+ request.decodedCall.args.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "decoded-args", children: request.decodedCall.args.map((argument, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: argument }, `${request.queueId}-arg-${index}`)) }) : null
24658
+ ] }) : request.decodedCall && !request.decodedCall.matched && request.decodedCall.selector ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "decoded-call unmatched", children: [
24659
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Unknown function" }),
24660
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "decoded-sig", children: [
24661
+ "Selector: ",
24662
+ request.decodedCall.selector
24663
+ ] })
24664
+ ] }) : null,
24665
+ isSigningMethod ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SigningMessageView, { request }) : null,
24539
24666
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "button-row", style: { marginTop: 14 }, children: [
24540
24667
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "button-primary", disabled: busy, onClick: () => onConfirm(request), children: busy ? "Waiting for wallet..." : actionLabel }),
24541
24668
  request.requiresApproval ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "button-danger", disabled: busy, onClick: () => onReject(request), children: "Reject" }) : null
@@ -24545,6 +24672,15 @@ function PendingTxCard({
24545
24672
 
24546
24673
  // src/ui/components/TxHistory.tsx
24547
24674
  var import_jsx_runtime2 = __toESM(require_jsx_runtime());
24675
+ var DEPLOYMENT_METHODS = /* @__PURE__ */ new Set([
24676
+ "eth_sendTransaction",
24677
+ "eth_sign",
24678
+ "personal_sign",
24679
+ "eth_signTypedData",
24680
+ "eth_signTypedData_v1",
24681
+ "eth_signTypedData_v3",
24682
+ "eth_signTypedData_v4"
24683
+ ]);
24548
24684
  function formatTimestamp2(value) {
24549
24685
  return new Date(value).toLocaleTimeString([], {
24550
24686
  hour: "2-digit",
@@ -24555,41 +24691,98 @@ function formatTimestamp2(value) {
24555
24691
  function getStatusClass(status) {
24556
24692
  return status === "confirmed" ? "success" : "error";
24557
24693
  }
24558
- function formatValue(value) {
24559
- return JSON.stringify(value ?? null, null, 2);
24694
+ function getMethodLabel2(method) {
24695
+ switch (method) {
24696
+ case "eth_sendTransaction":
24697
+ return "Send Transaction";
24698
+ case "eth_sign":
24699
+ return "Sign Message";
24700
+ case "personal_sign":
24701
+ return "Sign Message";
24702
+ case "eth_signTypedData":
24703
+ case "eth_signTypedData_v1":
24704
+ case "eth_signTypedData_v3":
24705
+ case "eth_signTypedData_v4":
24706
+ return "Sign Typed Data";
24707
+ default:
24708
+ return method;
24709
+ }
24710
+ }
24711
+ function getActionSummary(entry) {
24712
+ if (entry.decodedCall?.matched) {
24713
+ const name = entry.decodedCall.contractName ?? "Contract";
24714
+ const fn = entry.decodedCall.functionName ?? "call";
24715
+ return `${name}.${fn}`;
24716
+ }
24717
+ if (entry.method === "eth_sendTransaction") {
24718
+ const params = entry;
24719
+ const first = Array.isArray(params) ? params[0] : void 0;
24720
+ const tx = first && typeof first === "object" ? first : void 0;
24721
+ const hasTo = tx && typeof tx.to === "string" && tx.to.length > 2;
24722
+ return hasTo ? "Contract interaction" : "Contract deployment";
24723
+ }
24724
+ return "";
24725
+ }
24726
+ function getTxHash(entry) {
24727
+ if (typeof entry.result === "string" && entry.result.startsWith("0x") && entry.result.length === 66) {
24728
+ return entry.result;
24729
+ }
24730
+ return void 0;
24560
24731
  }
24561
24732
  function TxHistory({ entries }) {
24562
- if (!entries.length) {
24563
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "empty-state", children: "Completed and rejected requests will appear here during this session." });
24733
+ const relevant = entries.filter((e) => DEPLOYMENT_METHODS.has(e.method));
24734
+ if (!relevant.length) {
24735
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "empty-state", children: "Confirmed transactions and signing events will appear here during this session." });
24564
24736
  }
24565
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "card-list", children: entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("article", { className: "history-card", children: [
24566
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "history-top", children: [
24567
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
24568
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "method", children: entry.method }),
24569
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "timestamp", children: [
24570
- formatTimestamp2(entry.createdAt),
24571
- " to ",
24572
- formatTimestamp2(entry.completedAt)
24573
- ] })
24574
- ] }),
24575
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `tag ${getStatusClass(entry.status)}`, children: entry.status })
24576
- ] }),
24577
- entry.decodedCall?.matched ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: "decoded-call", children: [
24578
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("strong", { children: [
24579
- entry.decodedCall.contractName,
24580
- ".",
24581
- entry.decodedCall.functionName
24737
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "card-list", children: relevant.map((entry) => {
24738
+ const actionSummary = getActionSummary(entry);
24739
+ const txHash = getTxHash(entry);
24740
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("article", { className: "history-card", children: [
24741
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "history-top", children: [
24742
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
24743
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "method", children: getMethodLabel2(entry.method) }),
24744
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "timestamp", children: [
24745
+ formatTimestamp2(entry.createdAt),
24746
+ " \u2192 ",
24747
+ formatTimestamp2(entry.completedAt)
24748
+ ] })
24749
+ ] }),
24750
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `tag ${getStatusClass(entry.status)}`, children: entry.status })
24582
24751
  ] }),
24583
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: entry.decodedCall.signature })
24584
- ] }) : null,
24585
- entry.result !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("pre", { children: formatValue(entry.result) }) : null,
24586
- entry.error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("pre", { children: formatValue(entry.error) }) : null
24587
- ] }, entry.queueId)) });
24752
+ actionSummary ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "history-action", children: actionSummary }) : null,
24753
+ entry.decodedCall?.matched ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: "decoded-call", children: [
24754
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("strong", { children: [
24755
+ entry.decodedCall.contractName,
24756
+ ".",
24757
+ entry.decodedCall.functionName
24758
+ ] }),
24759
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "decoded-sig", children: entry.decodedCall.signature })
24760
+ ] }) : null,
24761
+ txHash ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "history-hash", children: [
24762
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "tx-field-label", children: "Tx hash" }),
24763
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "history-hash-value mono", children: [
24764
+ txHash.slice(0, 18),
24765
+ "...",
24766
+ txHash.slice(-8)
24767
+ ] })
24768
+ ] }) : null,
24769
+ entry.error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "history-error", children: entry.error.message }) : null
24770
+ ] }, entry.queueId);
24771
+ }) });
24588
24772
  }
24589
24773
 
24590
24774
  // src/ui/components/WalletBadge.tsx
24775
+ var import_react = __toESM(require_react());
24591
24776
  var import_jsx_runtime3 = __toESM(require_jsx_runtime());
24592
24777
  function WalletBadge({ wallet }) {
24778
+ const [copied, setCopied] = (0, import_react.useState)(false);
24779
+ function copyAddress() {
24780
+ if (!wallet.account) return;
24781
+ void navigator.clipboard.writeText(wallet.account).then(() => {
24782
+ setCopied(true);
24783
+ setTimeout(() => setCopied(false), 2e3);
24784
+ });
24785
+ }
24593
24786
  if (!wallet.available) {
24594
24787
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "network-pill", children: "MetaMask not detected" });
24595
24788
  }
@@ -24599,21 +24792,105 @@ function WalletBadge({ wallet }) {
24599
24792
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: wallet.locked ? "Locked or not connected" : "Waiting for account access" })
24600
24793
  ] });
24601
24794
  }
24602
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "network-pill", children: [
24603
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: wallet.networkName ?? wallet.chainId ?? "Connected" }),
24604
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: wallet.account ? `${wallet.account.slice(0, 6)}...${wallet.account.slice(-4)}` : "No account" })
24795
+ const shortAddress = wallet.account ? `${wallet.account.slice(0, 6)}...${wallet.account.slice(-4)}` : "No account";
24796
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "wallet-detail-pill", children: [
24797
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "wallet-network-row", children: [
24798
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "wallet-dot" }),
24799
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { className: "wallet-network-name", children: wallet.networkName ?? wallet.chainId ?? "Connected" })
24800
+ ] }),
24801
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "wallet-account-row", children: [
24802
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "wallet-address", title: wallet.account, children: shortAddress }),
24803
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "copy-btn", onClick: copyAddress, title: "Copy full address", children: copied ? "\u2713" : "Copy" })
24804
+ ] }),
24805
+ wallet.balance ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "wallet-balance", children: wallet.balance }) : null
24605
24806
  ] });
24606
24807
  }
24607
24808
 
24608
24809
  // src/ui/wallet.ts
24609
24810
  var CHAIN_NAMES = {
24811
+ // Ethereum
24610
24812
  "0x1": "Ethereum Mainnet",
24611
- "0xaa36a7": "Sepolia",
24612
- "0x89": "Polygon",
24613
- "0x2105": "Base",
24813
+ "0xaa36a7": "Sepolia Testnet",
24814
+ "0x5": "Goerli Testnet",
24815
+ // Polygon
24816
+ "0x89": "Polygon Mainnet",
24817
+ "0x13882": "Polygon Amoy Testnet",
24818
+ "0x13881": "Polygon Mumbai Testnet",
24819
+ "0x44d": "Polygon zkEVM",
24820
+ "0x5a2": "Polygon zkEVM Testnet",
24821
+ // BNB Smart Chain
24822
+ "0x38": "BNB Smart Chain",
24823
+ "0x61": "BNB Testnet",
24824
+ // Avalanche
24825
+ "0xa86a": "Avalanche C-Chain",
24826
+ "0xa869": "Avalanche Fuji Testnet",
24827
+ // Arbitrum
24614
24828
  "0xa4b1": "Arbitrum One",
24615
- "0xa": "Optimism",
24616
- "0x539": "Hardhat Local"
24829
+ "0x66eee": "Arbitrum Sepolia",
24830
+ "0x66eed": "Arbitrum Goerli",
24831
+ // Optimism
24832
+ "0xa": "Optimism Mainnet",
24833
+ "0xaa37dc": "Optimism Sepolia",
24834
+ "0x1a4": "Optimism Goerli",
24835
+ // Base
24836
+ "0x2105": "Base Mainnet",
24837
+ "0x14a34": "Base Sepolia",
24838
+ // zkSync Era
24839
+ "0x144": "zkSync Era Mainnet",
24840
+ "0x12c": "zkSync Era Sepolia",
24841
+ // Linea
24842
+ "0xe708": "Linea Mainnet",
24843
+ "0xe704": "Linea Goerli",
24844
+ // Scroll
24845
+ "0x82750": "Scroll Mainnet",
24846
+ "0x8274f": "Scroll Sepolia",
24847
+ // Fantom
24848
+ "0xfa": "Fantom Opera",
24849
+ "0xfa2": "Fantom Testnet",
24850
+ // Gnosis
24851
+ "0x64": "Gnosis Chain",
24852
+ "0x27d8": "Chiado Testnet",
24853
+ // Celo
24854
+ "0xa4ec": "Celo Mainnet",
24855
+ "0xaef3": "Celo Alfajores",
24856
+ // Blast
24857
+ "0x13e31": "Blast Mainnet",
24858
+ "0xa0c71fd": "Blast Sepolia",
24859
+ // Mantle
24860
+ "0x1388": "Mantle Mainnet",
24861
+ "0x1389": "Mantle Testnet",
24862
+ // Moonbeam
24863
+ "0x504": "Moonbeam",
24864
+ "0x505": "Moonriver",
24865
+ "0x507": "Moonbase Alpha",
24866
+ // Metis
24867
+ "0x440": "Metis Andromeda",
24868
+ // Cronos
24869
+ "0x19": "Cronos Mainnet",
24870
+ "0x152": "Cronos Testnet",
24871
+ // Local development
24872
+ "0x539": "Hardhat Local (1337)",
24873
+ "0x7a69": "Hardhat / Anvil Local"
24874
+ };
24875
+ var NATIVE_SYMBOLS = {
24876
+ "0x89": "POL",
24877
+ "0x13882": "POL",
24878
+ "0x13881": "POL",
24879
+ "0x44d": "ETH",
24880
+ "0x38": "BNB",
24881
+ "0x61": "BNB",
24882
+ "0xa86a": "AVAX",
24883
+ "0xa869": "AVAX",
24884
+ "0xfa": "FTM",
24885
+ "0xfa2": "FTM",
24886
+ "0x64": "xDAI",
24887
+ "0x27d8": "xDAI",
24888
+ "0xa4ec": "CELO",
24889
+ "0xaef3": "CELO",
24890
+ "0x504": "GLMR",
24891
+ "0x505": "MOVR",
24892
+ "0x19": "CRO",
24893
+ "0x152": "CRO"
24617
24894
  };
24618
24895
  function getNetworkName(chainId) {
24619
24896
  if (!chainId) {
@@ -24621,6 +24898,21 @@ function getNetworkName(chainId) {
24621
24898
  }
24622
24899
  return CHAIN_NAMES[chainId.toLowerCase()] ?? `Chain ${chainId}`;
24623
24900
  }
24901
+ function getNativeSymbol(chainId) {
24902
+ if (!chainId) return "ETH";
24903
+ return NATIVE_SYMBOLS[chainId.toLowerCase()] ?? "ETH";
24904
+ }
24905
+ function formatBalance(balanceHex, symbol) {
24906
+ try {
24907
+ const wei = BigInt(balanceHex);
24908
+ const ethValue = Number(wei) / 1e18;
24909
+ if (ethValue === 0) return `0 ${symbol}`;
24910
+ if (ethValue < 1e-4) return `< 0.0001 ${symbol}`;
24911
+ return `${ethValue.toFixed(4)} ${symbol}`;
24912
+ } catch {
24913
+ return `\u2014 ${symbol}`;
24914
+ }
24915
+ }
24624
24916
  function getEthereumProvider() {
24625
24917
  return window.ethereum;
24626
24918
  }
@@ -24639,6 +24931,20 @@ async function getWalletStatus(provider = getEthereumProvider()) {
24639
24931
  ]);
24640
24932
  const accounts = Array.isArray(accountsResult) ? accountsResult : [];
24641
24933
  const chainId = typeof chainIdResult === "string" ? chainIdResult : void 0;
24934
+ const nativeSymbol = getNativeSymbol(chainId);
24935
+ let balance;
24936
+ if (accounts.length > 0) {
24937
+ try {
24938
+ const balanceHex = await provider.request({
24939
+ method: "eth_getBalance",
24940
+ params: [accounts[0], "latest"]
24941
+ });
24942
+ if (typeof balanceHex === "string") {
24943
+ balance = formatBalance(balanceHex, nativeSymbol);
24944
+ }
24945
+ } catch {
24946
+ }
24947
+ }
24642
24948
  return {
24643
24949
  available: true,
24644
24950
  connected: accounts.length > 0,
@@ -24646,7 +24952,9 @@ async function getWalletStatus(provider = getEthereumProvider()) {
24646
24952
  walletName: provider.isMetaMask ? "MetaMask" : "Browser Wallet",
24647
24953
  account: accounts[0],
24648
24954
  chainId,
24649
- networkName: getNetworkName(chainId)
24955
+ networkName: getNetworkName(chainId),
24956
+ balance,
24957
+ nativeSymbol
24650
24958
  };
24651
24959
  }
24652
24960
  async function connectWallet(provider = getEthereumProvider()) {
@@ -24731,15 +25039,15 @@ function formatLastUpdated(timestamp) {
24731
25039
  });
24732
25040
  }
24733
25041
  function App() {
24734
- const [snapshot, setSnapshot] = (0, import_react.useState)(INITIAL_SNAPSHOT);
24735
- const [wallet, setWallet] = (0, import_react.useState)(EMPTY_WALLET_STATE);
24736
- const [socketConnected, setSocketConnected] = (0, import_react.useState)(false);
24737
- const [busyIds, setBusyIds] = (0, import_react.useState)([]);
24738
- const [errorMessage, setErrorMessage] = (0, import_react.useState)(null);
24739
- const socketRef = (0, import_react.useRef)(null);
24740
- const autoRunningIdsRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
25042
+ const [snapshot, setSnapshot] = (0, import_react2.useState)(INITIAL_SNAPSHOT);
25043
+ const [wallet, setWallet] = (0, import_react2.useState)(EMPTY_WALLET_STATE);
25044
+ const [socketConnected, setSocketConnected] = (0, import_react2.useState)(false);
25045
+ const [busyIds, setBusyIds] = (0, import_react2.useState)([]);
25046
+ const [errorMessage, setErrorMessage] = (0, import_react2.useState)(null);
25047
+ const socketRef = (0, import_react2.useRef)(null);
25048
+ const autoRunningIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
24741
25049
  const provider = getEthereumProvider();
24742
- (0, import_react.useEffect)(() => {
25050
+ (0, import_react2.useEffect)(() => {
24743
25051
  const socket = new WebSocket(`${window.location.origin.replace(/^http/, "ws")}/ws`);
24744
25052
  socketRef.current = socket;
24745
25053
  socket.onopen = async () => {
@@ -24769,7 +25077,7 @@ function App() {
24769
25077
  socket.close();
24770
25078
  };
24771
25079
  }, [provider]);
24772
- (0, import_react.useEffect)(() => {
25080
+ (0, import_react2.useEffect)(() => {
24773
25081
  const unsubscribe = subscribeToWallet(async () => {
24774
25082
  try {
24775
25083
  const nextWallet = await getWalletStatus(provider);
@@ -24812,7 +25120,7 @@ function App() {
24812
25120
  setBusyIds((current) => current.filter((id) => id !== request.queueId));
24813
25121
  }
24814
25122
  }
24815
- (0, import_react.useEffect)(() => {
25123
+ (0, import_react2.useEffect)(() => {
24816
25124
  if (!wallet.connected) {
24817
25125
  return;
24818
25126
  }
@@ -24900,8 +25208,8 @@ function App() {
24900
25208
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: wallet.networkName ?? wallet.chainId ?? "Not connected" })
24901
25209
  ] }),
24902
25210
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "metric", children: [
24903
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "label", children: "Wallet" }),
24904
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: wallet.connected ? wallet.walletName : "Disconnected" })
25211
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "label", children: "Balance" }),
25212
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: wallet.balance ?? (wallet.connected ? "Fetching\u2026" : "\u2014") })
24905
25213
  ] })
24906
25214
  ] }),
24907
25215
  warnings.length > 0 || errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { children: [