Skip to content

Commit c2b8a73

Browse files
authored
chore: add Shielding/Unshielding transactions to History (#2060)
1 parent cb38729 commit c2b8a73

File tree

11 files changed

+315
-137
lines changed

11 files changed

+315
-137
lines changed

apps/namadillo/src/App/Layout/SyncIndicator.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Tooltip } from "@namada/components";
22
import { accountBalanceAtom, transparentBalanceAtom } from "atoms/accounts";
33
import { indexerApiAtom } from "atoms/api";
44
import { shieldedBalanceAtom } from "atoms/balance";
5-
import { fetchBlockHeightByTimestamp } from "atoms/balance/services";
65
import { chainStatusAtom } from "atoms/chain";
6+
import { fetchBlockHeightByTimestamp } from "atoms/chain/services";
77
import { allProposalsAtom, votedProposalsAtom } from "atoms/proposals";
88
import {
99
indexerHeartbeatAtom,

apps/namadillo/src/App/Masp/ShieldedFungibleTable.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export const ShieldedFungibleTable = ({
7878
symbol={namadaAsset().symbol}
7979
amount={reward}
8080
className="text-yellow"
81+
decimalPlaces={3}
8182
/>
8283
: <SkeletonLoading
8384
width="120px"
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { CopyToClipboardControl, Tooltip } from "@namada/components";
2+
import { shortenAddress } from "@namada/utils";
3+
import { TokenCurrency } from "App/Common/TokenCurrency";
4+
import { AssetImage } from "App/Transfer/AssetImage";
5+
import { isShieldedAddress, isTransparentAddress } from "App/Transfer/common";
6+
import clsx from "clsx";
7+
import { FaInfoCircle, FaLock } from "react-icons/fa";
8+
import { IoCheckmarkCircleOutline } from "react-icons/io5";
9+
import { twMerge } from "tailwind-merge";
10+
import { TransferTransactionData } from "types";
11+
import keplrSvg from "../../integrations/assets/keplr.svg";
12+
13+
type TransactionCardProps = {
14+
transaction: TransferTransactionData;
15+
};
16+
17+
const getTitle = (transferTransaction: TransferTransactionData): string => {
18+
const { type } = transferTransaction;
19+
if (type === "IbcToShielded") return "IBC Shielding";
20+
if (type === "ShieldedToIbc") return "IBC Unshielding";
21+
return "";
22+
};
23+
24+
export const LocalStorageTransactionCard = ({
25+
transaction,
26+
}: TransactionCardProps): JSX.Element => {
27+
const renderKeplrIcon = (address: string): JSX.Element | null => {
28+
if (isShieldedAddress(address)) return null;
29+
if (isTransparentAddress(address)) return null;
30+
return <img src={keplrSvg} height={18} width={18} />;
31+
};
32+
const sender = transaction.sourceAddress;
33+
const receiver = transaction.destinationAddress;
34+
return (
35+
<article
36+
className={clsx(
37+
"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 items-center my-1 font-semibold",
38+
"gap-5 bg-neutral-800 rounded-sm px-5 py-5 text-white border border-transparent",
39+
"transition-colors duration-200 hover:border-neutral-500"
40+
)}
41+
>
42+
<div className="flex items-center gap-3">
43+
<i className={twMerge("text-2xl, text-success")}>
44+
<IoCheckmarkCircleOutline className="ml-1 mt-0.5 w-10 h-10" />
45+
</i>
46+
47+
<div className="flex flex-col">
48+
<h3 className="text-success flex relative group/tooltip">
49+
{getTitle(transaction)}{" "}
50+
<CopyToClipboardControl
51+
className="ml-1.5 text-neutral-400"
52+
value={transaction?.hash ?? ""}
53+
/>
54+
<Tooltip position="right" className="p-2 w-[150px] z-10">
55+
Copy transaction hash
56+
</Tooltip>
57+
</h3>
58+
<h3 className="text-neutral-400">
59+
{new Date(transaction.createdAt).toLocaleString("en-US", {
60+
day: "2-digit",
61+
month: "2-digit",
62+
year: "2-digit",
63+
hour: "2-digit",
64+
minute: "2-digit",
65+
})}
66+
</h3>
67+
</div>
68+
</div>
69+
70+
<div className="flex items-center">
71+
<div className="aspect-square w-10 h-10">
72+
<AssetImage asset={transaction.asset} />
73+
</div>
74+
<TokenCurrency
75+
className="text-white mt-1 ml-2"
76+
amount={transaction.displayAmount}
77+
symbol={transaction.asset.symbol}
78+
/>
79+
</div>
80+
<div className="flex flex-col">
81+
<h4
82+
className={
83+
(
84+
isShieldedAddress(sender ?? "") ||
85+
transaction.type === "ShieldedToIbc"
86+
) ?
87+
"text-yellow"
88+
: ""
89+
}
90+
>
91+
From
92+
</h4>
93+
<h4
94+
className={
95+
(
96+
isShieldedAddress(sender ?? "") ||
97+
transaction.type === "ShieldedToIbc"
98+
) ?
99+
"text-yellow"
100+
: ""
101+
}
102+
>
103+
{(
104+
isShieldedAddress(sender ?? "") ||
105+
transaction.type === "ShieldedToIbc"
106+
) ?
107+
<span className="flex items-center gap-1">
108+
<FaLock className="w-4 h-4" /> Shielded
109+
</span>
110+
: <div className="flex items-center gap-1">
111+
{renderKeplrIcon(sender ?? "")}
112+
{shortenAddress(sender ?? "", 10, 10)}
113+
</div>
114+
}
115+
</h4>
116+
</div>
117+
118+
<div className="flex flex-col relative">
119+
<h4 className={isShieldedAddress(receiver ?? "") ? "text-yellow" : ""}>
120+
To
121+
</h4>
122+
<div className="flex items-center justify-between">
123+
<h4
124+
className={isShieldedAddress(receiver ?? "") ? "text-yellow" : ""}
125+
>
126+
{isShieldedAddress(receiver ?? "") ?
127+
<span className="flex items-center gap-1">
128+
<FaLock className="w-4 h-4" /> Shielded
129+
</span>
130+
: <div className="flex items-center gap-1">
131+
{renderKeplrIcon(receiver ?? "")}
132+
{shortenAddress(receiver ?? "", 10, 10)}
133+
</div>
134+
}
135+
</h4>
136+
<div className="relative group/tooltip">
137+
<FaInfoCircle className="w-4 h-4 mb-3 mr-4" />
138+
<Tooltip position="left" className="p-2 w-[200px] z-10">
139+
This transaction is stored locally. It may not appear when viewing
140+
your history on other devices.
141+
</Tooltip>
142+
</div>
143+
</div>
144+
</div>
145+
</article>
146+
);
147+
};

apps/namadillo/src/App/Transactions/TransactionCard.tsx

Lines changed: 32 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import { CopyToClipboardControl, Tooltip } from "@namada/components";
2-
import { TransactionHistory as TransactionHistoryType } from "@namada/indexer-client";
32
import { shortenAddress } from "@namada/utils";
43
import { TokenCurrency } from "App/Common/TokenCurrency";
54
import { AssetImage } from "App/Transfer/AssetImage";
6-
import { isShieldedAddress, isTransparentAddress } from "App/Transfer/common";
7-
import { indexerApiAtom } from "atoms/api";
8-
import { fetchBlockTimestampByHeight } from "atoms/balance/services";
5+
import {
6+
isMaspAddress,
7+
isShieldedAddress,
8+
isTransparentAddress,
9+
} from "App/Transfer/common";
910
import { chainAssetsMapAtom, nativeTokenAddressAtom } from "atoms/chain";
11+
import { TransactionHistory as TransactionHistoryType } from "atoms/transactions/atoms";
1012
import { allValidatorsAtom } from "atoms/validators";
1113
import BigNumber from "bignumber.js";
1214
import clsx from "clsx";
1315
import { useAtomValue } from "jotai";
14-
import { useEffect, useState } from "react";
1516
import { FaLock } from "react-icons/fa6";
1617
import {
1718
IoCheckmarkCircleOutline,
@@ -61,19 +62,6 @@ export function getToken(
6162
return undefined;
6263
}
6364

64-
const getTitle = (kind: string | undefined, isReceived: boolean): string => {
65-
if (!kind) return "Unknown";
66-
if (isReceived) return "Receive";
67-
if (kind.startsWith("ibc")) return "IBC Transfer";
68-
if (kind === "bond") return "Stake";
69-
if (kind === "claimRewards") return "Claim Rewards";
70-
if (kind === "transparentTransfer") return "Transparent Transfer";
71-
if (kind === "shieldingTransfer") return "Shielding Transfer";
72-
if (kind === "unshieldingTransfer") return "Unshielding Transfer";
73-
if (kind === "shieldedTransfer") return "Shielded Transfer";
74-
return "Transfer";
75-
};
76-
7765
const getBondTransactionInfo = (
7866
tx: Tx["tx"]
7967
): { amount: BigNumber; sender?: string; receiver?: string } | undefined => {
@@ -126,7 +114,6 @@ export const TransactionCard = ({
126114
tx: transactionTopLevel,
127115
}: Props): JSX.Element => {
128116
const transaction = transactionTopLevel.tx;
129-
const isReceived = transactionTopLevel?.kind === "received";
130117
const nativeToken = useAtomValue(nativeTokenAddressAtom).data;
131118
const token = getToken(transaction, nativeToken ?? "");
132119
const chainAssetsMap = useAtomValue(chainAssetsMapAtom);
@@ -142,47 +129,43 @@ export const TransactionCard = ({
142129
: undefined;
143130
const receiver = txnInfo?.receiver;
144131
const sender = txnInfo?.sender;
132+
const isReceived = transactionTopLevel?.kind === "received";
133+
const isInternalUnshield =
134+
transactionTopLevel?.kind === "received" && isMaspAddress(sender ?? "");
145135
const transactionFailed = transaction?.exitCode === "rejected";
146136
const validators = useAtomValue(allValidatorsAtom);
147137
const validator = validators?.data?.find((v) => v.address === receiver);
148-
const api = useAtomValue(indexerApiAtom);
149-
const [timestamp, setTimestamp] = useState<number | undefined>(undefined);
150-
151-
useEffect(() => {
152-
const getBlockTimestamp = async (): Promise<void> => {
153-
// TODO: need to update the type on indexer
154-
// @ts-expect-error need to update the type on indexer
155-
if (transactionTopLevel?.blockHeight && api) {
156-
try {
157-
const timestamp = await fetchBlockTimestampByHeight(
158-
api,
159-
// @ts-expect-error need to update the type on indexer
160-
transactionTopLevel.blockHeight
161-
);
162-
setTimestamp(timestamp);
163-
} catch (error) {
164-
console.error("Failed to fetch block height:", error);
165-
}
166-
}
167-
};
168-
169-
getBlockTimestamp();
170-
// @ts-expect-error need to update the type on indexer
171-
}, [api, transactionTopLevel?.blockHeight]);
172138

173139
const renderKeplrIcon = (address: string): JSX.Element | null => {
174140
if (isShieldedAddress(address)) return null;
175141
if (isTransparentAddress(address)) return null;
176142
return <img src={keplrSvg} height={18} width={18} />;
177143
};
178144

145+
const getTitle = (tx: Tx["tx"]): string => {
146+
const kind = tx?.kind;
147+
148+
if (!kind) return "Unknown";
149+
if (isInternalUnshield) return "Unshielding Transfer";
150+
if (isReceived) return "Receive";
151+
if (kind.startsWith("ibc")) return "IBC Transfer";
152+
if (kind === "bond") return "Stake";
153+
if (kind === "claimRewards") return "Claim Rewards";
154+
if (kind === "transparentTransfer") return "Transparent Transfer";
155+
if (kind === "shieldingTransfer") return "Shielding Transfer";
156+
if (kind === "unshieldingTransfer") return "Unshielding Transfer";
157+
if (kind === "shieldedTransfer") return "Shielded Transfer";
158+
return "Transfer";
159+
};
160+
179161
return (
180162
<article
181163
className={twMerge(
182164
clsx(
183165
"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 items-center my-1 font-semibold",
184-
"gap-5 bg-neutral-800 rounded-sm px-5 py-5 text-white border border-transparent",
185-
"transition-colors duration-200 hover:border-neutral-500"
166+
"gap-5 bg-neutral-800 rounded-sm px-5 text-white border border-transparent",
167+
"transition-colors duration-200 hover:border-neutral-500",
168+
isBondingTransaction && validator?.imageUrl ? "py-3" : "py-5"
186169
)
187170
)}
188171
>
@@ -212,20 +195,20 @@ export const TransactionCard = ({
212195
})
213196
)}
214197
>
215-
{getTitle(transaction?.kind, isReceived)}{" "}
198+
{transactionFailed && "Failed"} {getTitle(transaction)}{" "}
216199
<div className="relative group/tooltip">
217200
<CopyToClipboardControl
218201
className="ml-1.5 text-neutral-400"
219202
value={transaction?.txId ?? ""}
220203
/>
221-
<Tooltip position="right" className="p-2 -mr-3 w-[150px]">
204+
<Tooltip position="right" className="p-2 -mr-3 w-[150px] z-10">
222205
Copy transaction hash
223206
</Tooltip>
224207
</div>
225208
</h3>
226209
<h3 className="text-neutral-400">
227-
{timestamp ?
228-
new Date(timestamp * 1000)
210+
{transactionTopLevel?.timestamp ?
211+
new Date(transactionTopLevel.timestamp * 1000)
229212
.toLocaleString("en-US", {
230213
day: "2-digit",
231214
month: "2-digit",

0 commit comments

Comments
 (0)