Skip to content

Commit 6101d95

Browse files
authored
Merge pull request #53 from kleros/feat(web)/frontend-support-erc20-transactions
feat(web): support for ERC20 Tokens in the General Escrow flow
2 parents a004444 + 82dc13d commit 6101d95

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1081
-232
lines changed

web/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"@web3modal/ethereum": "^2.7.1",
8080
"@web3modal/react": "^2.2.2",
8181
"@yornaath/batshit": "^0.9.0",
82+
"alchemy-sdk": "^3.3.1",
8283
"amqplib": "^0.10.3",
8384
"chart.js": "^3.9.1",
8485
"chartjs-adapter-moment": "^1.0.1",
15.4 KB
Loading

web/src/components/PreviewCard/EscrowTimeline/index.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface IEscrowTimeline {
1212
isPreview: boolean;
1313
transactionCreationTimestamp: number;
1414
status: boolean;
15-
token: string;
15+
assetSymbol: string;
1616
buyerAddress: string;
1717
sellerAddress: string;
1818
payments: Payment[];
@@ -26,7 +26,7 @@ const EscrowTimeline: React.FC<IEscrowTimeline> = ({
2626
isPreview,
2727
transactionCreationTimestamp,
2828
status,
29-
token,
29+
assetSymbol,
3030
buyerAddress,
3131
sellerAddress,
3232
payments,
@@ -41,7 +41,7 @@ const EscrowTimeline: React.FC<IEscrowTimeline> = ({
4141
isPreview,
4242
transactionCreationTimestamp,
4343
status,
44-
token,
44+
assetSymbol,
4545
buyerAddress,
4646
sellerAddress,
4747
payments,

web/src/components/PreviewCard/Terms/Description.tsx

+22-11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ const StyledP = styled.p`
88
word-break: break-word;
99
`;
1010

11+
const InlineBlockSpan = styled.span`
12+
display: inline-block;
13+
`;
14+
1115
interface IDescription {
1216
escrowType: string;
1317
deliverableText: string;
@@ -17,7 +21,7 @@ interface IDescription {
1721
sendingQuantity: string;
1822
sendingToken: string;
1923
sellerAddress: string;
20-
deadlineDate: Date;
24+
deadline: number;
2125
assetSymbol: string;
2226
buyer: string;
2327
}
@@ -31,19 +35,25 @@ const Description: React.FC<IDescription> = ({
3135
sendingQuantity,
3236
sendingToken,
3337
sellerAddress,
34-
deadlineDate,
38+
deadline,
3539
assetSymbol,
3640
}) => {
37-
const generalEscrowSummary =
38-
`By Paying ${sendingQuantity + " " + assetSymbol}, address ${buyerAddress} should receive` +
39-
` "${deliverableText}" from address ${sellerAddress} before the delivery deadline ${new Date(
40-
deadlineDate
41-
)}.`;
41+
const generalEscrowSummary = (
42+
<>
43+
By Paying {sendingQuantity}{" "}
44+
<InlineBlockSpan>{assetSymbol ? assetSymbol : <StyledSkeleton width={30} />}</InlineBlockSpan>, address{" "}
45+
{buyerAddress} should receive "{deliverableText}" from address {sellerAddress} before the delivery deadline{" "}
46+
{new Date(deadline).toString()}.
47+
</>
48+
);
4249

43-
const cryptoSwapSummary =
44-
`By Paying ${sendingQuantity + " " + sendingToken}, [Blockchain] address ${buyerAddress} should receive` +
45-
` ${receivingQuantity + " " + receivingToken} at the [Blockchain] address ${sellerAddress}` +
46-
` from [Blockchain] address TODO before the delivery deadline ${deadlineDate}.`;
50+
const cryptoSwapSummary = (
51+
<>
52+
By Paying {sendingQuantity} {sendingToken}, [Blockchain] address {buyerAddress} should receive {receivingQuantity}{" "}
53+
{receivingToken} at the [Blockchain] address {sellerAddress} from [Blockchain] address TODO before the delivery
54+
deadline {new Date(deadline).toString()}.
55+
</>
56+
);
4757

4858
return isUndefined(deliverableText) ? (
4959
<StyledSkeleton />
@@ -58,4 +68,5 @@ const Description: React.FC<IDescription> = ({
5868
</div>
5969
);
6070
};
71+
6172
export default Description;

web/src/components/PreviewCard/Terms/index.tsx

+13-15
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ interface ITerms {
1919
sendingQuantity: string;
2020
sendingToken: string;
2121
sellerAddress: string;
22-
deadlineDate: Date;
22+
deadline: number;
2323
assetSymbol: string;
2424
extraDescriptionUri: string;
2525
buyer: string;
@@ -29,31 +29,29 @@ const Terms: React.FC<ITerms> = ({
2929
escrowType,
3030
deliverableText,
3131
receivingQuantity,
32-
receivingToken,
3332
buyerAddress,
3433
sendingQuantity,
35-
sendingToken,
3634
sellerAddress,
37-
deadlineDate,
35+
deadline,
3836
assetSymbol,
3937
extraDescriptionUri,
4038
}) => {
4139
return (
4240
<Container>
4341
<Header />
4442
<Description
45-
escrowType={escrowType}
46-
deliverableText={deliverableText}
47-
receivingQuantity={receivingQuantity}
48-
receivingToken={receivingToken}
49-
buyerAddress={buyerAddress}
50-
sendingQuantity={sendingQuantity}
51-
sendingToken={sendingToken}
52-
sellerAddress={sellerAddress}
53-
deadlineDate={deadlineDate}
54-
assetSymbol={assetSymbol}
43+
{...{
44+
escrowType,
45+
deliverableText,
46+
receivingQuantity,
47+
buyerAddress,
48+
sendingQuantity,
49+
sellerAddress,
50+
deadline,
51+
assetSymbol,
52+
}}
5553
/>
56-
<AttachedFile extraDescriptionUri={extraDescriptionUri} />
54+
<AttachedFile {...{ extraDescriptionUri }} />
5755
</Container>
5856
);
5957
};

web/src/components/PreviewCard/index.tsx

+5-14
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,12 @@ interface IPreviewCard {
5252
escrowTitle: string;
5353
deliverableText: string;
5454
receivingQuantity: string;
55-
receivingToken: string;
5655
transactionCreationTimestamp: string;
5756
status: string;
58-
token: string;
5957
buyerAddress: string;
6058
sendingQuantity: string;
61-
sendingToken: string;
6259
sellerAddress: string;
63-
deadlineDate: string;
60+
deadline: number;
6461
assetSymbol: string;
6562
overrideIsList: boolean;
6663
extraDescriptionUri: string;
@@ -80,15 +77,12 @@ const PreviewCard: React.FC<IPreviewCard> = ({
8077
escrowTitle,
8178
deliverableText,
8279
receivingQuantity,
83-
receivingToken,
8480
transactionCreationTimestamp,
8581
status,
86-
token,
8782
buyerAddress,
8883
sendingQuantity,
89-
sendingToken,
9084
sellerAddress,
91-
deadlineDate,
85+
deadline,
9286
assetSymbol,
9387
overrideIsList,
9488
extraDescriptionUri,
@@ -108,9 +102,8 @@ const PreviewCard: React.FC<IPreviewCard> = ({
108102
<Divider />
109103
<TransactionInfo
110104
amount={sendingQuantity}
111-
assetSymbol={assetSymbol}
112105
isPreview={true}
113-
{...{ overrideIsList, deadlineDate, sellerAddress, buyerAddress }}
106+
{...{ overrideIsList, deadline, sellerAddress, buyerAddress, assetSymbol }}
114107
/>
115108
<Divider />
116109
</TransactionInfoContainer>
@@ -119,12 +112,10 @@ const PreviewCard: React.FC<IPreviewCard> = ({
119112
escrowType,
120113
deliverableText,
121114
receivingQuantity,
122-
receivingToken,
123115
buyerAddress,
124116
sendingQuantity,
125-
sendingToken,
126117
sellerAddress,
127-
deadlineDate,
118+
deadline,
128119
assetSymbol,
129120
extraDescriptionUri,
130121
}}
@@ -134,7 +125,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
134125
{...{
135126
isPreview,
136127
status,
137-
token,
128+
assetSymbol,
138129
transactionCreationTimestamp,
139130
buyerAddress,
140131
sellerAddress,

web/src/components/TransactionCard/index.tsx

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import React from "react";
22
import styled from "styled-components";
3-
import { formatEther } from "viem";
3+
import { responsiveSize } from "styles/responsiveSize";
44
import { Card } from "@kleros/ui-components-library";
5+
import { formatEther } from "viem";
56
import { useIsList } from "context/IsListProvider";
6-
import TransactionInfo from "../TransactionInfo";
7-
import StatusBanner from "./StatusBanner";
8-
import { responsiveSize } from "styles/responsiveSize";
97
import { mapStatusToEnum } from "utils/mapStatusToEnum";
108
import { isUndefined } from "utils/index";
9+
import { StyledSkeleton, StyledSkeletonTitle } from "../StyledSkeleton";
10+
import TransactionInfo from "../TransactionInfo";
11+
import StatusBanner from "./StatusBanner";
12+
import { useNavigateAndScrollTop } from "hooks/useNavigateAndScrollTop";
1113
import { useNativeTokenSymbol } from "hooks/useNativeTokenSymbol";
1214
import useFetchIpfsJson from "hooks/useFetchIpfsJson";
13-
import { useNavigateAndScrollTop } from "hooks/useNavigateAndScrollTop";
15+
import { useTokenMetadata } from "hooks/useTokenMetadata";
1416
import { TransactionDetailsFragment } from "src/graphql/graphql";
15-
import { StyledSkeleton, StyledSkeletonTitle } from "../StyledSkeleton";
1617

1718
const StyledCard = styled(Card)`
1819
width: 100%;
@@ -69,6 +70,8 @@ const TransactionCard: React.FC<ITransactionCard> = ({
6970
const transactionInfo = useFetchIpfsJson(transactionUri);
7071
const { isList } = useIsList();
7172
const nativeTokenSymbol = useNativeTokenSymbol();
73+
const { tokenMetadata } = useTokenMetadata(token);
74+
const erc20TokenSymbol = tokenMetadata?.symbol;
7275
const title = transactionInfo?.title;
7376
const navigateAndScrollTop = useNavigateAndScrollTop();
7477

@@ -83,7 +86,7 @@ const TransactionCard: React.FC<ITransactionCard> = ({
8386
{!isUndefined(title) ? <StyledTitle>{title}</StyledTitle> : <StyledSkeleton />}
8487
<TransactionInfo
8588
amount={formatEther(amount)}
86-
assetSymbol={!token ? nativeTokenSymbol : ""}
89+
assetSymbol={!token ? nativeTokenSymbol : erc20TokenSymbol}
8790
buyerAddress={buyer}
8891
sellerAddress={seller}
8992
deadlineDate={new Date(deadline * 1000).toLocaleString()}
@@ -104,7 +107,7 @@ const TransactionCard: React.FC<ITransactionCard> = ({
104107
)}
105108
<TransactionInfo
106109
amount={formatEther(amount)}
107-
assetSymbol={!token ? nativeTokenSymbol : ""}
110+
assetSymbol={!token ? nativeTokenSymbol : erc20TokenSymbol}
108111
buyerAddress={buyer}
109112
sellerAddress={seller}
110113
deadlineDate={new Date(deadline * 1000).toLocaleString()}

web/src/components/TransactionInfo/index.tsx

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from "react";
22
import styled, { css } from "styled-components";
3+
import Skeleton from "react-loading-skeleton";
34
import { landscapeStyle } from "styles/landscapeStyle";
45
import { Statuses } from "consts/statuses";
56
import { useIsList } from "context/IsListProvider";
@@ -70,7 +71,7 @@ const RestOfFieldsContainer = styled.div<{ isList?: boolean; isPreview?: boolean
7071

7172
export interface ITransactionInfo {
7273
amount?: string;
73-
deadlineDate: string;
74+
deadline: number;
7475
assetSymbol?: string;
7576
status?: Statuses;
7677
overrideIsList?: boolean;
@@ -82,7 +83,7 @@ export interface ITransactionInfo {
8283
const TransactionInfo: React.FC<ITransactionInfo> = ({
8384
amount,
8485
assetSymbol,
85-
deadlineDate,
86+
deadline,
8687
sellerAddress,
8788
buyerAddress,
8889
overrideIsList,
@@ -94,20 +95,24 @@ const TransactionInfo: React.FC<ITransactionInfo> = ({
9495
return (
9596
<Container isList={displayAsList} isPreview={isPreview}>
9697
<RestOfFieldsContainer isPreview={isPreview} isList={displayAsList}>
97-
{amount && assetSymbol ? (
98+
{amount ? (
9899
<Field
99100
icon={PileCoinsIcon}
100101
name="Amount"
101-
value={`${amount} ${assetSymbol}`}
102+
value={
103+
<>
104+
{amount} {!assetSymbol ? <Skeleton width={30} /> : assetSymbol}
105+
</>
106+
}
102107
displayAsList={displayAsList}
103108
isPreview={isPreview}
104109
/>
105110
) : null}
106-
{deadlineDate ? (
111+
{deadline ? (
107112
<Field
108113
icon={CalendarIcon}
109114
name="Delivery Deadline"
110-
value={deadlineDate}
115+
value={new Date(deadline).toLocaleString()}
111116
displayAsList={displayAsList}
112117
isPreview={isPreview}
113118
/>
@@ -134,4 +139,5 @@ const TransactionInfo: React.FC<ITransactionInfo> = ({
134139
</Container>
135140
);
136141
};
142+
137143
export default TransactionInfo;

web/src/context/NewTransactionContext.tsx

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import React, { createContext, useState, useContext, useEffect } from "react";
22

3+
export interface IToken {
4+
symbol: string;
5+
address: string;
6+
logo: string;
7+
}
8+
39
interface INewTransactionContext {
410
escrowType: string;
511
setEscrowType: (type: string) => void;
@@ -23,8 +29,8 @@ interface INewTransactionContext {
2329
setSellerAddress: (address: string) => void;
2430
sendingQuantity: string;
2531
setSendingQuantity: (quantity: string) => void;
26-
sendingToken: string;
27-
setSendingToken: (token: string) => void;
32+
sendingToken: IToken;
33+
setSendingToken: (token: IToken) => void;
2834
buyerAddress: string;
2935
setBuyerAddress: (address: string) => void;
3036
deadline: string;
@@ -61,7 +67,7 @@ const NewTransactionContext = createContext<INewTransactionContext>({
6167
setSellerAddress: () => {},
6268
sendingQuantity: "",
6369
setSendingQuantity: () => {},
64-
sendingToken: "",
70+
sendingToken: { address: "native", symbol: "", logo: "" },
6571
setSendingToken: () => {},
6672
buyerAddress: "",
6773
setBuyerAddress: () => {},
@@ -93,7 +99,9 @@ export const NewTransactionProvider: React.FC<{ children: React.ReactNode }> = (
9399
const [receivingToken, setReceivingToken] = useState<string>(localStorage.getItem("receivingToken") || "");
94100
const [sellerAddress, setSellerAddress] = useState<string>(localStorage.getItem("sellerAddress") || "");
95101
const [sendingQuantity, setSendingQuantity] = useState<string>(localStorage.getItem("sendingQuantity") || "");
96-
const [sendingToken, setSendingToken] = useState<string>(localStorage.getItem("sendingToken") || "");
102+
const [sendingToken, setSendingToken] = useState<IToken>(
103+
JSON.parse(localStorage.getItem("sendingToken")) || { address: "native", symbol: "", logo: "" }
104+
);
97105
const [buyerAddress, setBuyerAddress] = useState<string>(localStorage.getItem("buyerAddress") || "");
98106
const [isRecipientAddressResolved, setIsRecipientAddressResolved] = useState(false);
99107
const [deadline, setDeadline] = useState<string>(localStorage.getItem("deadline") || "");
@@ -111,7 +119,7 @@ export const NewTransactionProvider: React.FC<{ children: React.ReactNode }> = (
111119
setReceivingToken("");
112120
setSellerAddress("");
113121
setSendingQuantity("");
114-
setSendingToken("");
122+
setSendingToken({ address: "native", symbol: "", logo: "" });
115123
setBuyerAddress("");
116124
setDeadline("");
117125
setNotificationEmail("");
@@ -128,7 +136,7 @@ export const NewTransactionProvider: React.FC<{ children: React.ReactNode }> = (
128136
localStorage.setItem("receivingToken", receivingToken);
129137
localStorage.setItem("buyerAddress", buyerAddress);
130138
localStorage.setItem("sendingQuantity", sendingQuantity);
131-
localStorage.setItem("sendingToken", sendingToken);
139+
localStorage.setItem("sendingToken", JSON.stringify(sendingToken));
132140
localStorage.setItem("sellerAddress", sellerAddress);
133141
localStorage.setItem("deadline", deadline);
134142
localStorage.setItem("notificationEmail", notificationEmail);

0 commit comments

Comments
 (0)