Skip to content

Commit a8f188c

Browse files
authored
[Chainport #3] Add Chainport information to account overview and tx details (#216)
* Add chainport information to account overview and tx details * Fix Bridge Information Chainport Status * Address PR feedback * Fix bridge transaction status logic
1 parent 9cc9ffa commit a8f188c

File tree

19 files changed

+1145
-188
lines changed

19 files changed

+1145
-188
lines changed

package-lock.json

Lines changed: 371 additions & 82 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
},
2323
"license": "MPL-2.0",
2424
"dependencies": {
25-
"@ironfish/sdk": "2.2.0",
25+
"@ironfish/sdk": "2.4.0",
2626
"electron-serve": "^1.1.0",
2727
"keccak": "^3.0.4"
2828
},
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { BoxProps, Skeleton } from "@chakra-ui/react";
2+
import { useMemo } from "react";
3+
import { useIntl } from "react-intl";
4+
5+
import { trpcReact, TRPCRouterOutputs } from "@/providers/TRPCProvider";
6+
import { IRONFISH_NETWORK_ID } from "@/utils/chainport/constants";
7+
import {
8+
getMessageForStatus,
9+
useChainportTransactionStatus,
10+
} from "@/utils/chainport/useChainportTransactionStatus";
11+
12+
import { BridgeTransactionInformationShell } from "./BridgeTransactionInformationShell";
13+
14+
type Transaction = TRPCRouterOutputs["getTransaction"]["transaction"];
15+
16+
type Props = BoxProps & {
17+
transaction: Transaction;
18+
};
19+
20+
export function BridgeTransactionInformation({ transaction, ...rest }: Props) {
21+
const { formatMessage } = useIntl();
22+
const isSend = transaction.type === "send";
23+
24+
const encodedBridgeNoteMemo = useMemo(() => {
25+
const match = transaction.notes?.find((note) => {
26+
return note.memo && !note.memo.includes("fee_payment");
27+
});
28+
return match || null;
29+
}, [transaction.notes]);
30+
31+
const { data: chainportMeta } = trpcReact.getChainportMeta.useQuery();
32+
const { data: bridgeNoteMemo } = trpcReact.decodeMemo.useQuery(
33+
{
34+
memo: encodedBridgeNoteMemo?.memoHex ?? "",
35+
},
36+
{
37+
enabled: !!encodedBridgeNoteMemo,
38+
},
39+
);
40+
41+
const baseNetworkId = isSend ? IRONFISH_NETWORK_ID : bridgeNoteMemo?.[0];
42+
43+
const { data: chainportStatus, isLoading: isChainportStatusLoading } =
44+
trpcReact.getChainportTransactionStatus.useQuery(
45+
{
46+
transactionHash: transaction.hash,
47+
baseNetworkId: baseNetworkId ?? 0,
48+
},
49+
{
50+
enabled: !!baseNetworkId,
51+
},
52+
);
53+
54+
const targetNetwork = useMemo(() => {
55+
if (!chainportStatus || !chainportMeta) return null;
56+
57+
return chainportMeta.cp_network_ids[
58+
chainportStatus.target_network_id ?? ""
59+
];
60+
}, [chainportMeta, chainportStatus]);
61+
62+
const destinationTxHashContent = useMemo(() => {
63+
if (!chainportStatus || !chainportMeta) return null;
64+
65+
const baseUrl =
66+
chainportMeta.cp_network_ids[chainportStatus.target_network_id ?? ""]
67+
?.explorer_url;
68+
69+
return {
70+
href: baseUrl + "tx/" + chainportStatus.target_tx_hash,
71+
txHash: chainportStatus.target_tx_hash,
72+
};
73+
}, [chainportMeta, chainportStatus]);
74+
75+
const status = useChainportTransactionStatus(transaction);
76+
77+
const sourceNetwork = useMemo(() => {
78+
const result = chainportMeta?.cp_network_ids[baseNetworkId ?? ""];
79+
80+
if (!result) return;
81+
82+
return {
83+
name: result.label,
84+
icon: result.network_icon,
85+
};
86+
}, [baseNetworkId, chainportMeta?.cp_network_ids]);
87+
88+
if (isChainportStatusLoading) {
89+
return (
90+
<BridgeTransactionInformationShell
91+
status={<Skeleton>placeholder</Skeleton>}
92+
type={<Skeleton>pending</Skeleton>}
93+
address={<Skeleton>0x12345678</Skeleton>}
94+
{...rest}
95+
/>
96+
);
97+
}
98+
99+
return (
100+
<BridgeTransactionInformationShell
101+
status={formatMessage(getMessageForStatus(isSend ? status : "complete"))}
102+
type={transaction.type}
103+
address={bridgeNoteMemo?.[1] ?? ""}
104+
networkIcon={targetNetwork?.network_icon ?? ""}
105+
targetTxHash={destinationTxHashContent?.txHash}
106+
blockExplorerUrl={destinationTxHashContent?.href}
107+
sourceNetwork={sourceNetwork}
108+
{...rest}
109+
/>
110+
);
111+
}
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import {
2+
Box,
3+
BoxProps,
4+
Grid,
5+
GridItem,
6+
Heading,
7+
Text,
8+
HStack,
9+
VStack,
10+
Image,
11+
} from "@chakra-ui/react";
12+
import NextImage from "next/image";
13+
import { ReactNode } from "react";
14+
import { defineMessages, useIntl } from "react-intl";
15+
16+
import chainportIcon from "@/images/chainport/chainport-icon-lg.png";
17+
import { COLORS } from "@/ui/colors";
18+
import { ShadowCard } from "@/ui/ShadowCard/ShadowCard";
19+
20+
import { CopyAddress } from "../CopyAddress/CopyAddress";
21+
22+
const messages = defineMessages({
23+
heading: {
24+
defaultMessage: "Bridge Information",
25+
},
26+
status: {
27+
defaultMessage: "Status",
28+
},
29+
senderAddress: {
30+
defaultMessage: "Sender Address",
31+
},
32+
destinationAddress: {
33+
defaultMessage: "Destination Address",
34+
},
35+
destinationTxnHash: {
36+
defaultMessage: "Destination Txn Hash",
37+
},
38+
viewInBlockExplorer: {
39+
defaultMessage: "View in Block Explorer",
40+
},
41+
sourceNetwork: {
42+
defaultMessage: "Source Network",
43+
},
44+
});
45+
46+
type Props = BoxProps & {
47+
status: ReactNode;
48+
type: ReactNode;
49+
address: ReactNode;
50+
networkIcon?: string;
51+
targetTxHash?: string;
52+
blockExplorerUrl?: string;
53+
sourceNetwork?: {
54+
name: string;
55+
icon: string;
56+
};
57+
};
58+
59+
export function BridgeTransactionInformationShell({
60+
status,
61+
type,
62+
address,
63+
networkIcon,
64+
targetTxHash,
65+
blockExplorerUrl,
66+
sourceNetwork,
67+
...rest
68+
}: Props) {
69+
const { formatMessage } = useIntl();
70+
const isSend = type === "send";
71+
return (
72+
<Box {...rest}>
73+
<Heading as="h3" fontSize="2xl" mb={8}>
74+
{formatMessage(messages.heading)}
75+
</Heading>
76+
<Grid templateColumns="repeat(3, 1fr)" gap={5}>
77+
<GridItem display="flex" alignItems="stretch">
78+
<ShadowCard
79+
contentContainerProps={{
80+
px: 5,
81+
py: 6,
82+
display: "flex",
83+
alignItems: "center",
84+
}}
85+
w="100%"
86+
>
87+
<HStack justifyContent="space-between" w="100%">
88+
<VStack alignItems="flex-start">
89+
<Text
90+
color={COLORS.GRAY_MEDIUM}
91+
fontSize="md"
92+
_dark={{
93+
color: COLORS.DARK_MODE.GRAY_LIGHT,
94+
}}
95+
>
96+
{formatMessage(messages.status)}
97+
</Text>
98+
<Box fontSize="md">{status}</Box>
99+
</VStack>
100+
<NextImage
101+
width="48px"
102+
height="48px"
103+
src={chainportIcon}
104+
alt=""
105+
/>
106+
</HStack>
107+
</ShadowCard>
108+
</GridItem>
109+
110+
<GridItem display="flex" alignItems="stretch">
111+
<ShadowCard
112+
contentContainerProps={{
113+
px: 5,
114+
py: 6,
115+
display: "flex",
116+
alignItems: "center",
117+
}}
118+
w="100%"
119+
>
120+
<HStack justifyContent="space-between" w="100%">
121+
<VStack alignItems="flex-start">
122+
<Text
123+
color={COLORS.GRAY_MEDIUM}
124+
fontSize="md"
125+
_dark={{
126+
color: COLORS.DARK_MODE.GRAY_LIGHT,
127+
}}
128+
>
129+
{formatMessage(
130+
isSend
131+
? messages.destinationAddress
132+
: messages.senderAddress,
133+
)}
134+
</Text>
135+
<Box fontSize="md">
136+
{typeof address === "string"
137+
? `${address.slice(0, 6)}...${address.toString().slice(-4)}`
138+
: address}
139+
</Box>
140+
</VStack>
141+
{networkIcon && (
142+
<Image width="48px" height="48px" src={networkIcon} alt="" />
143+
)}
144+
</HStack>
145+
</ShadowCard>
146+
</GridItem>
147+
148+
{isSend && (
149+
<GridItem display="flex" alignItems="stretch">
150+
<ShadowCard
151+
contentContainerProps={{
152+
px: 5,
153+
py: 6,
154+
display: "flex",
155+
alignItems: "center",
156+
}}
157+
w="100%"
158+
>
159+
<HStack justifyContent="space-between" w="100%">
160+
<VStack alignItems="flex-start">
161+
<Text
162+
color={COLORS.GRAY_MEDIUM}
163+
fontSize="md"
164+
_dark={{
165+
color: COLORS.DARK_MODE.GRAY_LIGHT,
166+
}}
167+
>
168+
{formatMessage(messages.destinationTxnHash)}
169+
</Text>
170+
<Box fontSize="md">
171+
<VStack alignItems="flex-start">
172+
{targetTxHash ? (
173+
<CopyAddress
174+
fontSize="md"
175+
color={COLORS.BLACK}
176+
_dark={{ color: COLORS.WHITE }}
177+
address={targetTxHash}
178+
parts={2}
179+
/>
180+
) : (
181+
<Text></Text>
182+
)}
183+
{blockExplorerUrl && (
184+
<Box
185+
as="a"
186+
target="_blank"
187+
display="inline"
188+
href={blockExplorerUrl}
189+
_hover={{ textDecor: "underline" }}
190+
>
191+
<Text fontSize="xs" lineHeight="160%">
192+
{formatMessage(messages.viewInBlockExplorer)}
193+
</Text>
194+
</Box>
195+
)}
196+
</VStack>
197+
</Box>
198+
</VStack>
199+
{networkIcon && (
200+
<Image width="48px" height="48px" src={networkIcon} alt="" />
201+
)}
202+
</HStack>
203+
</ShadowCard>
204+
</GridItem>
205+
)}
206+
{!isSend && sourceNetwork && (
207+
<GridItem display="flex" alignItems="stretch">
208+
<ShadowCard
209+
contentContainerProps={{
210+
px: 5,
211+
py: 6,
212+
display: "flex",
213+
alignItems: "center",
214+
}}
215+
w="100%"
216+
>
217+
<HStack justifyContent="space-between" w="100%">
218+
<VStack alignItems="flex-start">
219+
<Text
220+
color={COLORS.GRAY_MEDIUM}
221+
fontSize="md"
222+
_dark={{
223+
color: COLORS.DARK_MODE.GRAY_LIGHT,
224+
}}
225+
>
226+
{formatMessage(messages.sourceNetwork)}
227+
</Text>
228+
<Box fontSize="md">{sourceNetwork?.name}</Box>
229+
</VStack>
230+
<Image
231+
width="48px"
232+
height="48px"
233+
src={sourceNetwork?.icon}
234+
alt=""
235+
/>
236+
</HStack>
237+
</ShadowCard>
238+
</GridItem>
239+
)}
240+
</Grid>
241+
</Box>
242+
);
243+
}

0 commit comments

Comments
 (0)