Skip to content

Commit 68d128b

Browse files
authoredOct 7, 2024··
Merge pull request #1694 from kleros/feat/devtools-ruler-functionality
Feat/devtools ruler functionality
2 parents 71e7d66 + a3146ab commit 68d128b

23 files changed

+994
-114
lines changed
 

‎web-devtools/.env.local.example

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
export NEXT_PUBLIC_ALCHEMY_API_KEY=
33
export NEXT_PUBLIC_DEPLOYMENT=devnet
44
export NEXT_PUBLIC_CORE_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-core-devnet/version/latest
5-
export NEXT_PUBLIC_DRT_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-drt-arbisep-devnet/version/latest
5+
export NEXT_PUBLIC_DRT_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-drt-arbisep-devnet/version/latest
6+
export NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=

‎web-devtools/package.json

+10-1
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,28 @@
3030
"@types/node": "^20",
3131
"@types/react": "18.2.0",
3232
"@types/react-dom": "^18.2.18",
33+
"@typescript-eslint/eslint-plugin": "^5.62.0",
34+
"@typescript-eslint/parser": "^5.62.0",
35+
"@typescript-eslint/utils": "^5.62.0",
3336
"@wagmi/cli": "^2.0.3",
37+
"eslint": "^8.56.0",
3438
"eslint-config-next": "^14.2.5",
39+
"eslint-config-prettier": "^8.10.0",
40+
"eslint-import-resolver-typescript": "^3.6.1",
41+
"eslint-plugin-react": "^7.33.2",
42+
"eslint-plugin-react-hooks": "^4.6.0",
3543
"ts-node": "^10.9.2",
3644
"typescript": "^5.5.3"
3745
},
3846
"dependencies": {
3947
"@kleros/kleros-sdk": "workspace:^",
40-
"@kleros/ui-components-library": "^2.10.0",
48+
"@kleros/ui-components-library": "^2.15.0",
4149
"graphql": "^16.9.0",
4250
"next": "14.2.4",
4351
"react": "^18.2.0",
4452
"react-dom": "^18.2.0",
4553
"react-markdown": "^8.0.7",
54+
"react-toastify": "^10.0.5",
4655
"typewriter-effect": "^2.21.0",
4756
"vanilla-jsoneditor": "^0.21.4",
4857
"viem": "^2.1.0",

‎web-devtools/src/app/(main)/layout.tsx

+18-5
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,35 @@
22
import React from "react";
33
import styled from "styled-components";
44

5+
import "react-toastify/dist/ReactToastify.css";
56
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
7+
import { ToastContainer } from "react-toastify";
68

79
import GraphqlBatcherProvider from "context/GraphqlBatcher";
10+
import Web3Provider from "context/Web3Provider";
811

912
const Main = styled.main`
1013
min-height: calc(100vh - 130px);
1114
`;
1215
const queryClient = new QueryClient();
1316

17+
const StyledToastContainer = styled(ToastContainer)`
18+
padding: 16px;
19+
padding-top: 70px;
20+
`;
21+
1422
const Layout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
1523
return (
16-
<QueryClientProvider client={queryClient}>
17-
<GraphqlBatcherProvider>
18-
<Main>{children}</Main>
19-
</GraphqlBatcherProvider>
20-
</QueryClientProvider>
24+
<Web3Provider>
25+
<QueryClientProvider client={queryClient}>
26+
<GraphqlBatcherProvider>
27+
<Main>
28+
<StyledToastContainer />
29+
{children}
30+
</Main>
31+
</GraphqlBatcherProvider>
32+
</QueryClientProvider>
33+
</Web3Provider>
2134
);
2235
};
2336

‎web-devtools/src/app/(main)/ruler/ChangeDeveloper.tsx

+60-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
import React from "react";
1+
import React, { useCallback, useMemo, useState } from "react";
22
import styled from "styled-components";
33

4+
import { Address, isAddress } from "viem";
5+
import { useAccount, usePublicClient } from "wagmi";
6+
47
import { Button } from "@kleros/ui-components-library";
58

9+
import { useRulerContext } from "context/RulerContext";
10+
import { useSimulateKlerosCoreRulerChangeRuler, useWriteKlerosCoreRulerChangeRuler } from "hooks/contracts/generated";
11+
import { isUndefined } from "utils/isUndefined";
12+
import { wrapWithToast } from "utils/wrapWithToast";
13+
614
import LabeledInput from "components/LabeledInput";
715

816
import Header from "./Header";
17+
import { DEFAULT_CHAIN } from "consts/chains";
918

