diff --git a/bot-pinner/package.json b/bot-pinner/package.json
index b04e1b20c..0723f6e85 100644
--- a/bot-pinner/package.json
+++ b/bot-pinner/package.json
@@ -4,7 +4,6 @@
"description": "Pinning of the court data to decentralized storage.",
"author": "Kleros",
"license": "MIT",
- "packageManager": "yarn@4.0.2+sha256.825003a0f561ad09a3b1ac4a3b3ea6207af2796d54f62a9420520915721f5186",
"volta": {
"node": "20.11.0"
},
@@ -19,6 +18,6 @@
"node": ">=16.13.0"
},
"devDependencies": {
- "@dappnode/dappnodesdk": "^0.3.11"
+ "@dappnode/dappnodesdk": "^0.3.35"
}
}
diff --git a/contracts/package.json b/contracts/package.json
index 07ce9b2b7..913ccef8c 100644
--- a/contracts/package.json
+++ b/contracts/package.json
@@ -4,9 +4,9 @@
"description": "Smart contracts for Kleros version 2",
"main": "typechain-types/index.ts",
"repository": "git@github.com:kleros/kleros-v2.git",
+ "homepage": "https://github.com/kleros/kleros-v2/tree/master/contracts#readme",
"author": "Kleros",
"license": "MIT",
- "packageManager": "yarn@4.0.2+sha256.825003a0f561ad09a3b1ac4a3b3ea6207af2796d54f62a9420520915721f5186",
"engines": {
"node": ">=16.0.0"
},
@@ -60,7 +60,7 @@
"build:types": "tsc --project tsconfig.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap"
},
"devDependencies": {
- "@defi-wonderland/natspec-smells": "^1.0.3",
+ "@defi-wonderland/natspec-smells": "^1.1.5",
"@kleros/kleros-v2-eslint-config": "workspace:^",
"@kleros/kleros-v2-prettier-config": "workspace:^",
"@kleros/kleros-v2-tsconfig": "workspace:^",
@@ -71,18 +71,19 @@
"@openzeppelin/contracts": "^5.1.0",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
- "@types/chai": "^4.3.11",
- "@types/mocha": "^10.0.6",
- "@types/node": "^20.17.1",
- "@wagmi/cli": "^2.1.16",
+ "@types/chai": "^4.3.20",
+ "@types/mocha": "^10.0.10",
+ "@types/node": "^20.17.6",
+ "@wagmi/cli": "^2.1.18",
"abitype": "^0.10.3",
- "chai": "^4.4.1",
- "dotenv": "^16.3.1",
+ "chai": "^4.5.0",
+ "dotenv": "^16.4.5",
+ "eslint": "^9.15.0",
"ethereumjs-util": "^7.1.5",
"ethers": "^6.13.4",
"graphql": "^16.9.0",
- "graphql-request": "^6.1.0",
- "hardhat": "2.22.15",
+ "graphql-request": "^7.1.2",
+ "hardhat": "2.22.16",
"hardhat-contract-sizer": "^2.10.0",
"hardhat-deploy": "^0.14.0",
"hardhat-deploy-ethers": "^0.4.2",
@@ -94,15 +95,17 @@
"node-fetch": "^3.3.2",
"pino": "^8.21.0",
"pino-pretty": "^10.3.1",
+ "prettier": "^3.3.3",
+ "prettier-plugin-solidity": "^1.4.1",
"shelljs": "^0.8.5",
"solhint-plugin-prettier": "^0.1.0",
"solidity-coverage": "^0.8.13",
"ts-node": "^10.9.2",
"typechain": "^8.3.2",
- "typescript": "^5.3.3"
+ "typescript": "^5.6.3"
},
"dependencies": {
"@kleros/vea-contracts": "^0.4.0",
- "viem": "^2.21.35"
+ "viem": "^2.21.48"
}
}
diff --git a/eslint-config/package.json b/eslint-config/package.json
index cd5f47ed2..7ce7052e5 100644
--- a/eslint-config/package.json
+++ b/eslint-config/package.json
@@ -5,25 +5,26 @@
"main": ".eslintrc.js",
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "^8.8.1",
- "@typescript-eslint/parser": "^8.8.1",
- "@typescript-eslint/utils": "^8.8.1",
+ "@typescript-eslint/eslint-plugin": "^8.15.0",
+ "@typescript-eslint/parser": "^8.15.0",
+ "@typescript-eslint/utils": "^8.15.0",
+ "eslint": "^9.15.0",
"eslint-config-prettier": "^9.1.0",
- "eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^5.2.1",
- "eslint-plugin-promise": "^6.0.0",
+ "eslint-plugin-promise": "^6.6.0",
"eslint-plugin-security": "^3.0.1",
- "eslint-utils": "^3.0.0"
+ "eslint-utils": "^3.0.0",
+ "prettier": "^3.3.3"
},
"devDependencies": {
- "@eslint/eslintrc": "^3.1.0",
- "@eslint/js": "^9.12.0",
- "globals": "^15.11.0",
- "typescript": "^5.3.3"
+ "@eslint/eslintrc": "^3.2.0",
+ "@eslint/js": "^9.15.0",
+ "globals": "^15.12.0",
+ "typescript": "^5.6.3"
},
"peerDependencies": {
- "eslint": "8.x"
+ "eslint": "^8.0.0 || ^9.0.0"
}
}
diff --git a/kleros-app/README.md b/kleros-app/README.md
new file mode 100644
index 000000000..1db6aeafe
--- /dev/null
+++ b/kleros-app/README.md
@@ -0,0 +1,112 @@
+# Kleros App
+
+Library for Kleros DApps with reusable abstractions and components.
+
+# Usage
+
+```node
+yarn install @kleros/kleros-app
+```
+
+## 1. Atlas Interaction
+
+- This library exports utilities to interact with Atlas (Kleros' backend) with minimal code.
+
+- AtlasProvider : Provides functions to interact with Atlas.
+
+> AtlasProvider needs to be wrapped with [](https://wagmi.sh/react/api/WagmiProvider) and [](https://tanstack.com/query/latest/docs/framework/react/reference/QueryClientProvider#queryclientprovider) to work properly.
+
+#### Usage
+
+1. At the root of your app, setup AtlasProvider.
+ **uri** : Atlas backend uri
+ **product** : The product / Kleros DApp interacting with Atlas (CourtV2, Curate, etc.)
+
+```typescript
+import { WagmiProvider } from 'wagmi'
+import { config } from './config'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { AtlasProvider, Products } from "@kleros/kleros-app";
+
+const queryClient = new QueryClient()
+
+function App() {
+ return
+
+
+
+ ...
+
+
+
+}
+```
+
+2. Once Provider is set up, use the functions provided.
+
+```typescript
+import React, { useCallback } from "react";
+
+import { useAccount } from "wagmi";
+import { useAtlasProvider } from "@kleros/kleros-app";
+import { Button } from "@kleros/ui-components-library";
+
+
+interface IEnsureAuth {
+ children: React.ReactElement;
+ className?: string;
+}
+
+const EnsureAuth: React.FC = ({ children, className }) => {
+ const { address } = useAccount();
+ const { isVerified, isSigningIn, authoriseUser } = useAtlasProvider();
+
+ const handleClick = useCallback(() => {
+ // authorise a user
+ authoriseUser()
+ .then((res) => { console.log(res)})
+ .catch((err) => {
+ console.log(err);
+ });
+ }, [authoriseUser]);
+
+ return isVerified ? (
+ children
+ ) : (
+
+ );
+};
+
+export default EnsureAuth;
+
+```
+
+3. [IAtlasProvider](https://github.com/kleros/kleros-v2/blob/feat/kleros-app/kleros-app/src/lib/atlas/providers/AtlasProvider.tsx)
+
+```typescript
+interface IAtlasProvider {
+ isVerified: boolean;
+ isSigningIn: boolean;
+ isAddingUser: boolean;
+ isFetchingUser: boolean;
+ isUpdatingUser: boolean;
+ isUploadingFile: boolean;
+ user: User | undefined;
+ userExists: boolean;
+ authoriseUser: () => Promise;
+ addUser: (userSettings: AddUserData) => Promise;
+ updateEmail: (userSettings: UpdateEmailData) => Promise;
+ uploadFile: (file: File, role: Roles) => Promise;
+ confirmEmail: (userSettings: ConfirmEmailData) => Promise<
+ ConfirmEmailResponse & {
+ isError: boolean;
+ }
+ >;
+}
+```
diff --git a/kleros-app/eslint.config.mjs b/kleros-app/eslint.config.mjs
new file mode 100644
index 000000000..d00b2c977
--- /dev/null
+++ b/kleros-app/eslint.config.mjs
@@ -0,0 +1,120 @@
+import { fixupConfigRules, fixupPluginRules } from "@eslint/compat";
+import react from "eslint-plugin-react";
+import reactHooks from "eslint-plugin-react-hooks";
+import security from "eslint-plugin-security";
+import _import from "eslint-plugin-import";
+import globals from "globals";
+import tsParser from "@typescript-eslint/parser";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import js from "@eslint/js";
+import { FlatCompat } from "@eslint/eslintrc";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const compat = new FlatCompat({
+ baseDirectory: __dirname,
+ recommendedConfig: js.configs.recommended,
+ allConfig: js.configs.all,
+});
+
+export default [
+ {
+ ignores: ["src/assets"],
+ },
+ ...fixupConfigRules(
+ compat.extends(
+ "plugin:react/recommended",
+ "plugin:react-hooks/recommended",
+ "plugin:import/recommended",
+ "plugin:import/react",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:prettier/recommended",
+ "prettier"
+ )
+ ),
+ {
+ plugins: {
+ react: fixupPluginRules(react),
+ "react-hooks": fixupPluginRules(reactHooks),
+ security: fixupPluginRules(security),
+ import: fixupPluginRules(_import),
+ },
+
+ languageOptions: {
+ globals: {
+ ...globals.browser,
+ ...globals.node,
+ Atomics: "readonly",
+ SharedArrayBuffer: "readonly",
+ },
+
+ parser: tsParser,
+ ecmaVersion: 2020,
+ sourceType: "module",
+
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+
+ settings: {
+ react: {
+ version: "^18.3.1",
+ },
+
+ "import/resolver": {
+ typescript: {
+ project: "./tsconfig.json",
+ },
+ },
+ },
+
+ rules: {
+ "max-len": [
+ "warn",
+ {
+ code: 120,
+ },
+ ],
+
+ "react/prop-types": 0,
+ "no-unused-vars": "off",
+
+ "@typescript-eslint/no-unused-vars": [
+ "error",
+ {
+ varsIgnorePattern: "(^_+[0-9]*$)|([iI]gnored$)|(^ignored)",
+ argsIgnorePattern: "(^_+[0-9]*$)|([iI]gnored$)|(^ignored)",
+ },
+ ],
+
+ "no-console": [
+ "error",
+ {
+ allow: ["warn", "error", "info", "debug"],
+ },
+ ],
+
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "security/detect-object-injection": "off",
+ "security/detect-non-literal-fs-filename": "off",
+
+ "import/extensions": [
+ "error",
+ "ignorePackages",
+ {
+ js: "never",
+ jsx: "never",
+ ts: "never",
+ tsx: "never",
+ },
+ ],
+
+ "import/no-unresolved": "off",
+ },
+ },
+];
diff --git a/kleros-app/package.json b/kleros-app/package.json
new file mode 100644
index 000000000..e05ec415f
--- /dev/null
+++ b/kleros-app/package.json
@@ -0,0 +1,66 @@
+{
+ "name": "@kleros/kleros-app",
+ "version": "2.0.1",
+ "description": "Library for Kleros DApps with reusable abstractions and components.",
+ "repository": "git@github.com:kleros/kleros-v2.git",
+ "homepage": "https://github.com/kleros/kleros-v2/tree/master/kleros-app#readme",
+ "author": "Kleros",
+ "license": "MIT",
+ "source": "src/lib/index.ts",
+ "types": "./dist/kleros-app.d.ts",
+ "module": "./dist/kleros-app.js",
+ "files": [
+ "dist"
+ ],
+ "type": "module",
+ "volta": {
+ "node": "20.11.0"
+ },
+ "publishConfig": {
+ "access": "public",
+ "tag": "latest"
+ },
+ "scripts": {
+ "clean": "rimraf dist",
+ "check-style": "eslint 'src/**/*.{ts,tsx}'",
+ "check-types": "tsc --noEmit",
+ "start": "vite dev src/",
+ "build": "yarn clean && vite build",
+ "release:patch": "scripts/publish.sh patch",
+ "release:minor": "scripts/publish.sh minor",
+ "release:major": "scripts/publish.sh major"
+ },
+ "prettier": "@kleros/kleros-v2-prettier-config",
+ "devDependencies": {
+ "@eslint/compat": "^1.2.3",
+ "@eslint/eslintrc": "^3.2.0",
+ "@eslint/js": "^9.15.0",
+ "@kleros/kleros-v2-eslint-config": "workspace:^",
+ "@kleros/kleros-v2-prettier-config": "workspace:^",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@typescript-eslint/eslint-plugin": "^8.15.0",
+ "@typescript-eslint/parser": "^8.15.0",
+ "eslint": "^9.15.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-import": "^2.31.0",
+ "globals": "^15.12.0",
+ "rimraf": "^6.0.1",
+ "typescript": "^5.6.3",
+ "vite": "^5.4.11",
+ "vite-plugin-dts": "^4.3.0",
+ "vite-plugin-node-polyfills": "^0.22.0"
+ },
+ "dependencies": {
+ "jose": "^5.9.6"
+ },
+ "peerDependencies": {
+ "@tanstack/react-query": "^5.59.20",
+ "graphql": "^16.9.0",
+ "graphql-request": "^7.1.2",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "viem": "^2.21.42",
+ "wagmi": "^2.13.0"
+ }
+}
diff --git a/kleros-app/scripts/publish.sh b/kleros-app/scripts/publish.sh
new file mode 100755
index 000000000..cf34d1a18
--- /dev/null
+++ b/kleros-app/scripts/publish.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+
+#--------------------------------------
+# Error handling
+#--------------------------------------
+
+set -Ee
+function _catch {
+ # Don't propagate to outer shell
+ exit 0
+}
+function _finally {
+ # TODO: rollback version bump
+ rm -rf $SCRIPT_DIR/../dist
+}
+trap _catch ERR
+trap _finally EXIT
+
+#--------------------------------------
+
+# Check if any tracked files are currently changed, ignoring untracked files
+if [ -n "$(git status --porcelain -uno)" ]; then
+ echo "Error: There are uncommitted changes in tracked files. Please commit or stash them before publishing."
+ exit 1
+fi
+
+yarn version $1
+
+version=$(cat package.json | jq -r .version)
+echo "Publishing version $version"
+
+git add package.json
+git commit -m "chore(kleros-app): release @kleros/kleros-app@$version"
+git tag "@kleros/kleros-app@$version" -m "@kleros/kleros-app@$version"
+git push
+git push --tags
+
+yarn clean
+yarn build
+yarn npm publish
diff --git a/kleros-app/src/App.tsx b/kleros-app/src/App.tsx
new file mode 100644
index 000000000..51426bc3c
--- /dev/null
+++ b/kleros-app/src/App.tsx
@@ -0,0 +1,18 @@
+import { createRoot } from "react-dom/client";
+import React from "react";
+
+const App = () => {
+ return (
+
+
+
Kleros
+
+
+ );
+};
+
+const app = document.getElementById("app");
+if (app) {
+ const root = createRoot(app);
+ root.render();
+}
diff --git a/kleros-app/src/index.html b/kleros-app/src/index.html
new file mode 100644
index 000000000..05506403d
--- /dev/null
+++ b/kleros-app/src/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Kleros App
+
+
+
+
+
+
+
diff --git a/kleros-app/src/lib/atlas/hooks/useSessionStorage.ts b/kleros-app/src/lib/atlas/hooks/useSessionStorage.ts
new file mode 100644
index 000000000..5cac8fb6e
--- /dev/null
+++ b/kleros-app/src/lib/atlas/hooks/useSessionStorage.ts
@@ -0,0 +1,23 @@
+import { useState } from "react";
+
+export function useSessionStorage(keyName: string, defaultValue: T) {
+ const [storedValue, setStoredValue] = useState(() => {
+ try {
+ const value = window.sessionStorage.getItem(keyName);
+
+ return value ? JSON.parse(value) : defaultValue;
+ } catch (err) {
+ return defaultValue;
+ }
+ });
+
+ const setValue = (newValue: T) => {
+ try {
+ window.sessionStorage.setItem(keyName, JSON.stringify(newValue));
+ } finally {
+ setStoredValue(newValue);
+ }
+ };
+
+ return [storedValue, setValue] as [T, (newValue: T) => void];
+}
diff --git a/kleros-app/src/lib/atlas/index.ts b/kleros-app/src/lib/atlas/index.ts
new file mode 100644
index 000000000..21968d708
--- /dev/null
+++ b/kleros-app/src/lib/atlas/index.ts
@@ -0,0 +1,2 @@
+export * from "./providers";
+export * from "./utils";
diff --git a/kleros-app/src/lib/atlas/providers/AtlasProvider.tsx b/kleros-app/src/lib/atlas/providers/AtlasProvider.tsx
new file mode 100644
index 000000000..89178035c
--- /dev/null
+++ b/kleros-app/src/lib/atlas/providers/AtlasProvider.tsx
@@ -0,0 +1,343 @@
+import React, { useMemo, createContext, useContext, useState, useCallback, useEffect } from "react";
+import { useQuery, useQueryClient } from "@tanstack/react-query";
+import { GraphQLClient } from "graphql-request";
+import { decodeJwt } from "jose";
+import { useAccount, useChainId, useSignMessage } from "wagmi";
+import {
+ createMessage,
+ getNonce,
+ loginUser,
+ addUser as addUserToAtlas,
+ fetchUser,
+ updateEmail as updateEmailInAtlas,
+ confirmEmail as confirmEmailInAtlas,
+ uploadToIpfs,
+ type User,
+ type AddUserData,
+ type UpdateEmailData,
+ type ConfirmEmailData,
+ type ConfirmEmailResponse,
+ Roles,
+ Products,
+ AuthorizationError,
+} from "../utils";
+
+import { GraphQLError } from "graphql";
+import { isUndefined } from "../../../utils";
+import { useSessionStorage } from "../hooks/useSessionStorage";
+
+interface IAtlasProvider {
+ isVerified: boolean;
+ isSigningIn: boolean;
+ isAddingUser: boolean;
+ isFetchingUser: boolean;
+ isUpdatingUser: boolean;
+ isUploadingFile: boolean;
+ user: User | undefined;
+ userExists: boolean;
+ authoriseUser: () => Promise;
+ addUser: (userSettings: AddUserData) => Promise;
+ updateEmail: (userSettings: UpdateEmailData) => Promise;
+ uploadFile: (file: File, role: Roles) => Promise;
+ confirmEmail: (userSettings: ConfirmEmailData) => Promise<
+ ConfirmEmailResponse & {
+ isError: boolean;
+ }
+ >;
+}
+
+const Context = createContext(undefined);
+
+interface AtlasConfig {
+ uri: string;
+ product: Products;
+}
+
+export const AtlasProvider: React.FC<{ config: AtlasConfig; children?: React.ReactNode }> = ({ children, config }) => {
+ const { address } = useAccount();
+ const chainId = useChainId();
+ const queryClient = useQueryClient();
+
+ const [authToken, setAuthToken] = useSessionStorage("authToken", undefined);
+ const [isSigningIn, setIsSigningIn] = useState(false);
+ const [isAddingUser, setIsAddingUser] = useState(false);
+ const [isUpdatingUser, setIsUpdatingUser] = useState(false);
+ const [isVerified, setIsVerified] = useState(false);
+ const [isUploadingFile, setIsUploadingFile] = useState(false);
+ const { signMessageAsync } = useSignMessage();
+
+ const atlasGqlClient = useMemo(() => {
+ const headers = authToken
+ ? {
+ authorization: `Bearer ${authToken}`,
+ }
+ : undefined;
+ return new GraphQLClient(`${config.uri}/graphql`, { headers });
+ }, [authToken]);
+
+ /**
+ * @description verifies user authorisation
+ * @returns boolean - true if user is authorized
+ */
+ const verifySession = useCallback(() => {
+ try {
+ if (!authToken || !address) return false;
+
+ const payload = decodeJwt(authToken);
+
+ if ((payload?.sub as string)?.toLowerCase() !== address.toLowerCase()) return false;
+ if (payload.exp && payload.exp < Date.now() / 1000) return false;
+
+ return true;
+ } catch {
+ return false;
+ }
+ }, [authToken, address]);
+
+ useEffect(() => {
+ let timeoutId: ReturnType;
+
+ const verifyAndSchedule = () => {
+ // initial verify check
+ const isValid = verifySession();
+ setIsVerified(isValid);
+
+ if (isValid && authToken) {
+ try {
+ const payload = decodeJwt(authToken);
+ const expiresIn = (payload.exp as number) * 1000 - Date.now();
+
+ timeoutId = setTimeout(verifyAndSchedule, Math.max(0, expiresIn));
+ } catch (err) {
+ console.error("Error decoding JWT:", err);
+ setIsVerified(false);
+ }
+ }
+ };
+
+ verifyAndSchedule();
+
+ return () => {
+ clearTimeout(timeoutId);
+ };
+ }, [authToken, verifySession, address]);
+
+ const {
+ data: user,
+ isLoading: isFetchingUser,
+ refetch: refetchUser,
+ } = useQuery(
+ {
+ queryKey: [`UserSettings`],
+ enabled: isVerified && !isUndefined(address),
+ queryFn: async () => {
+ try {
+ if (!isVerified || isUndefined(address)) return undefined;
+ return await fetchUser(atlasGqlClient);
+ } catch {
+ return undefined;
+ }
+ },
+ },
+ queryClient
+ );
+
+ useEffect(() => {
+ if (!isVerified) return;
+ refetchUser();
+ }, [isVerified, refetchUser]);
+
+ // remove old user's data on address change
+ useEffect(() => {
+ queryClient.removeQueries({ queryKey: ["UserSettings"] });
+ }, [address, queryClient]);
+
+ // this would change based on the fields we have and what defines a user to be existing
+ const userExists = useMemo(() => {
+ if (!user) return false;
+ return !isUndefined(user.email);
+ }, [user]);
+
+ async function fetchWithAuthErrorHandling(request: () => Promise): Promise {
+ try {
+ return await request();
+ } catch (error) {
+ if (
+ error instanceof AuthorizationError ||
+ (error instanceof GraphQLError && error?.extensions?.["code"] === "UNAUTHENTICATED")
+ ) {
+ setIsVerified(false);
+ }
+ throw error;
+ }
+ }
+
+ /**
+ * @description authorise user and enable authorised calls
+ */
+ const authoriseUser = useCallback(
+ async (statement?: string) => {
+ try {
+ if (!address || !chainId) return;
+ setIsSigningIn(true);
+ const nonce = await getNonce(atlasGqlClient, address);
+
+ const message = createMessage(address, nonce, chainId, statement);
+ const signature = await signMessageAsync({ message });
+
+ const token = await loginUser(atlasGqlClient, { message, signature });
+ setAuthToken(token);
+ } catch (err: unknown) {
+ throw err;
+ } finally {
+ setIsSigningIn(false);
+ }
+ },
+ [address, chainId, setAuthToken, signMessageAsync, atlasGqlClient]
+ );
+
+ /**
+ * @description adds a new user to atlas
+ * @param {AddUserData} userSettings - object containing data to be added
+ * @returns {Promise} A promise that resolves to true if the user was added successfully
+ */
+ const addUser = useCallback(
+ async (userSettings: AddUserData) => {
+ try {
+ if (!address || !isVerified) return false;
+ setIsAddingUser(true);
+
+ const userAdded = await fetchWithAuthErrorHandling(() => addUserToAtlas(atlasGqlClient, userSettings));
+ refetchUser();
+
+ return userAdded;
+ } catch (err: unknown) {
+ throw err;
+ } finally {
+ setIsAddingUser(false);
+ }
+ },
+ [address, isVerified, setIsAddingUser, atlasGqlClient, refetchUser]
+ );
+
+ /**
+ * @description updates user email in atlas
+ * @param {UpdateEmailData} userSettings - object containing data to be updated
+ * @returns {Promise} A promise that resolves to true if email was updated successfully
+ */
+ const updateEmail = useCallback(
+ async (userSettings: UpdateEmailData) => {
+ try {
+ if (!address || !isVerified) return false;
+ setIsUpdatingUser(true);
+
+ const emailUpdated = await fetchWithAuthErrorHandling(() => updateEmailInAtlas(atlasGqlClient, userSettings));
+ refetchUser();
+
+ return emailUpdated;
+ } catch (err: unknown) {
+ throw err;
+ } finally {
+ setIsUpdatingUser(false);
+ }
+ },
+ [address, isVerified, setIsUpdatingUser, atlasGqlClient, refetchUser]
+ );
+
+ /**
+ * @description upload file to ipfs
+ * @param {File} file - file to be uploaded
+ * @param {Roles} role - role for which file is being uploaded
+ * @returns {Promise} A promise that resolves to the ipfs cid if file was uploaded successfully else
+ * null
+ */
+ const uploadFile = useCallback(
+ async (file: File, role: Roles) => {
+ try {
+ if (!address || !isVerified || !config.uri || !authToken) return null;
+ setIsUploadingFile(true);
+
+ const hash = await fetchWithAuthErrorHandling(() =>
+ uploadToIpfs({ baseUrl: config.uri, authToken }, { file, name: file.name, role, product: config.product })
+ );
+ return hash ? `/ipfs/${hash}` : null;
+ } catch (err: unknown) {
+ throw err;
+ } finally {
+ setIsUploadingFile(false);
+ }
+ },
+ [address, isVerified, setIsUploadingFile, authToken, config.uri, config.product]
+ );
+
+ /**
+ * @description confirms user email in atlas
+ * @param {ConfirmEmailData} userSettings - object containing data to be sent
+ * @returns {Promise} A promise that resolves to true if email was confirmed successfully
+ */
+ const confirmEmail = useCallback(
+ async (userSettings: ConfirmEmailData): Promise => {
+ try {
+ setIsUpdatingUser(true);
+
+ const emailConfirmed = await confirmEmailInAtlas(atlasGqlClient, userSettings);
+
+ return { ...emailConfirmed, isError: false };
+ } catch (err: any) {
+ // eslint-disable-next-line
+ console.log("Confirm Email Error : ", err?.message);
+ return { isConfirmed: false, isTokenExpired: false, isTokenInvalid: false, isError: true };
+ }
+ },
+ [atlasGqlClient]
+ );
+
+ return (
+ ({
+ isVerified,
+ isSigningIn,
+ isAddingUser,
+ authoriseUser,
+ addUser,
+ user,
+ isFetchingUser,
+ updateEmail,
+ isUpdatingUser,
+ userExists,
+ isUploadingFile,
+ uploadFile,
+ confirmEmail,
+ }),
+ [
+ isVerified,
+ isSigningIn,
+ isAddingUser,
+ authoriseUser,
+ addUser,
+ user,
+ isFetchingUser,
+ updateEmail,
+ isUpdatingUser,
+ userExists,
+ isUploadingFile,
+ uploadFile,
+ confirmEmail,
+ ]
+ )}
+ >
+ {children}
+
+ );
+};
+
+export const useAtlasProvider = () => {
+ const context = useContext(Context);
+ if (!context) {
+ throw new Error("Context Provider not found.");
+ }
+ return context;
+};
+
+export default AtlasProvider;
diff --git a/kleros-app/src/lib/atlas/providers/index.ts b/kleros-app/src/lib/atlas/providers/index.ts
new file mode 100644
index 000000000..fbe2073a3
--- /dev/null
+++ b/kleros-app/src/lib/atlas/providers/index.ts
@@ -0,0 +1 @@
+export * from "./AtlasProvider";
diff --git a/kleros-app/src/lib/atlas/utils/addUser.ts b/kleros-app/src/lib/atlas/utils/addUser.ts
new file mode 100644
index 000000000..d04846b31
--- /dev/null
+++ b/kleros-app/src/lib/atlas/utils/addUser.ts
@@ -0,0 +1,37 @@
+import { GraphQLError } from "graphql";
+import { gql, type GraphQLClient } from "graphql-request";
+
+const query = gql`
+ mutation AddUser($settings: AddUserSettingsDto!) {
+ addUser(addUserSettings: $settings)
+ }
+`;
+
+export type AddUserData = {
+ email: string;
+};
+
+type AddUserResponse = {
+ addUser: boolean;
+};
+
+export async function addUser(client: GraphQLClient, userData: AddUserData): Promise {
+ const variables = {
+ settings: userData,
+ };
+
+ return client
+ .request(query, variables)
+ .then(async (response) => response.addUser)
+ .catch((errors) => {
+ // eslint-disable-next-line no-console
+ console.log("Add User error:", { errors });
+
+ const error = errors?.response?.errors?.[0];
+
+ if (error) {
+ throw new GraphQLError(error?.message, { ...error });
+ }
+ throw new Error("Unknown Error");
+ });
+}
diff --git a/web/src/utils/atlas/confirmEmail.ts b/kleros-app/src/lib/atlas/utils/confirmEmail.ts
similarity index 100%
rename from web/src/utils/atlas/confirmEmail.ts
rename to kleros-app/src/lib/atlas/utils/confirmEmail.ts
diff --git a/web/src/utils/atlas/createMessage.ts b/kleros-app/src/lib/atlas/utils/createMessage.ts
similarity index 76%
rename from web/src/utils/atlas/createMessage.ts
rename to kleros-app/src/lib/atlas/utils/createMessage.ts
index 9eb24ade9..ebb5531bb 100644
--- a/web/src/utils/atlas/createMessage.ts
+++ b/kleros-app/src/lib/atlas/utils/createMessage.ts
@@ -1,8 +1,6 @@
import { createSiweMessage } from "viem/siwe";
-import { DEFAULT_CHAIN } from "consts/chains";
-
-export const createMessage = (address: `0x${string}`, nonce: string, chainId: number = DEFAULT_CHAIN) => {
+export const createMessage = (address: `0x${string}`, nonce: string, chainId: number, statement?: string) => {
const domain = window.location.host;
const origin = window.location.origin;
@@ -12,7 +10,7 @@ export const createMessage = (address: `0x${string}`, nonce: string, chainId: nu
const message = createSiweMessage({
domain,
address,
- statement: "Sign In to Kleros with Ethereum.",
+ statement: statement ?? "Sign In to Kleros with Ethereum.",
uri: origin,
version: "1",
chainId,
diff --git a/kleros-app/src/lib/atlas/utils/fetchUser.ts b/kleros-app/src/lib/atlas/utils/fetchUser.ts
new file mode 100644
index 000000000..663318677
--- /dev/null
+++ b/kleros-app/src/lib/atlas/utils/fetchUser.ts
@@ -0,0 +1,34 @@
+import { gql, type GraphQLClient } from "graphql-request";
+
+export type User = {
+ email: string;
+ isEmailVerified: boolean;
+ emailUpdateableAt: string | null;
+};
+
+type GetUserResponse = {
+ user: User;
+};
+const query = gql`
+ query GetUser {
+ user {
+ email
+ isEmailVerified
+ emailUpdateableAt
+ }
+ }
+`;
+
+export async function fetchUser(client: GraphQLClient): Promise {
+ return client
+ .request(query)
+ .then((response) => response.user)
+ .catch((errors) => {
+ // eslint-disable-next-line no-console
+ console.log("Error fetching user :", { errors });
+ const errorMessage = Array.isArray(errors?.response?.errors)
+ ? errors.response.errors[0]?.message
+ : "Error fetching user";
+ throw Error(errorMessage);
+ });
+}
diff --git a/kleros-app/src/lib/atlas/utils/getNonce.ts b/kleros-app/src/lib/atlas/utils/getNonce.ts
new file mode 100644
index 000000000..951503a48
--- /dev/null
+++ b/kleros-app/src/lib/atlas/utils/getNonce.ts
@@ -0,0 +1,29 @@
+import { gql, type GraphQLClient } from "graphql-request";
+
+type GetNonce = {
+ nonce: string;
+};
+
+const query = gql`
+ mutation GetNonce($address: Address!) {
+ nonce(address: $address)
+ }
+`;
+
+export async function getNonce(client: GraphQLClient, address: string): Promise {
+ const variables = {
+ address,
+ };
+
+ return client
+ .request(query, variables)
+ .then((response) => response.nonce)
+ .catch((errors) => {
+ // eslint-disable-next-line no-console
+ console.log("Error fetching nonce for address:", address, { errors });
+ const errorMessage = Array.isArray(errors?.response?.errors)
+ ? errors.response.errors[0]?.message
+ : "Error fetching nonce";
+ throw Error(errorMessage);
+ });
+}
diff --git a/kleros-app/src/lib/atlas/utils/index.ts b/kleros-app/src/lib/atlas/utils/index.ts
new file mode 100644
index 000000000..46e105e64
--- /dev/null
+++ b/kleros-app/src/lib/atlas/utils/index.ts
@@ -0,0 +1,32 @@
+export enum Products {
+ CourtV1 = "CourtV1",
+ CourtV2 = "CourtV2",
+ Curate = "Curate",
+ Escrow = "Escrow",
+ Governor = "Governor",
+ ProofOfHumanity = "ProofOfHumanity",
+ Reality = "Reality",
+ Test = "Test",
+}
+
+export enum Roles {
+ Evidence = "evidence",
+ Generic = "generic",
+ IdentificationVideo = "identification-video",
+ CurateItemImage = "curate-item-image",
+ CurateItemFile = "curate-item-file",
+ Logo = "logo",
+ MetaEvidence = "meta-evidence",
+ Photo = "photo",
+ Policy = "policy",
+ Test = "test",
+}
+
+export * from "./loginUser";
+export * from "./getNonce";
+export * from "./createMessage";
+export * from "./addUser";
+export * from "./fetchUser";
+export * from "./updateEmail";
+export * from "./confirmEmail";
+export * from "./uploadToIpfs";
diff --git a/kleros-app/src/lib/atlas/utils/loginUser.ts b/kleros-app/src/lib/atlas/utils/loginUser.ts
new file mode 100644
index 000000000..968d6652f
--- /dev/null
+++ b/kleros-app/src/lib/atlas/utils/loginUser.ts
@@ -0,0 +1,38 @@
+import { gql, type GraphQLClient } from "graphql-request";
+
+const query = gql`
+ mutation Login($message: String!, $signature: String!) {
+ login(message: $message, signature: $signature)
+ }
+`;
+
+type AuthoriseUserData = {
+ signature: `0x${string}`;
+ message: string;
+};
+
+type Login = {
+ login: {
+ accessToken: string;
+ };
+};
+
+export async function loginUser(client: GraphQLClient, authData: AuthoriseUserData): Promise {
+ const variables = {
+ message: authData.message,
+ signature: authData.signature,
+ };
+
+ return client
+ .request(query, variables)
+ .then(async (response) => response.login.accessToken)
+ .catch((errors) => {
+ // eslint-disable-next-line no-console
+ console.log("Authorization error:", { errors });
+
+ const errorMessage = Array.isArray(errors?.response?.errors)
+ ? errors.response.errors[0]?.message
+ : "Unknown error";
+ throw new Error(errorMessage);
+ });
+}
diff --git a/kleros-app/src/lib/atlas/utils/updateEmail.ts b/kleros-app/src/lib/atlas/utils/updateEmail.ts
new file mode 100644
index 000000000..c217f5c5a
--- /dev/null
+++ b/kleros-app/src/lib/atlas/utils/updateEmail.ts
@@ -0,0 +1,35 @@
+import { GraphQLError } from "graphql";
+import { gql, type GraphQLClient } from "graphql-request";
+
+const query = gql`
+ mutation UpdateEmail($newEmail: String!) {
+ updateEmail(newEmail: $newEmail)
+ }
+`;
+
+export type UpdateEmailData = {
+ newEmail: string;
+};
+
+type UpdateEmailResponse = {
+ updateEmail: boolean;
+};
+
+export async function updateEmail(client: GraphQLClient, userData: UpdateEmailData): Promise {
+ const variables = userData;
+
+ return client
+ .request(query, variables)
+ .then(async (response) => response.updateEmail)
+ .catch((errors) => {
+ // eslint-disable-next-line no-console
+ console.log("Update Email error:", { errors });
+
+ const error = errors?.response?.errors?.[0];
+
+ if (error) {
+ throw new GraphQLError(error?.message, { ...error });
+ }
+ throw new Error("Unknown Error");
+ });
+}
diff --git a/kleros-app/src/lib/atlas/utils/uploadToIpfs.ts b/kleros-app/src/lib/atlas/utils/uploadToIpfs.ts
new file mode 100644
index 000000000..b3283890a
--- /dev/null
+++ b/kleros-app/src/lib/atlas/utils/uploadToIpfs.ts
@@ -0,0 +1,49 @@
+import { Products, Roles } from ".";
+
+export type IpfsUploadPayload = {
+ file: File;
+ name: string;
+ product: Products;
+ role: Roles;
+};
+
+type Config = {
+ baseUrl: string;
+ authToken: string;
+};
+
+export async function uploadToIpfs(config: Config, payload: IpfsUploadPayload): Promise {
+ const formData = new FormData();
+ formData.append("file", payload.file, payload.name);
+ formData.append("name", payload.name);
+ formData.append("product", payload.product);
+ formData.append("role", payload.role);
+
+ return fetch(`${config.baseUrl}/ipfs/file`, {
+ method: "POST",
+ headers: {
+ authorization: `Bearer ${config.authToken}`,
+ },
+ body: formData,
+ }).then(async (response) => {
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ message: "Error uploading to IPFS" }));
+
+ if (response.status === 401) throw new AuthorizationError(error.message);
+ throw new Error(error.message);
+ }
+
+ return await response.text();
+ });
+}
+
+export class AuthorizationError extends Error {
+ readonly name = "AuthorizationError" as const;
+ constructor(message: string) {
+ super(message);
+
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, this.constructor);
+ }
+ }
+}
diff --git a/kleros-app/src/lib/index.ts b/kleros-app/src/lib/index.ts
new file mode 100644
index 000000000..d057e151a
--- /dev/null
+++ b/kleros-app/src/lib/index.ts
@@ -0,0 +1 @@
+export * from "./atlas";
diff --git a/kleros-app/src/utils/index.ts b/kleros-app/src/utils/index.ts
new file mode 100644
index 000000000..b0d6c5b33
--- /dev/null
+++ b/kleros-app/src/utils/index.ts
@@ -0,0 +1,2 @@
+export const isUndefined = (maybeObject: any): maybeObject is undefined | null =>
+ typeof maybeObject === "undefined" || maybeObject === null;
diff --git a/kleros-app/tsconfig.json b/kleros-app/tsconfig.json
new file mode 100644
index 000000000..a657db74d
--- /dev/null
+++ b/kleros-app/tsconfig.json
@@ -0,0 +1,35 @@
+{
+ "extends": "@kleros/kleros-v2-tsconfig/react-library.json",
+ "compilerOptions": {
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "rootDir": "src",
+ "outDir": "dist",
+ "allowJs": true,
+ "forceConsistentCasingInFileNames": true,
+ "strictNullChecks": true,
+ "noUnusedLocals": true,
+ "skipLibCheck": true,
+ "allowSyntheticDefaultImports": true,
+ "removeComments": true,
+ "isolatedModules": true,
+ "moduleResolution": "node",
+ "strict": true,
+ "esModuleInterop": true,
+ "declaration": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "noImplicitAny": false,
+ "resolveJsonModule": true
+ },
+ "include": [
+ "src/lib/**/*"
+ ],
+ "exclude": [
+ "node_modules",
+ "dist"
+ ]
+}
diff --git a/kleros-app/vite.config.js b/kleros-app/vite.config.js
new file mode 100644
index 000000000..c1beec13c
--- /dev/null
+++ b/kleros-app/vite.config.js
@@ -0,0 +1,38 @@
+// vite.config.js
+import { resolve } from "path";
+import { defineConfig } from "vite";
+import dts from "vite-plugin-dts";
+import { nodePolyfills } from "vite-plugin-node-polyfills";
+
+export default defineConfig({
+ plugins: [
+ dts({ insertTypesEntry: true }),
+ nodePolyfills({
+ include: ["fs", "stream"],
+ }),
+ ],
+ build: {
+ lib: {
+ // Could also be a dictionary or array of multiple entry points
+ entry: resolve(__dirname, "src/lib/index.ts"),
+ name: "kleros-app",
+ fileName: "kleros-app",
+ },
+ sourcemap: true,
+ rollupOptions: {
+ // make sure to externalize deps that shouldn't be bundled
+ // into your library
+ external: ["react", "react-dom", "viem", "wagmi", "@tanstack/react-query", "graphql", "graphql-request"],
+ output: {
+ // Provide global variables to use in the UMD build
+ // for externalized deps
+ globals: {
+ react: "React",
+ "react-dom": "ReactDOM",
+ "@tanstack/react-query": "@tanstack/react-query",
+ "graphql-request": "graphql-request",
+ },
+ },
+ },
+ },
+});
diff --git a/kleros-sdk/package.json b/kleros-sdk/package.json
index 817ff5a27..c7cc0aeb8 100644
--- a/kleros-sdk/package.json
+++ b/kleros-sdk/package.json
@@ -3,6 +3,7 @@
"version": "2.1.8",
"description": "SDK for Kleros version 2",
"repository": "git@github.com:kleros/kleros-v2.git",
+ "homepage": "https://github.com/kleros/kleros-v2/tree/master/kleros-sdk#readme",
"author": "Kleros",
"license": "MIT",
"main": "./lib/src/index.js",
@@ -12,10 +13,6 @@
"lib/**/*",
"!lib/**/test/*"
],
- "packageManager": "yarn@4.0.2+sha256.825003a0f561ad09a3b1ac4a3b3ea6207af2796d54f62a9420520915721f5186",
- "engines": {
- "node": ">=16.0.0"
- },
"type": "commonjs",
"volta": {
"node": "20.11.0"
@@ -36,18 +33,19 @@
},
"devDependencies": {
"@types/mustache": "^4.2.5",
- "@vitest/ui": "^1.1.3",
- "mocha": "^10.2.0",
+ "@types/node": "^20.17.6",
+ "@vitest/ui": "^1.6.0",
+ "mocha": "^10.8.2",
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
- "typescript": "^5.3.3",
- "vitest": "^1.1.3"
+ "typescript": "^5.6.3",
+ "vitest": "^1.6.0"
},
"dependencies": {
"@reality.eth/reality-eth-lib": "^3.2.44",
"@urql/core": "^5.0.8",
"mustache": "^4.2.0",
- "viem": "^2.21.35",
- "zod": "^3.22.4"
+ "viem": "^2.21.48",
+ "zod": "^3.23.8"
}
}
diff --git a/package.json b/package.json
index dadf545c3..9bb845c7b 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,8 @@
"web-devtools",
"eslint-config",
"prettier-config",
- "tsconfig"
+ "tsconfig",
+ "kleros-app"
],
"packageManager": "yarn@4.5.1",
"volta": {
@@ -35,14 +36,14 @@
"devDependencies": {
"@commitlint/cli": "^17.8.1",
"@commitlint/config-conventional": "^17.8.1",
- "assert": "^2.0.0",
+ "assert": "^2.1.0",
"buffer": "^5.7.1",
"conventional-changelog-cli": "^2.2.2",
- "crypto-browserify": "^3.12.0",
+ "crypto-browserify": "^3.12.1",
"husky": "^8.0.3",
"lint-staged": "^13.3.0",
"os-browserify": "^0.3.0",
- "path-browserify": "^1.0.0",
+ "path-browserify": "^1.0.1",
"process": "^0.11.10",
"string_decoder": "^1.3.0"
},
diff --git a/prettier-config/package.json b/prettier-config/package.json
index b237e8474..906fe7732 100644
--- a/prettier-config/package.json
+++ b/prettier-config/package.json
@@ -4,9 +4,9 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
- "eslint": "^8.57.1",
- "prettier": "^2.8.8",
- "prettier-plugin-solidity": "^1.3.1"
+ "eslint": "^9.15.0",
+ "prettier": "^3.3.3",
+ "prettier-plugin-solidity": "^1.4.1"
},
"scripts": {
"lint:w": "eslint --fix '**/*.{gql,graphql,js,jsx,ts,tsx,json,md}'",
diff --git a/subgraph/package.json b/subgraph/package.json
index 44f07b9a4..21c2d258a 100644
--- a/subgraph/package.json
+++ b/subgraph/package.json
@@ -67,7 +67,6 @@
"start-local-indexer": "docker compose -f ../services/graph-node/docker-compose.yml up -d && docker compose -f ../services/graph-node/docker-compose.yml logs -f",
"stop-local-indexer": "docker compose -f ../services/graph-node/docker-compose.yml down && rm -rf ../services/graph-node/data"
},
- "packageManager": "yarn@4.0.2+sha256.825003a0f561ad09a3b1ac4a3b3ea6207af2796d54f62a9420520915721f5186",
"volta": {
"node": "20.11.0"
},
@@ -78,6 +77,7 @@
"@graphprotocol/graph-cli": "0.64.1",
"@kleros/kleros-v2-eslint-config": "workspace:^",
"@kleros/kleros-v2-prettier-config": "workspace:^",
+ "eslint": "^9.15.0",
"gluegun": "^5.2.0",
"matchstick-as": "0.6.0"
},
diff --git a/tsconfig/package.json b/tsconfig/package.json
index 47582628d..ba4899684 100644
--- a/tsconfig/package.json
+++ b/tsconfig/package.json
@@ -9,6 +9,7 @@
"devDependencies": {
"@kleros/kleros-v2-eslint-config": "*",
"@tsconfig/node18": "^18.2.4",
- "@tsconfig/node20": "^20.1.4"
+ "@tsconfig/node20": "^20.1.4",
+ "eslint": "^9.15.0"
}
}
diff --git a/web-devtools/eslint.config.mjs b/web-devtools/eslint.config.mjs
index 7a660d131..db3aa1a3d 100644
--- a/web-devtools/eslint.config.mjs
+++ b/web-devtools/eslint.config.mjs
@@ -30,7 +30,6 @@ export default [
"plugin:react-hooks/recommended",
"plugin:import/recommended",
"plugin:import/react",
- "plugin:security/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier"
@@ -41,7 +40,6 @@ export default [
react: fixupPluginRules(react),
"react-hooks": fixupPluginRules(reactHooks),
security: fixupPluginRules(security),
- import: fixupPluginRules(_import),
},
languageOptions: {
diff --git a/web-devtools/netlify.toml b/web-devtools/netlify.toml
index d12df6eae..f6ba62c58 100644
--- a/web-devtools/netlify.toml
+++ b/web-devtools/netlify.toml
@@ -7,8 +7,5 @@ YARN_ENABLE_GLOBAL_CACHE = "true"
# YARN_CACHE_FOLDER = "$HOME/.yarn_cache"
# YARN_VERSION = "3.2.0"
-[functions]
-directory = "web-devtools/netlify/functions/"
-
[dev]
framework = "nextjs"
\ No newline at end of file
diff --git a/web-devtools/package.json b/web-devtools/package.json
index 18c7c30ab..f6ae65db6 100644
--- a/web-devtools/package.json
+++ b/web-devtools/package.json
@@ -7,7 +7,6 @@
"author": "",
"license": "MIT",
"type": "module",
- "packageManager": "yarn@4.0.2+sha256.825003a0f561ad09a3b1ac4a3b3ea6207af2796d54f62a9420520915721f5186",
"volta": {
"node": "20.11.0",
"yarn": "4.5.1"
@@ -25,40 +24,47 @@
},
"prettier": "@kleros/kleros-v2-prettier-config",
"devDependencies": {
- "@graphql-codegen/cli": "^5.0.2",
- "@graphql-codegen/client-preset": "^4.3.2",
+ "@graphql-codegen/cli": "^5.0.3",
+ "@graphql-codegen/client-preset": "^4.5.1",
"@svgr/webpack": "^8.1.0",
- "@types/node": "^20.17.1",
- "@types/react": "18.2.0",
- "@types/react-dom": "^18.2.18",
- "@typescript-eslint/eslint-plugin": "^8.8.1",
- "@typescript-eslint/parser": "^8.8.1",
- "@typescript-eslint/utils": "^8.8.1",
- "@wagmi/cli": "^2.1.16",
- "eslint": "^8.57.1",
- "eslint-config-next": "^14.2.15",
+ "@types/node": "^20.17.6",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@types/react-is": "^18.3.0",
+ "@types/styled-components": "^5.1.34",
+ "@typescript-eslint/eslint-plugin": "^8.15.0",
+ "@typescript-eslint/parser": "^8.15.0",
+ "@typescript-eslint/utils": "^8.15.0",
+ "@wagmi/cli": "^2.1.18",
+ "eslint": "^9.15.0",
+ "eslint-config-next": "^15.0.3",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.3",
- "eslint-plugin-react": "^7.37.1",
- "eslint-plugin-react-hooks": "^4.6.2",
+ "eslint-plugin-react": "^7.37.2",
+ "eslint-plugin-react-hooks": "^5.0.0",
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
- "typescript": "^5.5.3"
+ "typescript": "^5.6.3"
},
"dependencies": {
"@kleros/kleros-sdk": "workspace:^",
"@kleros/ui-components-library": "^2.15.0",
+ "@tanstack/react-query": "^5.61.0",
+ "@wagmi/connectors": "^5.5.0",
+ "@wagmi/core": "^2.15.0",
"@web3modal/wagmi": "^5.1.11",
"graphql": "^16.9.0",
- "graphql-request": "^7.1.0",
- "next": "14.2.14",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-markdown": "^8.0.7",
- "react-toastify": "^10.0.5",
+ "graphql-request": "^7.1.2",
+ "next": "14.2.18",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-is": "^18.3.1",
+ "react-markdown": "^9.0.1",
+ "react-toastify": "^10.0.6",
+ "styled-components": "^5.3.3",
"typewriter-effect": "^2.21.0",
- "vanilla-jsoneditor": "^0.21.4",
- "viem": "^2.21.35",
- "wagmi": "^2.12.25"
+ "vanilla-jsoneditor": "^0.21.6",
+ "viem": "^2.21.50",
+ "wagmi": "^2.13.0"
}
}
diff --git a/web-devtools/src/app/(main)/dispute-template/CustomContextInputs.tsx b/web-devtools/src/app/(main)/dispute-template/CustomContextInputs.tsx
index 5fdfbc414..b13af9d4b 100644
--- a/web-devtools/src/app/(main)/dispute-template/CustomContextInputs.tsx
+++ b/web-devtools/src/app/(main)/dispute-template/CustomContextInputs.tsx
@@ -1,8 +1,11 @@
-import retrieveVariables from "@kleros/kleros-sdk/src/dataMappings/utils/retrieveVariables";
-import { Field } from "@kleros/ui-components-library";
-import { useMemo, useState } from "react";
+import React, { useMemo, useState } from "react";
import styled from "styled-components";
+
import { useDebounce } from "react-use";
+
+import retrieveVariables from "@kleros/kleros-sdk/src/dataMappings/utils/retrieveVariables";
+import { Field } from "@kleros/ui-components-library";
+
import WithHelpTooltip from "components/WithHelpTooltip";
const Container = styled.div`
diff --git a/web-devtools/src/app/(main)/dispute-template/FetchDisputeRequestInput.tsx b/web-devtools/src/app/(main)/dispute-template/FetchDisputeRequestInput.tsx
index 911a041e5..bf5f75272 100644
--- a/web-devtools/src/app/(main)/dispute-template/FetchDisputeRequestInput.tsx
+++ b/web-devtools/src/app/(main)/dispute-template/FetchDisputeRequestInput.tsx
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import styled from "styled-components";
import { useDebounce } from "react-use";
-import { GetEventArgs } from "viem";
+import { type GetEventArgs } from "viem";
import { Field } from "@kleros/ui-components-library";
@@ -115,7 +115,7 @@ const FetchDisputeRequestInput: React.FC = ({ setPara
value={chainId}
placeholder="Enter chain Id"
type="number"
- onChange={(e) => setChainId(Number(e.target.value))}
+ onChange={(e: React.ChangeEvent) => setChainId(Number(e.target.value))}
/>
Presets
diff --git a/web-devtools/src/app/(main)/dispute-template/FetchFromIdInput.tsx b/web-devtools/src/app/(main)/dispute-template/FetchFromIdInput.tsx
index c154e41ad..2456b23cd 100644
--- a/web-devtools/src/app/(main)/dispute-template/FetchFromIdInput.tsx
+++ b/web-devtools/src/app/(main)/dispute-template/FetchFromIdInput.tsx
@@ -60,7 +60,7 @@ const FetchFromIDInput: React.FC = ({
value={templateId}
placeholder="Enter template Id"
message={isLoading ? "fetching ..." : ""}
- onChange={(e) => setTemplateId(e.target.value)}
+ onChange={(e: React.ChangeEvent) => setTemplateId(e.target.value)}
/>
);
diff --git a/web-devtools/src/app/(main)/ruler/ChangeDeveloper.tsx b/web-devtools/src/app/(main)/ruler/ChangeDeveloper.tsx
index 4fffe8847..4a0031938 100644
--- a/web-devtools/src/app/(main)/ruler/ChangeDeveloper.tsx
+++ b/web-devtools/src/app/(main)/ruler/ChangeDeveloper.tsx
@@ -1,11 +1,12 @@
import React, { useCallback, useMemo, useState } from "react";
import styled from "styled-components";
-import { Address, isAddress } from "viem";
+import { type Address, isAddress } from "viem";
import { useAccount, usePublicClient } from "wagmi";
import { Button } from "@kleros/ui-components-library";
+import { DEFAULT_CHAIN } from "consts/chains";
import { useRulerContext } from "context/RulerContext";
import { useSimulateKlerosCoreRulerChangeRuler, useWriteKlerosCoreRulerChangeRuler } from "hooks/contracts/generated";
import { isUndefined } from "utils/isUndefined";
@@ -14,7 +15,6 @@ 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%;
diff --git a/web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx b/web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx
index a29664df6..87ba1d53e 100644
--- a/web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx
+++ b/web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx
@@ -1,18 +1,19 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import styled, { css } from "styled-components";
-import { Address, PublicClient } from "viem";
+import { type Address, type PublicClient } from "viem";
import { usePublicClient } from "wagmi";
import { Copiable, DropdownSelect, Field } from "@kleros/ui-components-library";
+import { DEFAULT_CHAIN } from "consts/chains";
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";
+import { shortenAddress } from "utils/shortenAddress";
import { validateAddress } from "utils/validateAddressOrEns";
+import { landscapeStyle } from "styles/landscapeStyle";
+
const Container = styled.div`
width: 100%;
display: flex;
diff --git a/web-devtools/src/components/ReactMarkdown.tsx b/web-devtools/src/components/ReactMarkdown.tsx
index 3770c1e93..68b561977 100644
--- a/web-devtools/src/components/ReactMarkdown.tsx
+++ b/web-devtools/src/components/ReactMarkdown.tsx
@@ -13,7 +13,7 @@ const ReactMarkdown: React.FC<{ children: string }> = ({ children }) => {
}
try {
return {children};
- } catch (error) {
+ } catch {
return Error rendering content
;
}
};
diff --git a/web-devtools/src/consts/chains.ts b/web-devtools/src/consts/chains.ts
index e670d5c49..23afcf66a 100644
--- a/web-devtools/src/consts/chains.ts
+++ b/web-devtools/src/consts/chains.ts
@@ -1,4 +1,4 @@
-import { Chain, mainnet, arbitrumSepolia, gnosisChiado } from "viem/chains";
+import { type Chain, mainnet, arbitrumSepolia, gnosisChiado } from "viem/chains";
export const SUPPORTED_CHAINS: Record = {
[arbitrumSepolia.id]: arbitrumSepolia,
diff --git a/web-devtools/src/context/GraphqlBatcher.tsx b/web-devtools/src/context/GraphqlBatcher.tsx
index 07cf720cd..74260761c 100644
--- a/web-devtools/src/context/GraphqlBatcher.tsx
+++ b/web-devtools/src/context/GraphqlBatcher.tsx
@@ -1,7 +1,7 @@
import React, { useMemo, createContext, useContext } from "react";
import { TypedDocumentNode } from "@graphql-typed-document-node/core";
-import { create, windowedFiniteBatchScheduler, Batcher } from "@yornaath/batshit";
+import { create, windowedFiniteBatchScheduler, type Batcher } from "@yornaath/batshit";
import { request } from "graphql-request";
import { debounceErrorToast } from "utils/debounceErrorToast";
diff --git a/web-devtools/src/context/RulerContext.tsx b/web-devtools/src/context/RulerContext.tsx
index 2e82c01cf..148b9e332 100644
--- a/web-devtools/src/context/RulerContext.tsx
+++ b/web-devtools/src/context/RulerContext.tsx
@@ -1,7 +1,8 @@
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
+import { type Address, isAddress } from "viem";
+
import { RULING_MODE } from "consts";
-import { Address, isAddress } from "viem";
import { useReadKlerosCoreRulerRulers, useReadKlerosCoreRulerSettings } from "hooks/contracts/generated";
import { useLocalStorage } from "hooks/useLocalStorage";
diff --git a/web-devtools/src/context/Web3Provider.tsx b/web-devtools/src/context/Web3Provider.tsx
index eeaee61ce..4e2651a18 100644
--- a/web-devtools/src/context/Web3Provider.tsx
+++ b/web-devtools/src/context/Web3Provider.tsx
@@ -63,7 +63,6 @@ const wagmiConfig = createConfig({
createWeb3Modal({
wagmiConfig,
projectId,
- // @ts-ignore
defaultChain: arbitrumSepolia,
themeVariables: {
"--w3m-color-mix": theme.klerosUIComponentsPrimaryPurple,
diff --git a/web-devtools/src/hooks/useLocalStorage.ts b/web-devtools/src/hooks/useLocalStorage.ts
index ae42cadcb..d83f7bebf 100644
--- a/web-devtools/src/hooks/useLocalStorage.ts
+++ b/web-devtools/src/hooks/useLocalStorage.ts
@@ -5,7 +5,7 @@ export function useLocalStorage(keyName: string, defaultValue: T) {
try {
const value = window.localStorage.getItem(keyName);
return value ? JSON.parse(value) : defaultValue;
- } catch (err) {
+ } catch {
return defaultValue;
}
});
diff --git a/web-devtools/src/layout/Header/navbar/Explore.tsx b/web-devtools/src/layout/Header/navbar/Explore.tsx
index 4bf98f686..7fd25668a 100644
--- a/web-devtools/src/layout/Header/navbar/Explore.tsx
+++ b/web-devtools/src/layout/Header/navbar/Explore.tsx
@@ -38,12 +38,12 @@ const Title = styled.h1`
)};
`;
-const StyledLink = styled(Link)<{ isActive: boolean }>`
+const StyledLink = styled(Link)<{ $isActive: boolean }>`
color: ${({ theme }) => theme.klerosUIComponentsPrimaryText};
text-decoration: none;
font-size: 16px;
- font-weight: ${({ isActive }) => (isActive ? "600" : "normal")};
+ font-weight: ${({ $isActive }) => ($isActive ? "600" : "normal")};
${landscapeStyle(
() => css`
@@ -70,7 +70,7 @@ const Explore: React.FC = () => {
{text}
diff --git a/web-devtools/src/layout/Header/navbar/index.tsx b/web-devtools/src/layout/Header/navbar/index.tsx
index dff4849d6..07984b35a 100644
--- a/web-devtools/src/layout/Header/navbar/index.tsx
+++ b/web-devtools/src/layout/Header/navbar/index.tsx
@@ -8,8 +8,8 @@ import { useOpenContext } from "../MobileHeader";
import Explore from "./Explore";
-const Wrapper = styled.div<{ isOpen: boolean }>`
- visibility: ${({ isOpen }) => (isOpen ? "visible" : "hidden")};
+const Wrapper = styled.div<{ $isOpen: boolean }>`
+ visibility: ${({ $isOpen }) => ($isOpen ? "visible" : "hidden")};
position: absolute;
top: 100%;
left: 0;
@@ -17,7 +17,7 @@ const Wrapper = styled.div<{ isOpen: boolean }>`
z-index: 30;
`;
-const Container = styled.div<{ isOpen: boolean }>`
+const Container = styled.div<{ $isOpen: boolean }>`
position: absolute;
top: 0;
left: 0;
@@ -29,8 +29,8 @@ const Container = styled.div<{ isOpen: boolean }>`
border: 1px solid ${({ theme }) => theme.klerosUIComponentsStroke};
box-shadow: 0px 2px 3px ${({ theme }) => theme.klerosUIComponentsDefaultShadow};
transform-origin: top;
- transform: scaleY(${({ isOpen }) => (isOpen ? "1" : "0")});
- visibility: ${({ isOpen }) => (isOpen ? "visible" : "hidden")};
+ transform: scaleY(${({ $isOpen }) => ($isOpen ? "1" : "0")});
+ visibility: ${({ $isOpen }) => ($isOpen ? "visible" : "hidden")};
transition-property: transform, visibility;
transition-duration: ${({ theme }) => theme.klerosUIComponentsTransitionSpeed};
transition-timing-function: ease;
@@ -47,8 +47,8 @@ const NavBar: React.FC = () => {
return (
<>
-
-
+
+
diff --git a/web-devtools/src/styles/landscapeStyle.ts b/web-devtools/src/styles/landscapeStyle.ts
index a54121f1f..353b33cf4 100644
--- a/web-devtools/src/styles/landscapeStyle.ts
+++ b/web-devtools/src/styles/landscapeStyle.ts
@@ -1,4 +1,4 @@
-import { css, DefaultTheme, FlattenInterpolation, ThemeProps } from "styled-components";
+import { type DefaultTheme, type ThemeProps, type FlattenInterpolation, css } from "styled-components";
export const BREAKPOINT_LANDSCAPE = 900;
diff --git a/web-devtools/src/utils/getDisputeRequestParamsFromTxn.ts b/web-devtools/src/utils/getDisputeRequestParamsFromTxn.ts
index 3957701c0..785f99fa4 100644
--- a/web-devtools/src/utils/getDisputeRequestParamsFromTxn.ts
+++ b/web-devtools/src/utils/getDisputeRequestParamsFromTxn.ts
@@ -1,5 +1,5 @@
import { getPublicClient } from "@wagmi/core";
-import { GetTransactionReceiptReturnType, decodeEventLog, getEventSelector } from "viem";
+import { type GetTransactionReceiptReturnType, decodeEventLog, getEventSelector } from "viem";
import { iArbitrableV2Abi } from "hooks/contracts/generated";
import { isUndefined } from "utils/isUndefined";
diff --git a/web-devtools/src/utils/validateAddressOrEns.ts b/web-devtools/src/utils/validateAddressOrEns.ts
index 35cb4b138..36b76319a 100644
--- a/web-devtools/src/utils/validateAddressOrEns.ts
+++ b/web-devtools/src/utils/validateAddressOrEns.ts
@@ -1,4 +1,4 @@
-import { PublicClient, isAddress } from "viem";
+import { type PublicClient, isAddress } from "viem";
import { normalize } from "viem/ens";
export const validateAddress = async (address: string, publicClient: PublicClient): Promise => {
diff --git a/web-devtools/src/utils/wrapWithToast.ts b/web-devtools/src/utils/wrapWithToast.ts
index 46578506b..9e6459061 100644
--- a/web-devtools/src/utils/wrapWithToast.ts
+++ b/web-devtools/src/utils/wrapWithToast.ts
@@ -1,5 +1,5 @@
import { toast, ToastPosition, Theme } from "react-toastify";
-import { PublicClient, TransactionReceipt } from "viem";
+import { type PublicClient, type TransactionReceipt } from "viem";
export const OPTIONS = {
position: "top-center" as ToastPosition,
diff --git a/web-devtools/tsconfig.json b/web-devtools/tsconfig.json
index 95fbe7e82..597ef15a9 100644
--- a/web-devtools/tsconfig.json
+++ b/web-devtools/tsconfig.json
@@ -1,5 +1,6 @@
{
"compilerOptions": {
+ "target": "ES2022",
"lib": [
"dom",
"dom.iterable",
diff --git a/web-devtools/wagmi.config.ts b/web-devtools/wagmi.config.ts
index 3ab7cfdf2..1391f0b5b 100644
--- a/web-devtools/wagmi.config.ts
+++ b/web-devtools/wagmi.config.ts
@@ -6,8 +6,8 @@ import { react, actions } from "@wagmi/cli/plugins";
import dotenv from "dotenv";
import { Abi, Chain } from "viem";
-import IArbitrableV2 from "@kleros/kleros-v2-contracts/artifacts/src/arbitration/interfaces/IArbitrableV2.sol/IArbitrableV2.json" assert { type: "json" };
-import IHomeGateway from "@kleros/kleros-v2-contracts/artifacts/src/gateway/interfaces/IHomeGateway.sol/IHomeGateway.json" assert { type: "json" };
+import IArbitrableV2 from "@kleros/kleros-v2-contracts/artifacts/src/arbitration/interfaces/IArbitrableV2.sol/IArbitrableV2.json" with { type: "json" };
+import IHomeGateway from "@kleros/kleros-v2-contracts/artifacts/src/gateway/interfaces/IHomeGateway.sol/IHomeGateway.json" with { type: "json" };
import { ArbitratorTypes, getArbitratorType } from "./src/consts/arbitratorTypes";
import { arbitrum, arbitrumSepolia, gnosis, gnosisChiado, mainnet, sepolia } from "viem/chains";
diff --git a/web/.eslintrc.json b/web/.eslintrc.json
deleted file mode 100644
index add57ed9c..000000000
--- a/web/.eslintrc.json
+++ /dev/null
@@ -1,156 +0,0 @@
-{
- "env": {
- "es6": true,
- "browser": true,
- "node": true
- },
- "root": true,
- "parser": "@typescript-eslint/parser",
- "parserOptions": {
- "ecmaVersion": 2020,
- "sourceType": "module",
- "ecmaFeatures": {
- "jsx": true
- }
- },
- "extends": [
- "eslint:recommended",
- "plugin:react/recommended",
- "plugin:react-hooks/recommended",
- "plugin:import/recommended",
- "plugin:import/react",
- "plugin:security/recommended",
- "plugin:@typescript-eslint/recommended",
- "plugin:prettier/recommended",
- "prettier"
- ],
- "globals": {
- "Atomics": "readonly",
- "SharedArrayBuffer": "readonly"
- },
- "plugins": [
- "react",
- "react-hooks",
- "security",
- "import"
- ],
- "ignorePatterns": [
- "src/assets"
- ],
- "settings": {
- "react": {
- "version": "^16.12.0"
- },
- "import/resolver": {
- "typescript": {
- "project": "./tsconfig.json"
- }
- }
- },
- "rules": {
- "max-len": [
- "warn",
- {
- "code": 120
- }
- ],
- "react/prop-types": 0,
- "no-unused-vars": "off",
- "@typescript-eslint/no-unused-vars": [
- "error",
- {
- "varsIgnorePattern": "(^_+[0-9]*$)|([iI]gnored$)|(^ignored)",
- "argsIgnorePattern": "(^_+[0-9]*$)|([iI]gnored$)|(^ignored)"
- }
- ],
- "no-console": [
- "error",
- {
- "allow": [
- "warn",
- "error",
- "info",
- "debug"
- ]
- }
- ],
- "@typescript-eslint/no-non-null-assertion": "off",
- "@typescript-eslint/no-explicit-any": "off",
- "security/detect-object-injection": "off",
- "security/detect-non-literal-fs-filename": "off",
- "import/extensions": [
- "error",
- "ignorePackages",
- {
- "js": "never",
- "jsx": "never",
- "ts": "never",
- "tsx": "never"
- }
- ],
- "import/no-unresolved": "off",
- "import/order": [
- "warn",
- {
- "groups": [
- "builtin",
- "external",
- "internal",
- "parent",
- "sibling",
- "index"
- ],
- "pathGroups": [
- {
- "pattern": "{react,styled-components}",
- "group": "external",
- "position": "before"
- },
- {
- "pattern": "@kleros/**",
- "group": "external",
- "position": "after"
- },
- {
- "pattern": "{svgs/**,assets/**}",
- "group": "internal",
- "position": "after"
- },
- {
- "pattern": "{hooks/**,utils/**,consts/**,types/**,context/**,connectors/**,}",
- "group": "internal",
- "position": "after"
- },
- {
- "pattern": "{queries/**,}",
- "group": "internal",
- "position": "after"
- },
- {
- "pattern": "{src/**,}",
- "group": "internal",
- "position": "after"
- },
- {
- "pattern": "{styles/**,}",
- "group": "internal",
- "position": "after"
- },
- {
- "pattern": "{layout/**,pages/**,components/**,}",
- "group": "internal",
- "position": "after"
- }
- ],
- "pathGroupsExcludedImportTypes": [
- "builtin"
- ],
- "newlines-between": "always",
- "alphabetize": {
- "order": "asc",
- "caseInsensitive": true
- }
- }
- ]
- }
-}
diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs
new file mode 100644
index 000000000..c7830b37a
--- /dev/null
+++ b/web/eslint.config.mjs
@@ -0,0 +1,180 @@
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+import { fixupConfigRules, fixupPluginRules } from "@eslint/compat";
+import { FlatCompat } from "@eslint/eslintrc";
+import js from "@eslint/js";
+import tsParser from "@typescript-eslint/parser";
+import _import from "eslint-plugin-import";
+import react from "eslint-plugin-react";
+import reactHooks from "eslint-plugin-react-hooks";
+import security from "eslint-plugin-security";
+import globals from "globals";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const compat = new FlatCompat({
+ baseDirectory: __dirname,
+ recommendedConfig: js.configs.recommended,
+ allConfig: js.configs.all,
+});
+
+export default [
+ {
+ ignores: ["src/assets"],
+ },
+ ...fixupConfigRules(
+ compat.extends(
+ "eslint:recommended",
+ "plugin:react/recommended",
+ "plugin:react-hooks/recommended",
+ "plugin:import/recommended",
+ "plugin:import/react",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:prettier/recommended",
+ "prettier"
+ )
+ ),
+ {
+ plugins: {
+ react: fixupPluginRules(react),
+ "react-hooks": fixupPluginRules(reactHooks),
+ security: fixupPluginRules(security),
+ import: fixupPluginRules(_import),
+ },
+
+ languageOptions: {
+ globals: {
+ ...globals.browser,
+ ...globals.node,
+ Atomics: "readonly",
+ SharedArrayBuffer: "readonly",
+ },
+
+ parser: tsParser,
+ ecmaVersion: 2020,
+ sourceType: "module",
+
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+
+ settings: {
+ react: {
+ version: "^18.3.1",
+ },
+
+ "import/resolver": {
+ typescript: {
+ project: "./tsconfig.json",
+ },
+ },
+ },
+
+ rules: {
+ "max-len": [
+ "warn",
+ {
+ code: 120,
+ },
+ ],
+
+ "react/prop-types": 0,
+ "no-unused-vars": "off",
+
+ "@typescript-eslint/no-unused-vars": [
+ "error",
+ {
+ varsIgnorePattern: "(^_+[0-9]*$)|([iI]gnored$)|(^ignored)",
+ argsIgnorePattern: "(^_+[0-9]*$)|([iI]gnored$)|(^ignored)",
+ },
+ ],
+
+ "no-console": [
+ "error",
+ {
+ allow: ["warn", "error", "info", "debug"],
+ },
+ ],
+
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "security/detect-object-injection": "off",
+ "security/detect-non-literal-fs-filename": "off",
+
+ "import/extensions": [
+ "error",
+ "ignorePackages",
+ {
+ js: "never",
+ jsx: "never",
+ ts: "never",
+ tsx: "never",
+ },
+ ],
+
+ "import/no-unresolved": "off",
+
+ "import/order": [
+ "warn",
+ {
+ groups: ["builtin", "external", "internal", "parent", "sibling", "index"],
+
+ pathGroups: [
+ {
+ pattern: "{react,styled-components}",
+ group: "external",
+ position: "before",
+ },
+ {
+ pattern: "@kleros/**",
+ group: "external",
+ position: "after",
+ },
+ {
+ pattern: "{svgs/**,assets/**}",
+ group: "internal",
+ position: "after",
+ },
+ {
+ pattern: "{hooks/**,utils/**,consts/**,types/**,context/**,connectors/**,}",
+ group: "internal",
+ position: "after",
+ },
+ {
+ pattern: "{queries/**,}",
+ group: "internal",
+ position: "after",
+ },
+ {
+ pattern: "{src/**,}",
+ group: "internal",
+ position: "after",
+ },
+ {
+ pattern: "{styles/**,}",
+ group: "internal",
+ position: "after",
+ },
+ {
+ pattern: "{layout/**,pages/**,components/**,}",
+ group: "internal",
+ position: "after",
+ },
+ ],
+
+ pathGroupsExcludedImportTypes: ["builtin"],
+ "newlines-between": "always",
+
+ alphabetize: {
+ order: "asc",
+ caseInsensitive: true,
+ },
+ },
+ ],
+ },
+ },
+];
diff --git a/web/netlify.toml b/web/netlify.toml
index 8cf55aefd..0ed0b0e78 100644
--- a/web/netlify.toml
+++ b/web/netlify.toml
@@ -6,6 +6,8 @@ NETLIFY_YARN_WORKSPACES = "true"
YARN_ENABLE_GLOBAL_CACHE = "true"
# YARN_CACHE_FOLDER = "$HOME/.yarn_cache"
# YARN_VERSION = "3.2.0"
+[build]
+command = "yarn workspace @kleros/kleros-v2-contracts install && yarn workspace @kleros/kleros-app install && yarn workspace @kleros/kleros-v2-web install && yarn workspace @kleros/kleros-v2-contracts build && yarn workspace @kleros/kleros-app build && yarn workspace @kleros/kleros-v2-web build-netlify"
[functions]
directory = "web/netlify/functions/"
diff --git a/web/package.json b/web/package.json
index cd93ab570..0e0f3c17c 100644
--- a/web/package.json
+++ b/web/package.json
@@ -22,7 +22,6 @@
"styles": "./src/styles",
"svgs": "./src/assets/svgs"
},
- "packageManager": "yarn@4.0.2+sha256.825003a0f561ad09a3b1ac4a3b3ea6207af2796d54f62a9420520915721f5186",
"scripts": {
"start": "yarn start-devnet",
"start-local": "scripts/runEnv.sh local 'yarn generate && vite'",
@@ -47,70 +46,78 @@
},
"prettier": "@kleros/kleros-v2-prettier-config",
"devDependencies": {
- "@graphql-codegen/cli": "^4.0.1",
- "@graphql-codegen/client-preset": "^4.2.0",
+ "@eslint/compat": "^1.2.3",
+ "@eslint/eslintrc": "^3.2.0",
+ "@eslint/js": "^9.15.0",
+ "@graphql-codegen/cli": "^5.0.3",
+ "@graphql-codegen/client-preset": "^4.5.1",
"@kleros/kleros-v2-eslint-config": "workspace:^",
"@kleros/kleros-v2-prettier-config": "workspace:^",
"@kleros/kleros-v2-tsconfig": "workspace:^",
- "@types/busboy": "^1.5.3",
- "@types/react": "18.2.0",
- "@types/react-dom": "^18.2.18",
+ "@types/busboy": "^1.5.4",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
"@types/styled-components": "^5.1.34",
- "@typescript-eslint/eslint-plugin": "^8.8.1",
- "@typescript-eslint/parser": "^8.8.1",
- "@typescript-eslint/utils": "^8.8.1",
- "@wagmi/cli": "^2.1.16",
- "eslint": "^8.57.1",
+ "@typescript-eslint/eslint-plugin": "^8.15.0",
+ "@typescript-eslint/parser": "^8.15.0",
+ "@typescript-eslint/utils": "^8.15.0",
+ "@wagmi/cli": "^2.1.18",
+ "eslint": "^9.15.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.3",
- "eslint-plugin-react": "^7.37.1",
+ "eslint-plugin-import": "^2.31.0",
+ "eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^4.6.2",
+ "globals": "^15.12.0",
"lru-cache": "^7.18.3",
- "typescript": "^5.3.3",
- "vite": "^5.4.2",
+ "typescript": "^5.6.3",
+ "vite": "^5.4.11",
"vite-plugin-node-polyfills": "^0.21.0",
- "vite-plugin-svgr": "^4.2.0",
+ "vite-plugin-svgr": "^4.3.0",
"vite-tsconfig-paths": "^4.3.2"
},
"dependencies": {
- "@cyntler/react-doc-viewer": "^1.16.3",
+ "@cyntler/react-doc-viewer": "^1.17.0",
+ "@kleros/kleros-app": "workspace:^",
"@kleros/kleros-sdk": "workspace:^",
"@kleros/kleros-v2-contracts": "workspace:^",
"@kleros/ui-components-library": "^2.15.0",
- "@lifi/wallet-management": "^3.0.3",
- "@lifi/widget": "^3.2.0",
- "@sentry/react": "^7.93.0",
- "@sentry/tracing": "^7.93.0",
- "@tanstack/react-query": "^5.40.1",
+ "@lifi/wallet-management": "^3.4.5",
+ "@lifi/widget": "^3.12.2",
+ "@sentry/react": "^7.120.0",
+ "@sentry/tracing": "^7.120.0",
+ "@tanstack/react-query": "^5.61.0",
"@types/react-modal": "^3.16.3",
- "@web3modal/wagmi": "^4.1.10",
+ "@wagmi/connectors": "^5.5.0",
+ "@wagmi/core": "^2.15.0",
+ "@web3modal/wagmi": "^4.2.3",
"@yornaath/batshit": "^0.9.0",
"chart.js": "^3.9.1",
"chartjs-adapter-moment": "^1.0.1",
"chartjs-plugin-datalabels": "^2.2.0",
- "core-js": "^3.35.0",
+ "core-js": "^3.39.0",
"ethers": "^5.7.2",
"graphql": "^16.9.0",
- "graphql-request": "~6.1.0",
- "jose": "^5.2.3",
+ "graphql-request": "^7.1.2",
+ "jose": "^5.9.6",
"moment": "^2.30.1",
- "overlayscrollbars": "^2.4.6",
- "overlayscrollbars-react": "^0.5.3",
+ "overlayscrollbars": "^2.10.0",
+ "overlayscrollbars-react": "^0.5.6",
"react": "^18.3.1",
"react-chartjs-2": "^4.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^3.1.4",
"react-identicons": "^1.2.5",
- "react-is": "^18.2.0",
- "react-loading-skeleton": "^3.3.1",
+ "react-is": "^18.3.1",
+ "react-loading-skeleton": "^3.5.0",
"react-markdown": "^8.0.7",
"react-modal": "^3.16.1",
- "react-router-dom": "^6.21.2",
+ "react-router-dom": "^6.28.0",
"react-scripts": "^5.0.1",
"react-toastify": "^9.1.3",
- "react-use": "^17.4.3",
- "styled-components": "^5.3.11",
- "viem": "^2.21.35",
- "wagmi": "^2.12.25"
+ "react-use": "^17.5.1",
+ "styled-components": "^5.3.3",
+ "viem": "^2.21.48",
+ "wagmi": "^2.13.0"
}
}
diff --git a/web/src/components/EnsureAuth.tsx b/web/src/components/EnsureAuth.tsx
index 3414bdf72..6d8d2af88 100644
--- a/web/src/components/EnsureAuth.tsx
+++ b/web/src/components/EnsureAuth.tsx
@@ -1,10 +1,9 @@
-import React from "react";
+import React, { useCallback } from "react";
import { useAccount } from "wagmi";
-
+import { useAtlasProvider } from "@kleros/kleros-app";
import { Button } from "@kleros/ui-components-library";
-
-import { useAtlasProvider } from "context/AtlasProvider";
+import { errorToast, infoToast, successToast } from "utils/wrapWithToast";
interface IEnsureAuth {
children: React.ReactElement;
@@ -14,12 +13,22 @@ interface IEnsureAuth {
const EnsureAuth: React.FC = ({ children, className }) => {
const { address } = useAccount();
const { isVerified, isSigningIn, authoriseUser } = useAtlasProvider();
+ const handleClick = useCallback(() => {
+ infoToast(`Signing in User...`);
+
+ authoriseUser()
+ .then(() => successToast("Signed In successfully!"))
+ .catch((err) => {
+ console.log(err);
+ errorToast(`Sign-In failed: ${err?.message}`);
+ });
+ }, [authoriseUser]);
return isVerified ? (
children
) : (