From 8f8ecae1bb169249aefbc9c81ed074116db6f489 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Wed, 9 Jul 2025 22:12:12 +1200 Subject: [PATCH 01/17] fix: cleanup --- .../src/App/Transactions/TransactionCard.tsx | 26 +++++++- .../App/Transactions/TransactionHistory.tsx | 61 +++++++++++++------ 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/apps/namadillo/src/App/Transactions/TransactionCard.tsx b/apps/namadillo/src/App/Transactions/TransactionCard.tsx index f3db54d834..c223674393 100644 --- a/apps/namadillo/src/App/Transactions/TransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionCard.tsx @@ -41,7 +41,12 @@ export function getToken( txn: Tx["tx"], nativeToken: string ): string | undefined { - if (txn?.kind === "bond" || txn?.kind === "unbond") return nativeToken; + if ( + txn?.kind === "bond" || + txn?.kind === "unbond" || + txn?.kind === "redelegation" + ) + return nativeToken; let parsed; try { parsed = txn?.data ? JSON.parse(txn.data) : undefined; @@ -81,6 +86,7 @@ const getBondOrUnbondTransactionInfo = ( receiver: parsed.validator, }; }; + const getTransactionInfo = ( tx: Tx["tx"], transparentAddress: string @@ -88,6 +94,20 @@ const getTransactionInfo = ( if (!tx?.data) return undefined; const parsed = typeof tx.data === "string" ? JSON.parse(tx.data) : tx.data; + + // Handle redelegation case + if ( + tx.kind === "redelegation" && + parsed.src_validator && + parsed.dest_validator + ) { + return { + amount: BigNumber(parsed.amount), + sender: parsed.src_validator, + receiver: parsed.dest_validator, + }; + } + const sections: RawDataSection[] = Array.isArray(parsed) ? parsed : [parsed]; const target = sections.find((s) => s.targets?.length); const source = sections.find((s) => s.sources?.length); @@ -219,6 +239,9 @@ export const TransactionCard = ({ if (isInternalUnshield) return "Unshielding Transfer"; if (isReceived) return "Receive"; if (kind.startsWith("ibc")) return "IBC Transfer"; + if (kind === "redelegation") return "Redelegate"; + if (kind === "voteProposal") return "Vote"; + if (kind === "withdraw") return "Withdraw"; if (kind === "bond") return "Stake"; if (kind === "unbond") return "Unstake"; if (kind === "claimRewards") return "Claim Rewards"; @@ -230,7 +253,6 @@ export const TransactionCard = ({ }; const displayAmount = getDisplayAmount(); - return (
{ timestamp: new Date(transaction.updatedAt).getTime() / 1000, })); - const handleFiltering = useCallback( - (transaction: TransactionHistoryType): boolean => { - const transactionKind = transaction.tx?.kind ?? ""; - if (filter.toLowerCase() === "all") { - return transferKindOptions.includes(transactionKind); - } else if (filter === "received") { - return transaction.kind === "received"; - } else if (filter === "transfer") { - return [ - "transparentTransfer", - "shieldingTransfer", - "unshieldingTransfer", - "shieldedTransfer", - ].includes(transactionKind); - } else if (filter === "ibc") { - return transactionKind.startsWith("ibc"); - } else return transactionKind === filter; - }, - [filter] - ); + const handleFiltering = (transaction: TransactionHistoryType): boolean => { + const transactionKind = transaction.tx?.kind ?? ""; + if (filter.toLowerCase() === "all") { + return transferKindOptions.includes(transactionKind); + } else if (filter === "received") { + return transaction.kind === "received"; + } else if (filter === "redelegation") { + return transactionKind === "redelegation"; + } else if (filter === "vote") { + return transactionKind === "voteProposal"; + } else if (filter === "withdraw") { + return transactionKind === "withdraw"; + } else if (filter === "transfer") { + return [ + "transparentTransfer", + "shieldingTransfer", + "unshieldingTransfer", + "shieldedTransfer", + ].includes(transactionKind); + } else if (filter === "ibc") { + return transactionKind.startsWith("ibc"); + } else return transactionKind === filter; + }; const JSONstringifyOrder = useCallback((obj: unknown): string => { const allKeys = new Set(); @@ -246,6 +252,21 @@ export const TransactionHistory = (): JSX.Element => { value: "Unstake", ariaLabel: "Unstake", }, + { + id: "redelegation", + value: "Redelegate", + ariaLabel: "Redelegate", + }, + { + id: "voteProposal", + value: "Vote", + ariaLabel: "Vote", + }, + { + id: "withdraw", + value: "Withdraw", + ariaLabel: "Withdraw", + }, ]} />
From 8eefec556afe58f2a9ce02d8f0b88f0409cf792b Mon Sep 17 00:00:00 2001 From: neocybereth Date: Wed, 9 Jul 2025 22:33:43 +1200 Subject: [PATCH 02/17] fix: cleanup --- .../src/App/Transactions/TransactionCard.tsx | 47 ++++++++++++++----- .../App/Transactions/TransactionHistory.tsx | 2 +- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/apps/namadillo/src/App/Transactions/TransactionCard.tsx b/apps/namadillo/src/App/Transactions/TransactionCard.tsx index c223674393..f735e4fa33 100644 --- a/apps/namadillo/src/App/Transactions/TransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionCard.tsx @@ -87,6 +87,18 @@ const getBondOrUnbondTransactionInfo = ( }; }; +const getRedelegationTransactionInfo = ( + tx: Tx["tx"] +): { amount: BigNumber; sender?: string; receiver?: string } | undefined => { + if (!tx?.data) return undefined; + const parsed = typeof tx.data === "string" ? JSON.parse(tx.data) : tx.data; + return { + amount: BigNumber(parsed.amount), + sender: parsed.src_validator, + receiver: parsed.dest_validator, + }; +}; + const getTransactionInfo = ( tx: Tx["tx"], transparentAddress: string @@ -96,17 +108,8 @@ const getTransactionInfo = ( const parsed = typeof tx.data === "string" ? JSON.parse(tx.data) : tx.data; // Handle redelegation case - if ( - tx.kind === "redelegation" && - parsed.src_validator && - parsed.dest_validator - ) { - return { - amount: BigNumber(parsed.amount), - sender: parsed.src_validator, - receiver: parsed.dest_validator, - }; - } + if (tx.kind === "redelegation") return getRedelegationTransactionInfo(tx); + // if(tx.kind === "voteProposal") return getVoteTransactionInfo(tx); const sections: RawDataSection[] = Array.isArray(parsed) ? parsed : [parsed]; const target = sections.find((s) => s.targets?.length); @@ -143,6 +146,8 @@ export const TransactionCard = ({ const isBondingOrUnbondingTransaction = ["bond", "unbond"].includes( transactionTopLevel?.tx?.kind ?? "" ); + const isRedelegationTransaction = + transactionTopLevel?.tx?.kind === "redelegation"; const { data: accounts } = useAtomValue(allDefaultAccountsAtom); const transparentAddress = @@ -160,6 +165,12 @@ export const TransactionCard = ({ const transactionFailed = transaction?.exitCode === "rejected"; const validators = useAtomValue(allValidatorsAtom); const validator = validators?.data?.find((v) => v.address === receiver); + const redelegationSource = validators?.data?.find( + (v) => v.address === txnInfo?.sender + ); + const redelegationTarget = validators?.data?.find( + (v) => v.address === txnInfo?.receiver + ); const getDisplayAmount = (): BigNumber => { if (!txnInfo?.amount) { @@ -340,6 +351,13 @@ export const TransactionCard = ({ Shielded + : isRedelegationTransaction ? + redelegationSource?.imageUrl ? + + : redelegationSource?.alias :
{renderKeplrIcon(sender ?? "")} {shortenAddress(sender ?? "", 10, 10)} @@ -365,6 +383,13 @@ export const TransactionCard = ({ className="w-9 h-9 mt-1 rounded-full" /> : shortenAddress(receiver ?? "", 10, 10) + : isRedelegationTransaction ? + redelegationTarget?.imageUrl ? + + : redelegationTarget?.alias :
{renderKeplrIcon(receiver ?? "")} {shortenAddress(receiver ?? "", 10, 10)} diff --git a/apps/namadillo/src/App/Transactions/TransactionHistory.tsx b/apps/namadillo/src/App/Transactions/TransactionHistory.tsx index 47805cf3ee..a10517e69e 100644 --- a/apps/namadillo/src/App/Transactions/TransactionHistory.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionHistory.tsx @@ -217,7 +217,7 @@ export const TransactionHistory = (): JSX.Element => { arrowContainerProps={{ className: "right-4" }} listContainerProps={{ className: - "w-[200px] mt-2 border border-white left-0 transform-none", + "w-[200px] mt-2 border border-white left-0 transform-none max-h-[200px] overflow-y-auto dark-scrollbar", }} listItemProps={{ className: "text-sm border-0 py-0 [&_label]:py-1.5", From 443fdb41dd0929270a4d1a04c2a73039ed7d3b20 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Wed, 9 Jul 2025 23:29:22 +1200 Subject: [PATCH 03/17] fix: mega cleanup and optimize --- .../src/App/Transactions/TransactionCard.tsx | 674 ++++++++++++------ .../App/Transactions/TransactionHistory.tsx | 1 + 2 files changed, 468 insertions(+), 207 deletions(-) diff --git a/apps/namadillo/src/App/Transactions/TransactionCard.tsx b/apps/namadillo/src/App/Transactions/TransactionCard.tsx index f735e4fa33..e1ec4f11ce 100644 --- a/apps/namadillo/src/App/Transactions/TransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionCard.tsx @@ -10,6 +10,7 @@ import { import { allDefaultAccountsAtom } from "atoms/accounts"; import { nativeTokenAddressAtom } from "atoms/chain"; import { namadaRegistryChainAssetsMapAtom } from "atoms/integrations"; +import { proposalFamily } from "atoms/proposals"; import { TransactionHistory as TransactionHistoryType } from "atoms/transactions/atoms"; import { allValidatorsAtom } from "atoms/validators"; import BigNumber from "bignumber.js"; @@ -20,7 +21,9 @@ import { IoCheckmarkCircleOutline, IoCloseCircleOutline, } from "react-icons/io5"; +import { useNavigate } from "react-router-dom"; import { twMerge } from "tailwind-merge"; +import { NamadaAsset } from "types"; import { isNamadaAsset, toDisplayAmount } from "utils"; import keplrSvg from "../../integrations/assets/keplr.svg"; @@ -37,6 +40,17 @@ type BondData = { validator: string; }; +type VoteTransactionInfo = { + proposalId: string; + vote: string; +}; + +type TransactionInfo = { + amount: BigNumber; + sender?: string; + receiver?: string; +}; + export function getToken( txn: Tx["tx"], nativeToken: string @@ -69,9 +83,20 @@ export function getToken( return undefined; } +const getVoteTransactionInfo = ( + tx: Tx["tx"] +): VoteTransactionInfo | undefined => { + if (!tx?.data) return undefined; + const parsed = typeof tx.data === "string" ? JSON.parse(tx.data) : tx.data; + return { + proposalId: parsed.id, + vote: parsed.vote, + }; +}; + const getBondOrUnbondTransactionInfo = ( tx: Tx["tx"] -): { amount: BigNumber; sender?: string; receiver?: string } | undefined => { +): TransactionInfo | undefined => { if (!tx?.data) return undefined; let parsed: BondData; @@ -89,7 +114,7 @@ const getBondOrUnbondTransactionInfo = ( const getRedelegationTransactionInfo = ( tx: Tx["tx"] -): { amount: BigNumber; sender?: string; receiver?: string } | undefined => { +): TransactionInfo | undefined => { if (!tx?.data) return undefined; const parsed = typeof tx.data === "string" ? JSON.parse(tx.data) : tx.data; return { @@ -102,15 +127,11 @@ const getRedelegationTransactionInfo = ( const getTransactionInfo = ( tx: Tx["tx"], transparentAddress: string -): { amount: BigNumber; sender?: string; receiver?: string } | undefined => { +): TransactionInfo | undefined => { if (!tx?.data) return undefined; const parsed = typeof tx.data === "string" ? JSON.parse(tx.data) : tx.data; - // Handle redelegation case - if (tx.kind === "redelegation") return getRedelegationTransactionInfo(tx); - // if(tx.kind === "voteProposal") return getVoteTransactionInfo(tx); - const sections: RawDataSection[] = Array.isArray(parsed) ? parsed : [parsed]; const target = sections.find((s) => s.targets?.length); const source = sections.find((s) => s.sources?.length); @@ -134,136 +155,116 @@ const getTransactionInfo = ( return amount ? { amount, sender, receiver } : undefined; }; -export const TransactionCard = ({ - tx: transactionTopLevel, -}: Props): JSX.Element => { - const transaction = transactionTopLevel.tx; +// Common hooks and utilities for all card types +const useTransactionCardData = ( + tx: Tx +): { + transaction: Tx["tx"]; + asset: NamadaAsset | undefined; + transparentAddress: string; + transactionFailed: boolean; + validators: ReturnType>; +} => { + const transaction = tx.tx; const nativeToken = useAtomValue(nativeTokenAddressAtom).data; const chainAssetsMap = useAtomValue(namadaRegistryChainAssetsMapAtom); const token = getToken(transaction, nativeToken ?? ""); - const asset = token ? chainAssetsMap.data?.[token] : undefined; - const isBondingOrUnbondingTransaction = ["bond", "unbond"].includes( - transactionTopLevel?.tx?.kind ?? "" - ); - const isRedelegationTransaction = - transactionTopLevel?.tx?.kind === "redelegation"; const { data: accounts } = useAtomValue(allDefaultAccountsAtom); - const transparentAddress = accounts?.find((acc) => isTransparentAddress(acc.address))?.address ?? ""; - - const txnInfo = - isBondingOrUnbondingTransaction ? - getBondOrUnbondTransactionInfo(transaction) - : getTransactionInfo(transaction, transparentAddress); - const receiver = txnInfo?.receiver; - const sender = txnInfo?.sender; - const isReceived = transactionTopLevel?.kind === "received"; - const isInternalUnshield = - transactionTopLevel?.kind === "received" && isMaspAddress(sender ?? ""); const transactionFailed = transaction?.exitCode === "rejected"; const validators = useAtomValue(allValidatorsAtom); - const validator = validators?.data?.find((v) => v.address === receiver); - const redelegationSource = validators?.data?.find( - (v) => v.address === txnInfo?.sender - ); - const redelegationTarget = validators?.data?.find( - (v) => v.address === txnInfo?.receiver - ); - const getDisplayAmount = (): BigNumber => { - if (!txnInfo?.amount) { - return BigNumber(0); - } - if (!asset) { - return txnInfo.amount; - } + return { + transaction, + asset, + transparentAddress, + transactionFailed, + validators, + }; +}; - // This is a temporary hack b/c NAM amounts are mixed in nam and unam for indexer before 3.2.0 - // Whenever the migrations are run and all transactions are in micro units we need to remove this - // before 3.2.0 -> mixed - // after 3.2.0 -> unam - const guessIsDisplayAmount = (): boolean => { - // Only check Namada tokens, not other chain tokens - if (!isNamadaAsset(asset)) { - return false; - } +const getDisplayAmount = ( + txnInfo: TransactionInfo | undefined, + asset: NamadaAsset | undefined, + transactionTopLevel: Tx +): BigNumber => { + if (!txnInfo?.amount) { + return BigNumber(0); + } + if (!asset) { + return txnInfo.amount; + } - // This is a fixed flag date that most operator have already upgraded to - // indexer 3.2.0, meaning all transactions after this time are safe - const timeFlag = new Date("2025-06-18T00:00:00").getTime() / 1000; - const txTimestamp = transactionTopLevel.timestamp; - if (txTimestamp && txTimestamp > timeFlag) { - return false; - } + // This is a temporary hack b/c NAM amounts are mixed in nam and unam for indexer before 3.2.0 + // Whenever the migrations are run and all transactions are in micro units we need to remove this + // before 3.2.0 -> mixed + // after 3.2.0 -> unam + const guessIsDisplayAmount = (): boolean => { + // Only check Namada tokens, not other chain tokens + if (!isNamadaAsset(asset)) { + return false; + } - // If the amount contains the float dot, like "1.000000", it's nam - const hasFloatAmount = (): boolean => { - try { - const stringData = transactionTopLevel.tx?.data; - const objData = stringData ? JSON.parse(stringData) : {}; - return [...objData.sources, ...objData.targets].find( - ({ amount }: { amount: string }) => amount.includes(".") - ); - } catch { - return false; - } - }; - if (hasFloatAmount()) { - return true; - } + // This is a fixed flag date that most operator have already upgraded to + // indexer 3.2.0, meaning all transactions after this time are safe + const timeFlag = new Date("2025-06-18T00:00:00").getTime() / 1000; + const txTimestamp = transactionTopLevel.timestamp; + if (txTimestamp && txTimestamp > timeFlag) { + return false; + } - // if it's a huge amount, it should be unam - if (txnInfo.amount.gte(new BigNumber(1_000_000))) { + // If the amount contains the float dot, like "1.000000", it's nam + const hasFloatAmount = (): boolean => { + try { + const stringData = transactionTopLevel.tx?.data; + const objData = stringData ? JSON.parse(stringData) : {}; + return [...objData.sources, ...objData.targets].find( + ({ amount }: { amount: string }) => amount.includes(".") + ); + } catch { return false; } + }; + if (hasFloatAmount()) { + return true; + } - // if it's a small amount, it should be nam - if (txnInfo.amount.lte(new BigNumber(10))) { - return true; - } - - // if has no more hints, just accept the data as it is + // if it's a huge amount, it should be unam + if (txnInfo.amount.gte(new BigNumber(1_000_000))) { return false; - }; + } - const isAlreadyDisplayAmount = guessIsDisplayAmount(); - if (isAlreadyDisplayAmount) { - // Do not transform to display amount in case it was already saved as display amount - return txnInfo.amount; + // if it's a small amount, it should be nam + if (txnInfo.amount.lte(new BigNumber(10))) { + return true; } - return toDisplayAmount(asset, txnInfo.amount); + // if has no more hints, just accept the data as it is + return false; }; - const renderKeplrIcon = (address: string): JSX.Element | null => { - if (isShieldedAddress(address)) return null; - if (isTransparentAddress(address)) return null; - return ; - }; + const isAlreadyDisplayAmount = guessIsDisplayAmount(); + if (isAlreadyDisplayAmount) { + // Do not transform to display amount in case it was already saved as display amount + return txnInfo.amount; + } - const getTitle = (tx: Tx["tx"]): string => { - const kind = tx?.kind; + return toDisplayAmount(asset, txnInfo.amount); +}; - if (!kind) return "Unknown"; - if (isInternalUnshield) return "Unshielding Transfer"; - if (isReceived) return "Receive"; - if (kind.startsWith("ibc")) return "IBC Transfer"; - if (kind === "redelegation") return "Redelegate"; - if (kind === "voteProposal") return "Vote"; - if (kind === "withdraw") return "Withdraw"; - if (kind === "bond") return "Stake"; - if (kind === "unbond") return "Unstake"; - if (kind === "claimRewards") return "Claim Rewards"; - if (kind === "transparentTransfer") return "Transparent Transfer"; - if (kind === "shieldingTransfer") return "Shielding Transfer"; - if (kind === "unshieldingTransfer") return "Unshielding Transfer"; - if (kind === "shieldedTransfer") return "Shielded Transfer"; - return "Transfer"; - }; +const renderKeplrIcon = (address: string): JSX.Element | null => { + if (isShieldedAddress(address)) return null; + if (isTransparentAddress(address)) return null; + return ; +}; - const displayAmount = getDisplayAmount(); +const TransactionCardContainer: React.FC<{ + children: React.ReactNode; + transactionFailed: boolean; + hasValidatorImage?: boolean; +}> = ({ children, hasValidatorImage = false }) => { return (
-
- + ); +}; + +const TransactionHeader: React.FC<{ + transactionFailed: boolean; + title: string; + wrapperId?: string; + timestamp?: number; +}> = ({ transactionFailed, title, wrapperId, timestamp }) => { + return ( +
+ + {!transactionFailed && ( + + )} + {transactionFailed && ( + + )} + + +
+

- {!transactionFailed && ( - - )} - {transactionFailed && ( - - )} - - -
-

+ + + Copy transaction hash + +

+

+

+ {timestamp ? + new Date(timestamp * 1000) + .toLocaleString("en-US", { + day: "2-digit", + month: "2-digit", + year: "2-digit", + hour: "2-digit", + minute: "2-digit", }) - )} - > - {transactionFailed && "Failed"} {getTitle(transaction)}{" "} -
- - - Copy transaction hash - + .replace(",", "") + : "-"} +

+
+
+ ); +}; + +const TransactionAmount: React.FC<{ + asset: NamadaAsset | undefined; + amount: BigNumber; +}> = ({ asset, amount }) => { + return ( +
+
+ +
+ +
+ ); +}; + +const BondUnbondTransactionCard: React.FC = ({ tx }) => { + const { transaction, asset, transactionFailed, validators } = + useTransactionCardData(tx); + const txnInfo = getBondOrUnbondTransactionInfo(transaction); + const displayAmount = getDisplayAmount(txnInfo, asset, tx); + const validator = validators?.data?.find( + (v) => v.address === txnInfo?.receiver + ); + + const getTitle = (): string => { + if (transaction?.kind === "bond") return "Stake"; + if (transaction?.kind === "unbond") return "Unstake"; + return "Bond/Unbond"; + }; + + return ( + + + +
+

Validator

+

+ {validator?.imageUrl ? +
+ {" "} + {validator?.alias} +
+ : shortenAddress(txnInfo?.receiver ?? "", 10, 10)} +

+
+
+ ); +}; + +const RedelegationTransactionCard: React.FC = ({ tx }) => { + const { transaction, asset, transactionFailed, validators } = + useTransactionCardData(tx); + const txnInfo = getRedelegationTransactionInfo(transaction); + const displayAmount = getDisplayAmount(txnInfo, asset, tx); + const redelegationSource = validators?.data?.find( + (v) => v.address === txnInfo?.sender + ); + const redelegationTarget = validators?.data?.find( + (v) => v.address === txnInfo?.receiver + ); + + return ( + + + + + +
+

From

+

+ {redelegationSource?.imageUrl ? +
+ {" "} + {redelegationSource?.alias}
-

-

- {transactionTopLevel?.timestamp ? - new Date(transactionTopLevel.timestamp * 1000) - .toLocaleString("en-US", { - day: "2-digit", - month: "2-digit", - year: "2-digit", - hour: "2-digit", - minute: "2-digit", - }) - .replace(",", "") - : "-"} -

-
+ : shortenAddress(txnInfo?.sender ?? "", 10, 10)} +
-
-
- -
- +
+

To

+

+ {redelegationTarget?.imageUrl ? +
+ {" "} + {redelegationTarget?.alias} +
+ : shortenAddress(txnInfo?.receiver ?? "", 10, 10)} +

+ + ); +}; - {!isBondingOrUnbondingTransaction && ( -
-

- From -

-

- {isShieldedAddress(sender ?? "") ? - - Shielded - - : isRedelegationTransaction ? - redelegationSource?.imageUrl ? - - : redelegationSource?.alias - :
- {renderKeplrIcon(sender ?? "")} - {shortenAddress(sender ?? "", 10, 10)} -
- } -

-
- )} +const VoteTransactionCard: React.FC = ({ tx }) => { + const { transaction, transactionFailed } = useTransactionCardData(tx); + const voteInfo = getVoteTransactionInfo(transaction); + const proposalId = + voteInfo?.proposalId ? BigInt(voteInfo.proposalId) : undefined; + const proposal = useAtomValue(proposalFamily(proposalId || BigInt(0))); + const navigate = useNavigate(); + + const proposalTitle = + proposal.data?.content.title ? + `#${voteInfo?.proposalId} - ${proposal.data.content.title.slice(0, 20)}...` + : `#${voteInfo?.proposalId}`; + const proposer = proposal.data?.author; + return ( + + +
+

Vote

+

+ {voteInfo?.vote ?? "-"} +

+
+
+

Proposal

+

+
+ + {proposal.data?.content.title && ( + + {`#${voteInfo?.proposalId} - ${proposal.data.content.title}`} + + )} +
+

+
+
+

Proposer

+

{proposer ? shortenAddress(proposer, 10, 10) : "-"}

+
+
+ ); +}; + +const WithdrawTransactionCard: React.FC = ({ tx }) => { + const { transaction, transactionFailed, validators } = + useTransactionCardData(tx); + + const txnInfo = JSON.parse(tx.tx?.data ?? "{}"); + const validator = validators?.data?.find( + (v) => v.address === txnInfo?.validator + ); + + return ( + + +
+

From Validator

+

+ {validator?.imageUrl ? +
+ {" "} + {validator?.alias} +
+ : shortenAddress(txnInfo?.validator ?? "", 10, 10)} +

+
+
+ ); +}; + +const GeneralTransactionCard: React.FC = ({ tx }) => { + const { transaction, asset, transparentAddress, transactionFailed } = + useTransactionCardData(tx); + const txnInfo = getTransactionInfo(transaction, transparentAddress); + const displayAmount = getDisplayAmount(txnInfo, asset, tx); + const receiver = txnInfo?.receiver; + const sender = txnInfo?.sender; + const isReceived = tx?.kind === "received"; + const isInternalUnshield = + tx?.kind === "received" && isMaspAddress(sender ?? ""); + + const getTitle = (): string => { + const kind = transaction?.kind; + + if (!kind) return "Unknown"; + if (isInternalUnshield) return "Unshielding Transfer"; + if (isReceived) return "Receive"; + if (kind.startsWith("ibc")) return "IBC Transfer"; + if (kind === "claimRewards") return "Claim Rewards"; + if (kind === "transparentTransfer") return "Transparent Transfer"; + if (kind === "shieldingTransfer") return "Shielding Transfer"; + if (kind === "unshieldingTransfer") return "Unshielding Transfer"; + if (kind === "shieldedTransfer") return "Shielded Transfer"; + return "Transfer"; + }; + + return ( + + + + + +
+

+ From +

+

+ {isShieldedAddress(sender ?? "") ? + + Shielded + + :
+ {renderKeplrIcon(sender ?? "")} + {shortenAddress(sender ?? "", 10, 10)} +
+ } +

+

- {isBondingOrUnbondingTransaction ? "Validator" : "To"} + To

{isShieldedAddress(receiver ?? "") ? Shielded - : isBondingOrUnbondingTransaction ? - validator?.imageUrl ? - - : shortenAddress(receiver ?? "", 10, 10) - : isRedelegationTransaction ? - redelegationTarget?.imageUrl ? - - : redelegationTarget?.alias :
{renderKeplrIcon(receiver ?? "")} {shortenAddress(receiver ?? "", 10, 10)} @@ -397,6 +635,28 @@ export const TransactionCard = ({ }

-
+ ); }; + +export const TransactionCard = ({ tx }: Props): JSX.Element => { + const transactionKind = tx.tx?.kind; + + if (transactionKind === "bond" || transactionKind === "unbond") { + return ; + } + + if (transactionKind === "redelegation") { + return ; + } + + if (transactionKind === "voteProposal") { + return ; + } + + if (transactionKind === "withdraw") { + return ; + } + + return ; +}; diff --git a/apps/namadillo/src/App/Transactions/TransactionHistory.tsx b/apps/namadillo/src/App/Transactions/TransactionHistory.tsx index a10517e69e..523a9fa0ff 100644 --- a/apps/namadillo/src/App/Transactions/TransactionHistory.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionHistory.tsx @@ -32,6 +32,7 @@ export const transferKindOptions = [ "withdraw", "bond", "unbond", + "revealPk", "received", ]; From 0f1fe78aa684259fb9d406923f473a8bf93291b1 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Wed, 9 Jul 2025 23:30:10 +1200 Subject: [PATCH 04/17] fix: cleanup --- apps/namadillo/src/App/Transactions/TransactionCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/namadillo/src/App/Transactions/TransactionCard.tsx b/apps/namadillo/src/App/Transactions/TransactionCard.tsx index e1ec4f11ce..1e9f032561 100644 --- a/apps/namadillo/src/App/Transactions/TransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionCard.tsx @@ -155,7 +155,7 @@ const getTransactionInfo = ( return amount ? { amount, sender, receiver } : undefined; }; -// Common hooks and utilities for all card types +// Common data for all teh card types const useTransactionCardData = ( tx: Tx ): { From 11a62803d4f6e90c0024b079eb5b354deeec61cd Mon Sep 17 00:00:00 2001 From: neocybereth Date: Wed, 9 Jul 2025 23:50:58 +1200 Subject: [PATCH 05/17] fix: add reveal pk bundle --- .../Transactions/BundledTransactionCard.tsx | 108 ++++++++++++++++++ .../App/Transactions/TransactionHistory.tsx | 95 ++++++++++++--- 2 files changed, 189 insertions(+), 14 deletions(-) create mode 100644 apps/namadillo/src/App/Transactions/BundledTransactionCard.tsx diff --git a/apps/namadillo/src/App/Transactions/BundledTransactionCard.tsx b/apps/namadillo/src/App/Transactions/BundledTransactionCard.tsx new file mode 100644 index 0000000000..9424710f1a --- /dev/null +++ b/apps/namadillo/src/App/Transactions/BundledTransactionCard.tsx @@ -0,0 +1,108 @@ +import { CopyToClipboardControl, Tooltip } from "@namada/components"; +import { shortenAddress } from "@namada/utils"; +import { TransactionHistory as TransactionHistoryType } from "atoms/transactions/atoms"; +import clsx from "clsx"; +import { + IoCheckmarkCircleOutline, + IoInformationCircleOutline, +} from "react-icons/io5"; +import { twMerge } from "tailwind-merge"; +import { TransactionCard } from "./TransactionCard"; + +type Props = { + revealPkTx: TransactionHistoryType; + mainTx: TransactionHistoryType; +}; + +export const BundledTransactionCard: React.FC = ({ + revealPkTx, + mainTx, +}) => { + const publicKey = + revealPkTx.tx?.data ? JSON.parse(revealPkTx.tx.data).public_key : ""; + + return ( +
+ {/* Reveal PK Row - Matching exact grid layout */} +
+ {/* Column 1: Icon + Title + Timestamp */} +
+ + + + {/* Vertical dashed line connecting to next transaction */} +
+
+

+ Reveal PK +
+ + + Revealing your public key registers your newly generated + Namada account on-chain so the network can associate deposits, + stake, and future signatures with a unique identity while + keeping your private key secret + +
+
+ + + Copy transaction hash + +
+

+

+ {revealPkTx.timestamp ? + new Date(revealPkTx.timestamp * 1000) + .toLocaleString("en-US", { + day: "2-digit", + month: "2-digit", + year: "2-digit", + hour: "2-digit", + minute: "2-digit", + }) + .replace(",", "") + : "-"} +

+
+
+ +
+

Public Key

+

+ + {shortenAddress(publicKey, 12, 12)} + +
+ + + Copy public key + +
+

+
+
+ + {/* Main Transaction Row - Strip all container styling to integrate seamlessly */} +
+ +
+
+ ); +}; diff --git a/apps/namadillo/src/App/Transactions/TransactionHistory.tsx b/apps/namadillo/src/App/Transactions/TransactionHistory.tsx index 523a9fa0ff..010217d526 100644 --- a/apps/namadillo/src/App/Transactions/TransactionHistory.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionHistory.tsx @@ -13,6 +13,7 @@ import { useAtomValue } from "jotai"; import { useCallback, useMemo, useState } from "react"; import { twMerge } from "tailwind-merge"; import { TransferTransactionData } from "types"; +import { BundledTransactionCard } from "./BundledTransactionCard"; import { LocalStorageTransactionCard } from "./LocalStorageTransactionCard"; import { PendingTransactionCard } from "./PendingTransactionCard"; import { TransactionCard } from "./TransactionCard"; @@ -36,6 +37,19 @@ export const transferKindOptions = [ "received", ]; +type BundledTransaction = + | { + type: "bundled"; + revealPkTx: TransactionHistoryType; + mainTx: TransactionHistoryType; + timestamp: number; + } + | { + type: "single"; + tx: TransactionHistoryType; + timestamp: number; + }; + export const TransactionHistory = (): JSX.Element => { const [currentPage, setCurrentPage] = useState(0); const [filter, setFilter] = useState("All"); @@ -128,12 +142,53 @@ export const TransactionHistory = (): JSX.Element => { }, [] ); - // Only show historical transactions that are in the transferKindOptions array + + // Bundle reveal PK transactions with the previous transaction + const bundleTransactions = ( + transactions: (TransactionHistoryType | TransferTransactionData)[] + ): BundledTransaction[] => { + const bundled: BundledTransaction[] = []; + let i = 0; + + while (i < transactions.length) { + const currentTx = transactions[i]; + + // Check if next transaction is revealPk that should be bundled with current + const nextTx = transactions[i + 1]; + if ( + i + 1 < transactions.length && + "tx" in nextTx && + nextTx.tx?.kind === "revealPk" + ) { + bundled.push({ + type: "bundled", + revealPkTx: nextTx as TransactionHistoryType, + mainTx: currentTx as TransactionHistoryType, + timestamp: currentTx.timestamp || 0, + }); + i += 2; // Skip both transactions + } else if ("tx" in currentTx && currentTx.tx?.kind === "revealPk") { + // Skip standalone revealPk transactions + i += 1; + } else { + // Add as single transaction + bundled.push({ + type: "single", + tx: currentTx as TransactionHistoryType, + timestamp: currentTx.timestamp || 0, + }); + i += 1; + } + } + + return bundled; + }; + const filteredTransactions = transactions?.results?.filter((transaction) => handleFiltering(transaction) ) ?? []; - // Remove duplicates + const historicalTransactions = filterDuplicateTransactions(filteredTransactions); @@ -142,30 +197,44 @@ export const TransactionHistory = (): JSX.Element => { ...completedIbcShieldTransactions, ].sort((a, b) => (b?.timestamp ?? 0) - (a?.timestamp ?? 0)); - // Calculate total pages based on the filtered transactions + // Bundle transactions after sorting + const bundledTransactions = bundleTransactions(allHistoricalTransactions); + + // Calculate total pages based on bundled transactions const totalPages = Math.max( 1, - Math.ceil(allHistoricalTransactions.length / ITEMS_PER_PAGE) + Math.ceil(bundledTransactions.length / ITEMS_PER_PAGE) ); const paginatedTransactions = useMemo(() => { const startIndex = currentPage * ITEMS_PER_PAGE; const endIndex = startIndex + ITEMS_PER_PAGE; - return allHistoricalTransactions.slice(startIndex, endIndex); - }, [allHistoricalTransactions, currentPage]); + return bundledTransactions.slice(startIndex, endIndex); + }, [bundledTransactions, currentPage]); const renderRow = ( - transaction: TransactionHistoryType, + transaction: BundledTransaction, index: number ): TableRow => { + const key = + transaction.type === "bundled" ? + `${transaction.revealPkTx.tx?.txId}-${transaction.mainTx.tx?.txId}` + : transaction.tx.tx?.txId || index.toString(); + return { - key: transaction.tx?.txId || index.toString(), + key, cells: [ - transaction?.tx ? - + transaction.type === "bundled" ? + + : transaction.tx?.tx ? + : , ], }; @@ -296,9 +365,7 @@ export const TransactionHistory = (): JSX.Element => { id="transactions-table" headers={[{ children: " ", className: "w-full" }]} renderRow={renderRow} - itemList={ - paginatedTransactions as unknown as TransactionHistoryType[] - } + itemList={paginatedTransactions} page={currentPage} pageCount={totalPages} onPageChange={handlePageChange} From 2db49363ff9eb3b4e1283b79cc7a6c76cee1aa33 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Wed, 9 Jul 2025 23:54:16 +1200 Subject: [PATCH 06/17] fix: cleanup --- .../namadillo/src/App/Transactions/BundledTransactionCard.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/namadillo/src/App/Transactions/BundledTransactionCard.tsx b/apps/namadillo/src/App/Transactions/BundledTransactionCard.tsx index 9424710f1a..ef5a834984 100644 --- a/apps/namadillo/src/App/Transactions/BundledTransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/BundledTransactionCard.tsx @@ -30,14 +30,11 @@ export const BundledTransactionCard: React.FC = ({ ) )} > - {/* Reveal PK Row - Matching exact grid layout */}
- {/* Column 1: Icon + Title + Timestamp */}
- {/* Vertical dashed line connecting to next transaction */}

@@ -99,7 +96,6 @@ export const BundledTransactionCard: React.FC = ({

- {/* Main Transaction Row - Strip all container styling to integrate seamlessly */}
From 36d29757d71119819bdcdb9bafe9a4d6546659ad Mon Sep 17 00:00:00 2001 From: neocybereth Date: Thu, 10 Jul 2025 12:05:16 +1200 Subject: [PATCH 07/17] fix: finalize --- .../LocalStorageTransactionCard.tsx | 68 +++++-- .../src/App/Transactions/TransactionCard.tsx | 191 +++++++++++++----- 2 files changed, 191 insertions(+), 68 deletions(-) diff --git a/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx b/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx index 16c6fd9ad4..3d1bba8925 100644 --- a/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx @@ -1,9 +1,17 @@ import { CopyToClipboardControl, Tooltip } from "@namada/components"; import { shortenAddress } from "@namada/utils"; +import { FiatCurrency } from "App/Common/FiatCurrency"; import { TokenCurrency } from "App/Common/TokenCurrency"; import { AssetImage } from "App/Transfer/AssetImage"; import { isShieldedAddress, isTransparentAddress } from "App/Transfer/common"; +import { + getNamadaAssetByIbcAsset, + namadaRegistryChainAssetsMapAtom, +} from "atoms/integrations"; +import { tokenPricesFamily } from "atoms/prices/atoms"; +import BigNumber from "bignumber.js"; import clsx from "clsx"; +import { useAtomValue } from "jotai"; import { FaLock } from "react-icons/fa"; import { IoCheckmarkCircleOutline, @@ -27,6 +35,31 @@ const getTitle = (transferTransaction: TransferTransactionData): string => { export const LocalStorageTransactionCard = ({ transaction, }: TransactionCardProps): JSX.Element => { + const namadaAssetsMap = useAtomValue(namadaRegistryChainAssetsMapAtom); + + // For IBC transactions, we need to find the corresponding Namada asset + const namadaAsset = + namadaAssetsMap.data ? + getNamadaAssetByIbcAsset( + transaction.asset, + Object.values(namadaAssetsMap.data) + ) + : undefined; + + // Use the Namada asset address if available, otherwise try the original asset address + const assetAddress = namadaAsset?.address || transaction.asset.address; + + const tokenPrices = useAtomValue( + tokenPricesFamily(assetAddress ? [assetAddress] : []) + ); + const tokenPrice = + assetAddress ? tokenPrices.data?.[assetAddress] : undefined; + + // Ensure displayAmount is a BigNumber before performing calculations + const displayAmount = BigNumber(transaction.displayAmount); + const dollarAmount = + tokenPrice ? displayAmount.multipliedBy(tokenPrice) : undefined; + const renderKeplrIcon = (address: string): JSX.Element | null => { if (isShieldedAddress(address)) return null; if (isTransparentAddress(address)) return null; @@ -94,25 +127,22 @@ export const LocalStorageTransactionCard = ({
- +
+ + {dollarAmount && ( + + )} +
-

- From -

+

From

-

- To -

+

To

{ return ; }; +const ValidatorTooltip: React.FC<{ + validator: Validator; + children: React.ReactNode; +}> = ({ validator, children }) => { + return ( +
+ {children} + +
+
+
+ {validator.imageUrl ? + {validator.alias + :
+ {validator.alias?.charAt(0) || "?"} +
+ } +
+
+

+ {validator.alias || "Unknown Validator"} +

+
+
+ +
+

+ {shortenAddress(validator.address, 16, 16)} +

+
+ +
+
+ Commission + + {formatPercentage(validator.commission)} + +
+
+ Approximate APR (%) + + {formatPercentage(validator.expectedApr)} + +
+
+ Voting Power + + {validator.votingPowerPercentage ? + formatPercentage( + new BigNumber(validator.votingPowerPercentage) + ) + : "0%"} + +
+
+
+
+
+ ); +}; + const TransactionCardContainer: React.FC<{ children: React.ReactNode; transactionFailed: boolean; @@ -347,16 +417,31 @@ const TransactionAmount: React.FC<{ asset: NamadaAsset | undefined; amount: BigNumber; }> = ({ asset, amount }) => { + const tokenPrices = useAtomValue( + tokenPricesFamily(asset?.address ? [asset.address] : []) + ); + const tokenPrice = + asset?.address ? tokenPrices.data?.[asset.address] : undefined; + const dollarAmount = tokenPrice ? amount.multipliedBy(tokenPrice) : undefined; + return (
- +
+ + {dollarAmount && ( + + )} +
); }; @@ -389,16 +474,20 @@ const BondUnbondTransactionCard: React.FC = ({ tx }) => { />
-

Validator

+

+ {transaction?.kind === "bond" ? "To Validator" : "From Validator"} +

{validator?.imageUrl ? -
- {" "} - {validator?.alias} -
+ +
+ {validator?.alias} + +
+
: shortenAddress(txnInfo?.receiver ?? "", 10, 10)}

@@ -435,31 +524,39 @@ const RedelegationTransactionCard: React.FC = ({ tx }) => {
-

From

+

From Validator

{redelegationSource?.imageUrl ? -
- {" "} - {redelegationSource?.alias} -
+ +
+ + {redelegationSource?.alias} + + +
+
: shortenAddress(txnInfo?.sender ?? "", 10, 10)}

-

To

+

To Validator

{redelegationTarget?.imageUrl ? -
- {" "} - {redelegationTarget?.alias} -
+ +
+ + {redelegationTarget?.alias} + + +
+
: shortenAddress(txnInfo?.receiver ?? "", 10, 10)}

@@ -489,7 +586,7 @@ const VoteTransactionCard: React.FC = ({ tx }) => { timestamp={tx.timestamp} />
-

Vote

+

Vote

= ({ tx }) => {

-

Proposal

+

Proposal

-

Proposer

+

Proposer

{proposer ? shortenAddress(proposer, 10, 10) : "-"}

@@ -548,16 +645,18 @@ const WithdrawTransactionCard: React.FC = ({ tx }) => { timestamp={tx.timestamp} />
-

From Validator

+

From Validator

{validator?.imageUrl ? -
- {" "} - {validator?.alias} -
+ +
+ {" "} + {validator?.alias} +
+
: shortenAddress(txnInfo?.validator ?? "", 10, 10)}

@@ -603,9 +702,7 @@ const GeneralTransactionCard: React.FC = ({ tx }) => {
-

- From -

+

From

{isShieldedAddress(sender ?? "") ? @@ -620,9 +717,7 @@ const GeneralTransactionCard: React.FC = ({ tx }) => {

-

- To -

+

To

{isShieldedAddress(receiver ?? "") ? From 93a60fc9d48f462ccdab93954082837c666e15d9 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Thu, 10 Jul 2025 12:13:15 +1200 Subject: [PATCH 08/17] fix: cleanup --- .../Transactions/BundledTransactionCard.tsx | 13 ++---- .../src/App/Transactions/TransactionCard.tsx | 40 +++++++++++------ .../App/Transactions/TransactionHistory.tsx | 43 ++++++++++--------- 3 files changed, 54 insertions(+), 42 deletions(-) diff --git a/apps/namadillo/src/App/Transactions/BundledTransactionCard.tsx b/apps/namadillo/src/App/Transactions/BundledTransactionCard.tsx index ef5a834984..64dfd3cd9c 100644 --- a/apps/namadillo/src/App/Transactions/BundledTransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/BundledTransactionCard.tsx @@ -83,15 +83,10 @@ export const BundledTransactionCard: React.FC = ({ {shortenAddress(publicKey, 12, 12)} -
- - - Copy public key - -
+

diff --git a/apps/namadillo/src/App/Transactions/TransactionCard.tsx b/apps/namadillo/src/App/Transactions/TransactionCard.tsx index 1d4aaf9aed..3d70b7d586 100644 --- a/apps/namadillo/src/App/Transactions/TransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionCard.tsx @@ -262,10 +262,13 @@ const renderKeplrIcon = (address: string): JSX.Element | null => { return ; }; -const ValidatorTooltip: React.FC<{ +const ValidatorTooltip = ({ + validator, + children, +}: { validator: Validator; children: React.ReactNode; -}> = ({ validator, children }) => { +}): JSX.Element => { return (
{children} @@ -330,11 +333,14 @@ const ValidatorTooltip: React.FC<{ ); }; -const TransactionCardContainer: React.FC<{ +const TransactionCardContainer = ({ + children, + hasValidatorImage = false, +}: { children: React.ReactNode; transactionFailed: boolean; hasValidatorImage?: boolean; -}> = ({ children, hasValidatorImage = false }) => { +}): JSX.Element => { return (
= ({ transactionFailed, title, wrapperId, timestamp }) => { +}): JSX.Element => { return (
= ({ asset, amount }) => { +}): JSX.Element => { const tokenPrices = useAtomValue( tokenPricesFamily(asset?.address ? [asset.address] : []) ); @@ -446,7 +460,7 @@ const TransactionAmount: React.FC<{ ); }; -const BondUnbondTransactionCard: React.FC = ({ tx }) => { +const BondUnbondTransactionCard = ({ tx }: Props): JSX.Element => { const { transaction, asset, transactionFailed, validators } = useTransactionCardData(tx); const txnInfo = getBondOrUnbondTransactionInfo(transaction); @@ -495,7 +509,7 @@ const BondUnbondTransactionCard: React.FC = ({ tx }) => { ); }; -const RedelegationTransactionCard: React.FC = ({ tx }) => { +const RedelegationTransactionCard = ({ tx }: Props): JSX.Element => { const { transaction, asset, transactionFailed, validators } = useTransactionCardData(tx); const txnInfo = getRedelegationTransactionInfo(transaction); @@ -564,7 +578,7 @@ const RedelegationTransactionCard: React.FC = ({ tx }) => { ); }; -const VoteTransactionCard: React.FC = ({ tx }) => { +const VoteTransactionCard = ({ tx }: Props): JSX.Element => { const { transaction, transactionFailed } = useTransactionCardData(tx); const voteInfo = getVoteTransactionInfo(transaction); const proposalId = @@ -624,7 +638,7 @@ const VoteTransactionCard: React.FC = ({ tx }) => { ); }; -const WithdrawTransactionCard: React.FC = ({ tx }) => { +const WithdrawTransactionCard = ({ tx }: Props): JSX.Element => { const { transaction, transactionFailed, validators } = useTransactionCardData(tx); @@ -664,7 +678,7 @@ const WithdrawTransactionCard: React.FC = ({ tx }) => { ); }; -const GeneralTransactionCard: React.FC = ({ tx }) => { +const GeneralTransactionCard = ({ tx }: Props): JSX.Element => { const { transaction, asset, transparentAddress, transactionFailed } = useTransactionCardData(tx); const txnInfo = getTransactionInfo(transaction, transparentAddress); diff --git a/apps/namadillo/src/App/Transactions/TransactionHistory.tsx b/apps/namadillo/src/App/Transactions/TransactionHistory.tsx index 010217d526..2279c9b279 100644 --- a/apps/namadillo/src/App/Transactions/TransactionHistory.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionHistory.tsx @@ -81,26 +81,29 @@ export const TransactionHistory = (): JSX.Element => { const handleFiltering = (transaction: TransactionHistoryType): boolean => { const transactionKind = transaction.tx?.kind ?? ""; - if (filter.toLowerCase() === "all") { - return transferKindOptions.includes(transactionKind); - } else if (filter === "received") { - return transaction.kind === "received"; - } else if (filter === "redelegation") { - return transactionKind === "redelegation"; - } else if (filter === "vote") { - return transactionKind === "voteProposal"; - } else if (filter === "withdraw") { - return transactionKind === "withdraw"; - } else if (filter === "transfer") { - return [ - "transparentTransfer", - "shieldingTransfer", - "unshieldingTransfer", - "shieldedTransfer", - ].includes(transactionKind); - } else if (filter === "ibc") { - return transactionKind.startsWith("ibc"); - } else return transactionKind === filter; + switch (filter.toLowerCase()) { + case "all": + return transferKindOptions.includes(transactionKind); + case "received": + return transaction.kind === "received"; + case "redelegation": + return transactionKind === "redelegation"; + case "vote": + return transactionKind === "voteProposal"; + case "withdraw": + return transactionKind === "withdraw"; + case "transfer": + return [ + "transparentTransfer", + "shieldingTransfer", + "unshieldingTransfer", + "shieldedTransfer", + ].includes(transactionKind); + case "ibc": + return transactionKind.startsWith("ibc"); + default: + return transactionKind === filter; + } }; const JSONstringifyOrder = useCallback((obj: unknown): string => { From 57590dd0cb57eaab8b1847a46e2081550c0e62a3 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Tue, 12 Aug 2025 14:47:18 +1200 Subject: [PATCH 09/17] fix: cleanup --- .../LocalStorageTransactionCard.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx b/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx index 3d1bba8925..f4c9386e32 100644 --- a/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx @@ -4,10 +4,8 @@ import { FiatCurrency } from "App/Common/FiatCurrency"; import { TokenCurrency } from "App/Common/TokenCurrency"; import { AssetImage } from "App/Transfer/AssetImage"; import { isShieldedAddress, isTransparentAddress } from "App/Transfer/common"; -import { - getNamadaAssetByIbcAsset, - namadaRegistryChainAssetsMapAtom, -} from "atoms/integrations"; +import { namadaRegistryChainAssetsMapAtom } from "atoms/integrations"; + import { tokenPricesFamily } from "atoms/prices/atoms"; import BigNumber from "bignumber.js"; import clsx from "clsx"; @@ -36,14 +34,17 @@ export const LocalStorageTransactionCard = ({ transaction, }: TransactionCardProps): JSX.Element => { const namadaAssetsMap = useAtomValue(namadaRegistryChainAssetsMapAtom); - - // For IBC transactions, we need to find the corresponding Namada asset const namadaAsset = namadaAssetsMap.data ? - getNamadaAssetByIbcAsset( - transaction.asset, - Object.values(namadaAssetsMap.data) - ) + Object.values(namadaAssetsMap.data).find((namadaAsset) => { + // If the transaction asset has an address, try to match it directly + if ( + transaction.asset.symbol && + namadaAsset.symbol === transaction.asset.symbol + ) { + return true; + } + }) : undefined; // Use the Namada asset address if available, otherwise try the original asset address From 05106f5caa369d14cb9d7a37b42cd6059456ac7a Mon Sep 17 00:00:00 2001 From: neocybereth Date: Fri, 22 Aug 2025 11:42:10 +1200 Subject: [PATCH 10/17] fix: cleanup --- .../LocalStorageTransactionCard.tsx | 18 +++----- .../src/App/Transactions/TransactionCard.tsx | 41 +++++++++++-------- package.json | 2 +- yarn.lock | 2 +- 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx b/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx index f4c9386e32..7c3d548748 100644 --- a/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx @@ -35,17 +35,9 @@ export const LocalStorageTransactionCard = ({ }: TransactionCardProps): JSX.Element => { const namadaAssetsMap = useAtomValue(namadaRegistryChainAssetsMapAtom); const namadaAsset = - namadaAssetsMap.data ? - Object.values(namadaAssetsMap.data).find((namadaAsset) => { - // If the transaction asset has an address, try to match it directly - if ( - transaction.asset.symbol && - namadaAsset.symbol === transaction.asset.symbol - ) { - return true; - } - }) - : undefined; + namadaAssetsMap.data && + Object.values(namadaAssetsMap.data).find((namadaAsset) => transaction.asset.symbol && + namadaAsset.symbol === transaction.asset.symbol); // Use the Namada asset address if available, otherwise try the original asset address const assetAddress = namadaAsset?.address || transaction.asset.address; @@ -54,12 +46,12 @@ export const LocalStorageTransactionCard = ({ tokenPricesFamily(assetAddress ? [assetAddress] : []) ); const tokenPrice = - assetAddress ? tokenPrices.data?.[assetAddress] : undefined; + assetAddress && tokenPrices.data?.[assetAddress]; // Ensure displayAmount is a BigNumber before performing calculations const displayAmount = BigNumber(transaction.displayAmount); const dollarAmount = - tokenPrice ? displayAmount.multipliedBy(tokenPrice) : undefined; + tokenPrice && displayAmount.multipliedBy(tokenPrice); const renderKeplrIcon = (address: string): JSX.Element | null => { if (isShieldedAddress(address)) return null; diff --git a/apps/namadillo/src/App/Transactions/TransactionCard.tsx b/apps/namadillo/src/App/Transactions/TransactionCard.tsx index 3d70b7d586..72cdb706fc 100644 --- a/apps/namadillo/src/App/Transactions/TransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionCard.tsx @@ -18,6 +18,8 @@ import { allValidatorsAtom } from "atoms/validators"; import BigNumber from "bignumber.js"; import clsx from "clsx"; import { useAtomValue } from "jotai"; +import { AtomWithQueryResult } from "jotai-tanstack-query"; +import { useCallback } from "react"; import { FaLock } from "react-icons/fa6"; import { IoCheckmarkCircleOutline, @@ -85,14 +87,23 @@ export function getToken( return undefined; } -const getVoteTransactionInfo = ( - tx: Tx["tx"] -): VoteTransactionInfo | undefined => { - if (!tx?.data) return undefined; - const parsed = typeof tx.data === "string" ? JSON.parse(tx.data) : tx.data; +const getVoteTransactionInfo = (tx: Tx["tx"]): VoteTransactionInfo => { + if (!tx?.data) + return { + proposalId: "", + vote: "", + }; + + let parsed; + try { + parsed = JSON.parse(tx.data); + } catch (error) { + parsed = tx.data; + } + return { - proposalId: parsed.id, - vote: parsed.vote, + proposalId: parsed.id || "", + vote: parsed.vote || "", }; }; @@ -103,7 +114,7 @@ const getBondOrUnbondTransactionInfo = ( let parsed: BondData; try { - parsed = typeof tx.data === "string" ? JSON.parse(tx.data) : tx.data; + parsed = JSON.parse(tx.data); } catch { return undefined; } @@ -165,7 +176,7 @@ const useTransactionCardData = ( asset: NamadaAsset | undefined; transparentAddress: string; transactionFailed: boolean; - validators: ReturnType>; + validators: AtomWithQueryResult; } => { const transaction = tx.tx; const nativeToken = useAtomValue(nativeTokenAddressAtom).data; @@ -434,9 +445,8 @@ const TransactionAmount = ({ const tokenPrices = useAtomValue( tokenPricesFamily(asset?.address ? [asset.address] : []) ); - const tokenPrice = - asset?.address ? tokenPrices.data?.[asset.address] : undefined; - const dollarAmount = tokenPrice ? amount.multipliedBy(tokenPrice) : undefined; + const tokenPrice = asset?.address && tokenPrices.data?.[asset.address]; + const dollarAmount = tokenPrice && amount.multipliedBy(tokenPrice); return (
@@ -469,11 +479,11 @@ const BondUnbondTransactionCard = ({ tx }: Props): JSX.Element => { (v) => v.address === txnInfo?.receiver ); - const getTitle = (): string => { + const getTitle = useCallback((): string => { if (transaction?.kind === "bond") return "Stake"; if (transaction?.kind === "unbond") return "Unstake"; return "Bond/Unbond"; - }; + }, [transaction?.kind]); return ( { const VoteTransactionCard = ({ tx }: Props): JSX.Element => { const { transaction, transactionFailed } = useTransactionCardData(tx); const voteInfo = getVoteTransactionInfo(transaction); - const proposalId = - voteInfo?.proposalId ? BigInt(voteInfo.proposalId) : undefined; + const proposalId = voteInfo?.proposalId && BigInt(voteInfo.proposalId); const proposal = useAtomValue(proposalFamily(proposalId || BigInt(0))); const navigate = useNavigate(); diff --git a/package.json b/package.json index 58b5ef5a7f..e0b0c34e72 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "git-commit-msg-linter": "^5.0.6", "husky": "^8.0.3", "lint-staged": "^15.2.0", - "prettier": "^3.3.3", + "prettier": "^3.6.2", "prettier-plugin-organize-imports": "^3.2.4", "stream-browserify": "^3.0.0", "typescript": "5.5.4", diff --git a/yarn.lock b/yarn.lock index bbaeeb51d4..da401802b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21736,4 +21736,4 @@ __metadata: resolution: "zxcvbn@npm:4.4.2" checksum: 10c0/862f101cc95247b30290bad67ade333e430a16763bb771ce4e4c5414396d987f9a64288225675c96bd6f8d3eba65da0dee119b2a4eaa2e249da3f540b036942e languageName: node - linkType: hard + linkType: hard \ No newline at end of file From f0eeff9c54dd83ec4b136831bb20dc5effe55e4c Mon Sep 17 00:00:00 2001 From: neocybereth Date: Fri, 22 Aug 2025 11:42:53 +1200 Subject: [PATCH 11/17] fix: restore prettier --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e0b0c34e72..58b5ef5a7f 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "git-commit-msg-linter": "^5.0.6", "husky": "^8.0.3", "lint-staged": "^15.2.0", - "prettier": "^3.6.2", + "prettier": "^3.3.3", "prettier-plugin-organize-imports": "^3.2.4", "stream-browserify": "^3.0.0", "typescript": "5.5.4", From 4516e76e6144b5c3811c08a59b958bdc6503e1c6 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Fri, 22 Aug 2025 12:11:51 +1200 Subject: [PATCH 12/17] fix: yarn lock --- yarn.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index da401802b1..a4c3b6e0b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21736,4 +21736,5 @@ __metadata: resolution: "zxcvbn@npm:4.4.2" checksum: 10c0/862f101cc95247b30290bad67ade333e430a16763bb771ce4e4c5414396d987f9a64288225675c96bd6f8d3eba65da0dee119b2a4eaa2e249da3f540b036942e languageName: node - linkType: hard \ No newline at end of file + linkType: hard + \ No newline at end of file From 116c7256ff887ebccf0d990a15cda23b0d150067 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Fri, 22 Aug 2025 12:12:16 +1200 Subject: [PATCH 13/17] fix: yarn lock --- yarn.lock | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index a4c3b6e0b8..da401802b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21736,5 +21736,4 @@ __metadata: resolution: "zxcvbn@npm:4.4.2" checksum: 10c0/862f101cc95247b30290bad67ade333e430a16763bb771ce4e4c5414396d987f9a64288225675c96bd6f8d3eba65da0dee119b2a4eaa2e249da3f540b036942e languageName: node - linkType: hard - \ No newline at end of file + linkType: hard \ No newline at end of file From 12da74b7e5193b3a87a76d1a521be83da1583fc8 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Fri, 22 Aug 2025 12:21:40 +1200 Subject: [PATCH 14/17] fix: clean --- apps/namadillo/src/App/Transactions/TransactionCard.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/namadillo/src/App/Transactions/TransactionCard.tsx b/apps/namadillo/src/App/Transactions/TransactionCard.tsx index 72cdb706fc..a2c89b8d38 100644 --- a/apps/namadillo/src/App/Transactions/TransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionCard.tsx @@ -129,7 +129,13 @@ const getRedelegationTransactionInfo = ( tx: Tx["tx"] ): TransactionInfo | undefined => { if (!tx?.data) return undefined; - const parsed = typeof tx.data === "string" ? JSON.parse(tx.data) : tx.data; + let parsed; + try { + parsed = JSON.parse(tx.data); + } catch (error) { + parsed = tx.data; + } + return { amount: BigNumber(parsed.amount), sender: parsed.src_validator, From f4803037713f89a90bef6ff881b223605115da71 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Fri, 22 Aug 2025 12:22:14 +1200 Subject: [PATCH 15/17] fix: linting --- apps/namadillo/src/App/Transactions/TransactionCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/namadillo/src/App/Transactions/TransactionCard.tsx b/apps/namadillo/src/App/Transactions/TransactionCard.tsx index a2c89b8d38..9ee26dfd09 100644 --- a/apps/namadillo/src/App/Transactions/TransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionCard.tsx @@ -97,7 +97,7 @@ const getVoteTransactionInfo = (tx: Tx["tx"]): VoteTransactionInfo => { let parsed; try { parsed = JSON.parse(tx.data); - } catch (error) { + } catch { parsed = tx.data; } @@ -132,7 +132,7 @@ const getRedelegationTransactionInfo = ( let parsed; try { parsed = JSON.parse(tx.data); - } catch (error) { + } catch { parsed = tx.data; } From 244cd749394e8cdc2d68c3d525f464a193957bc7 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Mon, 25 Aug 2025 19:28:35 +1200 Subject: [PATCH 16/17] fix: cleanup --- .../App/Transactions/LocalStorageTransactionCard.tsx | 11 +++++------ .../src/App/Transactions/TransactionCard.tsx | 9 ++------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx b/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx index 7c3d548748..597a5b0c62 100644 --- a/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/LocalStorageTransactionCard.tsx @@ -36,8 +36,9 @@ export const LocalStorageTransactionCard = ({ const namadaAssetsMap = useAtomValue(namadaRegistryChainAssetsMapAtom); const namadaAsset = namadaAssetsMap.data && - Object.values(namadaAssetsMap.data).find((namadaAsset) => transaction.asset.symbol && - namadaAsset.symbol === transaction.asset.symbol); + Object.values(namadaAssetsMap.data).find( + (namadaAsset) => namadaAsset.symbol === transaction.asset.symbol + ); // Use the Namada asset address if available, otherwise try the original asset address const assetAddress = namadaAsset?.address || transaction.asset.address; @@ -45,13 +46,11 @@ export const LocalStorageTransactionCard = ({ const tokenPrices = useAtomValue( tokenPricesFamily(assetAddress ? [assetAddress] : []) ); - const tokenPrice = - assetAddress && tokenPrices.data?.[assetAddress]; + const tokenPrice = assetAddress && tokenPrices.data?.[assetAddress]; // Ensure displayAmount is a BigNumber before performing calculations const displayAmount = BigNumber(transaction.displayAmount); - const dollarAmount = - tokenPrice && displayAmount.multipliedBy(tokenPrice); + const dollarAmount = tokenPrice && displayAmount.multipliedBy(tokenPrice); const renderKeplrIcon = (address: string): JSX.Element | null => { if (isShieldedAddress(address)) return null; diff --git a/apps/namadillo/src/App/Transactions/TransactionCard.tsx b/apps/namadillo/src/App/Transactions/TransactionCard.tsx index 9ee26dfd09..e5dcbd9274 100644 --- a/apps/namadillo/src/App/Transactions/TransactionCard.tsx +++ b/apps/namadillo/src/App/Transactions/TransactionCard.tsx @@ -87,13 +87,8 @@ export function getToken( return undefined; } -const getVoteTransactionInfo = (tx: Tx["tx"]): VoteTransactionInfo => { - if (!tx?.data) - return { - proposalId: "", - vote: "", - }; - +const getVoteTransactionInfo = (tx: Tx["tx"]): VoteTransactionInfo | void => { + if (!tx?.data) return; let parsed; try { parsed = JSON.parse(tx.data); From b4e911e252991626b660bde23a3f1b635d7f9610 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Wed, 27 Aug 2025 17:09:21 +1200 Subject: [PATCH 17/17] fix: clean --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index da401802b1..bbaeeb51d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21736,4 +21736,4 @@ __metadata: resolution: "zxcvbn@npm:4.4.2" checksum: 10c0/862f101cc95247b30290bad67ade333e430a16763bb771ce4e4c5414396d987f9a64288225675c96bd6f8d3eba65da0dee119b2a4eaa2e249da3f540b036942e languageName: node - linkType: hard \ No newline at end of file + linkType: hard