From 2282f332a646d7752c00bf33656081cdbc2cd79a Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 15 Oct 2026 10:13:15 -0500 Subject: [PATCH] feat(history): view the transactions involving an address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a "View transactions" action that opens the History tab pre-filtered to a chosen address. SetTransactionsAddressFilter() stores a pending filter that RenderTransactionsTab consumes on its next frame — pre-filling the search box and resetting the type filter to All — then the caller navigates to NavPage::History. This reuses the existing "prefill + navigate" pattern (cf. SetSendFromAddress) and all of History's rendering, detail and export. Triggers: - Contacts: a context-menu item and a 4th per-row action icon (copy / view-transactions / edit / delete; the trailing width is widened to fit). - Balance address list: a per-address context-menu item (own z/t addresses). - Receive tab: the recent-received rows now carry their address into the filter instead of jumping to History unfiltered. The History search also now matches from_address, so filtering by one of your own addresses catches sends FROM it, not only receives INTO it. Scoping is honest, not exhaustive: transparent is well covered, but shielded counterparties are private and the transparent sender of received funds is never recorded on-chain, so this shows transactions *involving* the address. On Lite, from_address is thin, so own-address send filtering is weaker. New i18n keys view_transactions / tt_view_transactions translated into all eight locales (no new glyphs). Builds + test suites pass on both variants. Co-Authored-By: Claude Opus 4.8 --- res/lang/de.json | 2 ++ res/lang/es.json | 2 ++ res/lang/fr.json | 2 ++ res/lang/ja.json | 2 ++ res/lang/ko.json | 2 ++ res/lang/pt.json | 2 ++ res/lang/ru.json | 2 ++ res/lang/zh.json | 2 ++ src/ui/windows/balance_components.cpp | 5 +++++ src/ui/windows/contacts_tab.cpp | 28 ++++++++++++++++++--------- src/ui/windows/receive_tab.cpp | 2 ++ src/ui/windows/transactions_tab.cpp | 21 ++++++++++++++++++++ src/ui/windows/transactions_tab.h | 13 +++++++++++++ src/util/i18n.cpp | 2 ++ 14 files changed, 78 insertions(+), 9 deletions(-) diff --git a/res/lang/de.json b/res/lang/de.json index 0e39c97..8cddb65 100644 --- a/res/lang/de.json +++ b/res/lang/de.json @@ -2156,6 +2156,7 @@ "tt_ui_opacity": "Karten- und Seitenleisten-Deckkraft (100% = vollständig undurchsichtig, niedriger = durchsichtiger)", "tt_validate": "Prüfen, ob eine DragonX-Adresse gültig ist", "tt_verbose": "Detaillierte Verbindungsdiagnosen,\\nDaemon-Status und Port-Besitzer-Info\\nin der Konsolen-Registerkarte protokollieren", + "tt_view_transactions": "Transaktionen mit dieser Adresse im Verlauf-Tab anzeigen", "tt_wallet_size_warn": "Geschützte Wallets wachsen mit den Witness-Daten jedes Notes — das Zusammenführen vieler Notes in eine Adresse verringert die Größe. Zuerst sichern.", "tt_wallets_button": "Ihre Wallet-Dateien auflisten und zwischen ihnen wechseln", "tt_website": "Die DragonX-Website öffnen", @@ -2222,6 +2223,7 @@ "view_all": "Alle anzeigen", "view_details": "Details anzeigen", "view_on_explorer": "Im Explorer anzeigen", + "view_transactions": "Transaktionen anzeigen", "waiting_for_daemon": "Warte auf Daemon-Verbindung...", "wallet": "WALLET", "wallet_degraded_notify": "Ihre Wallet wurde im eingeschränkten Modus geöffnet: Vorhandene Guthaben sind sicher und verfügbar, aber das Erstellen neuer Adressen und das Schützen von Guthaben sind deaktiviert. Sichern Sie Ihre Seed-Phrase und stellen Sie sie wieder her, um die Wallet vollständig zu reparieren.", diff --git a/res/lang/es.json b/res/lang/es.json index 703576f..4bb0fe0 100644 --- a/res/lang/es.json +++ b/res/lang/es.json @@ -2156,6 +2156,7 @@ "tt_ui_opacity": "Opacidad de tarjetas y barra lateral (100% = totalmente opaco, menor = más transparente)", "tt_validate": "Comprobar si una dirección DragonX es válida", "tt_verbose": "Registrar diagnósticos detallados de conexión,\\nestado del daemon e info de propietario de puerto\\nen la pestaña de Consola", + "tt_view_transactions": "Mostrar las transacciones relacionadas con esta dirección en la pestaña Historial", "tt_wallet_size_warn": "Las carteras blindadas crecen con los datos de testigo de cada nota — fusionar muchas notas en una sola dirección lo reduce. Haz una copia de seguridad primero.", "tt_wallets_button": "Enumera tus archivos de cartera y cambia entre ellos", "tt_website": "Abrir el sitio web de DragonX", @@ -2222,6 +2223,7 @@ "view_all": "Ver todo", "view_details": "Ver Detalles", "view_on_explorer": "Ver en Explorador", + "view_transactions": "Ver transacciones", "waiting_for_daemon": "Esperando conexión al daemon...", "wallet": "CARTERA", "wallet_degraded_notify": "Tu cartera se abrió en modo de funciones reducidas: los fondos existentes están seguros y disponibles, pero crear nuevas direcciones y blindar están desactivados. Respalda tu frase de recuperación y restáurala para reparar la cartera por completo.", diff --git a/res/lang/fr.json b/res/lang/fr.json index 529a3eb..cf13c5d 100644 --- a/res/lang/fr.json +++ b/res/lang/fr.json @@ -2156,6 +2156,7 @@ "tt_ui_opacity": "Opacité des cartes et de la barre latérale (100% = entièrement opaque, plus bas = plus transparent)", "tt_validate": "Vérifier si une adresse DragonX est valide", "tt_verbose": "Journaliser les diagnostics de connexion détaillés,\\nl'état du daemon et les informations de propriétaire de port\\ndans l'onglet Console", + "tt_view_transactions": "Afficher les transactions impliquant cette adresse dans l'onglet Historique", "tt_wallet_size_warn": "Les portefeuilles blindés grandissent avec les données de témoin de chaque note — fusionner de nombreuses notes en une seule adresse réduit cette taille. Sauvegardez d'abord.", "tt_wallets_button": "Répertoriez vos fichiers de portefeuille et passez de l'un à l'autre", "tt_website": "Ouvrir le site web DragonX", @@ -2222,6 +2223,7 @@ "view_all": "Tout afficher", "view_details": "Voir les détails", "view_on_explorer": "Voir dans l'explorateur", + "view_transactions": "Voir les transactions", "waiting_for_daemon": "En attente de la connexion au daemon...", "wallet": "PORTEFEUILLE", "wallet_degraded_notify": "Votre portefeuille s'est ouvert en mode à fonctionnalités réduites : les fonds existants sont en sécurité et dépensables, mais la création de nouvelles adresses et le blindage sont désactivés. Sauvegardez votre phrase de récupération et restaurez-la pour réparer entièrement le portefeuille.", diff --git a/res/lang/ja.json b/res/lang/ja.json index 64cbd26..ef1d586 100644 --- a/res/lang/ja.json +++ b/res/lang/ja.json @@ -2156,6 +2156,7 @@ "tt_ui_opacity": "カードとサイドバーの不透明度(100% = 完全不透明、低い = より透過)", "tt_validate": "DragonX アドレスが有効かどうかを確認", "tt_verbose": "詳細な接続診断、デーモン状態、\\nポート所有者情報をコンソールタブに記録", + "tt_view_transactions": "このアドレスに関連する取引を履歴タブに表示", "tt_wallet_size_warn": "シールドウォレットはノートごとのウィットネスデータによって肥大化します — 多数のノートを1つのアドレスに統合するとサイズを抑えられます。事前にバックアップしてください。", "tt_wallets_button": "ウォレットファイルを一覧表示して切り替えます", "tt_website": "DragonX ウェブサイトを開く", @@ -2222,6 +2223,7 @@ "view_all": "すべて表示", "view_details": "詳細を表示", "view_on_explorer": "エクスプローラーで表示", + "view_transactions": "取引を表示", "waiting_for_daemon": "デーモン接続を待機中...", "wallet": "ウォレット", "wallet_degraded_notify": "ウォレットは機能制限モードで開かれました。既存の資金は安全で使用可能ですが、新しいアドレスの作成とシールドは無効になっています。ウォレットを完全に修復するには、シードフレーズをバックアップしてそこから復元してください。", diff --git a/res/lang/ko.json b/res/lang/ko.json index 53a92d5..f1b1fa4 100644 --- a/res/lang/ko.json +++ b/res/lang/ko.json @@ -2156,6 +2156,7 @@ "tt_ui_opacity": "카드 및 사이드바 불투명도 (100% = 완전 불투명, 낮을수록 더 투명)", "tt_validate": "DragonX 주소가 유효한지 확인", "tt_verbose": "콘솔 탭에 상세 연결 진단,\\n데몬 상태 및 포트 소유자 정보 기록", + "tt_view_transactions": "이 주소와 관련된 거래를 기록 탭에 표시", "tt_wallet_size_warn": "보호 지갑은 각 노트의 위트니스 데이터로 인해 커집니다 — 여러 노트를 하나의 주소로 병합하면 크기를 줄일 수 있습니다. 먼저 백업하세요.", "tt_wallets_button": "지갑 파일 목록을 보고 전환합니다", "tt_website": "DragonX 웹사이트 열기", @@ -2222,6 +2223,7 @@ "view_all": "모두 보기", "view_details": "상세 보기", "view_on_explorer": "탐색기에서 보기", + "view_transactions": "거래 보기", "waiting_for_daemon": "데몬 연결 대기 중...", "wallet": "지갑", "wallet_degraded_notify": "지갑이 기능 제한 모드로 열렸습니다. 기존 자금은 안전하며 사용할 수 있지만, 새 주소 생성과 보호 처리는 비활성화되어 있습니다. 시드 문구를 백업하고 이를 통해 복원하면 지갑을 완전히 복구할 수 있습니다.", diff --git a/res/lang/pt.json b/res/lang/pt.json index 10fcead..67df707 100644 --- a/res/lang/pt.json +++ b/res/lang/pt.json @@ -2156,6 +2156,7 @@ "tt_ui_opacity": "Opacidade de cartões e barra lateral (100% = totalmente opaco, menor = mais transparente)", "tt_validate": "Verificar se um endereço DragonX é válido", "tt_verbose": "Registrar diagnósticos detalhados de conexão,\\nestado do daemon e info de proprietário de porta\\nna aba Console", + "tt_view_transactions": "Mostrar as transações que envolvem este endereço na aba Histórico", "tt_wallet_size_warn": "Carteiras blindadas crescem com os dados de testemunha de cada nota — mesclar várias notas em um único endereço reduz esse tamanho. Faça backup antes.", "tt_wallets_button": "Liste os arquivos de carteira e alterne entre eles", "tt_website": "Abrir o site do DragonX", @@ -2222,6 +2223,7 @@ "view_all": "Ver Tudo", "view_details": "Ver Detalhes", "view_on_explorer": "Ver no Explorador", + "view_transactions": "Ver transações", "waiting_for_daemon": "Aguardando conexão com o daemon...", "wallet": "CARTEIRA", "wallet_degraded_notify": "Sua carteira foi aberta em modo de função reduzida: os fundos existentes estão seguros e disponíveis para gasto, mas a criação de novos endereços e o blindamento estão desativados. Faça backup da sua frase de recuperação e restaure-a para reparar totalmente a carteira.", diff --git a/res/lang/ru.json b/res/lang/ru.json index 074e0bc..cb30f28 100644 --- a/res/lang/ru.json +++ b/res/lang/ru.json @@ -2156,6 +2156,7 @@ "tt_ui_opacity": "Непрозрачность карточек и боковой панели (100% = полностью непрозрачно, ниже = прозрачнее)", "tt_validate": "Проверить, действителен ли адрес DragonX", "tt_verbose": "Записывать подробную диагностику подключений,\\nсостояние демона и информацию о владельце порта\\nна вкладке Консоль", + "tt_view_transactions": "Показать транзакции с этим адресом на вкладке «История»", "tt_wallet_size_warn": "Защищённые кошельки растут за счёт данных witness каждой записи — объединение множества записей в один адрес уменьшает его размер. Сначала сделайте резервную копию.", "tt_wallets_button": "Показать файлы кошельков и переключаться между ними", "tt_website": "Открыть сайт DragonX", @@ -2222,6 +2223,7 @@ "view_all": "Показать всё", "view_details": "Подробнее", "view_on_explorer": "Посмотреть в обозревателе", + "view_transactions": "Показать транзакции", "waiting_for_daemon": "Ожидание подключения к daemon...", "wallet": "КОШЕЛЁК", "wallet_degraded_notify": "Ваш кошелёк открыт в режиме с ограниченной функциональностью: существующие средства в безопасности и доступны для расходования, но создание новых адресов и защита средств отключены. Сделайте резервную копию сид-фразы и восстановите из неё кошелёк, чтобы полностью его исправить.", diff --git a/res/lang/zh.json b/res/lang/zh.json index c0f62e3..a6c7cd9 100644 --- a/res/lang/zh.json +++ b/res/lang/zh.json @@ -2156,6 +2156,7 @@ "tt_ui_opacity": "卡片和侧边栏不透明度(100% = 完全不透明,越低越透明)", "tt_validate": "检查 DragonX 地址是否有效", "tt_verbose": "将详细连接诊断、守护进程状态\\n和端口所有者信息记录到控制台选项卡", + "tt_view_transactions": "在历史记录标签页中显示涉及此地址的交易", "tt_wallet_size_warn": "屏蔽钱包会随每个票据的见证数据增长——将多个票据合并到一个地址可以缩小体积。请先备份。", "tt_wallets_button": "列出您的钱包文件并在它们之间切换", "tt_website": "打开 DragonX 网站", @@ -2222,6 +2223,7 @@ "view_all": "查看全部", "view_details": "查看详情", "view_on_explorer": "在浏览器中查看", + "view_transactions": "查看交易", "waiting_for_daemon": "等待守护进程连接...", "wallet": "钱包", "wallet_degraded_notify": "您的钱包以受限功能模式打开:现有资金安全且可用,但无法创建新地址或进行屏蔽操作。请备份助记词并用它恢复钱包以完全修复。", diff --git a/src/ui/windows/balance_components.cpp b/src/ui/windows/balance_components.cpp index dd451d4..8963ff5 100644 --- a/src/ui/windows/balance_components.cpp +++ b/src/ui/windows/balance_components.cpp @@ -13,6 +13,7 @@ #include "address_transfer_dialog.h" #include "mining_tab_helpers.h" #include "send_tab.h" +#include "transactions_tab.h" #include "../../app.h" #include "../../config/settings.h" @@ -693,6 +694,10 @@ void RenderSharedAddressList(App* app, float listH, float availW, SetSendFromAddress(addr.address); app->setCurrentPage(NavPage::Send); } + if (ImGui::MenuItem(TR("view_transactions"))) { + SetTransactionsAddressFilter(addr.address); + app->setCurrentPage(NavPage::History); + } ImGui::Separator(); if (ImGui::MenuItem(TR("set_label"))) { AddressLabelDialog::show(app, addr.address, row.isZ); diff --git a/src/ui/windows/contacts_tab.cpp b/src/ui/windows/contacts_tab.cpp index 90ba1d2..c018aef 100644 --- a/src/ui/windows/contacts_tab.cpp +++ b/src/ui/windows/contacts_tab.cpp @@ -17,6 +17,8 @@ #include "../../util/texture_loader.h" #include "../../util/platform.h" #include "image_picker.h" +#include "transactions_tab.h" +#include "../sidebar.h" #include "../layout.h" #include "imgui.h" #include @@ -530,6 +532,11 @@ void RenderContactsTab(App* app) s_show_edit_dialog = true; s_focus_edit_field = true; }; + auto doViewTransactions = [&]() { + if (!selValid()) return; + SetTransactionsAddressFilter(book.entries()[s_selected_index].address); + app->setCurrentPage(ui::NavPage::History); + }; // Add/edit form — a modal popup layered over the tab, with a live contact preview and an avatar // picker (default Z/T badge, a Material icon, or a custom image via the in-app image picker). @@ -1367,7 +1374,7 @@ void RenderContactsTab(App* app) // Label (line 1) + muted address (line 2) — reserve trailing room for the globe + actions. const float actHit = icoF->LegacySize + 8.0f * dp; // per-action square hit area const float globeW = entry.isGlobal() ? (icoF->LegacySize + 8.0f * dp) : 0.0f; - const float trailW = 3.0f * actHit + 6.0f * dp + globeW; + const float trailW = 4.0f * actHit + 8.0f * dp + globeW; // copy / view-tx / edit / delete const float cy = mn.y + rowH * 0.5f; const float tx = (s_contactsShape == 2) ? (mn.x + tabW + pad) : (mn.x + pad + avR * 2.0f + pad); @@ -1399,10 +1406,11 @@ void RenderContactsTab(App* app) rightX -= gs.x + 8.0f * dp; } if (rowHovered || selected) { - const char* actGlyph[3] = { ICON_MD_CONTENT_COPY, ICON_MD_EDIT, ICON_MD_DELETE }; - const char* actTip[3] = { TR("copy_address"), TR("edit"), TR("delete") }; + // Order (left->right): copy, view-transactions, edit, delete. Drawn right-to-left. + const char* actGlyph[4] = { ICON_MD_CONTENT_COPY, ICON_MD_RECEIPT_LONG, ICON_MD_EDIT, ICON_MD_DELETE }; + const char* actTip[4] = { TR("copy_address"), TR("view_transactions"), TR("edit"), TR("delete") }; float ax = rightX - actHit; // rightmost action (delete), just left of the globe - for (int a = 2; a >= 0; --a) { + for (int a = 3; a >= 0; --a) { ImGui::SetCursorScreenPos(ImVec2(ax, cy - actHit * 0.5f)); ImGui::PushID(a + 100); const bool aClk = ImGui::InvisibleButton("##act", ImVec2(actHit, actHit)); @@ -1411,12 +1419,13 @@ void RenderContactsTab(App* app) if (aClk) { s_selected_index = static_cast(i); if (a == 0) doCopy(); - else if (a == 1) openEdit(); + else if (a == 1) doViewTransactions(); + else if (a == 2) openEdit(); // Delete mutates book.entries() (removeEntry -> erase) — deferring it past the // row loop avoids iterating a vector we shrink mid-loop (OOB on later rows). else rowDeleteRequested = true; } - const bool armedDel = (a == 2 && s_confirm_delete_idx == static_cast(i)); + const bool armedDel = (a == 3 && s_confirm_delete_idx == static_cast(i)); const ImU32 acol = armedDel ? material::ReadableError() : aHov ? material::OnSurface() : material::OnSurfaceMedium(); @@ -1454,10 +1463,11 @@ void RenderContactsTab(App* app) // Shared right-click context menu (opened by either view's row right-click; acts on the selection). if (openContextMenu) ImGui::OpenPopup("##contactCtx"); if (ImGui::BeginPopup("##contactCtx")) { - if (ImGui::MenuItem(TR("copy_address"))) doCopy(); - if (ImGui::MenuItem(TR("edit"))) openEdit(); + if (ImGui::MenuItem(TR("copy_address"))) doCopy(); + if (ImGui::MenuItem(TR("view_transactions"))) doViewTransactions(); + if (ImGui::MenuItem(TR("edit"))) openEdit(); ImGui::Separator(); - if (ImGui::MenuItem(TR("delete"))) doDelete(); + if (ImGui::MenuItem(TR("delete"))) doDelete(); ImGui::EndPopup(); } diff --git a/src/ui/windows/receive_tab.cpp b/src/ui/windows/receive_tab.cpp index dcd5d29..86f77cd 100644 --- a/src/ui/windows/receive_tab.cpp +++ b/src/ui/windows/receive_tab.cpp @@ -20,6 +20,7 @@ #include "../../wallet/lite_wallet_controller.h" #include "../../ui/widgets/qr_code.h" #include "../sidebar.h" +#include "transactions_tab.h" #include "../layout.h" #include "../schema/ui_schema.h" #include "../material/type.h" @@ -535,6 +536,7 @@ static void RenderRecentReceived(const AddressInfo& /* addr */, S.drawElement("tabs.balance", "row-hover-rounding").sizeOr(4.0f)); ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); if (ImGui::IsMouseClicked(0)) { + if (!tx.address.empty()) SetTransactionsAddressFilter(tx.address); app->setCurrentPage(ui::NavPage::History); } } diff --git a/src/ui/windows/transactions_tab.cpp b/src/ui/windows/transactions_tab.cpp index 10d2ccd..91e6a12 100644 --- a/src/ui/windows/transactions_tab.cpp +++ b/src/ui/windows/transactions_tab.cpp @@ -45,6 +45,17 @@ static bool containsIgnoreCase(const std::string& str, const std::string& search return dragonx::util::containsIgnoreCase(str, search); } +// Pending "view transactions for this address" request. Set by SetTransactionsAddressFilter() +// from Contacts / the Balance address list / the Receive tab, consumed on the next +// RenderTransactionsTab frame to pre-fill the search box (see the consume block below). +static std::string s_pending_addr_filter; +static bool s_pending_addr_filter_set = false; + +void SetTransactionsAddressFilter(const std::string& address) { + s_pending_addr_filter = address; + s_pending_addr_filter_set = true; +} + // A display-ready transaction that may be a merged autoshield pair. // For non-merged entries, send_idx or recv_idx is -1. struct DisplayTx { @@ -313,6 +324,15 @@ void RenderTransactionsTab(App* app) // Search & Filter bar // ================================================================ static char search_filter[128] = ""; + // Consume a pending "view transactions for this address" request: pre-fill the search box + // with the address and reset the type filter to All so no matching row is hidden. The + // changed search_str resets pagination/expansion via filterInputsChanged() below. + if (s_pending_addr_filter_set) { + strncpy(search_filter, s_pending_addr_filter.c_str(), sizeof(search_filter) - 1); + search_filter[sizeof(search_filter) - 1] = '\0'; + type_filter = 0; + s_pending_addr_filter_set = false; + } float searchMaxW = (searchInput.maxWidth >= 0) ? searchInput.maxWidth : 300.0f; float searchRatio = (searchInput.widthRatio >= 0) ? searchInput.widthRatio : 0.30f; float searchWidth = std::min(searchMaxW * hs, availWidth * searchRatio); @@ -530,6 +550,7 @@ void RenderTransactionsTab(App* app) } if (!search_str.empty()) { if (!containsIgnoreCase(dtx.address, search_str) && + !containsIgnoreCase(dtx.from_address, search_str) && !containsIgnoreCase(dtx.txid, search_str) && !containsIgnoreCase(dtx.memo, search_str) && !(dtx.is_shield && containsIgnoreCase(std::string("shielded"), search_str))) { diff --git a/src/ui/windows/transactions_tab.h b/src/ui/windows/transactions_tab.h index 94a2b2a..8229613 100644 --- a/src/ui/windows/transactions_tab.h +++ b/src/ui/windows/transactions_tab.h @@ -4,6 +4,8 @@ #pragma once +#include + namespace dragonx { class App; @@ -15,5 +17,16 @@ namespace ui { */ void RenderTransactionsTab(App* app); +/** + * @brief Pre-filter the Transactions (History) tab by an address. + * + * Sets the History search box to @p address and resets the type filter to All on the next + * render, then the caller navigates to the History tab. Used by the "View transactions" + * actions in Contacts, the Balance address list, and the Receive tab. Because shielded / + * received-from attribution is incomplete, this shows transactions *involving* the address, + * not a guaranteed-complete set. + */ +void SetTransactionsAddressFilter(const std::string& address); + } // namespace ui } // namespace dragonx diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 11834ea..150f699 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1195,6 +1195,8 @@ void I18n::loadBuiltinEnglish() strings_["mature"] = "Mature"; strings_["copy_txid"] = "Copy TxID"; strings_["view_details"] = "View Details"; + strings_["view_transactions"] = "View Transactions"; + strings_["tt_view_transactions"] = "Show transactions involving this address in the History tab"; strings_["full_details"] = "Full Details"; strings_["showing_transactions"] = "Showing %d\xe2\x80\x93%d of %d transactions (total: %zu)"; strings_["txs_count"] = "%d txs";