diff --git a/kleros-sdk/package.json b/kleros-sdk/package.json
index c7cc0aeb8..8b29618ac 100644
--- a/kleros-sdk/package.json
+++ b/kleros-sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@kleros/kleros-sdk",
- "version": "2.1.8",
+ "version": "2.1.10",
"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",
@@ -39,13 +39,16 @@
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.6.3",
+ "viem": "^2.21.48",
"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.48",
"zod": "^3.23.8"
+ },
+ "peerDependencies": {
+ "viem": "^2.21.48"
}
}
diff --git a/web-devtools/src/assets/svgs/socialmedia/discord.svg b/web-devtools/src/assets/svgs/socialmedia/discord.svg
index cd9ecfead..8689eefd8 100644
--- a/web-devtools/src/assets/svgs/socialmedia/discord.svg
+++ b/web-devtools/src/assets/svgs/socialmedia/discord.svg
@@ -1,10 +1,10 @@
-
} />
-
- } />
+ 0 ? filteredTabs[0].path : ""} replace />
+ )
+ }
+ />
+ 0 ? filteredTabs[0].path : ""} replace />} />
diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Header.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/Simulator/Header.tsx
similarity index 100%
rename from web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/Header.tsx
rename to web/src/pages/Courts/CourtDetails/StakePanel/Simulator/Header.tsx
diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/QuantityToSimulate.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/Simulator/QuantityToSimulate.tsx
similarity index 100%
rename from web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/QuantityToSimulate.tsx
rename to web/src/pages/Courts/CourtDetails/StakePanel/Simulator/QuantityToSimulate.tsx
diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/Simulator/index.tsx
similarity index 89%
rename from web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx
rename to web/src/pages/Courts/CourtDetails/StakePanel/Simulator/index.tsx
index 03bb047bb..95aadc68e 100644
--- a/web/src/pages/Courts/CourtDetails/StakePanel/SimulatorPopup/index.tsx
+++ b/web/src/pages/Courts/CourtDetails/StakePanel/Simulator/index.tsx
@@ -18,7 +18,6 @@ import { useHomePageExtraStats } from "queries/useHomePageExtraStats";
import { useJurorStakeDetailsQuery } from "queries/useJurorStakeDetailsQuery";
import GavelIcon from "svgs/icons/gavel.svg";
-import LawBalanceIcon from "svgs/icons/law-balance.svg";
import DiceIcon from "svgs/icons/dice.svg";
import DollarIcon from "svgs/icons/dollar.svg";
import ArrowRightIcon from "svgs/icons/arrow-right.svg";
@@ -115,12 +114,12 @@ const calculateJurorOdds = (newStake: number, totalStake: number): string => {
return `${odds.toFixed(2)}%`;
};
-interface ISimulatorPopup {
+interface ISimulator {
amountToStake: number;
isStaking: boolean;
}
-const SimulatorPopup: React.FC = ({ amountToStake, isStaking }) => {
+const Simulator: React.FC = ({ amountToStake, isStaking }) => {
const { id } = useParams();
const { address } = useAccount();
const { data: stakeData } = useJurorStakeDetailsQuery(address?.toLowerCase() as `0x${string}`);
@@ -155,7 +154,7 @@ const SimulatorPopup: React.FC = ({ amountToStake, isStaking })
};
}, [courtCurrentEffectiveStake, currentTreeVotesPerPnk, currentTreeDisputesPerPnk, currentTreeExpectedRewardPerPnk]);
- const { votes: totalVotes, cases: totalCases, rewards: totalRewards } = totals;
+ const { votes: totalVotes, rewards: totalRewards } = totals;
const courtFutureEffectiveStake = !isUndefined(courtCurrentEffectiveStake)
? Math.max(isStaking ? courtCurrentEffectiveStake + amountToStake : courtCurrentEffectiveStake - amountToStake, 0)
@@ -165,10 +164,7 @@ const SimulatorPopup: React.FC = ({ amountToStake, isStaking })
!isUndefined(courtFutureEffectiveStake) && !isUndefined(totalVotes)
? totalVotes / courtFutureEffectiveStake
: undefined;
- const futureTreeDisputesPerPnk =
- !isUndefined(courtFutureEffectiveStake) && !isUndefined(totalCases)
- ? totalCases / courtFutureEffectiveStake
- : undefined;
+
const futureTreeExpectedRewardPerPnk =
!isUndefined(courtFutureEffectiveStake) && !isUndefined(totalRewards)
? totalRewards / courtFutureEffectiveStake
@@ -187,15 +183,6 @@ const SimulatorPopup: React.FC = ({ amountToStake, isStaking })
? beautifyStatNumber(jurorFutureEffectiveStake * futureTreeVotesPerPnk)
: undefined;
- const currentExpectedCases =
- !isUndefined(jurorCurrentEffectiveStake) && !isUndefined(currentTreeDisputesPerPnk)
- ? beautifyStatNumber(jurorCurrentEffectiveStake * currentTreeDisputesPerPnk)
- : undefined;
- const futureExpectedCases =
- !isUndefined(jurorFutureEffectiveStake) && !isUndefined(futureTreeDisputesPerPnk)
- ? beautifyStatNumber(jurorFutureEffectiveStake * futureTreeDisputesPerPnk)
- : undefined;
-
const currentDrawingOdds =
!isUndefined(jurorCurrentEffectiveStake) && !isUndefined(courtCurrentEffectiveStake)
? calculateJurorOdds(jurorCurrentEffectiveStake, courtCurrentEffectiveStake)
@@ -223,12 +210,6 @@ const SimulatorPopup: React.FC = ({ amountToStake, isStaking })
currentValue: currentExpectedVotes,
futureValue: futureExpectedVotes,
},
- {
- title: "Cases",
- icon: ,
- currentValue: currentExpectedCases,
- futureValue: futureExpectedCases,
- },
{
title: "Drawing Odds",
icon: ,
@@ -252,7 +233,7 @@ const SimulatorPopup: React.FC = ({ amountToStake, isStaking })
{simulatorItems.map((item, index) => (
-
+
{item.icon}
{item.tooltipMsg ? (
@@ -286,4 +267,4 @@ const SimulatorPopup: React.FC = ({ amountToStake, isStaking })
);
};
-export default SimulatorPopup;
+export default Simulator;
diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx
index 54bb6140f..3a0d1942c 100644
--- a/web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx
+++ b/web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx
@@ -1,4 +1,5 @@
import React, { useCallback, useEffect, useMemo } from "react";
+import styled from "styled-components";
import { useParams } from "react-router-dom";
import { useAccount, usePublicClient } from "wagmi";
@@ -19,10 +20,10 @@ import {
} from "hooks/contracts/generated";
import { useCourtDetails } from "hooks/queries/useCourtDetails";
import { isUndefined } from "utils/index";
+import { parseWagmiError } from "utils/parseWagmiError";
import { wrapWithToast } from "utils/wrapWithToast";
import { EnsureChain } from "components/EnsureChain";
-import styled from "styled-components";
export enum ActionType {
allowance = "allowance",
@@ -154,9 +155,9 @@ const StakeWithdrawButton: React.FC = ({
useEffect(() => {
if (setStakeError) {
- setErrorMsg(setStakeError?.shortMessage ?? setStakeError.message);
+ setErrorMsg(parseWagmiError(setStakeError));
}
- }, [setStakeError]);
+ }, [setStakeError, setErrorMsg]);
const { text, checkDisabled, onClick } = buttonProps[isAllowance ? ActionType.allowance : action];
return (
diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx
index 166871a05..4e77e5456 100644
--- a/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx
+++ b/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx
@@ -13,12 +13,11 @@ import { uncommify } from "utils/commify";
import InputDisplay from "./InputDisplay";
import { ActionType } from "./StakeWithdrawButton";
-import SimulatorPopup from "./SimulatorPopup";
+import Simulator from "./Simulator";
const Container = styled.div`
position: relative;
width: 100%;
- margin-top: 32px;
display: flex;
flex-direction: column;
gap: 28px;
@@ -100,7 +99,7 @@ const StakePanel: React.FC<{ courtName: string; id: string }> = ({ courtName = "
/>
)}
-
+
);
};
diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx
index d695b1ea9..15941591f 100644
--- a/web/src/pages/Courts/CourtDetails/Stats.tsx
+++ b/web/src/pages/Courts/CourtDetails/Stats.tsx
@@ -8,7 +8,6 @@ import { Accordion, DropdownSelect } from "@kleros/ui-components-library";
import EthereumIcon from "svgs/icons/ethereum.svg";
import BalanceIcon from "svgs/icons/law-balance.svg";
-import BalanceWithPNKIcon from "svgs/icons/law-balance-with-pnk.svg";
import MinStake from "svgs/icons/min-stake.svg";
import VotesPerPNKIcon from "svgs/icons/votes-per-pnk.svg";
import PNKIcon from "svgs/icons/pnk.svg";
@@ -27,7 +26,7 @@ import { useHomePageExtraStats } from "queries/useHomePageExtraStats";
import { calculateSubtextRender } from "utils/calculateSubtextRender";
import { formatETH, formatPNK, formatUnitsWei, formatUSD } from "utils/format";
import { isUndefined } from "utils/index";
-import { beautifyStatNumber } from "utils/beautifyStatNumber";
+import { beautifyStatNumber, unbeautifyStatNumber } from "utils/beautifyStatNumber";
import StatDisplay, { IStatDisplay } from "components/StatDisplay";
import { StyledSkeleton } from "components/StyledSkeleton";
@@ -36,7 +35,7 @@ import Info from "./Info";
const StyledAccordion = styled(Accordion)`
width: 100%;
- margin-bottom: 12px;
+ margin-top: ${responsiveSize(24, 32)};
> * > button {
justify-content: unset;
background-color: ${({ theme }) => theme.whiteBackground} !important;
@@ -67,6 +66,7 @@ const AllTimeContainer = styled(TimeDisplayContainer)`
const TimeSelectorContainer = styled(TimeDisplayContainer)`
padding-top: 12px;
+ padding-bottom: 12px;
flex-wrap: wrap;
`;
@@ -88,7 +88,7 @@ const StyledCard = styled.div`
height: fit-content;
display: grid;
gap: 32px;
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ grid-template-columns: repeat(auto-fit, minmax(156px, 1fr));
padding-top: ${responsiveSize(28, 32)};
padding-bottom: ${responsiveSize(20, 0)};
@@ -141,6 +141,17 @@ const stats: IStat[] = [
color: "purple",
icon: VoteStake,
},
+ {
+ title: "Reward per Vote",
+ coinId: 1,
+ getText: (data) => {
+ const jurorReward = formatUnitsWei(data?.feeForJuror);
+ return jurorReward;
+ },
+ getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.feeForJuror)) * (coinPrice ?? 0)),
+ color: "purple",
+ icon: EthereumIcon,
+ },
{
title: "Active Jurors",
getText: (data) => data?.numberStakedJurors,
@@ -168,7 +179,7 @@ const stats: IStat[] = [
icon: BalanceIcon,
},
{
- title: "ETH paid to Jurors",
+ title: "Total ETH paid",
coinId: 1,
getText: (data) => formatETH(data?.paidETH),
getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.paidETH)) * (coinPrice ?? 0)),
@@ -195,18 +206,15 @@ interface ITimeframedStat {
title: string | React.ReactNode;
coinId?: number;
getText: (data: ITimeframedStatData) => string;
+ getSubtext?: (data: CourtDetailsQuery["court"], coinPrice?: number) => string;
color: IStatDisplay["color"];
icon: React.FC>;
}
const timeRanges = [
- { value: 7, text: "Last 7 days" },
{ value: 30, text: "Last 30 days" },
{ value: 90, text: "Last 90 days" },
- /* we can uncomment as court creation time increases,
- but it's a bit tricky because this affects every court */
- // { value: 180, text: "Last 180 days" },
- // { value: 365, text: "Last 365 days" },
+ { value: 180, text: "Last 180 days" },
{ value: "allTime", text: "All Time" },
];
@@ -227,12 +235,26 @@ const Stats = () => {
};
const timeframedStats: ITimeframedStat[] = [
+ {
+ title: (
+
+ PNK for 1 Vote
+
+ ),
+ coinId: 0,
+ getText: (data) => beautifyStatNumber(data?.treeVotesPerPnk, true),
+ getSubtext: (data, coinPrice) =>
+ formatUSD(unbeautifyStatNumber(beautifyStatNumber(data?.treeVotesPerPnk, true)) * (coinPrice ?? 0)),
+ color: "orange",
+ icon: VotesPerPNKIcon,
+ },
{
title: (
PNK for 1 USD
),
+ coinId: 0,
getText: (data) => {
const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk;
const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined;
@@ -240,6 +262,13 @@ const Stats = () => {
const pnkNeeded = treeExpectedRewardPerPnk * ethPriceUSD;
return beautifyStatNumber(pnkNeeded, true);
},
+ getSubtext: (data, coinPrice) => {
+ const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk;
+ const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined;
+ if (!ethPriceUSD || !treeExpectedRewardPerPnk) return "N/A";
+ const pnkNeeded = treeExpectedRewardPerPnk * ethPriceUSD;
+ return formatUSD(unbeautifyStatNumber(beautifyStatNumber(pnkNeeded, true)) * (coinPrice ?? 0));
+ },
color: "purple",
icon: PNKUSDIcon,
},
@@ -249,44 +278,22 @@ const Stats = () => {
PNK for 1 ETH
),
+ coinId: 0,
getText: (data) => {
const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk;
if (!treeExpectedRewardPerPnk) return "N/A";
const pnkNeeded = treeExpectedRewardPerPnk;
return beautifyStatNumber(pnkNeeded, true);
},
+ getSubtext: (data, coinPrice) => {
+ const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk;
+ if (!treeExpectedRewardPerPnk) return "N/A";
+ const pnkNeeded = treeExpectedRewardPerPnk;
+ return formatUSD(unbeautifyStatNumber(beautifyStatNumber(pnkNeeded, true)) * (coinPrice ?? 0));
+ },
color: "blue",
icon: PNKETHIcon,
},
- {
- title: (
-
- PNK for 1 Vote
-
- ),
- getText: (data) => {
- const treeVotesPerPnk = data?.treeVotesPerPnk;
- return beautifyStatNumber(treeVotesPerPnk, true);
- },
- color: "orange",
- icon: VotesPerPNKIcon,
- },
- {
- title: (
-
- PNK for 1 Case
-
- ),
- getText: (data) => {
- const treeDisputesPerPnk = data?.treeDisputesPerPnk;
- return beautifyStatNumber(treeDisputesPerPnk, true);
- },
- color: "orange",
- icon: BalanceWithPNKIcon,
- },
];
return (
@@ -326,15 +333,17 @@ const Stats = () => {
defaultValue={selectedRange}
callback={handleTimeRangeChange}
/>
-
+
- {timeframedStats.map(({ title, getText, color, icon }) => {
+ {timeframedStats.map(({ title, coinId, getText, getSubtext, color, icon }) => {
+ const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined;
return (
}
+ subtext={calculateSubtextRender(foundCourt, getSubtext, coinPrice)}
/>
);
})}
diff --git a/web/src/pages/Courts/CourtDetails/index.tsx b/web/src/pages/Courts/CourtDetails/index.tsx
index 271ea9891..4164ba7c7 100644
--- a/web/src/pages/Courts/CourtDetails/index.tsx
+++ b/web/src/pages/Courts/CourtDetails/index.tsx
@@ -20,7 +20,6 @@ import HowItWorks from "components/HowItWorks";
import LatestCases from "components/LatestCases";
import Staking from "components/Popup/MiniGuides/Staking";
import { StyledSkeleton } from "components/StyledSkeleton";
-import { Divider } from "components/Divider";
import ScrollTop from "components/ScrollTop";
import Description from "./Description";
@@ -35,38 +34,39 @@ const CourtHeader = styled.h1`
justify-content: space-between;
gap: 24px;
flex-wrap: wrap;
-`;
-
-const CourtInfo = styled.div`
- display: flex;
- flex-direction: column;
- gap: 16px;
+ margin-bottom: 24px;
${landscapeStyle(
() => css`
- gap: 32px;
+ margin-bottom: 32px;
`
)};
`;
+const CourtInfo = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+`;
+
const ButtonContainer = styled.div`
display: flex;
flex-wrap: wrap;
- flex-direction: column;
- align-items: flex-start;
- gap: 16px;
+ flex-direction: row;
+ justify-content: center;
+ gap: 20px;
${landscapeStyle(
() => css`
align-items: flex-end;
- gap: 32px;
`
)};
`;
const StyledCard = styled(Card)`
padding: ${responsiveSize(16, 32)};
- margin-top: ${responsiveSize(16, 24)};
+ padding-bottom: 16px;
+ margin-top: 12px;
width: 100%;
height: auto;
min-height: 100px;
@@ -87,7 +87,7 @@ const CourtDetails: React.FC = () => {
const courtPath = getCourtsPath(data?.court, id);
- const items = [{ text: "🏛️", value: "0" }];
+ const items = [];
items.push(
...(courtPath?.map((node) => ({
text: node.name,
@@ -101,20 +101,19 @@ const CourtDetails: React.FC = () => {
{policy ? policy.name : }
- {items.length > 1 ? : }
+ {items.length > 1 && items[0]?.value !== 1 ? : null}
+ {!isProductionDeployment() && }
- {!isProductionDeployment() && }
-
-
+
diff --git a/web/src/pages/Dashboard/Courts/CourtCard/CourtName.tsx b/web/src/pages/Dashboard/Courts/CourtCard/CourtName.tsx
index 4ff454167..6b53b480d 100644
--- a/web/src/pages/Dashboard/Courts/CourtCard/CourtName.tsx
+++ b/web/src/pages/Dashboard/Courts/CourtCard/CourtName.tsx
@@ -4,7 +4,8 @@ import styled, { css } from "styled-components";
import { landscapeStyle } from "styles/landscapeStyle";
import ArrowIcon from "svgs/icons/arrow.svg";
-import { Link } from "react-router-dom";
+
+import { StyledArrowLink } from "components/StyledArrowLink";
const Container = styled.div`
display: flex;
@@ -20,24 +21,19 @@ const Container = styled.div`
}
${landscapeStyle(
- () =>
- css`
- justify-content: flex-start;
- width: auto;
- `
+ () => css`
+ justify-content: flex-start;
+ width: auto;
+ `
)}
`;
-const StyledLink = styled(Link)`
- display: flex;
- gap: 8px;
- align-items: center;
+const ReStyledArrowLink = styled(StyledArrowLink)`
+ font-size: 14px;
+
> svg {
height: 15px;
width: 15px;
- path {
- fill: ${({ theme }) => theme.primaryBlue};
- }
}
`;
@@ -50,9 +46,9 @@ const CourtName: React.FC = ({ name, id }) => {
return (
{name}
-
+
Open Court
-
+
);
};
diff --git a/web/src/pages/Dashboard/JurorInfo/Coherency.tsx b/web/src/pages/Dashboard/JurorInfo/Coherence.tsx
similarity index 92%
rename from web/src/pages/Dashboard/JurorInfo/Coherency.tsx
rename to web/src/pages/Dashboard/JurorInfo/Coherence.tsx
index ae164a75d..712884d22 100644
--- a/web/src/pages/Dashboard/JurorInfo/Coherency.tsx
+++ b/web/src/pages/Dashboard/JurorInfo/Coherence.tsx
@@ -25,7 +25,7 @@ const tooltipMsg =
" (after all the appeal instances). If the juror vote is the same as " +
" the majority of jurors it's considered a Coherent Vote.";
-interface ICoherency {
+interface ICoherence {
userLevelData: {
level: number;
title: string;
@@ -35,7 +35,7 @@ interface ICoherency {
isMiniGuide: boolean;
}
-const Coherency: React.FC = ({ userLevelData, totalCoherentVotes, totalResolvedVotes, isMiniGuide }) => {
+const Coherence: React.FC = ({ userLevelData, totalCoherentVotes, totalResolvedVotes, isMiniGuide }) => {
const votesContent = (