Skip to content

Commit 454a790

Browse files
authored
Merge branch 'dev' into fix(web)/write-functions-to-viem
2 parents 9152499 + 2a86251 commit 454a790

File tree

10 files changed

+1904
-83
lines changed

10 files changed

+1904
-83
lines changed

.github/workflows/contracts-testing.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
nodejs.org:443
3030
objects.githubusercontent.com:443
3131
registry.yarnpkg.com:443
32+
registry.npmjs.org:443
3233
3334
- name: Setup Node.js environment
3435
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
"nanoid^3.3.1": "^3.3.4",
5656
"node-fetch": "^2.6.7",
5757
"underscore@npm^3.0.4": "^1.12.1",
58-
"eth-sig-util@npm:^1.4.2": "3.0.0"
58+
"eth-sig-util@npm:^1.4.2": "3.0.0",
59+
"fast-xml-parser": "^4.2.5"
5960
},
6061
"scripts": {
6162
"check-prerequisites": "scripts/check-prerequisites.sh",

web/netlify.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ YARN_ENABLE_GLOBAL_CACHE = "true"
99

1010
[functions]
1111
directory = "web/netlify/functions/"
12+
13+
[dev]
14+
framework = "parcel"
Lines changed: 104 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,109 @@
11
import { Handler } from "@netlify/functions";
2-
import fetch from "node-fetch";
3-
4-
const ESTUARI_API_KEY = process.env["ESTUARY_API_KEY"];
5-
const ESTUARI_URL = process.env["ESTUARY_GATEWAY"];
6-
7-
export const handler: Handler = async (event, context) => {
8-
context.callbackWaitsForEmptyEventLoop = false;
9-
if (event.body) {
10-
const newHeaders = event.headers;
11-
delete newHeaders.host;
12-
const response = await fetch(ESTUARI_URL, {
13-
method: "POST",
14-
headers: {
15-
Authorization: `Bearer ${ESTUARI_API_KEY}`,
16-
...newHeaders,
17-
},
18-
body: Buffer.from(event.body, "base64"),
19-
});
20-
21-
const parsedResponse = await response.json();
2+
import { File, FilebaseClient } from "@filebase/client";
3+
import amqp, { Connection } from "amqplib";
4+
import busboy from "busboy";
5+
6+
const { FILEBASE_TOKEN, RABBITMQ_URL, FILEBASE_API_WRAPPER } = process.env;
7+
const filebase = new FilebaseClient({ token: FILEBASE_TOKEN ?? "" });
8+
9+
type FormElement =
10+
| { isFile: true; filename: string; mimeType: string; content: Buffer }
11+
| { isFile: false; content: string };
12+
type FormData = { [key: string]: FormElement };
13+
14+
const emitRabbitMQLog = async (cid: string, operation: string) => {
15+
let connection: Connection | undefined;
16+
try {
17+
connection = await amqp.connect(RABBITMQ_URL ?? "");
18+
const channel = await connection.createChannel();
19+
20+
await channel.assertExchange("ipfs", "topic");
21+
channel.publish("ipfs", operation, Buffer.from(cid));
22+
23+
//eslint-disable-next-line no-console
24+
console.log(`Sent IPFS CID '${cid}' to exchange 'ipfs'`);
25+
} catch (err) {
26+
console.warn(err);
27+
} finally {
28+
if (typeof connection !== "undefined") await connection.close();
29+
}
30+
};
31+
32+
const parseMultipart = ({ headers, body, isBase64Encoded }) =>
33+
new Promise<FormData>((resolve, reject) => {
34+
const fields: FormData = {};
35+
36+
const bb = busboy({ headers });
37+
38+
bb.on("file", (name, file, { filename, mimeType }) =>
39+
file.on("data", (content) => {
40+
fields[name] = { isFile: true, filename, mimeType, content };
41+
})
42+
)
43+
.on("field", (name, value) => {
44+
if (value) fields[name] = { isFile: false, content: value };
45+
})
46+
.on("close", () => resolve(fields))
47+
.on("error", (err) => reject(err));
48+
49+
bb.write(body, isBase64Encoded ? "base64" : "binary");
50+
bb.end();
51+
});
52+
53+
const pinToFilebase = async (data: FormData, dapp: string, operation: string): Promise<Array<string>> => {
54+
const cids = new Array<string>();
55+
for (const [_, dataElement] of Object.entries(data)) {
56+
if (dataElement.isFile) {
57+
const { filename, mimeType, content } = dataElement;
58+
const path = `${filename}`;
59+
const cid = await filebase.storeDirectory([new File([content], path, { type: mimeType })]);
60+
await emitRabbitMQLog(cid, operation);
61+
cids.push(cid);
62+
}
63+
}
64+
65+
return cids;
66+
};
67+
68+
export const handler: Handler = async (event) => {
69+
const { queryStringParameters } = event;
70+
71+
if (
72+
!queryStringParameters ||
73+
!queryStringParameters.dapp ||
74+
!queryStringParameters.key ||
75+
!queryStringParameters.operation
76+
) {
77+
return {
78+
statusCode: 400,
79+
body: JSON.stringify({ message: "Invalid query parameters" }),
80+
};
81+
}
82+
83+
const { dapp, key, operation } = queryStringParameters;
84+
85+
if (key !== FILEBASE_API_WRAPPER) {
86+
return {
87+
statusCode: 403,
88+
body: JSON.stringify({ message: "Invalid API key" }),
89+
};
90+
}
91+
92+
try {
93+
const parsed = await parseMultipart(event);
94+
const cids = await pinToFilebase(parsed, dapp, operation);
95+
96+
return {
97+
statusCode: 200,
98+
body: JSON.stringify({
99+
message: "File has been stored successfully",
100+
cids,
101+
}),
102+
};
103+
} catch (err: any) {
22104
return {
23-
statusCode: response.status,
24-
body: JSON.stringify(parsedResponse),
105+
statusCode: 500,
106+
body: JSON.stringify({ message: err.message }),
25107
};
26108
}
27-
return {
28-
statusCode: 500,
29-
body: JSON.stringify({ message: "Invalid body format" }),
30-
};
31109
};

web/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
"@netlify/functions": "^1.6.0",
4444
"@parcel/transformer-svg-react": "~2.8.0",
4545
"@parcel/watcher": "~2.1.0",
46+
"@types/amqplib": "^0.10.1",
47+
"@types/busboy": "^1.5.0",
4648
"@types/react": "^18.2.12",
4749
"@types/react-dom": "^18.2.5",
4850
"@types/styled-components": "^5.1.26",
@@ -59,13 +61,15 @@
5961
"typescript": "^4.9.5"
6062
},
6163
"dependencies": {
64+
"@filebase/client": "^0.0.4",
6265
"@kleros/kleros-v2-contracts": "workspace:^",
6366
"@kleros/ui-components-library": "^2.5.2",
6467
"@sentry/react": "^7.55.2",
6568
"@sentry/tracing": "^7.55.2",
6669
"@types/react-modal": "^3.16.0",
6770
"@web3modal/ethereum": "^2.2.2",
6871
"@web3modal/react": "^2.2.2",
72+
"amqplib": "^0.10.3",
6973
"chart.js": "^3.9.1",
7074
"chartjs-adapter-moment": "^1.0.1",
7175
"core-js": "^3.31.0",

web/src/hooks/useCoinPrice.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import useSWR from "swr";
2+
3+
const fetchCoinPrice = async (...coinIds) => {
4+
const fetchData = async (coinId) => {
5+
const response = await fetch(`https://api.coingecko.com/api/v3/coins/${coinId}`);
6+
const data = await response.json();
7+
return data.market_data.current_price.usd;
8+
};
9+
10+
const prices = await Promise.all(coinIds.map(fetchData));
11+
return prices;
12+
};
13+
14+
export const useCoinPrice = (coinIds: string[]) => {
15+
const { data: prices, error } = useSWR<number[]>(coinIds, fetchCoinPrice);
16+
return {
17+
prices,
18+
error,
19+
};
20+
};

web/src/layout/Header/navbar/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ const Container = styled.div<{ isOpen: boolean }>`
3535

3636
const NavBar: React.FC = () => {
3737
const [isSolutionsOpen, toggleSolution] = useToggle(false);
38-
3938
const { isOpen } = useOpenContext();
4039
useLockBodyScroll(isOpen);
4140

web/src/pages/Courts/CourtDetails/Stats.tsx

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ import styled from "styled-components";
33
import { formatUnits, formatEther } from "viem";
44
import { useParams } from "react-router-dom";
55
import { useCourtDetails, CourtDetailsQuery } from "queries/useCourtDetails";
6+
import { useCoinPrice } from "hooks/useCoinPrice";
67
import StatDisplay, { IStatDisplay } from "components/StatDisplay";
7-
import JurorIcon from "svgs/icons/user.svg";
88
import BalanceIcon from "svgs/icons/law-balance.svg";
99
import MinStake from "svgs/icons/min-stake.svg";
1010
import { commify } from "utils/commify";
1111
import VoteStake from "svgs/icons/vote-stake.svg";
1212
import PNKIcon from "svgs/icons/pnk.svg";
1313
import PNKRedistributedIcon from "svgs/icons/redistributed-pnk.svg";
1414
import EthereumIcon from "svgs/icons/ethereum.svg";
15+
import { isUndefined } from "~src/utils";
1516

1617
const StyledCard = styled.div`
1718
width: auto;
@@ -25,24 +26,29 @@ const StyledCard = styled.div`
2526

2627
interface IStat {
2728
title: string;
29+
coinId?: number;
2830
getText: (data: CourtDetailsQuery["court"]) => string;
29-
getSubtext: (data: CourtDetailsQuery["court"]) => string;
31+
getSubtext: (data: CourtDetailsQuery["court"], coinPrice?: number) => string;
3032
color: IStatDisplay["color"];
3133
icon: React.FC<React.SVGAttributes<SVGElement>>;
3234
}
3335

3436
const stats: IStat[] = [
3537
{
3638
title: "Min Stake",
39+
coinId: 0,
3740
getText: (data) => commify(formatUnits(data?.minStake, 18)),
38-
getSubtext: (data) => (parseInt(formatUnits(data?.minStake, 18)) * 0.029).toFixed(2).toString() + "$",
41+
getSubtext: (data, coinPrice) =>
42+
(parseInt(formatUnits(data?.minStake, 18)) * (coinPrice ?? 0)).toFixed(2).toString() + "$",
3943
color: "purple",
4044
icon: MinStake,
4145
},
4246
{
4347
title: "Vote Stake",
48+
coinId: 0,
4449
getText: (data) => commify(formatUnits(data?.minStake, 18)),
45-
getSubtext: (data) => (parseInt(formatUnits(data?.minStake, 18)) * 0.029).toFixed(2).toString() + "$",
50+
getSubtext: (data, coinPrice) =>
51+
(parseInt(formatUnits(data?.minStake, 18)) * (coinPrice ?? 0)).toFixed(2).toString() + "$",
4652
color: "purple",
4753
icon: VoteStake,
4854
},
@@ -55,8 +61,10 @@ const stats: IStat[] = [
5561
},
5662
{
5763
title: "PNK Staked",
64+
coinId: 0,
5865
getText: (data) => commify(formatUnits(data?.stake, 18)),
59-
getSubtext: (data) => (parseInt(formatUnits(data?.stake, 18)) * 0.029).toFixed(2).toString() + "$",
66+
getSubtext: (data, coinPrice) =>
67+
(parseInt(formatUnits(data?.stake, 18)) * (coinPrice ?? 0)).toFixed(2).toString() + "$",
6068
color: "purple",
6169
icon: PNKIcon,
6270
},
@@ -76,15 +84,19 @@ const stats: IStat[] = [
7684
},
7785
{
7886
title: "ETH paid to Jurors",
79-
getText: (data) => commify(formatEther(data?.paidETH)),
80-
getSubtext: (data) => (parseInt(formatUnits(data?.paidETH, 18)) * 1600).toFixed(2).toString() + "$",
87+
coinId: 1,
88+
getText: (data) => commify(formatEther(BigInt(data?.paidETH))),
89+
getSubtext: (data, coinPrice) =>
90+
(Number(formatUnits(data?.paidETH, 18)) * (coinPrice ?? 0)).toFixed(2).toString() + "$",
8191
color: "blue",
8292
icon: EthereumIcon,
8393
},
8494
{
8595
title: "PNK redistributed",
96+
coinId: 0,
8697
getText: (data) => commify(formatUnits(data?.paidPNK, 18)),
87-
getSubtext: (data) => (parseInt(formatUnits(data?.paidPNK, 18)) * 0.029).toFixed(2).toString() + "$",
98+
getSubtext: (data, coinPrice) =>
99+
(parseInt(formatUnits(data?.paidPNK, 18)) * (coinPrice ?? 0)).toFixed(2).toString() + "$",
88100
color: "purple",
89101
icon: PNKRedistributedIcon,
90102
},
@@ -93,16 +105,20 @@ const stats: IStat[] = [
93105
const Stats = () => {
94106
const { id } = useParams();
95107
const { data } = useCourtDetails(id);
108+
const { prices } = useCoinPrice(["kleros", "ethereum"]);
96109
return (
97110
<StyledCard>
98-
{stats.map(({ title, getText, getSubtext, color, icon }, i) => (
99-
<StatDisplay
100-
key={i}
101-
{...{ title, color, icon }}
102-
text={data ? getText(data.court) : "Fetching..."}
103-
subtext={data ? getSubtext(data.court) : "Fetching..."}
104-
/>
105-
))}
111+
{stats.map(({ title, coinId, getText, getSubtext, color, icon }, i) => {
112+
const coinPrice = prices && !isUndefined(coinId) ? prices[coinId] : undefined;
113+
return (
114+
<StatDisplay
115+
key={i}
116+
{...{ title, color, icon }}
117+
text={data ? getText(data.court) : "Fetching..."}
118+
subtext={data ? getSubtext(data.court, coinPrice) : "Fetching..."}
119+
/>
120+
);
121+
})}
106122
</StyledCard>
107123
);
108124
};

0 commit comments

Comments
 (0)