1019
const Container = styled.div`
1120
width: 100%;
@@ -25,14 +34,60 @@ const StyledLabel = styled.label`
2534
`;
2635

2736
const ChangeDeveloper: React.FC = () => {
37+
const { isConnected, chainId } = useAccount();
38+
const { arbitrable, currentDeveloper, refetchData } = useRulerContext();
39+
const [newDeveloper, setNewDeveloper] = useState("");
40+
const [isChanging, setIsChanging] = useState(false);
41+
const publicClient = usePublicClient();
42+
43+
const isValid = useMemo(() => newDeveloper === "" || isAddress(newDeveloper), [newDeveloper]);
44+
45+
const {
46+
data: changeRulerConfig,
47+
isLoading,
48+
isError,
49+
} = useSimulateKlerosCoreRulerChangeRuler({
50+
query: {
51+
enabled: !isUndefined(arbitrable) && !isUndefined(newDeveloper) && isAddress(newDeveloper),
52+
},
53+
args: [(arbitrable ?? "") as Address, newDeveloper as Address],
54+
});
55+
56+
const { writeContractAsync: changeRuler } = useWriteKlerosCoreRulerChangeRuler();
57+
58+
const handleClick = useCallback(() => {
59+
if (!publicClient || !changeRulerConfig) return;
60+
setIsChanging(true);
61+
wrapWithToast(async () => changeRuler(changeRulerConfig.request), publicClient)
62+
.then(() => refetchData())
63+
.finally(() => setIsChanging(false));
64+
}, [publicClient, changeRulerConfig, changeRuler, refetchData]);
65+
66+
const isDisabled = useMemo(
67+
() =>
68+
!isConnected ||
69+
chainId !== DEFAULT_CHAIN ||
70+
!changeRulerConfig ||
71+
isError ||
72+
isLoading ||
73+
isChanging ||
74+
isUndefined(arbitrable) ||
75+
!isValid,
76+
[changeRulerConfig, isError, isLoading, isChanging, arbitrable, isValid, isConnected, chainId]
77+
);
2878
return (
2979
<Container>
30-
<Header text="Developer" />
80+
<Header text="Developer" tooltipMsg="Address of the current ruler of the selected arbitrable" />
3181
<InputContainer>
32-
<StyledLabel>Current Developer : 0xbe8d95497E53aB41d5A45CC8def90d0e59b49f99</StyledLabel>
33-
<LabeledInput label="New Developer" />
82+
<StyledLabel>Current Developer : {currentDeveloper ?? "None"}</StyledLabel>
83+
<LabeledInput
84+
label="New Developer"
85+
onChange={(e) => setNewDeveloper(e.target.value)}
86+
message={isValid ? "" : "Invalid Address"}
87+
variant={isValid ? "" : "error"}
88+
/>
3489
</InputContainer>
35-
<Button text="Update" />
90+
<Button text="Update" onClick={handleClick} isLoading={isLoading || isChanging} disabled={isDisabled} />
3691
</Container>
3792
);
3893
};
+16-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
import React from "react";
22
import styled from "styled-components";
3+
import WithHelpTooltip from "components/WithHelpTooltip";
34

4-
const Container = styled.h2`
5+
const Container = styled.div`
6+
width: 100%;
7+
display: flex;
8+
align-items: center;
59
border-bottom: 1px solid ${({ theme }) => theme.klerosUIComponentsStroke};
610
padding: 8px 0px;
711
`;
812

9-
const Header: React.FC<{ text: string }> = ({ text }) => <Container>{text}</Container>;
13+
const Title = styled.h2`
14+
margin: 0;
15+
`;
16+
17+
const Header: React.FC<{ text: string; tooltipMsg: string }> = ({ text, tooltipMsg }) => (
18+
<Container>
19+
<WithHelpTooltip tooltipMsg={tooltipMsg} place="right">
20+
<Title>{text}</Title>
21+
</WithHelpTooltip>
22+
</Container>
23+
);
1024

1125
export default Header;

‎web-devtools/src/app/(main)/ruler/ManualRuling.tsx

+89-9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
1-
import React, { useState } from "react";
1+
"use client";
2+
import React, { useCallback, useMemo, useState } from "react";
23
import styled from "styled-components";
34

5+
import { RULING_MODE } from "consts";
6+
import { useAccount, usePublicClient } from "wagmi";
7+
48
import { Button } from "@kleros/ui-components-library";
59

10+
import { useRulerContext } from "context/RulerContext";
11+
import {
12+
useSimulateKlerosCoreRulerChangeRulingModeToManual,
13+
useSimulateKlerosCoreRulerExecuteRuling,
14+
useWriteKlerosCoreRulerChangeRulingModeToManual,
15+
useWriteKlerosCoreRulerExecuteRuling,
16+
} from "hooks/contracts/generated";
17+
import { isUndefined } from "utils/isUndefined";
18+
import { wrapWithToast } from "utils/wrapWithToast";
19+
620
import LabeledInput from "components/LabeledInput";
721

