diff --git a/web-devtools/.env.local.example b/web-devtools/.env.local.example
index 7e7efe9fa..acab203c2 100644
--- a/web-devtools/.env.local.example
+++ b/web-devtools/.env.local.example
@@ -2,4 +2,5 @@
export NEXT_PUBLIC_ALCHEMY_API_KEY=
export NEXT_PUBLIC_DEPLOYMENT=devnet
export NEXT_PUBLIC_CORE_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-core-devnet/version/latest
-export NEXT_PUBLIC_DRT_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-drt-arbisep-devnet/version/latest
\ No newline at end of file
+export NEXT_PUBLIC_DRT_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-drt-arbisep-devnet/version/latest
+export NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=
\ No newline at end of file
diff --git a/web-devtools/package.json b/web-devtools/package.json
index 4513cdc6b..b01cede32 100644
--- a/web-devtools/package.json
+++ b/web-devtools/package.json
@@ -30,19 +30,28 @@
"@types/node": "^20",
"@types/react": "18.2.0",
"@types/react-dom": "^18.2.18",
+ "@typescript-eslint/eslint-plugin": "^5.62.0",
+ "@typescript-eslint/parser": "^5.62.0",
+ "@typescript-eslint/utils": "^5.62.0",
"@wagmi/cli": "^2.0.3",
+ "eslint": "^8.56.0",
"eslint-config-next": "^14.2.5",
+ "eslint-config-prettier": "^8.10.0",
+ "eslint-import-resolver-typescript": "^3.6.1",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-react-hooks": "^4.6.0",
"ts-node": "^10.9.2",
"typescript": "^5.5.3"
},
"dependencies": {
"@kleros/kleros-sdk": "workspace:^",
- "@kleros/ui-components-library": "^2.10.0",
+ "@kleros/ui-components-library": "^2.15.0",
"graphql": "^16.9.0",
"next": "14.2.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^8.0.7",
+ "react-toastify": "^10.0.5",
"typewriter-effect": "^2.21.0",
"vanilla-jsoneditor": "^0.21.4",
"viem": "^2.1.0",
diff --git a/web-devtools/src/app/(main)/layout.tsx b/web-devtools/src/app/(main)/layout.tsx
index dcd0d0f0f..3d754b11f 100644
--- a/web-devtools/src/app/(main)/layout.tsx
+++ b/web-devtools/src/app/(main)/layout.tsx
@@ -2,22 +2,35 @@
import React from "react";
import styled from "styled-components";
+import "react-toastify/dist/ReactToastify.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { ToastContainer } from "react-toastify";
import GraphqlBatcherProvider from "context/GraphqlBatcher";
+import Web3Provider from "context/Web3Provider";
const Main = styled.main`
min-height: calc(100vh - 130px);
`;
const queryClient = new QueryClient();
+const StyledToastContainer = styled(ToastContainer)`
+ padding: 16px;
+ padding-top: 70px;
+`;
+
const Layout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
return (
-
-
- {children}
-
-
+
+
+
+
+
+ {children}
+
+
+
+
);
};
diff --git a/web-devtools/src/app/(main)/ruler/ChangeDeveloper.tsx b/web-devtools/src/app/(main)/ruler/ChangeDeveloper.tsx
index 8d9624ff7..6b6f62edc 100644
--- a/web-devtools/src/app/(main)/ruler/ChangeDeveloper.tsx
+++ b/web-devtools/src/app/(main)/ruler/ChangeDeveloper.tsx
@@ -1,11 +1,20 @@
-import React from "react";
+import React, { useCallback, useMemo, useState } from "react";
import styled from "styled-components";
+import { Address, isAddress } from "viem";
+import { useAccount, usePublicClient } from "wagmi";
+
import { Button } from "@kleros/ui-components-library";
+import { useRulerContext } from "context/RulerContext";
+import { useSimulateKlerosCoreRulerChangeRuler, useWriteKlerosCoreRulerChangeRuler } from "hooks/contracts/generated";
+import { isUndefined } from "utils/isUndefined";
+import { wrapWithToast } from "utils/wrapWithToast";
+
import LabeledInput from "components/LabeledInput";
import Header from "./Header";
+import { DEFAULT_CHAIN } from "consts/chains";
const Container = styled.div`
width: 100%;
@@ -25,14 +34,60 @@ const StyledLabel = styled.label`
`;
const ChangeDeveloper: React.FC = () => {
+ const { isConnected, chainId } = useAccount();
+ const { arbitrable, currentDeveloper, refetchData } = useRulerContext();
+ const [newDeveloper, setNewDeveloper] = useState("");
+ const [isChanging, setIsChanging] = useState(false);
+ const publicClient = usePublicClient();
+
+ const isValid = useMemo(() => newDeveloper === "" || isAddress(newDeveloper), [newDeveloper]);
+
+ const {
+ data: changeRulerConfig,
+ isLoading,
+ isError,
+ } = useSimulateKlerosCoreRulerChangeRuler({
+ query: {
+ enabled: !isUndefined(arbitrable) && !isUndefined(newDeveloper) && isAddress(newDeveloper),
+ },
+ args: [(arbitrable ?? "") as Address, newDeveloper as Address],
+ });
+
+ const { writeContractAsync: changeRuler } = useWriteKlerosCoreRulerChangeRuler();
+
+ const handleClick = useCallback(() => {
+ if (!publicClient || !changeRulerConfig) return;
+ setIsChanging(true);
+ wrapWithToast(async () => changeRuler(changeRulerConfig.request), publicClient)
+ .then(() => refetchData())
+ .finally(() => setIsChanging(false));
+ }, [publicClient, changeRulerConfig, changeRuler, refetchData]);
+
+ const isDisabled = useMemo(
+ () =>
+ !isConnected ||
+ chainId !== DEFAULT_CHAIN ||
+ !changeRulerConfig ||
+ isError ||
+ isLoading ||
+ isChanging ||
+ isUndefined(arbitrable) ||
+ !isValid,
+ [changeRulerConfig, isError, isLoading, isChanging, arbitrable, isValid, isConnected, chainId]
+ );
return (
-
+
- Current Developer : 0xbe8d95497E53aB41d5A45CC8def90d0e59b49f99
-
+ Current Developer : {currentDeveloper ?? "None"}
+ setNewDeveloper(e.target.value)}
+ message={isValid ? "" : "Invalid Address"}
+ variant={isValid ? "" : "error"}
+ />
-
+
);
};
diff --git a/web-devtools/src/app/(main)/ruler/Header.tsx b/web-devtools/src/app/(main)/ruler/Header.tsx
index bd462f1a1..0b86d0419 100644
--- a/web-devtools/src/app/(main)/ruler/Header.tsx
+++ b/web-devtools/src/app/(main)/ruler/Header.tsx
@@ -1,11 +1,25 @@
import React from "react";
import styled from "styled-components";
+import WithHelpTooltip from "components/WithHelpTooltip";
-const Container = styled.h2`
+const Container = styled.div`
+ width: 100%;
+ display: flex;
+ align-items: center;
border-bottom: 1px solid ${({ theme }) => theme.klerosUIComponentsStroke};
padding: 8px 0px;
`;
-const Header: React.FC<{ text: string }> = ({ text }) => {text};
+const Title = styled.h2`
+ margin: 0;
+`;
+
+const Header: React.FC<{ text: string; tooltipMsg: string }> = ({ text, tooltipMsg }) => (
+
+
+ {text}
+
+
+);
export default Header;
diff --git a/web-devtools/src/app/(main)/ruler/ManualRuling.tsx b/web-devtools/src/app/(main)/ruler/ManualRuling.tsx
index 477b1476f..0354fc7aa 100644
--- a/web-devtools/src/app/(main)/ruler/ManualRuling.tsx
+++ b/web-devtools/src/app/(main)/ruler/ManualRuling.tsx
@@ -1,11 +1,26 @@
-import React, { useState } from "react";
+"use client";
+import React, { useCallback, useMemo, useState } from "react";
import styled from "styled-components";
+import { RULING_MODE } from "consts";
+import { useAccount, usePublicClient } from "wagmi";
+
import { Button } from "@kleros/ui-components-library";
+import { useRulerContext } from "context/RulerContext";
+import {
+ useSimulateKlerosCoreRulerChangeRulingModeToManual,
+ useSimulateKlerosCoreRulerExecuteRuling,
+ useWriteKlerosCoreRulerChangeRulingModeToManual,
+ useWriteKlerosCoreRulerExecuteRuling,
+} from "hooks/contracts/generated";
+import { isUndefined } from "utils/isUndefined";
+import { wrapWithToast } from "utils/wrapWithToast";
+
import LabeledInput from "components/LabeledInput";
import Header from "./Header";
+import { DEFAULT_CHAIN } from "consts/chains";
const Container = styled.div`
width: 100%;
@@ -22,14 +37,75 @@ const SelectContainer = styled.div`
`;
const ManualRuling: React.FC = () => {
- const [tie, setTie] = useState(false);
- const [overriden, setOverriden] = useState(false);
+ const { isConnected, chainId } = useAccount();
+ const { arbitrable, arbitrableSettings } = useRulerContext();
+ const [isSending, setIsSending] = useState(false);
+ const [tie, setTie] = useState(arbitrableSettings?.tied ?? false);
+ const [overridden, setOverridden] = useState(arbitrableSettings?.overridden ?? false);
+ const [ruling, setRuling] = useState(arbitrableSettings?.ruling);
const [disputeId, setDisputeId] = useState();
- const [ruling, setRuling] = useState();
+
+ const publicClient = usePublicClient();
+
+ const { data: manualModeConfig } = useSimulateKlerosCoreRulerChangeRulingModeToManual({
+ query: {
+ enabled: arbitrableSettings?.rulingMode !== RULING_MODE.Manual && !isUndefined(arbitrable),
+ },
+ args: [arbitrable as `0x${string}`],
+ });
+ const { writeContractAsync: changeToManualMode } = useWriteKlerosCoreRulerChangeRulingModeToManual();
+
+ const isDisabled = useMemo(() => {
+ return (
+ !isConnected ||
+ chainId !== DEFAULT_CHAIN ||
+ isUndefined(disputeId) ||
+ isUndefined(ruling) ||
+ isUndefined(arbitrable)
+ );
+ }, [disputeId, ruling, arbitrable, isConnected, chainId]);
+
+ const {
+ data: executeConfig,
+ isLoading: isLoadingExecuteConfig,
+ isError,
+ } = useSimulateKlerosCoreRulerExecuteRuling({
+ query: {
+ enabled: arbitrableSettings?.rulingMode === RULING_MODE.Manual && !isUndefined(arbitrable) && !isDisabled,
+ },
+ args: [BigInt(disputeId ?? 0), BigInt(ruling ?? 0), tie, overridden],
+ });
+
+ const { writeContractAsync: executeRuling } = useWriteKlerosCoreRulerExecuteRuling();
+
+ const handleRuling = useCallback(async () => {
+ if (!publicClient) return;
+ if (arbitrableSettings?.rulingMode !== RULING_MODE.Manual) {
+ if (!manualModeConfig) return;
+ setIsSending(true);
+
+ wrapWithToast(async () => await changeToManualMode(manualModeConfig.request), publicClient)
+ .then(async (res) => {
+ if (res.status && executeConfig) {
+ wrapWithToast(async () => await executeRuling(executeConfig.request), publicClient);
+ }
+ })
+ .finally(() => setIsSending(false));
+ } else if (executeConfig) {
+ setIsSending(true);
+
+ wrapWithToast(async () => await executeRuling(executeConfig.request), publicClient).finally(() =>
+ setIsSending(false)
+ );
+ }
+ }, [publicClient, executeConfig, manualModeConfig, arbitrableSettings, changeToManualMode, executeRuling]);
return (
-
+
{
setOverriden((prev) => !prev)}
+ checked={overridden}
+ onChange={() => setOverridden((prev) => !prev)}
/>
-
-
+
);
};
diff --git a/web-devtools/src/app/(main)/ruler/RulingModes.tsx b/web-devtools/src/app/(main)/ruler/RulingModes.tsx
index 8b9f1db4e..b119ea179 100644
--- a/web-devtools/src/app/(main)/ruler/RulingModes.tsx
+++ b/web-devtools/src/app/(main)/ruler/RulingModes.tsx
@@ -1,13 +1,27 @@
-import React, { useState } from "react";
+import React, { useCallback, useMemo, useState } from "react";
import styled from "styled-components";
import { RULING_MODE } from "consts";
+import { useAccount, usePublicClient } from "wagmi";
import { Button, Radio } from "@kleros/ui-components-library";
+import { useRulerContext } from "context/RulerContext";
+import {
+ useSimulateKlerosCoreRulerChangeRulingModeToAutomaticPreset,
+ useSimulateKlerosCoreRulerChangeRulingModeToAutomaticRandom,
+ useSimulateKlerosCoreRulerChangeRulingModeToManual,
+ useWriteKlerosCoreRulerChangeRulingModeToAutomaticPreset,
+ useWriteKlerosCoreRulerChangeRulingModeToAutomaticRandom,
+ useWriteKlerosCoreRulerChangeRulingModeToManual,
+} from "hooks/contracts/generated";
+import { isUndefined } from "utils/isUndefined";
+import { wrapWithToast } from "utils/wrapWithToast";
+
import LabeledInput from "components/LabeledInput";
import Header from "./Header";
+import { DEFAULT_CHAIN } from "consts/chains";
const Container = styled.div`
width: 100%;
@@ -31,15 +45,180 @@ const AutomaticPresetInputsContainer = styled.div`
flex-wrap: wrap;
`;
+const StyledLabel = styled.label``;
+
const RulingModes: React.FC = () => {
- const [tie, setTie] = useState(false);
- const [overriden, setOverriden] = useState(false);
- const [ruling, setRuling] = useState();
- const [rulingMode, setRulingMode] = useState(null);
+ const { isConnected, chainId } = useAccount();
+ const { arbitrable, arbitrableSettings } = useRulerContext();
+ const [rulingMode, setRulingMode] = useState(RULING_MODE.Uninitialized);
+ const [tie, setTie] = useState(false);
+ const [overridden, setOverridden] = useState(false);
+ const [ruling, setRuling] = useState(0);
+ const [isSending, setIsSending] = useState(false);
+
+ const publicClient = usePublicClient();
+
+ const {
+ data: manualModeConfig,
+ isError: manualModeConfigError,
+ isLoading: isLoadingManualConfig,
+ } = useSimulateKlerosCoreRulerChangeRulingModeToManual({
+ query: {
+ enabled:
+ rulingMode === RULING_MODE.Manual &&
+ !isUndefined(arbitrable) &&
+ arbitrableSettings?.rulingMode !== RULING_MODE.Manual,
+ },
+ args: [arbitrable as `0x${string}`],
+ });
+ const { writeContractAsync: changeToManualMode, isPending: isChangingToManualMode } =
+ useWriteKlerosCoreRulerChangeRulingModeToManual();
+
+ const {
+ data: automaticPresetConfig,
+ isError: automaticPresetConfigError,
+ isLoading: isLoadingAutomaticPresetConfig,
+ } = useSimulateKlerosCoreRulerChangeRulingModeToAutomaticPreset({
+ query: {
+ enabled:
+ rulingMode === RULING_MODE.AutomaticPreset &&
+ !isUndefined(arbitrable) &&
+ (arbitrableSettings?.rulingMode !== RULING_MODE.AutomaticPreset ||
+ arbitrableSettings?.ruling !== ruling ||
+ arbitrableSettings?.tied !== tie ||
+ arbitrableSettings?.overridden !== overridden),
+ },
+ args: [arbitrable as `0x${string}`, BigInt(ruling), tie, overridden],
+ });
+ const { writeContractAsync: changeToAutomaticPreset, isPending: isChangingToAutomaticPreset } =
+ useWriteKlerosCoreRulerChangeRulingModeToAutomaticPreset();
+
+ const {
+ data: automaticRandomConfig,
+ isError: automaticRandomConfigError,
+ isLoading: isLoadingAutomaticRandomConfig,
+ } = useSimulateKlerosCoreRulerChangeRulingModeToAutomaticRandom({
+ query: {
+ enabled:
+ rulingMode === RULING_MODE.AutomaticRandom &&
+ !isUndefined(arbitrable) &&
+ arbitrableSettings?.rulingMode !== RULING_MODE.AutomaticRandom,
+ },
+ args: [arbitrable as `0x${string}`],
+ });
+ const { writeContractAsync: changeToAutomaticRandom, isPending: isChangingToAutomaticRandom } =
+ useWriteKlerosCoreRulerChangeRulingModeToAutomaticRandom();
+
+ const isDisabled = useMemo(() => {
+ if (!arbitrable || !isConnected || chainId !== DEFAULT_CHAIN) return true;
+ switch (rulingMode) {
+ case RULING_MODE.Manual:
+ return (
+ rulingMode === arbitrableSettings?.rulingMode ||
+ manualModeConfigError ||
+ isChangingToManualMode ||
+ isLoadingManualConfig
+ );
+ case RULING_MODE.AutomaticPreset:
+ return (
+ automaticPresetConfigError ||
+ isChangingToAutomaticPreset ||
+ isLoadingAutomaticPresetConfig ||
+ (rulingMode === arbitrableSettings?.rulingMode &&
+ arbitrableSettings?.ruling === ruling &&
+ arbitrableSettings?.tied === tie &&
+ arbitrableSettings?.overridden === overridden)
+ );
+ default:
+ return (
+ rulingMode === arbitrableSettings?.rulingMode ||
+ automaticRandomConfigError ||
+ isChangingToAutomaticRandom ||
+ isLoadingAutomaticRandomConfig
+ );
+ }
+ }, [
+ arbitrable,
+ rulingMode,
+ manualModeConfigError,
+ isChangingToManualMode,
+ automaticPresetConfigError,
+ isChangingToAutomaticPreset,
+ automaticRandomConfigError,
+ isChangingToAutomaticRandom,
+ isLoadingManualConfig,
+ isLoadingAutomaticRandomConfig,
+ isLoadingAutomaticPresetConfig,
+ arbitrableSettings,
+ tie,
+ overridden,
+ ruling,
+ isConnected,
+ chainId,
+ ]);
+
+ const isLoading = useMemo(() => {
+ switch (rulingMode) {
+ case RULING_MODE.Manual:
+ return isChangingToManualMode || isLoadingManualConfig;
+ case RULING_MODE.AutomaticPreset:
+ return isChangingToAutomaticPreset || isLoadingAutomaticPresetConfig;
+ default:
+ return isChangingToAutomaticRandom || isLoadingAutomaticRandomConfig;
+ }
+ }, [
+ rulingMode,
+ isChangingToManualMode,
+ isChangingToAutomaticPreset,
+ isChangingToAutomaticRandom,
+ isLoadingManualConfig,
+ isLoadingAutomaticRandomConfig,
+ isLoadingAutomaticPresetConfig,
+ ]);
+
+ const handleUpdate = useCallback(() => {
+ if (!publicClient) return;
+ setIsSending(true);
+ switch (rulingMode) {
+ case RULING_MODE.Manual:
+ if (!manualModeConfig) return;
+ wrapWithToast(async () => await changeToManualMode(manualModeConfig.request), publicClient).finally(() =>
+ setIsSending(false)
+ );
+ return;
+ case RULING_MODE.AutomaticPreset:
+ if (!automaticPresetConfig) return;
+ wrapWithToast(async () => await changeToAutomaticPreset(automaticPresetConfig.request), publicClient).finally(
+ () => setIsSending(false)
+ );
+ return;
+ default:
+ if (!automaticRandomConfig) return;
+ wrapWithToast(async () => await changeToAutomaticRandom(automaticRandomConfig.request), publicClient).finally(
+ () => setIsSending(false)
+ );
+ return;
+ }
+ }, [
+ rulingMode,
+ automaticPresetConfig,
+ manualModeConfig,
+ automaticRandomConfig,
+ publicClient,
+ changeToAutomaticPreset,
+ changeToAutomaticRandom,
+ changeToManualMode,
+ ]);
return (
-
+
+
+ Current mode: {getRulingModeText(arbitrableSettings?.rulingMode)}
+
{
{
- setRulingMode(RULING_MODE.RandomPreset);
+ setRulingMode(RULING_MODE.AutomaticRandom);
}}
/>
{
setRulingMode(RULING_MODE.AutomaticPreset);
}}
/>
-
- setRuling(Number(e.target.value))}
- />
- setTie((prev) => !prev)} />
- setOverriden((prev) => !prev)}
- />
-
+ {rulingMode === RULING_MODE.AutomaticPreset && (
+
+ setRuling(Number(e.target.value))}
+ disabled={rulingMode !== RULING_MODE.AutomaticPreset}
+ />
+ setTie((prev) => !prev)}
+ disabled={rulingMode !== RULING_MODE.AutomaticPreset}
+ />
+ setOverridden((prev) => !prev)}
+ disabled={rulingMode !== RULING_MODE.AutomaticPreset}
+ />
+
+ )}
-
-
+
);
};
+const getRulingModeText = (mode?: RULING_MODE) => {
+ if (!mode) return "Uninitialized";
+ switch (mode) {
+ case RULING_MODE.Manual:
+ return "Manual";
+ case RULING_MODE.AutomaticRandom:
+ return "Automatic Random";
+ case RULING_MODE.AutomaticPreset:
+ return "Automatic Preset";
+ default:
+ return "Uninitialized";
+ }
+};
+
export default RulingModes;
diff --git a/web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx b/web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx
new file mode 100644
index 000000000..c6647227a
--- /dev/null
+++ b/web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx
@@ -0,0 +1,131 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import styled, { css } from "styled-components";
+
+import { Address } from "viem";
+
+import { Copiable, DropdownSelect, Field } from "@kleros/ui-components-library";
+
+import { useRulerContext } from "context/RulerContext";
+import { shortenAddress } from "utils/shortenAddress";
+import { klerosCoreAddress } from "hooks/contracts/generated";
+import { DEFAULT_CHAIN } from "consts/chains";
+import { landscapeStyle } from "styles/landscapeStyle";
+
+const Container = styled.div`
+ width: 100%;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-around;
+ gap: 16px;
+ align-items: center;
+ margin: 16px 0;
+ padding: 8px 16px;
+ border-radius: 3px;
+ background-color: ${({ theme }) => theme.klerosUIComponentsWhiteBackground};
+`;
+
+const AddressContainer = styled.div`
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: center;
+`;
+
+const Arbitrables = styled.div`
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
+ gap: 8px;
+`;
+const StyledLabel = styled.label``;
+
+const SelectContainer = styled.div`
+ position: relative;
+`;
+
+const StyledField = styled(Field)`
+ width: auto;
+ ${landscapeStyle(
+ () => css`
+ min-width: 250px;
+ `
+ )}
+`;
+
+const StyledDropdown = styled(DropdownSelect)`
+ position: absolute;
+ z-index: 0;
+ top: 40px;
+ left: 0;
+ width: 100%;
+ > button {
+ display: none;
+ }
+ > div {
+ z-index: 1;
+ width: 100%;
+ > div {
+ width: 100%;
+ }
+ }
+ .simplebar-content {
+ > div {
+ width: 100%;
+ }
+ }
+`;
+
+const SelectArbitrable: React.FC = () => {
+ const { arbitrable, setArbitrable, knownArbitrables } = useRulerContext();
+ const ref = useRef(null);
+ const [isClient, setIsClient] = useState(false);
+
+ // hydration workaround, local storage is inevitably going to be different, so knownArbitrables will be different
+ // server and client side
+ useEffect(() => {
+ setIsClient(true);
+ }, []);
+
+ const items = useMemo(
+ () =>
+ !isClient ? [] : knownArbitrables.map((arbitrable) => ({ text: shortenAddress(arbitrable), value: arbitrable })),
+ [isClient, knownArbitrables]
+ );
+
+ const openDropdown = useCallback(() => {
+ if (!ref.current || knownArbitrables.length === 0) return;
+
+ const child = ref.current.firstElementChild?.firstChild as HTMLButtonElement;
+ child.click();
+ }, [knownArbitrables, ref]);
+
+ return (
+
+
+ Ruler Address:
+
+ {shortenAddress(klerosCoreAddress[DEFAULT_CHAIN])}
+
+
+
+ Arbitrable:
+
+ setArbitrable(val as Address)} />
+ {
+ setArbitrable(e.target.value as Address);
+ }}
+ onClick={openDropdown}
+ />
+
+
+
+ );
+};
+
+export default SelectArbitrable;
diff --git a/web-devtools/src/app/(main)/ruler/page.tsx b/web-devtools/src/app/(main)/ruler/page.tsx
index ae455020e..e6f061969 100644
--- a/web-devtools/src/app/(main)/ruler/page.tsx
+++ b/web-devtools/src/app/(main)/ruler/page.tsx
@@ -1,16 +1,18 @@
"use client";
-import React from "react";
+import React, { useEffect, useState } from "react";
import styled from "styled-components";
-import { DropdownCascader } from "@kleros/ui-components-library";
-
-import { SelectArbitrable } from "utils/dummyData";
+import RulerContextProvider from "context/RulerContext";
import { responsiveSize } from "styles/responsiveSize";
import ChangeDeveloper from "./ChangeDeveloper";
import ManualRuling from "./ManualRuling";
import RulingModes from "./RulingModes";
+import SelectArbitrable from "./SelectArbitrable";
+import ConnectWallet from "components/ConnectWallet";
+import { useAccount } from "wagmi";
+import { DEFAULT_CHAIN } from "consts/chains";
const Container = styled.div`
min-height: calc(100vh - 160px);
@@ -22,42 +24,26 @@ const Container = styled.div`
padding: ${responsiveSize(32, 72)} ${responsiveSize(16, 132)} ${responsiveSize(76, 96)};
`;
-const Arbitrables = styled.div`
- width: 100%;
- background-color: ${({ theme }) => theme.klerosUIComponentsWhiteBackground};
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- justify-content: center;
- align-items: center;
- gap: 8px;
- margin: 16px 0;
- padding: 8px 16px;
- border-radius: 3px;
+const StyledConnectWallet = styled(ConnectWallet)`
+ align-self: flex-start;
`;
-const StyledLabel = styled.label``;
-
const Ruler: React.FC = () => {
- return (
-
- Ruler
-
-
- Arbitrable:
- {
- //todo;
- }}
- items={SelectArbitrable}
- />
-
+ const { isConnected, chainId } = useAccount();
+ const [isClient, setIsClient] = useState(false);
-
-
-
-
+ useEffect(() => setIsClient(true), []);
+ return (
+
+
+ Ruler
+
+ {isClient && (!isConnected || chainId !== DEFAULT_CHAIN) ? : null}
+
+
+
+
+
);
};
export default Ruler;
diff --git a/web-devtools/src/assets/svgs/icons/help.svg b/web-devtools/src/assets/svgs/icons/help.svg
new file mode 100644
index 000000000..35cea89d6
--- /dev/null
+++ b/web-devtools/src/assets/svgs/icons/help.svg
@@ -0,0 +1,5 @@
+
diff --git a/web-devtools/src/components/ConnectWallet/index.tsx b/web-devtools/src/components/ConnectWallet/index.tsx
new file mode 100644
index 000000000..354940988
--- /dev/null
+++ b/web-devtools/src/components/ConnectWallet/index.tsx
@@ -0,0 +1,57 @@
+import React, { useCallback } from "react";
+
+import { useWeb3Modal, useWeb3ModalState } from "@web3modal/wagmi/react";
+import { useAccount, useSwitchChain } from "wagmi";
+
+import { Button } from "@kleros/ui-components-library";
+
+import { SUPPORTED_CHAINS, DEFAULT_CHAIN } from "consts/chains";
+
+export const SwitchChainButton: React.FC<{ className?: string }> = ({ className }) => {
+ // TODO isLoading is not documented, but exists in the type, might have changed to isPending
+ const { switchChain, isLoading } = useSwitchChain();
+ const handleSwitch = useCallback(() => {
+ if (!switchChain) {
+ console.error("Cannot switch network. Please do it manually.");
+ return;
+ }
+ try {
+ switchChain({ chainId: DEFAULT_CHAIN });
+ } catch (err) {
+ console.error(err);
+ }
+ }, [switchChain]);
+ return (
+
+ );
+};
+
+const ConnectButton: React.FC<{ className?: string }> = ({ className }) => {
+ const { open } = useWeb3Modal();
+ const { open: isOpen } = useWeb3ModalState();
+ return (
+