Skip to content

Feat: Dispute Kits helper in contracts package #2066

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion contracts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@ All notable changes to this package will be documented in this file.

The format is based on [Common Changelog](https://common-changelog.org/).

## [0.11.0] - 2025-08-01
## [0.12.0] - 2025-08-05

### Changed

- **Breaking:** Make `viem` a peer dependency, it should be provided by the consuming package ([`4594536`](https://github.com/kleros/kleros-v2/commit/4594536c))

### Added

- Add helper function `getDisputeKitsViem` to retrieve a deployment's available dispute kit infos including their capabilities (`isShutter`, `isGated`) ([`5a81f9e`](https://github.com/kleros/kleros-v2/commit/5a81f9ec))

## [0.11.0] - 2025-08-02

### Changed

Expand Down Expand Up @@ -107,6 +117,7 @@ The format is based on [Common Changelog](https://common-changelog.org/).

## [0.8.1] - 2025-04-10

[0.12.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%2Fkleros-v2-contracts@0.12.0
[0.11.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%2Fkleros-v2-contracts@0.11.0
[0.10.0]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%2Fkleros-v2-contracts@0.10.0
[0.9.4]: https://github.com/kleros/kleros-v2/releases/tag/@kleros%2Fkleros-v2-contracts@0.9.4
Expand Down
85 changes: 85 additions & 0 deletions contracts/deployments/disputeKitsViem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { getContracts } from "./contractsViem";
import { Abi, AbiEvent, getAbiItem, PublicClient } from "viem";
import { DeploymentName } from "./utils";

export type DisputeKitContracts = ReturnType<typeof getContracts>;
export type DisputeKit =
| NonNullable<DisputeKitContracts["disputeKitClassic"]>
| NonNullable<DisputeKitContracts["disputeKitShutter"]>
| NonNullable<DisputeKitContracts["disputeKitGated"]>
| NonNullable<DisputeKitContracts["disputeKitGatedShutter"]>
| null;
export type DisputeKitInfos = {
address: `0x${string}`;
contract: DisputeKit;
isGated: boolean;
isShutter: boolean;
};
export type DisputeKitByIds = Record<string, DisputeKitInfos>;

const fetchDisputeKits = async (client: PublicClient, klerosCoreAddress: `0x${string}`, klerosCoreAbi: Abi) => {
const DisputeKitCreated = getAbiItem({
abi: klerosCoreAbi,
name: "DisputeKitCreated",
}) as AbiEvent;
const logs = await client.getLogs({
address: klerosCoreAddress,
event: DisputeKitCreated,
fromBlock: 0n,
toBlock: "latest",
});
return Object.fromEntries(
logs
.filter((log) => {
const args = log.args as Record<string, unknown>;
return "_disputeKitID" in args && "_disputeKitAddress" in args;
})
.map((log) => {
const { _disputeKitID, _disputeKitAddress } = log.args as {
_disputeKitID: bigint;
_disputeKitAddress: string;
};
return {
disputeKitID: _disputeKitID,
disputeKitAddress: _disputeKitAddress,
};
})
.map(({ disputeKitID, disputeKitAddress }) => [disputeKitID!.toString(), disputeKitAddress as `0x${string}`])
);
};
Comment on lines +20 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Address potential issues with type safety and performance.

  1. Line 47: The non-null assertion on disputeKitID! is risky. While you check for the existence of _disputeKitID in the filter, it could still be null or undefined.

  2. Line 28: Using fromBlock: 0n will query all blocks from genesis, which could be expensive on mainnet. Consider if there's a known deployment block to start from.

Apply this diff to improve type safety:

-      .map(({ disputeKitID, disputeKitAddress }) => [disputeKitID!.toString(), disputeKitAddress as `0x${string}`])
+      .map(({ disputeKitID, disputeKitAddress }) => [disputeKitID.toString(), disputeKitAddress as `0x${string}`])

The non-null assertion can be removed since the filter already ensures disputeKitID exists and the bigint type guarantees it's not null.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fetchDisputeKits = async (client: PublicClient, klerosCoreAddress: `0x${string}`, klerosCoreAbi: Abi) => {
const DisputeKitCreated = getAbiItem({
abi: klerosCoreAbi,
name: "DisputeKitCreated",
}) as AbiEvent;
const logs = await client.getLogs({
address: klerosCoreAddress,
event: DisputeKitCreated,
fromBlock: 0n,
toBlock: "latest",
});
return Object.fromEntries(
logs
.filter((log) => {
const args = log.args as Record<string, unknown>;
return "_disputeKitID" in args && "_disputeKitAddress" in args;
})
.map((log) => {
const { _disputeKitID, _disputeKitAddress } = log.args as {
_disputeKitID: bigint;
_disputeKitAddress: string;
};
return {
disputeKitID: _disputeKitID,
disputeKitAddress: _disputeKitAddress,
};
})
.map(({ disputeKitID, disputeKitAddress }) => [disputeKitID!.toString(), disputeKitAddress as `0x${string}`])
);
};
const fetchDisputeKits = async (
client: PublicClient,
klerosCoreAddress: `0x${string}`,
klerosCoreAbi: Abi
) => {
const DisputeKitCreated = getAbiItem({
abi: klerosCoreAbi,
name: "DisputeKitCreated",
}) as AbiEvent;
const logs = await client.getLogs({
address: klerosCoreAddress,
event: DisputeKitCreated,
fromBlock: 0n,
toBlock: "latest",
});
return Object.fromEntries(
logs
.filter((log) => {
const args = log.args as Record<string, unknown>;
return "_disputeKitID" in args && "_disputeKitAddress" in args;
})
.map((log) => {
const { _disputeKitID, _disputeKitAddress } = log.args as {
_disputeKitID: bigint;
_disputeKitAddress: string;
};
return {
disputeKitID: _disputeKitID,
disputeKitAddress: _disputeKitAddress,
};
})
.map(({ disputeKitID, disputeKitAddress }) => [
disputeKitID.toString(),
disputeKitAddress as `0x${string}`,
])
);
};
🤖 Prompt for AI Agents
In contracts/deployments/disputeKitsViem.ts lines 20 to 49, remove the non-null
assertion on disputeKitID in the final map since the filter ensures it exists
and the bigint type prevents null or undefined values. Additionally, replace
fromBlock: 0n with a specific known deployment block number to avoid expensive
queries from genesis on mainnet, improving performance.


export const getDisputeKits = async (client: PublicClient, deployment: DeploymentName): Promise<DisputeKitByIds> => {
const { klerosCore, disputeKitClassic, disputeKitShutter, disputeKitGated, disputeKitGatedShutter } = getContracts({
publicClient: client,
deployment: deployment,
});

const isDefined = <T>(kit: T): kit is NonNullable<T> => kit != null;
const disputeKitContracts = [disputeKitClassic, disputeKitShutter, disputeKitGated, disputeKitGatedShutter].filter(
isDefined
);
const shutterEnabled = [disputeKitShutter, disputeKitGatedShutter].filter(isDefined);
const gatedEnabled = [disputeKitGated, disputeKitGatedShutter].filter(isDefined);

const disputeKitMap = await fetchDisputeKits(client, klerosCore.address, klerosCore.abi);

return Object.fromEntries(
Object.entries(disputeKitMap).map(([disputeKitID, address]) => {
const contract =
disputeKitContracts.find((contract) => contract.address.toLowerCase() === address.toLowerCase()) ?? null;
return [
disputeKitID,
{
address,
contract: contract satisfies DisputeKit,
isGated: contract
? gatedEnabled.some((gated) => contract.address.toLowerCase() === gated.address.toLowerCase())
: false,
isShutter: contract
? shutterEnabled.some((shutter) => contract.address.toLowerCase() === shutter.address.toLowerCase())
: false,
},
];
})
);
};
3 changes: 3 additions & 0 deletions contracts/deployments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ export * from "./utils";
// Contracts getters
export { getContracts as getContractsEthers } from "./contractsEthers";
export { getContracts as getContractsViem } from "./contractsViem";

// Dispute kits getters
export { getDisputeKits as getDisputeKitsViem, type DisputeKitByIds, type DisputeKitInfos } from "./disputeKitsViem";
11 changes: 9 additions & 2 deletions contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
"@types/mocha": "^10.0.10",
"@types/node": "^20.17.6",
"@types/sinon": "^17.0.4",
"@wagmi/cli": "^2.2.0",
"@wagmi/cli": "^2.3.2",
"abitype": "^0.10.3",
"chai": "^4.5.0",
"dotenv": "^16.6.1",
Expand Down Expand Up @@ -157,7 +157,14 @@
"@kleros/vea-contracts": "^0.6.0",
"@openzeppelin/contracts": "^5.4.0",
"@shutter-network/shutter-sdk": "0.0.2",
"isomorphic-fetch": "^3.0.0",
"isomorphic-fetch": "^3.0.0"
},
"peerDependencies": {
"viem": "^2.24.1"
},
"peerDependenciesMeta": {
"viem": {
"optional": false
}
}
Comment on lines +160 to 169
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Good approach making viem a peer dependency.

Making viem a peer dependency is the correct approach for a library package, allowing consuming applications to control the version. However, ensure the version requirement aligns with the root package.json resolution.

Verify that the viem version requirement ^2.24.1 is compatible with the resolution in the root package.json:


🏁 Script executed:

#!/bin/bash
# Description: Check viem version alignment between package.json files

echo "Root package.json viem resolution:"
jq -r '.resolutions["viem@npm:2.x"]' package.json

echo "Contracts package.json viem peer dependency:"
jq -r '.peerDependencies.viem' contracts/package.json

Length of output: 303


Align viem peer dependency with root resolution

The root package.json forces viem@npm:2.x to ^2.23.2, but contracts/package.json currently requires ^2.24.1. This mismatch will cause unsatisfied peer dependency errors.

• contracts/package.json (around line 162):

  • Change
    "viem": "^2.24.1"
  • To
    "viem": "^2.23.2"
--- a/contracts/package.json
+++ b/contracts/package.json
@@ peerDependencies
-    "viem": "^2.24.1"
+    "viem": "^2.23.2"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"isomorphic-fetch": "^3.0.0"
},
"peerDependencies": {
"viem": "^2.24.1"
},
"peerDependenciesMeta": {
"viem": {
"optional": false
}
}
"isomorphic-fetch": "^3.0.0"
},
"peerDependencies": {
"viem": "^2.23.2"
},
"peerDependenciesMeta": {
"viem": {
"optional": false
}
}
🤖 Prompt for AI Agents
In contracts/package.json around lines 160 to 169, the peer dependency version
for "viem" is set to "^2.24.1", which conflicts with the root package.json that
uses "^2.23.2". To fix this, change the "viem" version in the peerDependencies
section from "^2.24.1" to "^2.23.2" to align with the root resolution and avoid
peer dependency errors.

}
33 changes: 33 additions & 0 deletions contracts/scripts/getDisputeKits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { getDisputeKits } from "../deployments/disputeKitsViem";
import { createPublicClient, http } from "viem";
import { arbitrumSepolia } from "viem/chains";

const rpc = process.env.ARBITRUM_SEPOLIA_RPC;
if (!rpc) {
throw new Error("ARBITRUM_SEPOLIA_RPC is not set");
}

const client = createPublicClient({
chain: arbitrumSepolia,
transport: http(rpc),
});

async function main() {
try {
console.log("Fetching DisputeKitCreated events...");
const disputeKitResult = await getDisputeKits(client, "devnet");
console.log(disputeKitResult);
} catch (error) {
console.error("Error fetching events:", error);
throw error;
}
}
Comment on lines +15 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the deployment parameter matches the target chain.

The script connects to arbitrumSepolia but passes "devnet" as the deployment parameter. Ensure this alignment is correct for your intended usage.

Verify the deployment parameter usage:


🏁 Script executed:

#!/bin/bash
# Description: Check available deployment configurations to verify "devnet" parameter

echo "Available deployment configurations:"
fd -e ts -e js . contracts/deployments/ --exec basename {} .ts .js | sort | uniq

Length of output: 981


🏁 Script executed:

#!/bin/bash
# Correctly list deployment configurations by stripping directory and extension
echo "Available deployment configurations:"
fd -e ts -e js contracts/deployments/ -type f \
  | sed -e 's#.*/##' -e 's/\.[^.]*$//' \
  | sort -u

Length of output: 348


🏁 Script executed:

#!/bin/bash
# Properly list deployment configurations using fd's '-t f' flag
echo "Available deployment configurations:"
fd -e ts -e js -t f contracts/deployments/ \
  | sed -e 's#.*/##' -e 's/\.[^.]*$//' \
  | sort -u

Length of output: 568


🏁 Script executed:

#!/bin/bash
# List all deployment configurations in contracts/deployments
echo "Available deployment configurations:"
fd -e ts -e js -t f . contracts/deployments/ \
  | sed -e 's#.*/##' -e 's/\.[^.]*$//' \
  | sort -u

Length of output: 319


Ensure the deployment parameter matches the intended chain

In contracts/scripts/getDisputeKits.ts (around the getDisputeKits call), you’re passing "devnet"—but the available deployment configs include:

• arbitr-um
• arbitr-umSepolia
arbitrumSepoliaDevnet
• devnet.viem
• …and others

To target Arbitrum Sepolia on devnet, update the parameter accordingly:

- const disputeKitResult = await getDisputeKits(client, "devnet");
+ const disputeKitResult = await getDisputeKits(client, "arbitrumSepoliaDevnet");

If your intent was to use a different network or client (e.g. viem vs. ethers), pick the matching config name from contracts/deployments/.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function main() {
try {
console.log("Fetching DisputeKitCreated events...");
const disputeKitResult = await getDisputeKits(client, "devnet");
console.log(disputeKitResult);
} catch (error) {
console.error("Error fetching events:", error);
throw error;
}
}
async function main() {
try {
console.log("Fetching DisputeKitCreated events...");
const disputeKitResult = await getDisputeKits(client, "arbitrumSepoliaDevnet");
console.log(disputeKitResult);
} catch (error) {
console.error("Error fetching events:", error);
throw error;
}
}
🤖 Prompt for AI Agents
In contracts/scripts/getDisputeKits.ts around lines 15 to 24, the deployment
parameter passed to getDisputeKits is currently "devnet", which does not match
the intended Arbitrum Sepolia devnet configuration. Update the parameter to
"arbitrumSepoliaDevnet" or the appropriate config name from
contracts/deployments/ that corresponds to the target network and client you
want to use, ensuring the deployment parameter aligns with the actual deployment
configuration.


if (require.main === module) {
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@
"elliptic@npm:6.5.4": "npm:6.6.1",
"word-wrap@npm:~1.2.3": "npm:1.2.5",
"@codemirror/state": "npm:6.5.2",
"undici@npm:7.3.0": "npm:7.5.0"
"undici@npm:7.3.0": "npm:7.5.0",
"viem@npm:2.x": "npm:^2.23.2"
Comment on lines +80 to +81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Version mismatch between viem resolution and peer dependency requirement.

The resolution specifies ^2.23.2 but contracts/package.json requires ^2.24.1 as a peer dependency. This mismatch could cause installation issues or runtime incompatibilities.

Apply this diff to align the resolution with the peer dependency requirement:

-    "viem@npm:2.x": "npm:^2.23.2"
+    "viem@npm:2.x": "npm:^2.24.1"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"undici@npm:7.3.0": "npm:7.5.0",
"viem@npm:2.x": "npm:^2.23.2"
"undici@npm:7.3.0": "npm:7.5.0",
"viem@npm:2.x": "npm:^2.24.1"
🤖 Prompt for AI Agents
In package.json at lines 80 to 81, the version resolution for "viem" is set to
"^2.23.2" which conflicts with the peer dependency requirement "^2.24.1"
specified in contracts/package.json. Update the "viem" resolution version to
"^2.24.1" to ensure consistency and avoid installation or runtime issues.

},
"scripts": {
"check-prerequisites": "scripts/check-prerequisites.sh",
Expand Down
Loading
Loading