feat(bots): dispute-archival-bot-first-iteration#2280
Conversation
❌ Deploy Preview for kleros-v2-testnet-devtools failed. Why did it fail? →
|
❌ Deploy Preview for kleros-v2-neo failed. Why did it fail? →
|
WalkthroughThis PR introduces a complete dispute archival system: a DisputeArchive Solidity contract and deploy artifacts, subgraph schema and mapping updates to record archived disputes, and a TypeScript archival bot with utilities to fetch dispute data, assemble snapshots, upload to IPFS, and register CIDs on-chain. ChangesDispute Archival Contract & Bot Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Comment |
✅ Deploy Preview for kleros-v2-testnet ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (1)
bots/dispute-archival-bot/.env.example (1)
14-14: 💤 Low valueAdd trailing newline for POSIX compliance.
The file is missing a trailing newline at the end. While not critical, adding one aligns with POSIX text file standards and prevents potential issues with certain text processing tools.
📝 Proposed fix
export ALCHEMY_API_KEY="" -export PRIVATE_KEY="" \ No newline at end of file +export PRIVATE_KEY=""🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@bots/dispute-archival-bot/.env.example` at line 14, The file ends with the environment variable declaration export PRIVATE_KEY="" and lacks a trailing newline; update the .env.example so that the final line (export PRIVATE_KEY="") is followed by a single newline character at EOF to make the file POSIX-compliant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@bots/dispute-archival-bot/index.ts`:
- Around line 16-32: The loop over disputeIds should be hardened so a failure
for one dispute doesn't stop the whole run: wrap the per-dispute workflow inside
a try/catch around the calls to isDisputeArchived(id),
fetchDisputeArchiveSnapshot(BigInt(id)), uploadToIpfs(id, snapshot), and
registerCid(id, cid) (i.e., the body of the for (const id of disputeIds) loop),
log a clear error message including the id and the caught error, and continue to
the next id in the catch block; keep the existing success logs for
archived/skipped and completed cases.
In `@bots/dispute-archival-bot/package.json`:
- Around line 11-15: The package.json incorrectly lists "dotenv" and "viem"
under devDependencies but they are imported at runtime (e.g., "dotenv/config" at
the entry point and "viem" used in rpc.ts, contract.ts,
fetchDisputeEvidences.ts, fetchDisputeDetailsFromSubgraph.ts); move "dotenv" and
"viem" from devDependencies into dependencies in package.json (remove them from
the devDependencies section and add them to dependencies) and then run your
package manager to update lockfile so production installs include them.
- Line 6: The start script currently uses "node index.ts" which fails because
tsconfig.json has emitDeclarationOnly: true and no compiled JS exists; update
the package.json "start" script (the start npm script) to use a TypeScript
runtime executor such as tsx (e.g., "tsx index.ts") or change to run the
compiled output (e.g., build step then "node dist/index.js"); ensure any new
dependency (tsx) is added to devDependencies and update scripts accordingly
(e.g., add a "build" that compiles and adjust start to run compiled JS if you
prefer a build step).
In `@bots/dispute-archival-bot/README.md`:
- Line 7: The README has incorrect heading levels: change the three subordinate
headings currently written as "### Archived data includes:", and the other two
similar headings at the same file, to "##" so they are direct subsections under
the top-level title; update each instance of "###" to "##" for those headings to
satisfy MD001 and maintain proper hierarchy.
In `@bots/dispute-archival-bot/utils/fetchDisputeDetailsFromSubgraph.ts`:
- Around line 156-157: The current single GraphQL `query` in
fetchDisputeDetailsFromSubgraph.ts that requests multiple list fields (rounds,
drawnJurors, localRounds, answers) can silently drop items if any list exceeds
subgraph page limits; change the implementation to first fetch the top-level
dispute by id (keep the existing `query` but only select scalar/top-level
fields), then implement dedicated paginated queries for each heavy collection
(e.g., queries and helper functions like paginateRounds, paginateDrawnJurors,
paginateLocalRounds, paginateAnswers or a generic paginateSubgraphCollection)
that page by `first/skip` or use cursor-based pagination and filter by
disputeId/roundId as appropriate, and then merge those paginated results into
the dispute snapshot before returning from fetchDisputeDetailsFromSubgraph.
In `@bots/dispute-archival-bot/utils/fetchDisputeEvidences.ts`:
- Around line 26-27: The GraphQL query in fetchDisputeEvidences currently
requests evidences(first: 1000) which silently truncates results for groups
>1000; update the logic in fetchDisputeEvidences to implement cursor (or offset)
pagination for the evidences field (using the evidenceGroupID, orderBy:
timestamp, orderDirection: asc) by repeatedly querying with an after cursor (or
increasing offset), appending each page to the accumulated evidences array and
stopping when a page returns fewer than the pageSize; apply the same change to
the other evidences query occurrences referenced in the diff (the block around
lines 44-77) so all evidence fetches fully paginate instead of using a
single-page cap.
In `@bots/dispute-archival-bot/utils/fetchDisputeIds.ts`:
- Around line 10-16: The current GraphQL query constant (query) in
fetchDisputeIds only requests first: 1000 and will miss any disputes beyond
that; update fetchDisputeIds to paginate and fetch all disputes (use either
skip+first loop or cursor-based pagination if supported), replacing the
hardcoded query with a parameterized query that accepts first and skip (or after
cursor), iterate requests until no more results, collect all dispute ids into a
single array, and ensure existing error handling/logging in fetchDisputeIds
continues to operate per-request.
In `@bots/dispute-archival-bot/utils/fetchPopulatedDisputeData.ts`:
- Around line 29-35: The catch block in fetchPopulatedDisputeData silently
returns undefined and only console.logs the error; change it to (1) replace
console.log with a structured logger call (e.g., processLogger.error) including
disputeId and full error object, (2) push the disputeId and error metadata into
a shared failure collector (e.g., failedDisputeIds array or failedDisputeRecords
map) so callers can retry or report, and (3) differentiate transient vs
permanent failures by inspecting the error (e.g., error.name, instanceof
network/fetch error, or HTTP status on the response) and record a failureType
flag (transient|permanent) in the collector and/or emit a metric/alert call
(e.g., metrics.increment('dispute.fetch.fail', {type})). Ensure the symbol names
referenced are fetchPopulatedDisputeData, disputeId,
failedDisputeIds/failedDisputeRecords, processLogger, and metrics so you can
locate and wire this behavior into existing flows.
In `@bots/dispute-archival-bot/utils/uploadToIpfs.ts`:
- Around line 27-31: The code reads json.cids.length without validating the
response shape; update the uploadToIpfs logic to first ensure the parsed json is
an object and that json.cids exists and is an array (e.g.,
Array.isArray(json.cids)) before checking length, and throw the same upload
error (including disputeId) when cids is missing or not an array; locate the
check around the variable named json in the uploadToIpfs function and replace
the direct json.cids.length access with a defensive check that handles
missing/null responses and non-array cids.
In `@contracts/src/archive/DisputeArchive.sol`:
- Around line 54-56: The updateOwner function currently allows assigning
address(0), which can brick admin actions; modify the updateOwner(address
newOwner) function to reject zero addresses by adding a require(newOwner !=
address(0), "new owner is the zero address") (or similar) before setting owner,
and optionally emit an OwnershipTransferred/OwnerUpdated event after changing
owner to help auditing; reference updateOwner, owner, and onlyOwner when making
the change and ensure register and amend remain guarded by onlyOwner as before.
---
Nitpick comments:
In `@bots/dispute-archival-bot/.env.example`:
- Line 14: The file ends with the environment variable declaration export
PRIVATE_KEY="" and lacks a trailing newline; update the .env.example so that the
final line (export PRIVATE_KEY="") is followed by a single newline character at
EOF to make the file POSIX-compliant.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d8484e87-8539-4311-9610-b3b29a92c9d0
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (22)
bots/dispute-archival-bot/.env.examplebots/dispute-archival-bot/.gitignorebots/dispute-archival-bot/README.mdbots/dispute-archival-bot/abi/DisputeArchive.tsbots/dispute-archival-bot/config.tsbots/dispute-archival-bot/index.tsbots/dispute-archival-bot/package.jsonbots/dispute-archival-bot/tsconfig.jsonbots/dispute-archival-bot/utils/contract.tsbots/dispute-archival-bot/utils/fetchDisputeArchiveSnapshot.tsbots/dispute-archival-bot/utils/fetchDisputeDetailsFromSubgraph.tsbots/dispute-archival-bot/utils/fetchDisputeEvidences.tsbots/dispute-archival-bot/utils/fetchDisputeIds.tsbots/dispute-archival-bot/utils/fetchDisputeTemplate.tsbots/dispute-archival-bot/utils/fetchPopulatedDisputeData.tsbots/dispute-archival-bot/utils/rpc.tsbots/dispute-archival-bot/utils/types.tsbots/dispute-archival-bot/utils/uploadToIpfs.tscontracts/deploy/00-home-chan-dispute-archive.tscontracts/deployments/arbitrumSepoliaDevnet/DisputeArchive.jsoncontracts/src/archive/DisputeArchive.solpackage.json
| for (const id of disputeIds) { | ||
| const isArchived = await isDisputeArchived(id); | ||
| if (isArchived) { | ||
| console.log(`Skipping dispute ${id}. Already archived.`); | ||
| continue; | ||
| } | ||
|
|
||
| console.log(`Archiving dispute ${id} ...`); | ||
|
|
||
| const snapshot = await fetchDisputeArchiveSnapshot(BigInt(id)); | ||
|
|
||
| const cid = await uploadToIpfs(id, snapshot); | ||
|
|
||
| const hash = await registerCid(id, cid); | ||
|
|
||
| console.log(`Dispute ${id} archived with cid: ${cid}. Transaction hash: ${hash}`); | ||
| } |
There was a problem hiding this comment.
Isolate per-dispute failures so one error doesn’t stop the whole run.
A single thrown error currently aborts processing of all subsequent disputes.
🛠️ Suggested loop hardening
for (const id of disputeIds) {
- const isArchived = await isDisputeArchived(id);
- if (isArchived) {
- console.log(`Skipping dispute ${id}. Already archived.`);
- continue;
- }
-
- console.log(`Archiving dispute ${id} ...`);
-
- const snapshot = await fetchDisputeArchiveSnapshot(BigInt(id));
-
- const cid = await uploadToIpfs(id, snapshot);
-
- const hash = await registerCid(id, cid);
-
- console.log(`Dispute ${id} archived with cid: ${cid}. Transaction hash: ${hash}`);
+ try {
+ const isArchived = await isDisputeArchived(id);
+ if (isArchived) {
+ console.log(`Skipping dispute ${id}. Already archived.`);
+ continue;
+ }
+
+ console.log(`Archiving dispute ${id} ...`);
+ const snapshot = await fetchDisputeArchiveSnapshot(BigInt(id));
+ const cid = await uploadToIpfs(id, snapshot);
+ const hash = await registerCid(id, cid);
+ console.log(`Dispute ${id} archived with cid: ${cid}. Transaction hash: ${hash}`);
+ } catch (err) {
+ console.error(`Failed to archive dispute ${id}:`, err);
+ continue;
+ }
}📝 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.
| for (const id of disputeIds) { | |
| const isArchived = await isDisputeArchived(id); | |
| if (isArchived) { | |
| console.log(`Skipping dispute ${id}. Already archived.`); | |
| continue; | |
| } | |
| console.log(`Archiving dispute ${id} ...`); | |
| const snapshot = await fetchDisputeArchiveSnapshot(BigInt(id)); | |
| const cid = await uploadToIpfs(id, snapshot); | |
| const hash = await registerCid(id, cid); | |
| console.log(`Dispute ${id} archived with cid: ${cid}. Transaction hash: ${hash}`); | |
| } | |
| for (const id of disputeIds) { | |
| try { | |
| const isArchived = await isDisputeArchived(id); | |
| if (isArchived) { | |
| console.log(`Skipping dispute ${id}. Already archived.`); | |
| continue; | |
| } | |
| console.log(`Archiving dispute ${id} ...`); | |
| const snapshot = await fetchDisputeArchiveSnapshot(BigInt(id)); | |
| const cid = await uploadToIpfs(id, snapshot); | |
| const hash = await registerCid(id, cid); | |
| console.log(`Dispute ${id} archived with cid: ${cid}. Transaction hash: ${hash}`); | |
| } catch (err) { | |
| console.error(`Failed to archive dispute ${id}:`, err); | |
| continue; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bots/dispute-archival-bot/index.ts` around lines 16 - 32, The loop over
disputeIds should be hardened so a failure for one dispute doesn't stop the
whole run: wrap the per-dispute workflow inside a try/catch around the calls to
isDisputeArchived(id), fetchDisputeArchiveSnapshot(BigInt(id)), uploadToIpfs(id,
snapshot), and registerCid(id, cid) (i.e., the body of the for (const id of
disputeIds) loop), log a clear error message including the id and the caught
error, and continue to the next id in the catch block; keep the existing success
logs for archived/skipped and completed cases.
| "packageManager": "yarn@4.10.3", | ||
| "type": "module", | ||
| "scripts": { | ||
| "start": "node index.ts" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
BOT_PKG=$(fd -a '^package\.json$' bots/dispute-archival-bot | head -n1)
BOT_TSCONFIG=$(fd -a '^tsconfig\.json$' bots/dispute-archival-bot | head -n1)
echo "start script:"
jq -r '.scripts.start' "$BOT_PKG"
echo "ts runtime helpers:"
jq -r '.dependencies.tsx // .devDependencies.tsx // "tsx:not-declared"' "$BOT_PKG"
jq -r '.dependencies["ts-node"] // .devDependencies["ts-node"] // "ts-node:not-declared"' "$BOT_PKG"
echo "emitDeclarationOnly:"
jq -r '.compilerOptions.emitDeclarationOnly' "$BOT_TSCONFIG"Repository: kleros/kleros-v2
Length of output: 172
🏁 Script executed:
# Check the tsconfig.json configuration
cat bots/dispute-archival-bot/tsconfig.jsonRepository: kleros/kleros-v2
Length of output: 338
🏁 Script executed:
# Check if index.ts exists and examine it
ls -la bots/dispute-archival-bot/index.ts 2>&1 || echo "index.ts not found"
# Also check for any compiled versions (index.js, dist/, build/, etc.)
find bots/dispute-archival-bot -maxdepth 2 -type f \( -name "index.js" -o -name "index.mjs" \) 2>&1
find bots/dispute-archival-bot -maxdepth 2 -type d \( -name "dist" -o -name "build" -o -name "out" \) 2>&1Repository: kleros/kleros-v2
Length of output: 144
🏁 Script executed:
# Check the full package.json to see the complete context
cat bots/dispute-archival-bot/package.jsonRepository: kleros/kleros-v2
Length of output: 420
🏁 Script executed:
# Check the parent tsconfig being extended
cat bots/dispute-archival-bot/node_modules/@kleros/kleros-v2-tsconfig/base20.json 2>/dev/null || echo "Cannot access parent config in sandbox"
# Also verify if there's a build script or any way TypeScript is supposed to be compiled
cd bots/dispute-archival-bot && find . -name "*.json" -o -name "*.ts" -o -name "*.js" | grep -E "(build|compile|tsconfig)" | head -20Repository: kleros/kleros-v2
Length of output: 115
The start script requires a TypeScript runtime executor to work.
With emitDeclarationOnly: true in tsconfig.json, TypeScript only generates declaration files (.d.ts), not executable JavaScript. Running node index.ts directly will fail because Node.js cannot execute TypeScript natively. Add a runtime executor like tsx:
Suggested fix
"scripts": {
- "start": "node index.ts"
+ "start": "tsx index.ts"
},
@@
"dependencies": {
- "@kleros/kleros-sdk": "workspace:^"
+ "@kleros/kleros-sdk": "workspace:^",
+ "tsx": "^4.20.6"
},🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bots/dispute-archival-bot/package.json` at line 6, The start script currently
uses "node index.ts" which fails because tsconfig.json has emitDeclarationOnly:
true and no compiled JS exists; update the package.json "start" script (the
start npm script) to use a TypeScript runtime executor such as tsx (e.g., "tsx
index.ts") or change to run the compiled output (e.g., build step then "node
dist/index.js"); ensure any new dependency (tsx) is added to devDependencies and
update scripts accordingly (e.g., add a "build" that compiles and adjust start
to run compiled JS if you prefer a build step).
| "devDependencies": { | ||
| "@kleros/kleros-v2-tsconfig": "workspace:^", | ||
| "@types/node": "^25.6.0", | ||
| "dotenv": "^17.4.2", | ||
| "viem": "^2.24.1" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
BOT_PKG=$(fd -a '^package\.json$' bots/dispute-archival-bot | head -n1)
echo "runtime imports of dotenv/viem in bot sources:"
rg -n --type=ts -C2 'from "dotenv"|from "viem"|import "dotenv/config"' bots/dispute-archival-bot
echo "declared dependency sections:"
jq -r '{dependencies, devDependencies}' "$BOT_PKG"Repository: kleros/kleros-v2
Length of output: 1848
dotenv and viem should be runtime dependencies, not dev-only.
These packages are imported directly at runtime: dotenv/config loads at the entry point, and viem is used across multiple runtime utility modules (rpc.ts, contract.ts, fetchDisputeEvidences.ts, fetchDisputeDetailsFromSubgraph.ts). Keeping them in devDependencies will break production installs that omit dev packages.
✅ Suggested fix
"dependencies": {
- "@kleros/kleros-sdk": "workspace:^"
+ "@kleros/kleros-sdk": "workspace:^",
+ "dotenv": "^17.4.2",
+ "viem": "^2.24.1"
},
"devDependencies": {
"@kleros/kleros-v2-tsconfig": "workspace:^",
- "@types/node": "^25.6.0",
- "dotenv": "^17.4.2",
- "viem": "^2.24.1"
+ "@types/node": "^25.6.0"
}📝 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.
| "devDependencies": { | |
| "@kleros/kleros-v2-tsconfig": "workspace:^", | |
| "@types/node": "^25.6.0", | |
| "dotenv": "^17.4.2", | |
| "viem": "^2.24.1" | |
| "dependencies": { | |
| "@kleros/kleros-sdk": "workspace:^", | |
| "dotenv": "^17.4.2", | |
| "viem": "^2.24.1" | |
| }, | |
| "devDependencies": { | |
| "@kleros/kleros-v2-tsconfig": "workspace:^", | |
| "@types/node": "^25.6.0" | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bots/dispute-archival-bot/package.json` around lines 11 - 15, The
package.json incorrectly lists "dotenv" and "viem" under devDependencies but
they are imported at runtime (e.g., "dotenv/config" at the entry point and
"viem" used in rpc.ts, contract.ts, fetchDisputeEvidences.ts,
fetchDisputeDetailsFromSubgraph.ts); move "dotenv" and "viem" from
devDependencies into dependencies in package.json (remove them from the
devDependencies section and add them to dependencies) and then run your package
manager to update lockfile so production installs include them.
|
|
||
| DisputeArchive Contract : [arbitrum sepolia](https://sepolia.arbiscan.io/address/0x13713cf6D261704A07aB8a44d88CE5d795fdF99d) (For testing, archived the mainnet beta disputes on arbitrum sepolia) | ||
|
|
||
| ### Archived data includes: |
There was a problem hiding this comment.
Fix heading hierarchy (MD001) to avoid markdownlint warnings.
Line 7 jumps from # to ###; the section headings should be ## under the top title.
📘 Suggested markdown fix
-### Archived data includes:
+## Archived data includes:
@@
-### Notes:
+## Notes:
@@
-### Pending
+## PendingAlso applies to: 14-14, 21-21
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 7-7: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3
(MD001, heading-increment)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bots/dispute-archival-bot/README.md` at line 7, The README has incorrect
heading levels: change the three subordinate headings currently written as "###
Archived data includes:", and the other two similar headings at the same file,
to "##" so they are direct subsections under the top-level title; update each
instance of "###" to "##" for those headings to satisfy MD001 and maintain
proper hierarchy.
| // assuming that we don't have more than 1000 items to fetch, so no need for pagination | ||
| const query = ` |
There was a problem hiding this comment.
Nested list data can be silently incomplete in this query.
This query fetches multiple list fields in one shot without pagination (rounds, drawnJurors, localRounds, answers). If any exceed the subgraph page defaults, the archive snapshot will miss data.
A safer pattern is: fetch top-level dispute once, then paginate each heavy child collection with dedicated queries keyed by dispute/round IDs.
Also applies to: 189-290
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bots/dispute-archival-bot/utils/fetchDisputeDetailsFromSubgraph.ts` around
lines 156 - 157, The current single GraphQL `query` in
fetchDisputeDetailsFromSubgraph.ts that requests multiple list fields (rounds,
drawnJurors, localRounds, answers) can silently drop items if any list exceeds
subgraph page limits; change the implementation to first fetch the top-level
dispute by id (keep the existing `query` but only select scalar/top-level
fields), then implement dedicated paginated queries for each heavy collection
(e.g., queries and helper functions like paginateRounds, paginateDrawnJurors,
paginateLocalRounds, paginateAnswers or a generic paginateSubgraphCollection)
that page by `first/skip` or use cursor-based pagination and filter by
disputeId/roundId as appropriate, and then merge those paginated results into
the dispute snapshot before returning from fetchDisputeDetailsFromSubgraph.
| evidences(where: { evidenceGroup: $evidenceGroupID }, orderBy: timestamp, orderDirection: asc, first: 1000) { | ||
| id |
There was a problem hiding this comment.
Paginate evidence fetch to avoid silent truncation.
The current single-page query caps results at 1000, so disputes with more evidences will be archived incompletely.
🔧 Suggested direction (cursor/offset pagination)
- query Evidences($evidenceGroupID: String) {
- evidences(where: { evidenceGroup: $evidenceGroupID }, orderBy: timestamp, orderDirection: asc, first: 1000) {
+ query Evidences($evidenceGroupID: String!, $first: Int!, $skip: Int!) {
+ evidences(
+ where: { evidenceGroup: $evidenceGroupID }
+ orderBy: timestamp
+ orderDirection: asc
+ first: $first
+ skip: $skip
+ ) {
...
}
} export async function fetchDisputeEvidences(externalDisputeId: string) {
+ const pageSize = 1000;
+ const all: Evidence[] = [];
+ let skip = 0;
+
+ while (true) {
+ // fetch page with variables: { evidenceGroupID: externalDisputeId, first: pageSize, skip }
+ // parse/validate as today
+ // const page = json.data.evidences;
+ // all.push(...page);
+ // if (page.length < pageSize) break;
+ // skip += pageSize;
+ }
+
+ return all;
- return json.data.evidences;
}Also applies to: 44-77
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bots/dispute-archival-bot/utils/fetchDisputeEvidences.ts` around lines 26 -
27, The GraphQL query in fetchDisputeEvidences currently requests
evidences(first: 1000) which silently truncates results for groups >1000; update
the logic in fetchDisputeEvidences to implement cursor (or offset) pagination
for the evidences field (using the evidenceGroupID, orderBy: timestamp,
orderDirection: asc) by repeatedly querying with an after cursor (or increasing
offset), appending each page to the accumulated evidences array and stopping
when a page returns fewer than the pageSize; apply the same change to the other
evidences query occurrences referenced in the diff (the block around lines
44-77) so all evidence fetches fully paginate instead of using a single-page
cap.
| const query = ` | ||
| query Disputes { | ||
| disputes(first: 1000) { | ||
| id | ||
| } | ||
| } | ||
| `; |
There was a problem hiding this comment.
Implement pagination to handle more than 1000 disputes.
The GraphQL query has a hardcoded limit of first: 1000, which means only the first 1000 disputes will be fetched. If the system contains more disputes, newer ones will be silently excluded from archival without any error or warning.
For production use, implement pagination using GraphQL's skip and first parameters to fetch all disputes, or use cursor-based pagination if the subgraph supports it.
💡 Example pagination approach
export async function fetchDisputeIds() {
const config = getEnvConfig();
const allIds: string[] = [];
let skip = 0;
const first = 1000;
let hasMore = true;
while (hasMore) {
const response = await fetch(config.coreSubgraphUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: `
query Disputes($first: Int!, $skip: Int!) {
disputes(first: $first, skip: $skip, orderBy: id) {
id
}
}
`,
variables: { first, skip },
}),
});
// ... error handling ...
const disputes = json.data.disputes;
allIds.push(...disputes.map(d => d.id));
hasMore = disputes.length === first;
skip += first;
}
return allIds;
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bots/dispute-archival-bot/utils/fetchDisputeIds.ts` around lines 10 - 16, The
current GraphQL query constant (query) in fetchDisputeIds only requests first:
1000 and will miss any disputes beyond that; update fetchDisputeIds to paginate
and fetch all disputes (use either skip+first loop or cursor-based pagination if
supported), replacing the hardcoded query with a parameterized query that
accepts first and skip (or after cursor), iterate requests until no more
results, collect all dispute ids into a single array, and ensure existing error
handling/logging in fetchDisputeIds continues to operate per-request.
| } catch (error: unknown) { | ||
| if (error instanceof Error) { | ||
| // dispute is broken | ||
| console.log(`Unable to fetch populated data for dispute ${disputeId}: ${error.message}`); | ||
| } | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
Improve error visibility for failed dispute fetches.
The current error handling silently returns undefined when dispute data cannot be fetched, with only a console log for visibility. This approach could lead to incomplete archives without proper tracking of which disputes failed and why.
For production use, consider:
- Collecting failed dispute IDs for retry or manual review
- Emitting metrics/alerts when disputes fail to fetch
- Differentiating between transient failures (network issues) and permanent failures (broken dispute data)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bots/dispute-archival-bot/utils/fetchPopulatedDisputeData.ts` around lines 29
- 35, The catch block in fetchPopulatedDisputeData silently returns undefined
and only console.logs the error; change it to (1) replace console.log with a
structured logger call (e.g., processLogger.error) including disputeId and full
error object, (2) push the disputeId and error metadata into a shared failure
collector (e.g., failedDisputeIds array or failedDisputeRecords map) so callers
can retry or report, and (3) differentiate transient vs permanent failures by
inspecting the error (e.g., error.name, instanceof network/fetch error, or HTTP
status on the response) and record a failureType flag (transient|permanent) in
the collector and/or emit a metric/alert call (e.g.,
metrics.increment('dispute.fetch.fail', {type})). Ensure the symbol names
referenced are fetchPopulatedDisputeData, disputeId,
failedDisputeIds/failedDisputeRecords, processLogger, and metrics so you can
locate and wire this behavior into existing flows.
| const json = (await response.json()) as UploadResponse; | ||
|
|
||
| if (json.cids.length === 0) { | ||
| throw new Error(`uploadToIpfs: Failed to upload data for dispute ${disputeId}. No CID returned.`); | ||
| } |
There was a problem hiding this comment.
Validate response shape before reading cids.length.
If cids is missing or not an array, this path throws a runtime TypeError instead of a controlled upload error.
✅ Small defensive fix
const json = (await response.json()) as UploadResponse;
-
- if (json.cids.length === 0) {
+ if (!Array.isArray(json.cids) || json.cids.length === 0) {
throw new Error(`uploadToIpfs: Failed to upload data for dispute ${disputeId}. No CID returned.`);
}📝 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.
| const json = (await response.json()) as UploadResponse; | |
| if (json.cids.length === 0) { | |
| throw new Error(`uploadToIpfs: Failed to upload data for dispute ${disputeId}. No CID returned.`); | |
| } | |
| const json = (await response.json()) as UploadResponse; | |
| if (!Array.isArray(json.cids) || json.cids.length === 0) { | |
| throw new Error(`uploadToIpfs: Failed to upload data for dispute ${disputeId}. No CID returned.`); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@bots/dispute-archival-bot/utils/uploadToIpfs.ts` around lines 27 - 31, The
code reads json.cids.length without validating the response shape; update the
uploadToIpfs logic to first ensure the parsed json is an object and that
json.cids exists and is an array (e.g., Array.isArray(json.cids)) before
checking length, and throw the same upload error (including disputeId) when cids
is missing or not an array; locate the check around the variable named json in
the uploadToIpfs function and replace the direct json.cids.length access with a
defensive check that handles missing/null responses and non-array cids.
| function updateOwner(address newOwner) external onlyOwner { | ||
| owner = newOwner; | ||
| } |
There was a problem hiding this comment.
Prevent zero-address owner assignment to avoid bricking admin actions.
Line 55 currently allows address(0), which can permanently disable register, amend, and future ownership updates.
🛡️ Suggested fix
+ error InvalidOwner();
+
function updateOwner(address newOwner) external onlyOwner {
+ require(newOwner != address(0), InvalidOwner());
owner = newOwner;
}📝 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.
| function updateOwner(address newOwner) external onlyOwner { | |
| owner = newOwner; | |
| } | |
| error InvalidOwner(); | |
| function updateOwner(address newOwner) external onlyOwner { | |
| if (newOwner == address(0)) revert InvalidOwner(); | |
| owner = newOwner; | |
| } |
| function updateOwner(address newOwner) external onlyOwner { | |
| owner = newOwner; | |
| } | |
| function updateOwner(address newOwner) external onlyOwner { | |
| require(newOwner != address(0), "InvalidOwner"); | |
| owner = newOwner; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@contracts/src/archive/DisputeArchive.sol` around lines 54 - 56, The
updateOwner function currently allows assigning address(0), which can brick
admin actions; modify the updateOwner(address newOwner) function to reject zero
addresses by adding a require(newOwner != address(0), "new owner is the zero
address") (or similar) before setting owner, and optionally emit an
OwnershipTransferred/OwnerUpdated event after changing owner to help auditing;
reference updateOwner, owner, and onlyOwner when making the change and ensure
register and amend remain guarded by onlyOwner as before.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@subgraph/core/src/archive/disputeArchiveStubs.ts`:
- Around line 95-100: The Court entity is loaded and its fields numberDisputes
and numberClosedDisputes are incremented but not persisted; after mutating court
in the block that calls Court.load(courtId.toString()) and before returning,
call court.save() to persist changes (ensure you still call
updateCourtCumulativeMetric(courtId.toString(), ONE, blockTimestamp,
"numberDisputes") as currently done).
In `@subgraph/core/src/DisputeArchive.ts`:
- Around line 41-45: The handler handleDisputeAmended currently calls
createArchiveDispute(event.params.id, ONE, ...) which uses a synthetic courtId
and can create a new Dispute when none exists; change handleDisputeAmended to
first check for an existing Dispute entity (e.g., Dispute.load(event.params.id))
and only call createArchiveDispute when that Dispute exists; if the Dispute does
not exist, either create only the archive-specific entity (without passing a
placeholder ONE) or call createArchiveDispute with a sentinel (null/undefined)
and update createArchiveDispute to early-return or avoid creating a new Dispute
when courtId is missing so no synthetic court linkage is introduced.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5553abad-a7ed-449e-a7b3-4ad05ef6df31
📒 Files selected for processing (11)
bots/dispute-archival-bot/abi/DisputeArchive.tsbots/dispute-archival-bot/index.tsbots/dispute-archival-bot/utils/contract.tscontracts/deployments/arbitrumSepoliaDevnet/DisputeArchive.jsoncontracts/src/archive/DisputeArchive.solsubgraph/core/schema.graphqlsubgraph/core/src/DisputeArchive.tssubgraph/core/src/archive/disputeArchiveStubs.tssubgraph/core/src/entities/Dispute.tssubgraph/core/subgraph.template.yamlsubgraph/core/subgraph.yaml
✅ Files skipped from review due to trivial changes (3)
- subgraph/core/src/entities/Dispute.ts
- subgraph/core/schema.graphql
- contracts/deployments/arbitrumSepoliaDevnet/DisputeArchive.json
🚧 Files skipped from review as they are similar to previous changes (3)
- bots/dispute-archival-bot/abi/DisputeArchive.ts
- bots/dispute-archival-bot/utils/contract.ts
- bots/dispute-archival-bot/index.ts
| const court = Court.load(courtId.toString()); | ||
| if (!court) return; | ||
| court.numberDisputes = court.numberDisputes.plus(ONE); | ||
| // archived dispute is closed already | ||
| court.numberClosedDisputes = court.numberClosedDisputes.plus(ONE); | ||
| updateCourtCumulativeMetric(courtId.toString(), ONE, blockTimestamp, "numberDisputes"); |
There was a problem hiding this comment.
Persist updated Court counters before returning.
Line 97 and Line 99 mutate court, but without court.save() those changes are not written to the store.
💡 Suggested fix
const court = Court.load(courtId.toString());
if (!court) return;
court.numberDisputes = court.numberDisputes.plus(ONE);
// archived dispute is closed already
court.numberClosedDisputes = court.numberClosedDisputes.plus(ONE);
+ court.save();
updateCourtCumulativeMetric(courtId.toString(), ONE, blockTimestamp, "numberDisputes");📝 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.
| const court = Court.load(courtId.toString()); | |
| if (!court) return; | |
| court.numberDisputes = court.numberDisputes.plus(ONE); | |
| // archived dispute is closed already | |
| court.numberClosedDisputes = court.numberClosedDisputes.plus(ONE); | |
| updateCourtCumulativeMetric(courtId.toString(), ONE, blockTimestamp, "numberDisputes"); | |
| const court = Court.load(courtId.toString()); | |
| if (!court) return; | |
| court.numberDisputes = court.numberDisputes.plus(ONE); | |
| // archived dispute is closed already | |
| court.numberClosedDisputes = court.numberClosedDisputes.plus(ONE); | |
| court.save(); | |
| updateCourtCumulativeMetric(courtId.toString(), ONE, blockTimestamp, "numberDisputes"); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@subgraph/core/src/archive/disputeArchiveStubs.ts` around lines 95 - 100, The
Court entity is loaded and its fields numberDisputes and numberClosedDisputes
are incremented but not persisted; after mutating court in the block that calls
Court.load(courtId.toString()) and before returning, call court.save() to
persist changes (ensure you still call
updateCourtCumulativeMetric(courtId.toString(), ONE, blockTimestamp,
"numberDisputes") as currently done).
| export function handleDisputeAmended(event: ArchivedDisputeAmended): void { | ||
| createArchiveDispute( | ||
| event.params.id, | ||
| ONE, // this is placeholder, will not be used | ||
| event.params.cid, |
There was a problem hiding this comment.
Avoid creating archive-only disputes from ArchivedDisputeAmended with placeholder courtId.
Line 44 passes a synthetic court ID (ONE), but Line 26 can still create a new dispute when missing. That can corrupt indexed data with an incorrect court linkage.
💡 Suggested fix
function createArchiveDispute(
disputeId: BigInt,
courtId: BigInt,
cid: string,
reason: string | null,
timestamp: BigInt,
blockNumber: BigInt,
- transactionHash: Bytes
+ transactionHash: Bytes,
+ allowCreateIfMissing: boolean
): void {
const dispute = Dispute.load(disputeId.toString());
// if dispute is present, then it is an amend event
if (dispute) {
dispute.isArchived = true;
dispute.archiveCid = cid;
dispute.save();
return;
}
+ if (!allowCreateIfMissing) {
+ log.warning("ArchivedDisputeAmended received for unknown dispute {}", [disputeId.toString()]);
+ return;
+ }
createArchiveOnlyDispute(disputeId, courtId, cid, reason, timestamp, blockNumber, transactionHash);
}
@@
createArchiveDispute(
@@
- event.transaction.hash
+ event.transaction.hash,
+ true
);
}
@@
createArchiveDispute(
@@
- event.transaction.hash
+ event.transaction.hash,
+ false
);
}📝 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.
| export function handleDisputeAmended(event: ArchivedDisputeAmended): void { | |
| createArchiveDispute( | |
| event.params.id, | |
| ONE, // this is placeholder, will not be used | |
| event.params.cid, | |
| export function handleDisputeAmended(event: ArchivedDisputeAmended): void { | |
| createArchiveDispute( | |
| event.params.id, | |
| ONE, // this is placeholder, will not be used | |
| event.params.cid, | |
| null, | |
| event.block.timestamp, | |
| event.block.number, | |
| event.transaction.hash, | |
| false | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@subgraph/core/src/DisputeArchive.ts` around lines 41 - 45, The handler
handleDisputeAmended currently calls createArchiveDispute(event.params.id, ONE,
...) which uses a synthetic courtId and can create a new Dispute when none
exists; change handleDisputeAmended to first check for an existing Dispute
entity (e.g., Dispute.load(event.params.id)) and only call createArchiveDispute
when that Dispute exists; if the Dispute does not exist, either create only the
archive-specific entity (without passing a placeholder ONE) or call
createArchiveDispute with a sentinel (null/undefined) and update
createArchiveDispute to early-return or avoid creating a new Dispute when
courtId is missing so no synthetic court linkage is introduced.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@web/src/hooks/queries/useDisputeArchiveSnapshot.ts`:
- Around line 234-239: The code uses isUndefined(archiveCid) which misses null
values so getIpfsUrl(archiveCid) can be called with null; update the guard in
useDisputeArchiveSnapshot (the block around archiveCid and the fetch) to return
null when archiveCid is either undefined or null (e.g., check archiveCid == null
or use a utility like isNil) before calling getIpfsUrl(archiveCid) and
performing the fetch to avoid invalid IPFS requests.
In `@web/src/hooks/queries/usePopulatedDisputeData.ts`:
- Around line 39-40: The current enablement logic in usePopulatedDisputeData
uses isUndefined which permits null and can cause the DisputeTemplate($id: ID!)
query to run with a null/invalid id; update the condition that references
disputeData.dispute?.arbitrableChainId and disputeData.dispute?.templateId (and
the similar checks around lines 56-57) to ensure the values are non-null and
non-undefined (e.g., check !== undefined && !== null or use a non-nullish check
like value != null or a truthy check for the id) before enabling the query, and
keep the archivedData guard (!isLoadingArchivedData && archivedData?.populated)
as-is but ensure archivedData.populated is explicitly checked for null/undefined
too so the query never receives a null id.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cf37c272-76cd-4cb7-b286-6210269abfaf
📒 Files selected for processing (8)
subgraph/core/src/archive/disputeArchiveStubs.tsweb/src/components/Verdict/FinalDecision.tsxweb/src/hooks/queries/useDisputeArchiveSnapshot.tsweb/src/hooks/queries/useDisputeDetailsQuery.tsweb/src/hooks/queries/useEvidences.tsweb/src/hooks/queries/usePopulatedDisputeData.tsweb/src/hooks/queries/useVotingHistory.tsweb/src/pages/Cases/CaseDetails/MaintenanceButtons/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- subgraph/core/src/archive/disputeArchiveStubs.ts
| const archiveCid = dispute?.archiveCid; | ||
| if (isUndefined(archiveCid)) { | ||
| return null; | ||
| } | ||
|
|
||
| const response = await fetch(getIpfsUrl(archiveCid)); |
There was a problem hiding this comment.
Handle null archiveCid before IPFS fetch.
archiveCid can be null for non-archived disputes. The current isUndefined check misses that and can trigger invalid IPFS fetches (Line 239), causing query errors on normal disputes.
Suggested fix
- const archiveCid = dispute?.archiveCid;
- if (isUndefined(archiveCid)) {
+ const archiveCid = dispute?.archiveCid;
+ if (archiveCid == null || archiveCid === "") {
return 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.
| const archiveCid = dispute?.archiveCid; | |
| if (isUndefined(archiveCid)) { | |
| return null; | |
| } | |
| const response = await fetch(getIpfsUrl(archiveCid)); | |
| const archiveCid = dispute?.archiveCid; | |
| if (archiveCid == null || archiveCid === "") { | |
| return null; | |
| } | |
| const response = await fetch(getIpfsUrl(archiveCid)); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/hooks/queries/useDisputeArchiveSnapshot.ts` around lines 234 - 239,
The code uses isUndefined(archiveCid) which misses null values so
getIpfsUrl(archiveCid) can be called with null; update the guard in
useDisputeArchiveSnapshot (the block around archiveCid and the fetch) to return
null when archiveCid is either undefined or null (e.g., check archiveCid == null
or use a utility like isNil) before calling getIpfsUrl(archiveCid) and
performing the fetch to avoid invalid IPFS requests.
| ((!isUndefined(disputeData.dispute?.arbitrableChainId) && !isUndefined(disputeData.dispute?.templateId)) || | ||
| (!isLoadingArchivedData && !isUndefined(archivedData?.populated))); |
There was a problem hiding this comment.
Tighten enablement checks for nullable template/chain fields.
isUndefined allows null, so the hook can enable and execute DisputeTemplate($id: ID!) with an undefined ID (Line 56). This is a runtime failure path for disputes with nullable metadata.
Suggested fix
- const isEnabled =
+ const hasTemplateInputs =
+ disputeData?.dispute?.arbitrableChainId != null && disputeData?.dispute?.templateId != null;
+
+ const isEnabled =
!isUndefined(disputeID) &&
!isUndefined(disputeData) &&
!isUndefined(disputeData?.dispute) &&
- ((!isUndefined(disputeData.dispute?.arbitrableChainId) && !isUndefined(disputeData.dispute?.templateId)) ||
+ (hasTemplateInputs ||
(!isLoadingArchivedData && !isUndefined(archivedData?.populated)));Also applies to: 56-57
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/src/hooks/queries/usePopulatedDisputeData.ts` around lines 39 - 40, The
current enablement logic in usePopulatedDisputeData uses isUndefined which
permits null and can cause the DisputeTemplate($id: ID!) query to run with a
null/invalid id; update the condition that references
disputeData.dispute?.arbitrableChainId and disputeData.dispute?.templateId (and
the similar checks around lines 56-57) to ensure the values are non-null and
non-undefined (e.g., check !== undefined && !== null or use a non-nullish check
like value != null or a truthy check for the id) before enabling the query, and
keep the archivedData guard (!isLoadingArchivedData && archivedData?.populated)
as-is but ensure archivedData.populated is explicitly checked for null/undefined
too so the query never receives a null id.


Diagram (tentative)
Summary by CodeRabbit
New Features
Documentation
PR-Codex overview
This PR introduces a
DisputeArchivefeature, allowing for the archival of dispute data from Kleros into IPFS, along with updates to related queries and components to support this functionality.Detailed summary
.envand.env.examplefor environment variables.package.jsonto include new dependencies and scripts.GraphQLErrorandGraphQLResponsetypes intypes.ts.schema.graphqlto includeisArchivedandarchiveCid.DisputeArchivecontract with events for archiving disputes.