822
import Header from "./Header";
23+
import { DEFAULT_CHAIN } from "consts/chains";
924

1025
const Container = styled.div`
1126
width: 100%;
@@ -22,14 +37,75 @@ const SelectContainer = styled.div`
2237
`;
2338

2439
const ManualRuling: React.FC = () => {
25-
const [tie, setTie] = useState<boolean>(false);
26-
const [overriden, setOverriden] = useState<boolean>(false);
40+
const { isConnected, chainId } = useAccount();
41+
const { arbitrable, arbitrableSettings } = useRulerContext();
42+
const [isSending, setIsSending] = useState<boolean>(false);
43+
const [tie, setTie] = useState(arbitrableSettings?.tied ?? false);
44+
const [overridden, setOverridden] = useState(arbitrableSettings?.overridden ?? false);
45+
const [ruling, setRuling] = useState(arbitrableSettings?.ruling);
2746
const [disputeId, setDisputeId] = useState<number>();
28-
const [ruling, setRuling] = useState<number>();
47+
48+
const publicClient = usePublicClient();
49+
50+
const { data: manualModeConfig } = useSimulateKlerosCoreRulerChangeRulingModeToManual({
51+
query: {
52+
enabled: arbitrableSettings?.rulingMode !== RULING_MODE.Manual && !isUndefined(arbitrable),
53+
},
54+
args: [arbitrable as `0x${string}`],
55+
});
56+
const { writeContractAsync: changeToManualMode } = useWriteKlerosCoreRulerChangeRulingModeToManual();
57+
58+
const isDisabled = useMemo(() => {
59+
return (
60+
!isConnected ||
61+
chainId !== DEFAULT_CHAIN ||
62+
isUndefined(disputeId) ||
63+
isUndefined(ruling) ||
64+
isUndefined(arbitrable)
65+
);
66+
}, [disputeId, ruling, arbitrable, isConnected, chainId]);
67+
68+
const {
69+
data: executeConfig,
70+
isLoading: isLoadingExecuteConfig,
71+
isError,
72+
} = useSimulateKlerosCoreRulerExecuteRuling({
73+
query: {
74+
enabled: arbitrableSettings?.rulingMode === RULING_MODE.Manual && !isUndefined(arbitrable) && !isDisabled,
75+
},
76+
args: [BigInt(disputeId ?? 0), BigInt(ruling ?? 0), tie, overridden],
77+
});
78+
79+
const { writeContractAsync: executeRuling } = useWriteKlerosCoreRulerExecuteRuling();
80+
81+
const handleRuling = useCallback(async () => {
82+
if (!publicClient) return;
83+
if (arbitrableSettings?.rulingMode !== RULING_MODE.Manual) {
84+
if (!manualModeConfig) return;
85+
setIsSending(true);
86+
87+
wrapWithToast(async () => await changeToManualMode(manualModeConfig.request), publicClient)
88+
.then(async (res) => {
89+
if (res.status && executeConfig) {
90+
wrapWithToast(async () => await executeRuling(executeConfig.request), publicClient);
91+
}
92+
})
93+
.finally(() => setIsSending(false));
94+
} else if (executeConfig) {
95+
setIsSending(true);
96+
97+
wrapWithToast(async () => await executeRuling(executeConfig.request), publicClient).finally(() =>
98+
setIsSending(false)
99+
);
100+
}
101+
}, [publicClient, executeConfig, manualModeConfig, arbitrableSettings, changeToManualMode, executeRuling]);
29102

30103
return (
31104
<Container>
32-
<Header text="Manual Ruling" />
105+
<Header
106+
text="Manual Ruling"
107+
tooltipMsg="Provide Manual ruling for the arbitrator. This operation will change the ruling mode to Manual, if the ruling mode is not Manual"
108+
/>
33109
<SelectContainer>
34110
<LabeledInput
35111
label="Dispute ID"
@@ -43,12 +119,16 @@ const ManualRuling: React.FC = () => {
43119
<LabeledInput
44120
label="Overidden"
45121
inputType="checkbox"
46-
checked={overriden}
47-
onChange={() => setOverriden((prev) => !prev)}
122+
checked={overridden}
123+
onChange={() => setOverridden((prev) => !prev)}
48124
/>
49125
</SelectContainer>
50-
51-
<Button text="Rule" />
126+
<Button
127+
text="Rule"
128+
onClick={handleRuling}
129+
isLoading={isLoadingExecuteConfig || isSending}
130+
disabled={isDisabled || isError || isSending || isLoadingExecuteConfig}
131+
/>
52132
</Container>
53133
);
54134
};

0 commit comments

Comments
 (0)
Please sign in to comment.