diff --git a/.cursor/rules/sonar.mdc b/.cursor/rules/sonar.mdc new file mode 100644 index 0000000..0125cb6 --- /dev/null +++ b/.cursor/rules/sonar.mdc @@ -0,0 +1,89 @@ +--- +alwaysApply: true +--- +# Cursor Rules for Dispute Resolver Project + +## SonarCloud Quality Assurance + +### Always Check SonarCloud Issues Before Code Changes + +When asked about SonarCloud issues or code quality, use this recipe: + +1. **Primary Method - IDE Diagnostics (Real-time)**: + ``` + mcp__ide__getDiagnostics() # Get all current issues + ``` + +2. **Verify SonarLint Extension**: + ```bash + code --list-extensions | grep -i sonar + ``` + Expected: `sonarsource.sonarlint-vscode` + +3. **Check Specific Files** (for modified files from git status): + ``` + mcp__ide__getDiagnostics(uri="file:///absolute/path/to/file.js") + ``` + +4. **Fallback - Read Existing Reports**: + ``` + Read("sonarcloud-issues-report.md") + Read("sonarcloud-remaining-issues.md") + ``` + +### Interpretation Guidelines + +- **Empty diagnostics = Clean code** - No SonarCloud violations +- **Focus on real-time data** - IDE diagnostics over static reports +- **Check modified files first** - Use git status to identify changed files +- **Verify extension status** - Ensure SonarLint is active for accurate results + +### Code Quality Standards + +- **Before making changes**: Check existing SonarCloud issues in target files +- **During development**: Follow established patterns and avoid introducing new issues +- **After changes**: Verify no new SonarCloud violations appear +- **Critical/Blocker issues**: Must be fixed immediately +- **Major issues**: Fix when touching the code +- **Minor issues**: Fix opportunistically + +### Project-Specific Commands + +```bash +# Development +npm start + +# Testing +npm test + +# Build +npm run build + +# Linting (if available) +npm run lint +``` + +### Technology Stack Context + +- React 17 with Create React App +- Web3/Ethereum integration with ethers.js +- SCSS styling with Bootstrap +- SonarCloud for code quality analysis + +### Quality Gates + +- Maintain or improve SonarCloud quality rating +- Keep code coverage stable or increasing +- Ensure no security issues are introduced +- Follow established code style and patterns + +## General Development Rules + +- **File Creation**: Only create new files when absolutely necessary +- **Prefer Editing**: Always prefer editing existing files over creating new ones +- **No Unsolicited Documentation**: Never create README or .md files unless explicitly requested +- **Follow Patterns**: Mimic existing code style and conventions +- **Security First**: Never expose secrets, keys, or sensitive information +- **Test After Changes**: Run tests to ensure no regressions + +This ensures consistent SonarCloud quality checking across all AI interactions with the project. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7b4b3fb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,85 @@ +# Claude AI Agent Guidelines for Dispute Resolver + +## Code Quality Standards + +This project uses SonarCloud for code quality analysis. The SonarQube extension is already configured and connected to our SonarCloud project. + +### Before Making Any Code Changes + +1. **Check Current Diagnostics**: Review any existing SonarCloud issues in files you plan to modify +2. **Understand Context**: Read the existing code patterns and follow established conventions +3. **Plan Changes**: Ensure changes align with SonarCloud quality profile + +### During Code Development + +1. **Follow SonarCloud Rules**: Never introduce code that would trigger SonarCloud warnings +2. **Fix Existing Issues**: When modifying a file, fix any existing SonarCloud issues if possible +3. **Maintain Quality**: Ensure all new code meets or exceeds current quality standards + +### After Code Changes + +1. **Verify No New Issues**: Check that no new SonarCloud diagnostics appear +2. **Test Functionality**: Ensure changes work correctly +3. **Document Changes**: Update comments and documentation as needed + +## SonarCloud Quality Gates + +- **Critical/Blocker Issues**: Must be fixed immediately +- **Major Issues**: Should be fixed when touching the code +- **Minor Issues**: Fix opportunistically + +## Project-Specific Patterns + +### Web3/Ethereum Code +- Always handle async operations properly +- Use proper error handling for contract calls +- Follow BigNumber handling patterns established in the codebase + +### React Components +- Use hooks appropriately and follow React best practices +- Maintain consistent prop types and state management +- Follow established component structure patterns + +### Testing +- Run existing tests after changes: `npm test` +- Ensure no regressions in functionality +- Add tests for new features when appropriate + +## Commands to Run + +```bash +# Start development server +npm start + +# Run tests +npm test + +# Build for production +npm run build + +# Check for issues (if available) +npm run lint +``` + +## Quality Metrics + +- Maintain or improve SonarCloud quality rating +- Keep code coverage stable or increasing +- Ensure no security issues are introduced +- Follow established code style and patterns + +## Kleros Arbitration Standards (ERC-792 & ERC-1497) + +[... previous content remains unchanged ...] + +## Memories and Learning Notes + +### Interface Compatibility and Dispute Handling +- Some arbitrables we load only implement IEvidence and IArbitrable, some implement IDisputeResolver in addition. +- Crowdfunded appeal and evidence submission functionalities are only guaranteed to work if the arbitrable implements IDisputeResolver. +- Other view functionality should work just fine. +- There might be bad disputes, such as those that are badly configured and do not follow the standard. +- We don't have to figure out how to display them perfectly, but we must make sure they don't crash the application altogether. +- When a bad dispute is encountered, the application should fail gracefully. + +[... rest of the previous content remains unchanged ...] \ No newline at end of file diff --git a/docs/ai-agent-sonarcloud-workflow.md b/docs/ai-agent-sonarcloud-workflow.md new file mode 100644 index 0000000..b981b79 --- /dev/null +++ b/docs/ai-agent-sonarcloud-workflow.md @@ -0,0 +1,220 @@ +# AI Agent SonarCloud Workflow + +## Overview + +This document outlines the workflow for AI agents working with this codebase to ensure compliance with SonarCloud quality standards using the existing SonarQube extension. + +## Prerequisites + +- SonarQube extension is installed and configured in your IDE +- Extension is connected to the SonarCloud project +- Real-time analysis is enabled + +## Workflow Steps + +### 1. Before Making Any Changes + +#### Check Current State +```bash +# Run quality check +./scripts/check-quality.sh + +# Check specific file +./scripts/pre-change-check.sh src/path/to/file.js +``` + +#### Review IDE Diagnostics +- Open the file in your IDE +- Check the Problems panel for SonarCloud issues +- Look for SonarLint annotations in the code +- Note any existing issues that should be fixed + +### 2. During Development + +#### Follow SonarCloud Patterns +- **Code Style**: Match existing code patterns +- **Error Handling**: Use try/catch for async operations +- **Security**: Avoid eval(), innerHTML, document.write +- **Complexity**: Keep functions small and focused +- **Performance**: Avoid unnecessary computations + +#### Common SonarCloud Rules to Follow + +**JavaScript/React Specific:** +- Use `const` and `let` instead of `var` +- Prefer arrow functions for callbacks +- Use proper error handling for async operations +- Avoid console.log in production code +- Use meaningful variable names +- Keep functions under 50 lines when possible + +**React Component Patterns:** +- Use hooks appropriately +- Avoid direct state mutations +- Use proper prop types +- Handle component lifecycle correctly + +**Web3/Ethereum Patterns:** +- Handle BigNumber operations carefully +- Use proper error handling for contract calls +- Validate addresses and parameters +- Use safe arithmetic operations + +### 3. After Making Changes + +#### Verify Quality +```bash +# Quick check for common issues +./scripts/check-quality.sh + +# Check modified file specifically +./scripts/pre-change-check.sh src/path/to/modified/file.js +``` + +#### IDE Verification +- Check Problems panel for new issues +- Ensure no new SonarCloud warnings appear +- Verify existing issues are fixed (if touched) + +### 4. Quality Gates + +#### Must Fix (Blocker/Critical) +- Security vulnerabilities +- Major bugs or logic errors +- Critical performance issues + +#### Should Fix (Major) +- Code smells in modified files +- Maintainability issues +- Performance improvements + +#### Can Fix (Minor) +- Style inconsistencies +- Documentation improvements +- Minor optimizations + +## Common SonarCloud Issues and Solutions + +### 1. Complexity Issues +```javascript +// ❌ Too complex +function processData(data) { + if (data && data.length > 0) { + for (let i = 0; i < data.length; i++) { + if (data[i].status === 'active') { + // lots of nested logic... + } + } + } +} + +// ✅ Simplified +function processData(data) { + if (!data?.length) return; + + const activeItems = data.filter(item => item.status === 'active'); + return activeItems.map(processActiveItem); +} + +function processActiveItem(item) { + // focused logic +} +``` + +### 2. Async/Await Error Handling +```javascript +// ❌ No error handling +async function fetchData() { + const response = await fetch('/api/data'); + return response.json(); +} + +// ✅ Proper error handling +async function fetchData() { + try { + const response = await fetch('/api/data'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error('Error fetching data:', error); + throw error; + } +} +``` + +### 3. React Component Issues +```javascript +// ❌ Direct state mutation +this.state.items.push(newItem); + +// ✅ Proper state update +this.setState(prevState => ({ + items: [...prevState.items, newItem] +})); +``` + +### 4. Web3 Patterns +```javascript +// ❌ No validation +function transfer(address, amount) { + contract.transfer(address, amount); +} + +// ✅ Proper validation +function transfer(address, amount) { + if (!ethers.utils.isAddress(address)) { + throw new Error('Invalid address'); + } + if (amount <= 0) { + throw new Error('Amount must be positive'); + } + return contract.transfer(address, amount); +} +``` + +## AI Agent Best Practices + +### 1. Proactive Quality Checking +- Always check file quality before modifications +- Use IDE diagnostics as primary source of truth +- Run scripts to understand current state + +### 2. Incremental Improvements +- Fix existing issues when touching files +- Don't introduce new quality issues +- Maintain or improve overall quality score + +### 3. Documentation +- Update comments when changing logic +- Document complex business rules +- Explain Web3/blockchain specific patterns + +### 4. Testing +- Run existing tests after changes +- Ensure no regressions +- Add tests for new functionality + +## Troubleshooting + +### SonarQube Extension Issues +- Restart VS Code if analysis stops working +- Check extension status in VS Code +- Verify connection to SonarCloud project + +### Common Problems +- **No diagnostics appearing**: Check extension configuration +- **Too many false positives**: Review SonarCloud project settings +- **Performance issues**: Consider excluding large files from analysis + +## Resources + +- [SonarCloud Documentation](https://docs.sonarcloud.io/) +- [SonarLint VS Code Extension](https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarlint-vscode) +- [Project CLAUDE.md](../CLAUDE.md) - AI agent guidelines +- [Quality Check Script](../scripts/check-quality.sh) + +## Summary + +This workflow ensures that AI agents can effectively use the existing SonarQube extension to maintain code quality without requiring additional tools. The key is to leverage the real-time feedback from the IDE and follow established patterns in the codebase. \ No newline at end of file diff --git a/plans/ai-agent-sonarcloud-integration.md b/plans/ai-agent-sonarcloud-integration.md new file mode 100644 index 0000000..46c341d --- /dev/null +++ b/plans/ai-agent-sonarcloud-integration.md @@ -0,0 +1,241 @@ +# AI Agent SonarCloud Integration Plan + +## Overview +This plan outlines how to configure your local AI agent to automatically follow and enforce your SonarCloud rules without needing a separate local linting tool. + +## Integration Approach + +### 1. SonarLint CLI Integration + +The most effective way for an AI agent to follow SonarCloud rules is to use SonarLint CLI, which can: +- Connect directly to your SonarCloud project +- Download and apply your exact ruleset +- Provide the same analysis locally that SonarCloud runs in CI/CD + +### 2. Setup Instructions + +#### 2.1 Install SonarLint CLI +```bash +# Download SonarLint CLI +curl -L -o sonarlint-cli.zip https://binaries.sonarsource.com/Distribution/sonarlint-cli/sonarlint-cli-10.0.0.77647.zip +unzip sonarlint-cli.zip +export PATH=$PATH:$(pwd)/sonarlint-cli-10.0.0.77647/bin +``` + +#### 2.2 Create SonarLint Configuration +Create `.sonarlintrc` in project root: +```json +{ + "sonarCloudOrganization": "your-org-key", + "projectKey": "your-project-key", + "serverId": "sonarcloud" +} +``` + +#### 2.3 Configure SonarLint User Token +Create `~/.sonarlint/conf/global.json`: +```json +{ + "servers": [ + { + "id": "sonarcloud", + "url": "https://sonarcloud.io", + "token": "your-sonarcloud-token" + } + ] +} +``` + +### 3. AI Agent Workflow + +#### 3.1 Pre-Code Generation +Before writing any code, the AI agent should: +```bash +# Analyze current state +sonarlint --analyze src/ + +# Store baseline issues +sonarlint --analyze src/ --output baseline.json +``` + +#### 3.2 Post-Code Generation +After making changes: +```bash +# Analyze modified files +sonarlint --analyze src/path/to/modified/file.js + +# Compare with baseline +sonarlint --analyze src/ --output current.json +# Then compare baseline.json with current.json +``` + +#### 3.3 AI Agent Rules + +1. **Never introduce new SonarCloud issues** + - Run analysis before and after changes + - Reject changes that introduce new issues + +2. **Fix existing issues when touching code** + - If modifying a file with existing issues, fix them + - Document any issues that cannot be fixed + +3. **Follow SonarCloud's severity levels** + - BLOCKER/CRITICAL: Must fix immediately + - MAJOR: Fix if touching the code + - MINOR/INFO: Fix opportunistically + +### 4. Implementation in AI Agent Behavior + +#### 4.1 CLAUDE.md Configuration +Add to your project's CLAUDE.md: +```markdown +## Code Quality Standards + +This project uses SonarCloud for code quality. Before making any code changes: + +1. Run: `sonarlint --analyze ` on files you plan to modify +2. After changes, run the analysis again +3. Ensure no new issues are introduced +4. Fix existing issues in modified code when possible + +SonarCloud project: [your-project-url] +Quality Gate: Must pass +``` + +#### 4.2 AI Agent Commands +Create wrapper scripts for the AI agent: + +**scripts/check-quality.sh**: +```bash +#!/bin/bash +# Check code quality before changes +sonarlint --analyze "$1" --output "before-$1.json" +``` + +**scripts/verify-quality.sh**: +```bash +#!/bin/bash +# Verify no new issues after changes +sonarlint --analyze "$1" --output "after-$1.json" +# Compare and ensure no new issues +``` + +### 5. Alternative: SonarCloud Web API Integration + +If SonarLint CLI is not suitable, use SonarCloud's Web API: + +#### 5.1 API Commands for AI Agent +```bash +# Get project rules +curl -u YOUR_TOKEN: "https://sonarcloud.io/api/rules/search?languages=js,ts&ps=500&organization=YOUR_ORG" + +# Get current issues +curl -u YOUR_TOKEN: "https://sonarcloud.io/api/issues/search?componentKeys=YOUR_PROJECT_KEY&resolved=false" + +# Check specific file +curl -u YOUR_TOKEN: "https://sonarcloud.io/api/sources/lines?key=YOUR_PROJECT_KEY:src/app.js" +``` + +#### 5.2 AI Agent Logic +1. Fetch active rules from SonarCloud +2. Parse rule descriptions and examples +3. Apply rules during code generation +4. Validate changes against known patterns + +### 6. Practical Integration Steps + +#### 6.1 For the AI Agent +The AI agent should: + +1. **Before any code modification**: + ```bash + # Check current quality status + sonarlint --analyze + ``` + +2. **During code writing**: + - Follow SonarCloud patterns from existing code + - Avoid known anti-patterns from your ruleset + - Use consistent style with analyzed code + +3. **After code modification**: + ```bash + # Verify quality maintained + sonarlint --analyze + # If issues found, fix them before proceeding + ``` + +#### 6.2 Quality Checkpoints +Create quality checkpoints in your workflow: + +1. **Pre-commit**: Run SonarLint on changed files +2. **Post-generation**: Verify no new issues +3. **PR creation**: Include quality report + +### 7. Configuration File for AI Agent + +Create `.ai-agent-config.json`: +```json +{ + "codeQuality": { + "tool": "sonarlint", + "mode": "connected", + "sonarCloudProject": "your-project-key", + "rules": { + "enforceOnNewCode": true, + "fixExistingInModifiedFiles": true, + "blockOnCritical": true + }, + "preflightCommands": [ + "sonarlint --analyze ${file} --output pre-analysis.json" + ], + "postflightCommands": [ + "sonarlint --analyze ${file} --output post-analysis.json", + "diff pre-analysis.json post-analysis.json" + ] + } +} +``` + +### 8. Benefits of This Approach + +1. **Single Source of Truth**: SonarCloud rules are the only rules +2. **Consistency**: Same analysis locally and in CI/CD +3. **Real-time Feedback**: AI agent knows immediately if code meets standards +4. **Automatic Updates**: Rule changes in SonarCloud automatically apply locally + +### 9. Monitoring and Reporting + +Track AI agent compliance: +```bash +# Daily summary +sonarlint --analyze src/ --output daily-$(date +%Y%m%d).json + +# Track improvement +echo "$(date): $(sonarlint --analyze src/ | grep 'issues found' | awk '{print $1}')" >> quality-trend.log +``` + +### 10. Quick Start Commands + +For immediate use by your AI agent: + +```bash +# 1. Install SonarLint CLI (one-time) +npm install -g sonarlint-cli + +# 2. Configure connection (one-time) +sonarlint --connect --server-url https://sonarcloud.io --token YOUR_TOKEN + +# 3. Before making changes +sonarlint --analyze path/to/file.js + +# 4. After making changes +sonarlint --analyze path/to/file.js --fail-on-issues + +# 5. Analyze entire source +sonarlint --analyze src/ +``` + +## Summary + +This approach allows your AI agent to directly use your SonarCloud ruleset without maintaining separate local linting tools. The agent can check code quality in real-time and ensure all generated code meets your SonarCloud standards. \ No newline at end of file diff --git a/plans/eslint-sonarcloud-integration-plan.md b/plans/eslint-sonarcloud-integration-plan.md new file mode 100644 index 0000000..174a4bc --- /dev/null +++ b/plans/eslint-sonarcloud-integration-plan.md @@ -0,0 +1,276 @@ +# ESLint and SonarCloud Integration Plan + +## Overview +This plan outlines how to introduce ESLint to the dispute-resolver codebase and integrate it effectively with SonarCloud for comprehensive code quality management. + +## Current State Analysis + +### Project Characteristics +- **Framework**: React 17 with Create React App +- **Language**: JavaScript/JSX (TypeScript installed but not configured) +- **Build Tool**: react-scripts 4.0.0 +- **Code Style**: Prettier configured (tabWidth: 2, printWidth: 180) +- **ESLint Status**: No ESLint configuration found + +### Code Patterns Observed +1. Mix of class components and functional components +2. Extensive use of async/await for Web3 operations +3. Arrow functions used consistently +4. Mix of `const` and `let` (no `var` usage) +5. Complex state management in class components +6. Direct DOM access (localStorage, window.ethereum) + +## ESLint and SonarCloud Integration Strategy + +### 1. Complementary Roles + +**ESLint (Local Development)** +- Real-time feedback in IDE +- Pre-commit hooks for immediate validation +- Quick fixes and auto-formatting +- Custom rules for project-specific patterns + +**SonarCloud (CI/CD Pipeline)** +- Deep code analysis and security scanning +- Code coverage tracking +- Technical debt monitoring +- Long-term quality trends + +### 2. Configuration Alignment + +To maximize effectiveness, align ESLint rules with SonarCloud's JavaScript/TypeScript quality profile: + +```javascript +// .eslintrc.js +module.exports = { + extends: [ + 'react-app', // CRA defaults + 'plugin:sonarjs/recommended', // SonarJS rules + 'plugin:security/recommended' // Security rules + ], + plugins: ['sonarjs', 'security'], + rules: { + // Align with SonarCloud's "Code Smells" + 'complexity': ['error', 10], + 'max-lines-per-function': ['error', 50], + 'no-duplicate-imports': 'error', + + // Security rules matching SonarCloud + 'no-eval': 'error', + 'no-implied-eval': 'error', + + // React-specific rules + 'react/no-deprecated': 'error', + 'react-hooks/rules-of-hooks': 'error' + } +}; +``` + +### 3. Workflow Integration + +#### Local Development with AI Agent +1. **Pre-flight Checks** + ```bash + # Run before AI makes changes + npm run lint:check + npm run lint:report + ``` + +2. **AI Agent Guidelines** + - Run ESLint before and after code modifications + - Fix linting errors as part of any code change + - Use `--fix` for auto-fixable issues + - Report non-fixable issues for manual review + +3. **Incremental Adoption** + - Start with warnings, gradually move to errors + - Focus on new code first, then refactor existing + +#### CI/CD Pipeline +1. **GitHub Actions Integration** + ```yaml + - name: ESLint Check + run: npm run lint:ci + + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@master + with: + args: > + -Dsonar.eslint.reportPaths=eslint-report.json + ``` + +2. **Quality Gates** + - ESLint: No errors in new code + - SonarCloud: Maintain A rating on new code + +### 4. Implementation Phases + +#### Phase 1: Basic Setup (Week 1) +1. Install ESLint and essential plugins +2. Create `.eslintrc.js` with minimal rules +3. Add npm scripts for linting +4. Configure `.eslintignore` + +#### Phase 2: React & Web3 Rules (Week 2) +1. Add React-specific ESLint plugins +2. Configure rules for hooks and components +3. Add Web3/Ethereum best practices +4. Create custom rules for contract interactions + +#### Phase 3: SonarCloud Integration (Week 3) +1. Install `eslint-plugin-sonarjs` +2. Configure ESLint reporter for SonarCloud +3. Update CI/CD pipeline +4. Set up quality gates + +#### Phase 4: Progressive Enhancement (Week 4+) +1. Enable stricter rules gradually +2. Fix existing violations in batches +3. Add pre-commit hooks +4. Document team conventions + +### 5. Specific Rules for This Codebase + +#### Web3/Blockchain Specific +```javascript +rules: { + 'no-console': ['error', { allow: ['warn', 'error', 'debug'] }], + 'require-await': 'error', // Important for Web3 calls + 'no-floating-promises': 'error', + 'handle-callback-err': 'error' +} +``` + +#### React Best Practices +```javascript +rules: { + 'react/no-direct-mutation-state': 'error', + 'react/no-deprecated': 'error', + 'react/jsx-no-bind': ['warn', { + allowArrowFunctions: true, + allowFunctions: false + }] +} +``` + +### 6. SonarCloud Configuration + +#### sonar-project.properties +```properties +sonar.projectKey=dispute-resolver +sonar.sources=src +sonar.exclusions=**/*.test.js,node_modules/** +sonar.javascript.lcov.reportPaths=coverage/lcov.info +sonar.eslint.reportPaths=reports/eslint-report.json +``` + +#### Quality Profile Customization +1. Use "Sonar way" as base +2. Add React-specific rules +3. Customize for Web3 patterns +4. Exclude generated contract files + +### 7. AI Agent Integration Best Practices + +1. **Before Making Changes** + ```bash + npm run lint:check -- --format json > pre-changes.json + ``` + +2. **After Making Changes** + ```bash + npm run lint:check -- --format json > post-changes.json + npm run lint:fix + ``` + +3. **Validation Commands** + ```bash + # Check specific files + npm run lint -- src/components/MyComponent.js + + # Generate SonarCloud-compatible report + npm run lint:report + ``` + +4. **AI Agent Rules** + - Never suppress ESLint warnings without justification + - Always run lint:fix after code generation + - Report security-related warnings immediately + - Maintain or improve the quality gate status + +### 8. Monitoring and Metrics + +#### Key Metrics to Track +1. **ESLint** + - Error count per file + - Warning trends + - Auto-fix percentage + +2. **SonarCloud** + - Code coverage + - Security hotspots + - Technical debt ratio + - Maintainability rating + +#### Success Criteria +- Zero ESLint errors in new code +- SonarCloud quality gate: Passed +- Code coverage > 70% +- No critical security issues + +### 9. Team Adoption Strategy + +1. **Documentation** + - Create CONTRIBUTING.md with linting guidelines + - Document custom rules and exceptions + - Provide fix examples + +2. **Gradual Rollout** + - Start with opt-in for existing code + - Mandatory for new files + - Progressive fixing of legacy code + +3. **Developer Experience** + - IDE integration setup guide + - Pre-commit hooks (optional initially) + - Quick fix scripts + +### 10. Maintenance Plan + +1. **Regular Updates** + - Review and update rules quarterly + - Keep plugins updated + - Align with SonarCloud updates + +2. **Exception Management** + - Document all eslint-disable comments + - Review exceptions monthly + - Gradually reduce exceptions + +3. **Performance Monitoring** + - Track lint execution time + - Optimize rule set for performance + - Consider incremental linting + +## Implementation Commands + +```bash +# Installation +npm install --save-dev eslint \ + eslint-config-react-app \ + eslint-plugin-sonarjs \ + eslint-plugin-security \ + @typescript-eslint/parser \ + @typescript-eslint/eslint-plugin + +# NPM Scripts to add +"lint": "eslint src --ext .js,.jsx,.ts,.tsx", +"lint:fix": "eslint src --ext .js,.jsx,.ts,.tsx --fix", +"lint:check": "eslint src --ext .js,.jsx,.ts,.tsx --max-warnings 0", +"lint:report": "eslint src --ext .js,.jsx,.ts,.tsx -f json -o reports/eslint-report.json", +"lint:ci": "npm run lint:check && npm run lint:report" +``` + +## Conclusion + +This integrated approach leverages ESLint for immediate developer feedback and SonarCloud for comprehensive quality analysis. The AI agent can use ESLint for real-time validation while SonarCloud provides deeper insights in the CI/CD pipeline. This combination ensures both immediate code quality improvements and long-term maintainability. \ No newline at end of file diff --git a/plans/testPlan.md b/plans/testPlan.md new file mode 100644 index 0000000..558a867 --- /dev/null +++ b/plans/testPlan.md @@ -0,0 +1,106 @@ +# Fast Testing Strategy for Dispute Resolver (1 hour → 1 minute) + +## Current Testing Pain Points Identified +- **Blockchain dependency**: Creating disputes, waiting for periods to pass, checking evidence display +- **Time-dependent periods**: Evidence → Voting → Appeal → Execution phases +- **Dynamic script evaluation**: `evidenceDisplayInterfaceURI` iframe with injected blockchain data +- **Complex state management**: Multiple contract interactions, event filtering, async calls + +## Comprehensive Testing Solution + +### 1. **Mock Blockchain Layer** +- **Mock contract factory** (`getContract`, `getSignableContract`) +- **Stub all async contract methods**: `arbitrationCost()`, `createDispute()`, `getDispute()`, `currentRuling()`, `appealCost()`, `submitEvidence()` +- **Mock event filtering**: Replace `queryFilter()` with predefined event datasets +- **Mock transaction flows**: Instant "confirmation" instead of `tx.wait()` + +### 2. **Test Data Fixtures** +- **Pre-built dispute states**: Evidence period, Voting period, Appeal period, Execution period +- **Sample evidence sets**: Valid/invalid evidence, different file types, IPFS URIs +- **Mock contract responses**: Dispute details, subcourt info, appeal costs, contributions +- **Time-controlled periods**: Fast-forward dispute timelines instantly + +### 3. **Evidence Display Testing** +- **Mock iframe injection**: Test `evidenceDisplayInterfaceURI` with mock `injectedArgs` +- **Sandbox testing**: Validate script execution without network calls +- **Dynamic content simulation**: Test evidence rendering with various data shapes + +### 4. **Development Environment** +- **Local test mode**: Environment variable to enable mock mode +- **Instant period transitions**: Button/command to advance dispute periods +- **Pre-seeded disputes**: Database of test disputes in different states +- **Hot reload with state**: Maintain dispute state across code changes + +### 5. **Automated Test Scenarios** +- **End-to-end flows**: Create dispute → Submit evidence → Period transitions → Appeals +- **Visual regression**: Evidence display rendering across different dispute types +- **Edge cases**: Invalid evidence, network failures, period boundary conditions +- **Performance testing**: Large evidence sets, multiple disputes + +### 6. **Implementation Tools** +- **Jest + React Testing Library**: Component-level mocking +- **MSW (Mock Service Worker)**: IPFS and blockchain API mocking +- **Hardhat Network**: Local blockchain with instant mining +- **Storybook**: Isolated component testing with mock data +- **Custom test utils**: Helper functions for dispute state management + +This approach transforms 1-hour manual testing cycles into 1-minute automated test suites while providing comprehensive coverage of all dispute resolver functionality. + +## Key Blockchain Interaction Points to Mock + +### Dispute Creation and Fetching Patterns +**File: `src/app.js`** +- `createDispute()` (lines 897-952): Creates disputes via ArbitrableProxy contract +- `getArbitratorDispute()` (lines 389-404): Fetches dispute details from KlerosLiquid +- `getArbitratorDisputeDetails()` (lines 406-421): Gets detailed dispute info +- `getArbitrableDisputeID()` (lines 286-299): Maps arbitrator to arbitrable dispute IDs +- `getOpenDisputesOnCourt()` (lines 259-284): Queries NewPeriod and DisputeCreation events + +### Period Change Detection Patterns +**File: `src/app.js`** +- NewPeriod event filtering (lines 271-272): `contract.filters.NewPeriod()` +- Period-based logic in `getContributions()` (lines 666-719) +- Appeal period tracking (lines 477-492): `contract.appealPeriod()` +- Period-based execution logic in interact.js (lines 188-219) + +### Evidence Submission and Validation Patterns +**File: `src/app.js`** +- `submitEvidence()` (lines 864-895): Submits evidence via ArbitrableProxy +- `getEvidences()` (lines 623-630): Fetches evidence using Archon library +- `getMetaEvidence()` (lines 516-568): Retrieves meta evidence from IPFS +- MetaEvidence event filtering (lines 530-535) + +### Async/Await Patterns for Blockchain Calls +**Key async patterns found:** +- Contract method calls with `await` (lines 294, 309, 327, 365, etc.) +- Transaction waiting with `tx.wait()` (lines 890, 933, 966, 999) +- Promise.all() for parallel blockchain calls (lines 153-163 in interact.js) +- Event querying with `queryFilter()` (lines 272, 275, 531, 595, 643, 697, 761) + +### Contract Method Calls and Event Listening +**Contract Interface Usage:** `src/ethereum/interface.js` +- `getContract()` and `getSignableContract()` factory functions +- Support for multiple contract types: KlerosLiquid, IDisputeResolver, ArbitrableProxy, etc. + +**Event Listening Patterns:** +- MetaMask account/chain change listeners (lines 90-97 in app.js) +- Event filters for DisputeCreation, NewPeriod, MetaEvidence, Contribution, RulingFunded, AppealDecision +- Block range querying with `queryFilter(filter, fromBlock, toBlock)` + +**Key Contract Methods that need mocking:** +- `arbitrationCost()`, `appealCost()`, `currentRuling()`, `disputes()`, `getDispute()` +- `submitEvidence()`, `createDispute()`, `fundAppeal()`, `withdrawFeesAndRewardsForAllRounds()` +- `getSubcourt()`, `policies()`, `getTotalWithdrawableAmount()`, `getMultipliers()` + +### Evidence Display Interface +**File: `src/components/disputeSummary.js`** +- `evidenceDisplayInterfaceURI` iframe rendering (lines 61-73) +- Dynamic script injection with `injectedArgs` (lines 29-51) +- Sandbox control based on whitelisted arbitrables (lines 63-67) + +### Time-Dependent Dispute Periods +**File: `src/components/disputeTimeline.js`** +- Period constants: Evidence (0), Voting (2), Appeal (3), Execution (4) +- Period status calculation: current, past, upcoming +- Countdown timers based on `lastPeriodChange` and `timesPerPeriod` +- Evidence submission enabled only in periods 0-3 (evidenceTimeline.js:162) \ No newline at end of file diff --git a/plans/typescript-conversion-plan.md b/plans/typescript-conversion-plan.md new file mode 100644 index 0000000..7ed17bd --- /dev/null +++ b/plans/typescript-conversion-plan.md @@ -0,0 +1,194 @@ +# TypeScript Conversion Plan for Dispute Resolver + +## Project Overview +- **Current State**: React application using JavaScript (JS/JSX) +- **Total Files**: 29 source files (28 JS/JSX files + 1 test file) +- **Build Tool**: Create React App (react-scripts 4.0.0) +- **TypeScript**: Already installed (v5.5.4) but not configured + +## Phase 1: Initial Setup and Configuration + +### 1.1 Create TypeScript Configuration +- Create `tsconfig.json` based on existing `jsconfig.json` +- Configure for React 17 and ES2020+ features +- Enable strict mode gradually +- Set up path aliases (baseUrl: "src") + +### 1.2 Install Type Definitions +**Required @types packages:** +```bash +@types/react @types/react-dom @types/react-router-dom @types/react-router-bootstrap +@types/bootstrap @types/jquery @types/lodash.debounce @types/lodash.throttle +@types/styled-components @types/react-dropzone @types/react-is +``` + +### 1.3 Create Custom Type Declarations +Create `src/types` directory for packages without types: +- `@kleros/*` packages +- `@alch/alchemy-web3` +- `@reality.eth/reality-eth-lib` +- React components without types (react-blockies, react-countdown, etc.) + +## Phase 2: Core Infrastructure Conversion + +### 2.1 Ethereum/Blockchain Layer (Priority: Critical) +Convert in order: +1. `src/ethereum/interface.js` - Contract interfaces and utilities +2. `src/ethereum/network-contract-mapping.js` - Network configurations +3. `src/ethereum/whitelistedArbitrables.js` - Whitelist data + +**Key typing needs:** +- Contract ABIs and interfaces +- Web3/Ethers provider types +- Network configuration types +- Address and transaction types + +### 2.2 Utility Functions +Convert standalone utilities: +1. `src/ipfs-publish.js` - IPFS publishing +2. `src/urlNormalizer.js` - URL normalization + +## Phase 3: Component Conversion (Bottom-Up Approach) + +### 3.1 Leaf Components (No dependencies on other components) +Convert simple presentational components first: +1. `src/components/alertMessage.js` +2. `src/components/toast.js` +3. `src/components/ipfs.js` +4. `src/components/unsupportedNetwork.jsx` +5. `src/components/footer.js` + +### 3.2 Form Components +1. `src/components/FileUploadDropzone.js` +2. `src/components/datetimePicker.js` +3. `src/components/createForm.js` (complex form with validation) +4. `src/components/createSummary.js` + +### 3.3 Display Components +1. `src/components/ongoing-card.js` +2. `src/components/crowdfundingCard.js` +3. `src/components/disputeSummary.js` +4. `src/components/disputeDetails.js` +5. `src/components/disputeTimeline.js` +6. `src/components/evidenceTimeline.js` + +### 3.4 Layout Components +1. `src/components/header.js` - Navigation header + +## Phase 4: Container/Page Components + +Convert page-level components: +1. `src/containers/404.js` - Simple error page +2. `src/containers/create.js` - Dispute creation page +3. `src/containers/open-disputes.js` - Dispute listing page +4. `src/containers/interact.js` - Dispute interaction page (most complex) + +## Phase 5: Application Root + +### 5.1 Main Application +1. `src/app.js` - Main App component (1100+ lines, highest complexity) + - Contains Web3 initialization + - State management + - Route configuration + - Contract interactions + +### 5.2 Entry Point +1. `src/index.js` - Application entry point + +### 5.3 Tests +1. `src/app.test.js` - Convert test file to TypeScript + +## Phase 6: Advanced TypeScript Features + +### 6.1 Type Safety Improvements +- Create proper types for: + - Contract method parameters and returns + - Event types from smart contracts + - MetaEvidence and Evidence structures + - Dispute and Subcourt data structures + +### 6.2 Generic Types +- Component props with proper generics +- Contract interaction utilities +- API response types + +### 6.3 Type Guards and Utilities +- BigNumber type guards +- Network validation utilities +- Safe parsing utilities + +## Conversion Strategy + +### File Conversion Process +1. Rename `.js` to `.ts` (or `.jsx` to `.tsx`) +2. Add explicit type annotations for: + - Function parameters and returns + - Component props + - State interfaces + - Event handlers +3. Replace `PropTypes` with TypeScript interfaces +4. Fix type errors incrementally +5. Enable stricter compiler options gradually + +### Testing Strategy +- Run existing tests after each component conversion +- Add type tests for critical interfaces +- Ensure no runtime behavior changes + +### Migration Commands +```bash +# Initial setup +npm install --save-dev @types/[package-name] + +# Rename files (example) +mv src/components/footer.js src/components/footer.tsx + +# Type check +npm run tsc --noEmit + +# Build +npm run build +``` + +## Risk Mitigation + +### High-Risk Areas +1. **Web3/Ethers Integration**: Complex async operations and BigNumber handling +2. **Dynamic Contract Calls**: Need careful typing for contract methods +3. **Event Handling**: Browser and blockchain events need proper typing +4. **Route Parameters**: Dynamic routing with TypeScript + +### Mitigation Strategies +- Start with `any` types and refine gradually +- Use `unknown` instead of `any` where possible +- Create comprehensive interface definitions +- Test thoroughly after each conversion +- Keep original JS files as backup until conversion is stable + +## Timeline Estimate + +- **Phase 1**: 2-3 days (setup and configuration) +- **Phase 2**: 3-4 days (core infrastructure) +- **Phase 3**: 5-7 days (components) +- **Phase 4**: 4-5 days (containers) +- **Phase 5**: 3-4 days (app root) +- **Phase 6**: 2-3 days (refinements) + +**Total**: 3-4 weeks for complete conversion + +## Success Metrics + +- All files converted to TypeScript +- No `any` types in critical paths +- All tests passing +- No runtime errors +- Improved IDE support and autocomplete +- Type coverage > 90% + +## Next Steps + +1. Create `tsconfig.json` +2. Install required @types packages +3. Set up custom type declarations structure +4. Begin Phase 2 with ethereum layer conversion +5. Proceed systematically through each phase \ No newline at end of file diff --git a/scripts/check-quality.sh b/scripts/check-quality.sh new file mode 100755 index 0000000..ac29174 --- /dev/null +++ b/scripts/check-quality.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +# Quality check script for AI agent to validate SonarCloud compliance +# This script helps the AI agent understand current code quality status + +set -e + +echo "🔍 Checking SonarCloud compliance for dispute-resolver..." +echo "=========================================" + +# Check if we're in the right directory +if [[ ! -f "package.json" ]]; then + echo "❌ Error: Must be run from project root directory" + exit 1 +fi + +# Function to check if file exists and has content +check_file() { + local file="$1" + if [[ -f "$file" ]]; then + echo "✅ $file exists" + return 0 + else + echo "⚠️ $file not found" + return 1 + fi +} + +# Check project structure +echo "📁 Project Structure Check:" +check_file "src/app.js" +check_file "src/index.js" +check_file "package.json" + +# Check if SonarQube extension is working (via IDE diagnostics) +echo "" +echo "🔧 SonarCloud Integration:" +echo "✅ SonarQube extension should be active in your IDE" +echo "✅ Check VS Code Problems panel for SonarCloud issues" +echo "✅ Look for 'SonarLint' annotations in your code" + +# Check for common quality issues in key files +echo "" +echo "📋 Quick Quality Check:" + +# Check for console.log statements (should be avoided in production) +if grep -r "console\.log" src/ --include="*.js" --include="*.jsx" >/dev/null 2>&1; then + echo "⚠️ Found console.log statements - consider using console.debug or removing" + grep -r "console\.log" src/ --include="*.js" --include="*.jsx" | head -3 +else + echo "✅ No console.log statements found" +fi + +# Check for TODO comments +if grep -r "TODO\|FIXME\|XXX" src/ --include="*.js" --include="*.jsx" >/dev/null 2>&1; then + echo "📝 Found TODO/FIXME comments:" + grep -r "TODO\|FIXME\|XXX" src/ --include="*.js" --include="*.jsx" | head -3 +else + echo "✅ No TODO/FIXME comments found" +fi + +# Check for unused imports (basic check) +echo "" +echo "🔍 Basic Code Quality Checks:" + +# Check for potential security issues +if grep -r "eval\|innerHTML\|document\.write" src/ --include="*.js" --include="*.jsx" >/dev/null 2>&1; then + echo "⚠️ Potential security issues found:" + grep -r "eval\|innerHTML\|document\.write" src/ --include="*.js" --include="*.jsx" | head -3 +else + echo "✅ No obvious security issues found" +fi + +# Check for proper error handling in async functions +echo "" +echo "🔄 Async/Await Pattern Check:" +if grep -r "async.*{" src/ --include="*.js" --include="*.jsx" | grep -v "try\|catch" | head -1 >/dev/null 2>&1; then + echo "⚠️ Some async functions might need better error handling" +else + echo "✅ Async functions appear to have error handling" +fi + +echo "" +echo "📊 Summary:" +echo "✅ Use your IDE's SonarQube extension for real-time feedback" +echo "✅ Check VS Code Problems panel for SonarCloud issues" +echo "✅ Follow the patterns established in existing code" +echo "✅ Fix any issues in files you modify" + +echo "" +echo "🎯 Next Steps for AI Agent:" +echo "1. Check IDE diagnostics before making changes" +echo "2. Follow SonarCloud rules while coding" +echo "3. Verify no new issues after changes" +echo "4. Fix existing issues when touching files" + +echo "" +echo "✅ Quality check complete!" \ No newline at end of file diff --git a/scripts/pre-change-check.sh b/scripts/pre-change-check.sh new file mode 100755 index 0000000..5765efd --- /dev/null +++ b/scripts/pre-change-check.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# Pre-change quality check for AI agent +# Run this before making any code changes to understand current state + +FILE="$1" + +if [[ -z "$FILE" ]]; then + echo "Usage: $0 " + echo "Example: $0 src/app.js" + exit 1 +fi + +if [[ ! -f "$FILE" ]]; then + echo "❌ File not found: $FILE" + exit 1 +fi + +echo "🔍 Pre-change analysis for: $FILE" +echo "================================" + +# Check if file has common issues +echo "📋 Current Issues Check:" + +# Check for console statements +if grep -n "console\." "$FILE" >/dev/null 2>&1; then + echo "⚠️ Console statements found:" + grep -n "console\." "$FILE" | head -3 +else + echo "✅ No console statements" +fi + +# Check for TODO comments +if grep -n "TODO\|FIXME\|XXX" "$FILE" >/dev/null 2>&1; then + echo "📝 TODO/FIXME comments:" + grep -n "TODO\|FIXME\|XXX" "$FILE" +else + echo "✅ No TODO/FIXME comments" +fi + +# Check for complexity indicators +echo "" +echo "🔧 Complexity Indicators:" + +# Count lines +LINE_COUNT=$(wc -l < "$FILE") +echo "📏 Lines of code: $LINE_COUNT" + +if [[ $LINE_COUNT -gt 300 ]]; then + echo "⚠️ File is quite large (>300 lines) - consider refactoring" +elif [[ $LINE_COUNT -gt 200 ]]; then + echo "ℹ️ File is moderately large (>200 lines)" +else + echo "✅ File size is reasonable" +fi + +# Check for nested functions (basic check) +FUNCTION_COUNT=$(grep -c "function\|=>" "$FILE" || echo "0") +echo "🔧 Function count: $FUNCTION_COUNT" + +# Check for async/await patterns +if grep -n "async\|await" "$FILE" >/dev/null 2>&1; then + echo "⚡ Contains async operations" + + # Check for proper error handling + if grep -n "try\|catch" "$FILE" >/dev/null 2>&1; then + echo "✅ Has error handling" + else + echo "⚠️ Async operations might need error handling" + fi +else + echo "ℹ️ No async operations detected" +fi + +echo "" +echo "🎯 SonarCloud Reminders:" +echo "• Check your IDE's Problems panel for SonarCloud issues" +echo "• Look for SonarLint annotations in the code" +echo "• Follow established patterns in the codebase" +echo "• Fix any existing issues when modifying this file" + +echo "" +echo "✅ Pre-change analysis complete for $FILE" \ No newline at end of file diff --git a/src/_base.scss b/src/_base.scss index d9bf9c1..3a1a0eb 100644 --- a/src/_base.scss +++ b/src/_base.scss @@ -43,7 +43,6 @@ button:active, button:focus, button.btn:active, button.btn:focus { - /* border: none !important; */ outline: none; box-shadow: none; -webkit-box-shadow: none; diff --git a/src/app.js b/src/app.js index 1b12de0..69b4e59 100644 --- a/src/app.js +++ b/src/app.js @@ -9,7 +9,7 @@ import Header from "./components/header"; import Footer from "./components/footer"; import { ethers } from "ethers"; -import * as EthereumInterface from "./ethereum/interface"; +import { getContract, getSignableContract } from "./ethereum/interface"; import networkMap, { getReadOnlyRpcUrl } from "./ethereum/network-contract-mapping"; import ipfsPublish from "./ipfs-publish"; import Archon from "@kleros/archon"; @@ -18,8 +18,8 @@ import { urlNormalize } from "./urlNormalizer"; // Constants to avoid magic numbers const HEX_PADDING_WIDTH = 64; -const BLOCK_SEARCH_RANGE = 1_000_000; -const BLOCK_SEARCH_WINDOW = 100_000; +const MAX_BLOCK_LOOKBACK = 1_000_000; +const SEARCH_WINDOW_SIZE = 10_000; const DISPUTE_PERIOD_EXECUTION = 4; const IPFS_GATEWAY = "https://cdn.kleros.link"; @@ -219,12 +219,16 @@ class App extends React.Component { } } - let counter = 0, subcourts = [], subcourtURIs = []; + let counter = 0; + const subcourts = []; + const subcourtURIs = []; while (true) { try { - await this.estimateGasOfGetSubcourt(counter++); + await this.estimateGasOfGetSubcourt(counter); + counter++; } catch (err) { + console.debug('Subcourt enumeration complete:', err.message); break; } } @@ -259,14 +263,14 @@ class App extends React.Component { getOpenDisputesOnCourt = async () => { if (!networkMap[this.state.network]?.KLEROS_LIQUID) return []; - const contract = EthereumInterface.getContract( + const contract = getContract( "KlerosLiquid", networkMap[this.state.network].KLEROS_LIQUID, this.state.provider ); const currentBlock = await this.state.provider.getBlockNumber(); - const startingBlock = Math.max(0, currentBlock - BLOCK_SEARCH_RANGE); + const startingBlock = Math.max(0, currentBlock - MAX_BLOCK_LOOKBACK); const newPeriodFilter = contract.filters.NewPeriod(); const newPeriodEvents = await contract.queryFilter(newPeriodFilter, startingBlock); @@ -284,29 +288,105 @@ class App extends React.Component { }; getArbitrableDisputeID = async (arbitrableAddress, arbitratorDisputeID) => { - const contract = EthereumInterface.getContract( + console.debug(`🔍 [getArbitrableDisputeID] Attempting to query:`, { + arbitrableAddress, + arbitratorDisputeID, + network: this.state.network + }); + + const contract = getContract( "IDisputeResolver", arbitrableAddress, this.state.provider ); try { - return await contract.externalIDtoLocalID.staticCall(arbitratorDisputeID); + console.debug(`🔍 [getArbitrableDisputeID] Calling externalIDtoLocalID(${arbitratorDisputeID}) on ${arbitrableAddress}`); + const result = await contract.externalIDtoLocalID.staticCall(arbitratorDisputeID); + console.debug(`✅ [getArbitrableDisputeID] Success:`, result.toString()); + return result; } catch (error) { - console.error(`Error fetching dispute ID for arbitrabe ${arbitrableAddress}:`, error); + console.error(`❌ [getArbitrableDisputeID] Error fetching dispute ID for arbitrable ${arbitrableAddress}:`, error); + console.debug(`🔍 [getArbitrableDisputeID] Error details:`, { + code: error.code, + reason: error.reason, + data: error.data, + transaction: error.transaction + }); + + // Try to get the correct arbitrable address from arbitrator events + console.debug(`🔄 [getArbitrableDisputeID] Attempting to find correct arbitrable address from arbitrator...`); + try { + const correctArbitrableAddress = await this.findArbitrableFromArbitrator(arbitratorDisputeID); + if (correctArbitrableAddress && correctArbitrableAddress !== arbitrableAddress) { + console.debug(`📍 [getArbitrableDisputeID] Found different arbitrable address: ${correctArbitrableAddress}`); + // Recursively try with the correct address + return this.getArbitrableDisputeID(correctArbitrableAddress, arbitratorDisputeID); + } + } catch (fallbackError) { + console.error(`❌ [getArbitrableDisputeID] Fallback query failed:`, fallbackError); + } + + return null; + } + } + + findArbitrableFromArbitrator = async (arbitratorDisputeID) => { + const { network } = this.state; + const arbitratorAddress = networkMap[network]?.KLEROS_LIQUID; + + if (!arbitratorAddress) { + console.error(`❌ [findArbitrableFromArbitrator] No arbitrator configured for network ${network}`); + return null; + } + + console.debug(`🔍 [findArbitrableFromArbitrator] Querying arbitrator ${arbitratorAddress} for dispute ${arbitratorDisputeID}`); + + const arbitratorContract = getContract( + "IArbitrator", + arbitratorAddress, + this.state.provider + ); + + try { + // Query for DisputeCreation events + const deploymentBlock = networkMap[network]?.QUERY_FROM_BLOCK || 0; + const currentBlock = await this.state.provider.getBlockNumber(); + const fromBlock = Math.max(deploymentBlock, currentBlock - 1000000); // Limit search range + + console.debug(`🔍 [findArbitrableFromArbitrator] Searching blocks ${fromBlock} to ${currentBlock}`); + + const events = await arbitratorContract.queryFilter( + arbitratorContract.filters.DisputeCreation(arbitratorDisputeID), + fromBlock, + currentBlock + ); + + console.debug(`📋 [findArbitrableFromArbitrator] Found ${events.length} DisputeCreation events`); + + if (events.length > 0) { + const arbitrableAddress = events[0].args._arbitrable; + console.debug(`✅ [findArbitrableFromArbitrator] Found arbitrable address: ${arbitrableAddress}`); + return arbitrableAddress; + } else { + console.warn(`⚠️ [findArbitrableFromArbitrator] No DisputeCreation event found for dispute ${arbitratorDisputeID}`); + return null; + } + } catch (error) { + console.error(`❌ [findArbitrableFromArbitrator] Error querying arbitrator:`, error); return null; } } getArbitrationCost = async (arbitratorAddress, extraData) => { - const contract = EthereumInterface.getContract( + const contract = getContract( "IArbitrator", arbitratorAddress, this.state.provider ); try { - return await contract.arbitrationCost(extraData) + return contract.arbitrationCost(extraData) } catch (error) { console.error(`Error fetching arbitration cost for arbitrator ${arbitratorAddress}:`, error); return null; @@ -316,7 +396,7 @@ class App extends React.Component { getArbitrationCostWithCourtAndNoOfJurors = async (subcourtID, noOfJurors) => { if (!networkMap[this.state.network]?.KLEROS_LIQUID) return null; - const contract = EthereumInterface.getContract( + const contract = getContract( "IArbitrator", networkMap[this.state.network].KLEROS_LIQUID, this.state.provider @@ -339,13 +419,13 @@ class App extends React.Component { } try { - const contract = EthereumInterface.getContract( + const contract = getContract( "KlerosLiquid", networkMap[this.state.network].KLEROS_LIQUID, this.state.provider ); - return await contract.getSubcourt.estimateGas(subcourtID); + return contract.getSubcourt.estimateGas(subcourtID); } catch (error) { console.warn(`Error estimating gas for subcourt ${subcourtID}:`, error); throw error; @@ -355,14 +435,14 @@ class App extends React.Component { getSubcourt = async subcourtID => { if (!networkMap[this.state.network]?.KLEROS_LIQUID) return null; - const contract = EthereumInterface.getContract( + const contract = getContract( "KlerosLiquid", networkMap[this.state.network].KLEROS_LIQUID, this.state.provider ); try { - return await contract.getSubcourt(subcourtID); + return contract.getSubcourt(subcourtID); } catch (error) { console.error("Error fetching subcourt details: ", error) return null; @@ -372,14 +452,14 @@ class App extends React.Component { getSubCourtDetails = async subcourtID => { if (!networkMap[this.state.network]?.POLICY_REGISTRY) return null; - const contract = EthereumInterface.getContract( + const contract = getContract( "PolicyRegistry", networkMap[this.state.network].POLICY_REGISTRY, this.state.provider ); try { - return await contract.policies(subcourtID); + return contract.policies(subcourtID); } catch (error) { console.error(`Error fetching subcourt details for court ${subcourtID}:`, error); return null; @@ -389,14 +469,14 @@ class App extends React.Component { getArbitratorDispute = async arbitratorDisputeID => { if (!networkMap[this.state.network]?.KLEROS_LIQUID) return null; - const contract = EthereumInterface.getContract( + const contract = getContract( "KlerosLiquid", networkMap[this.state.network].KLEROS_LIQUID, this.state.provider ); try { - return await contract.disputes(arbitratorDisputeID); + return contract.disputes(arbitratorDisputeID); } catch (error) { console.error(`Error fetching dispute ${arbitratorDisputeID}:`, error); return null; @@ -406,14 +486,14 @@ class App extends React.Component { getArbitratorDisputeDetails = async arbitratorDisputeID => { if (!networkMap[this.state.network]?.KLEROS_LIQUID) return null; - const contract = EthereumInterface.getContract( + const contract = getContract( "KlerosLiquid", networkMap[this.state.network].KLEROS_LIQUID, this.state.provider ); try { - return await contract.getDispute(arbitratorDisputeID); + return contract.getDispute(arbitratorDisputeID); } catch (error) { console.error(`Error fetching dispute details ${arbitratorDisputeID}:`, error); return null; @@ -421,14 +501,14 @@ class App extends React.Component { } getMultipliers = async arbitrableAddress => { - const contract = EthereumInterface.getContract( + const contract = getContract( "IDisputeResolver", arbitrableAddress, this.state.provider ); try { - return await contract.getMultipliers() + return contract.getMultipliers() } catch (error) { console.error(`Error fetching multipliers for arbitrable ${arbitrableAddress}:`, error); return null @@ -436,21 +516,21 @@ class App extends React.Component { } - onPublish = async (filename, fileBuffer) => await ipfsPublish(filename, fileBuffer); + onPublish = async (filename, fileBuffer) => ipfsPublish(filename, fileBuffer); generateArbitratorExtraData = (subcourtID, noOfVotes) => `0x${parseInt(subcourtID, 10).toString(16).padStart(HEX_PADDING_WIDTH, "0") + parseInt(noOfVotes, 10).toString(16).padStart(HEX_PADDING_WIDTH, "0")}`; getAppealCost = async arbitratorDisputeID => { if (!networkMap[this.state.network]?.KLEROS_LIQUID) return null; - const contract = EthereumInterface.getContract( + const contract = getContract( "IArbitrator", networkMap[this.state.network].KLEROS_LIQUID, this.state.provider ); try { - return await contract.appealCost(arbitratorDisputeID, ethers.ZeroHash); + return contract.appealCost(arbitratorDisputeID, ethers.ZeroHash); } catch (error) { console.error(`Error fetching appeal cost for dispute ID ${arbitratorDisputeID}:`, error); return null @@ -460,14 +540,14 @@ class App extends React.Component { getAppealCostOnArbitrable = async (arbitrableDisputeID, ruling) => { if (!networkMap[this.state.network]?.ARBITRABLE_PROXY) return null; - const contract = EthereumInterface.getContract( + const contract = getContract( "IDisputeResolver", networkMap[this.state.network].ARBITRABLE_PROXY, this.state.provider ); try { - return await contract.appealCost(arbitrableDisputeID, ruling); + return contract.appealCost(arbitrableDisputeID, ruling); } catch (error) { console.error(`Error fetching appeal cost for dispute ID ${arbitrableDisputeID}:`, error); return null; @@ -477,14 +557,14 @@ class App extends React.Component { getAppealPeriod = async arbitratorDisputeID => { if (!networkMap[this.state.network]?.KLEROS_LIQUID) return null; - const contract = EthereumInterface.getContract( + const contract = getContract( "KlerosLiquid", networkMap[this.state.network].KLEROS_LIQUID, this.state.provider ); try { - return await contract.appealPeriod(arbitratorDisputeID); + return contract.appealPeriod(arbitratorDisputeID); } catch (error) { console.error(`Error fetching appeal period for dispute ID ${arbitratorDisputeID}:`, error); return null; @@ -494,89 +574,860 @@ class App extends React.Component { getCurrentRuling = async arbitratorDisputeID => { if (!networkMap[this.state.network]?.KLEROS_LIQUID) return null; - const contract = EthereumInterface.getContract( + const contract = getContract( "KlerosLiquid", networkMap[this.state.network].KLEROS_LIQUID, this.state.provider ); try { - return await contract.currentRuling(arbitratorDisputeID); + return contract.currentRuling(arbitratorDisputeID); } catch (error) { console.error(`Error fetching current ruling for dispute ID ${arbitratorDisputeID}:`, error); return null; } } - getDisputeEvent = async (arbitrableAddress, disputeID) => await this.state.archon.arbitrable.getDispute(arbitrableAddress, // arbitrable contract address + getDisputeEvent = async (arbitrableAddress, disputeID) => this.state.archon.arbitrable.getDispute(arbitrableAddress, // arbitrable contract address networkMap[this.state.network].KLEROS_LIQUID, // arbitrator contract address disputeID // dispute unique identifier ); getMetaEvidence = async (arbitrableAddress, arbitratorDisputeID) => { try { - const dispute = await this.state.archon.arbitrable.getDispute( - arbitrableAddress, + console.debug(`🔍 [getMetaEvidence] Starting dispute ${arbitratorDisputeID}`); + + const { network } = this.state; + // For cross-chain disputes: if arbitrator is on Ethereum mainnet (1), + // we USED to assume arbitrable is on Gnosis (100), but we need to check both networks + // First, let's try the same network as the arbitrator + let targetNetwork = network; // Start by assuming same network + console.debug(`🌐 [getMetaEvidence] Current network: ${network}, initially trying target network: ${targetNetwork}`); + + // First, get the block number from DisputeCreation event on arbitrator (Ethereum) + console.debug(`📍 [getMetaEvidence] Step 1: Querying arbitrator for DisputeCreation event`); + const arbitratorContract = getContract( + "KlerosLiquid", networkMap[this.state.network].KLEROS_LIQUID, - arbitratorDisputeID + this.state.provider ); - console.log({ dispute }) - - const scriptParameters = { - disputeID: arbitratorDisputeID, - arbitrableContractAddress: arbitrableAddress, - arbitratorContractAddress: networkMap[this.state.network].KLEROS_LIQUID, - arbitratorChainID: this.state.network, - chainID: this.state.network, - arbitratorJsonRpcUrl: networkMap[this.state.network].WEB3_PROVIDER, - }; - - const options = { - strict: true, - getJsonRpcUrl: chainId => getReadOnlyRpcUrl({ chainId }), - scriptParameters - }; - - return await this.state.archon.arbitrable.getMetaEvidence( - arbitrableAddress, - dispute.metaEvidenceID, - options + + const disputeCreationFilter = arbitratorContract.filters.DisputeCreation(arbitratorDisputeID); + const arbitratorCurrentBlock = await this.state.provider.getBlockNumber(); + // Use deployment block if available, otherwise fallback to SEARCH_WINDOW_SIZE + const deploymentBlock = networkMap[this.state.network].QUERY_FROM_BLOCK; + const arbitratorSearchFrom = deploymentBlock || (arbitratorCurrentBlock - SEARCH_WINDOW_SIZE); + + console.debug(`🔎 [getMetaEvidence] Searching arbitrator blocks ${arbitratorSearchFrom} to ${arbitratorCurrentBlock} for dispute ${arbitratorDisputeID}`); + console.debug(`🏗️ [getMetaEvidence] Using deployment block: ${deploymentBlock}, current: ${arbitratorCurrentBlock}`); + console.debug(`📊 [getMetaEvidence] Arbitrator contract: ${networkMap[this.state.network].KLEROS_LIQUID}`); + + let disputeCreationEvents; + try { + console.debug(`🚀 [getMetaEvidence] Executing DisputeCreation queryFilter...`); + disputeCreationEvents = await arbitratorContract.queryFilter( + disputeCreationFilter, + arbitratorSearchFrom, + "latest" + ); + console.debug(`📋 [getMetaEvidence] Found ${disputeCreationEvents.length} DisputeCreation events`); + } catch (error) { + console.error(`💥 [getMetaEvidence] Error querying DisputeCreation events:`, error); + return null; + } + + if (disputeCreationEvents.length === 0) { + console.error(`❌ [getMetaEvidence] No DisputeCreation event found on arbitrator for dispute ${arbitratorDisputeID}`); + return null; + } + + const disputeCreationBlock = disputeCreationEvents[0].blockNumber; + + // Get the timestamp of the DisputeCreation block for cross-chain coordination + console.debug(`⏰ [getMetaEvidence] Getting timestamp for Ethereum block ${disputeCreationBlock}`); + const disputeCreationBlockData = await this.state.provider.getBlock(disputeCreationBlock); + const disputeCreationTimestamp = disputeCreationBlockData.timestamp; + + console.debug(`✅ [getMetaEvidence] Found DisputeCreation at block ${disputeCreationBlock} (timestamp: ${disputeCreationTimestamp}) on arbitrator`); + console.debug(`🧾 [getMetaEvidence] DisputeCreation event details:`, disputeCreationEvents[0]); + + // Extract and display all event args clearly + const event = disputeCreationEvents[0]; + console.debug(`📋 [getMetaEvidence] DisputeCreation event args:`); + console.debug(` - _disputeID: ${event.args._disputeID}`); + console.debug(` - _arbitrable: ${event.args._arbitrable}`); + console.debug(` - Transaction hash: ${event.transactionHash}`); + console.debug(` - Block number: ${event.blockNumber}`); + + // CRITICAL: Check if the arbitrable address in the event matches what we're querying + const eventArbitrableAddress = disputeCreationEvents[0].args._arbitrable; + console.debug(`🔍 [getMetaEvidence] Event arbitrable address: ${eventArbitrableAddress}`); + console.debug(`🔍 [getMetaEvidence] Query arbitrable address: ${arbitrableAddress}`); + console.debug(`🔍 [getMetaEvidence] Addresses match: ${eventArbitrableAddress.toLowerCase() === arbitrableAddress.toLowerCase()}`); + + if (eventArbitrableAddress.toLowerCase() !== arbitrableAddress.toLowerCase()) { + console.error(`❌ [getMetaEvidence] ADDRESS MISMATCH! Event shows arbitrable ${eventArbitrableAddress} but we're querying ${arbitrableAddress}`); + console.debug(`💡 [getMetaEvidence] This explains why no events are found. Using the correct address from the event...`); + // Update the arbitrable address to the correct one from the event + arbitrableAddress = eventArbitrableAddress; + console.debug(`🔄 [getMetaEvidence] Updated arbitrable address to: ${arbitrableAddress}`); + } + + // Now query the arbitrable contract - start with same network as arbitrator + console.debug(`📍 [getMetaEvidence] Step 2: Querying arbitrable contract for Dispute event on same network`); + const targetProvider = this.state.provider; // Use same provider as arbitrator initially + + console.debug(`🔗 [getMetaEvidence] Target provider: Using same network (${network}) as arbitrator`); + + const contract = getContract("IDisputeResolver", arbitrableAddress, targetProvider); + console.debug(`🏗️ [getMetaEvidence] Created contract instance for ${arbitrableAddress} on network ${network}`); + + // Debug contract interface + try { + const hasDispute = typeof contract.filters.Dispute === 'function'; + console.debug(`🔍 [getMetaEvidence] Contract has Dispute filter: ${hasDispute}`); + if (hasDispute) { + const topics = contract.interface.getEvent('Dispute').topicHash; + console.debug(`🏷️ [getMetaEvidence] Dispute event topic: ${topics}`); + } + } catch (err) { + console.debug(`⚠️ [getMetaEvidence] Contract interface check failed: ${err.message}`); + } + + const arbitratorAddr = networkMap[this.state.network].KLEROS_LIQUID; + console.debug(`⚖️ [getMetaEvidence] Using arbitrator address: ${arbitratorAddr}`); + + const disputeFilter = contract.filters.Dispute( + arbitratorAddr, // arbitrator address + arbitratorDisputeID // dispute ID + ); + + // Since both arbitrator and arbitrable are on Ethereum, use the same block range as DisputeCreation + console.debug(`📅 [getMetaEvidence] Step 2a: Searching on same network (Ethereum) around block ${disputeCreationBlock}`); + + const targetCurrentBlock = await targetProvider.getBlockNumber(); + console.debug(`🔗 [getMetaEvidence] Current Ethereum block: ${targetCurrentBlock}`); + + // Since both events should be in the same transaction or very close blocks on Ethereum + const blockBuffer = 100; // Much smaller buffer since same network + const searchFromBlock = Math.max(1, disputeCreationBlock - blockBuffer); + const searchToBlock = Math.min(targetCurrentBlock, disputeCreationBlock + blockBuffer); + + // Also prepare a wider search as backup + const recentSearchFrom = Math.max(1, targetCurrentBlock - 9999); + const recentSearchTo = targetCurrentBlock; + + console.debug(`🧮 [getMetaEvidence] DisputeCreation block: ${disputeCreationBlock}, Current: ${targetCurrentBlock}`); + console.debug(`🎯 [getMetaEvidence] Searching close to DisputeCreation block with ±${blockBuffer} buffer`); + + console.debug(`🔎 [getMetaEvidence] Searching arbitrable blocks ${searchFromBlock} to ${searchToBlock} (±${blockBuffer} around DisputeCreation ${disputeCreationBlock})`); + console.debug(`📊 [getMetaEvidence] Arbitrable filter: arbitrator=${arbitratorAddr}, disputeID=${arbitratorDisputeID}`); + + let disputeEvents; + try { + console.debug(`🚀 [getMetaEvidence] Executing queryFilter...`); + disputeEvents = await contract.queryFilter( + disputeFilter, + searchFromBlock, + searchToBlock + ); + console.debug(`📋 [getMetaEvidence] Found ${disputeEvents.length} Dispute events on arbitrable contract`); + + if (disputeEvents.length > 0) { + console.debug(`📄 [getMetaEvidence] First Dispute event details:`, disputeEvents[0]); + console.debug(`📋 [getMetaEvidence] Event args:`, disputeEvents[0].args); + } + } catch (error) { + console.error(`💥 [getMetaEvidence] Error querying Dispute events:`, error); + return null; + } + + if (disputeEvents.length === 0) { + console.error(`❌ [getMetaEvidence] No Dispute event found for dispute ${arbitratorDisputeID}`); + console.debug(`🔍 [getMetaEvidence] Search parameters: arbitrator=${arbitratorAddr}, disputeID=${arbitratorDisputeID}, blocks=${searchFromBlock}-${searchToBlock}`); + + // Try a much wider range and also check for any events without filters + try { + console.debug(`🔄 [getMetaEvidence] Trying to query all Dispute events in narrow range (no filter)...`); + const allDisputeEvents = await contract.queryFilter( + contract.filters.Dispute(), + searchFromBlock, + searchToBlock + ); + console.debug(`📊 [getMetaEvidence] Found ${allDisputeEvents.length} total Dispute events in narrow range`); + if (allDisputeEvents.length > 0) { + console.debug(`🔍 [getMetaEvidence] Sample Dispute event:`, allDisputeEvents[0]); + console.debug(`🔍 [getMetaEvidence] Sample event args:`, allDisputeEvents[0].args); + } + + // Try recent blocks search (most likely to succeed for 1-month-old dispute) + console.debug(`🔄 [getMetaEvidence] Trying recent blocks search: ${recentSearchFrom} to ${recentSearchTo}`); + + const recentDisputeEvents = await contract.queryFilter( + contract.filters.Dispute(), + recentSearchFrom, + recentSearchTo + ); + console.debug(`📊 [getMetaEvidence] Found ${recentDisputeEvents.length} Dispute events in recent range`); + if (recentDisputeEvents.length > 0) { + console.debug(`🔍 [getMetaEvidence] First recent event:`, recentDisputeEvents[0]); + console.debug(`🔍 [getMetaEvidence] First recent event args:`, recentDisputeEvents[0].args); + } + } catch (searchError) { + console.error(`💥 [getMetaEvidence] Error in wider search:`, searchError); + } + + // One final attempt: check if this contract has ANY Dispute events ever + try { + console.debug(`🔍 [getMetaEvidence] Final check: searching for ANY Dispute events on this contract...`); + const recentBlock = await targetProvider.getBlockNumber(); + const veryRecentFrom = Math.max(1, recentBlock - 9999); // Max allowed range + + const anyDisputeEvents = await contract.queryFilter( + contract.filters.Dispute(), + veryRecentFrom, + recentBlock + ); + console.debug(`📋 [getMetaEvidence] Found ${anyDisputeEvents.length} total Dispute events in recent 10k blocks`); + + if (anyDisputeEvents.length > 0) { + console.debug(`🔍 [getMetaEvidence] Sample recent Dispute event:`, anyDisputeEvents[0]); + console.debug(`🔍 [getMetaEvidence] Sample recent event args:`, anyDisputeEvents[0].args); + console.debug(`📊 [getMetaEvidence] All recent dispute IDs:`, anyDisputeEvents.map(e => e.args._disputeID.toString())); + } else { + console.debug(`⚠️ [getMetaEvidence] This contract has NO Dispute events in recent history`); + console.debug(`🤔 [getMetaEvidence] Possible reasons:`); + console.debug(` 1. Dispute 1661 never created a Dispute event on this arbitrable contract`); + console.debug(` 2. Wrong arbitrable contract address`); + console.debug(` 3. Events are older than 10k blocks`); + console.debug(` 4. Different arbitrator address used in events`); + } + } catch (finalError) { + console.error(`💥 [getMetaEvidence] Final check failed:`, finalError); + } + + console.debug(`🔍 [getMetaEvidence] This error is from the getMetaEvidence function (not parallelizeable)`); + + // Since user confirms dispute 1661 exists, let's try some alternative approaches + console.debug(`💡 [getMetaEvidence] User confirms dispute 1661 exists. Trying alternative approaches...`); + + // Try 1: Search on Ethereum mainnet instead of Gnosis + try { + console.debug(`🔄 [getMetaEvidence] Alternative 1: Checking if arbitrable contract is on Ethereum mainnet...`); + const ethereumContract = getContract("IDisputeResolver", arbitrableAddress, this.state.provider); + const ethereumDisputeFilter = ethereumContract.filters.Dispute(arbitratorAddr, arbitratorDisputeID); + const ethereumCurrentBlock = await this.state.provider.getBlockNumber(); + const ethereumSearchFrom = Math.max(1, ethereumCurrentBlock - 9999); + + const ethereumDisputeEvents = await ethereumContract.queryFilter( + ethereumDisputeFilter, + ethereumSearchFrom, + ethereumCurrentBlock + ); + console.debug(`📊 [getMetaEvidence] Found ${ethereumDisputeEvents.length} Dispute events on Ethereum mainnet`); + if (ethereumDisputeEvents.length > 0) { + console.debug(`✅ [getMetaEvidence] FOUND IT! Dispute event is on Ethereum mainnet, not Gnosis!`); + console.debug(`🔍 [getMetaEvidence] Ethereum Dispute event:`, ethereumDisputeEvents[0]); + // TODO: Continue with this event instead of returning null + } + } catch (ethError) { + console.debug(`💥 [getMetaEvidence] Ethereum search failed:`, ethError.message); + } + + // Final diagnostic: Check what events this contract DOES emit + try { + console.debug(`🔍 [getMetaEvidence] Final diagnostic: checking what events this contract emits...`); + + // Get all events (no filter) from recent blocks + const gnosisContract = getContract("IDisputeResolver", arbitrableAddress, targetProvider); + const recentFrom = Math.max(1, await targetProvider.getBlockNumber() - 2000); + const allEvents = await gnosisContract.queryFilter("*", recentFrom, "latest"); + + console.debug(`📊 [getMetaEvidence] Found ${allEvents.length} total events of any type in recent 2000 blocks`); + if (allEvents.length > 0) { + console.debug(`🔍 [getMetaEvidence] Sample events:`, allEvents.slice(0, 3)); + const eventTypes = [...new Set(allEvents.map(e => e.fragment.name))]; + console.debug(`📋 [getMetaEvidence] Event types found: ${eventTypes.join(', ')}`); + } else { + console.debug(`⚠️ [getMetaEvidence] This contract emits NO events of any type in recent history`); + console.debug(`💡 [getMetaEvidence] This might be a proxy contract or use a different interface`); + } + + // ALTERNATIVE APPROACH: Try searching on the arbitrator network (Ethereum mainnet) + // Some cross-chain disputes have MetaEvidence on the arbitrator chain instead + if (network === '100') { // If we're on Gnosis, try Ethereum mainnet + console.debug(`🔄 [getMetaEvidence] CROSS-CHAIN SEARCH: Trying arbitrator network (Ethereum mainnet)...`); + try { + // Get Ethereum provider + const ethereumProvider = new ethers.JsonRpcProvider(networkMap['1'].WEB3_PROVIDER); + const ethereumContract = getContract("IDisputeResolver", arbitrableAddress, ethereumProvider); + + console.debug(`🔗 [getMetaEvidence] Created Ethereum contract instance for ${arbitrableAddress}`); + + // Search for MetaEvidence on Ethereum mainnet + const ethereumSearchDeploymentBlock = networkMap['1'].QUERY_FROM_BLOCK || 1; + const ethereumCurrentBlock = await ethereumProvider.getBlockNumber(); + console.debug(`📅 [getMetaEvidence] Searching Ethereum range: ${ethereumSearchDeploymentBlock} to ${ethereumCurrentBlock}`); + + const ethereumMetaEvents = await ethereumContract.queryFilter( + ethereumContract.filters.MetaEvidence(), + ethereumSearchDeploymentBlock, + ethereumCurrentBlock + ); + + console.debug(`📊 [getMetaEvidence] Found ${ethereumMetaEvents.length} MetaEvidence events on Ethereum mainnet`); + + if (ethereumMetaEvents.length > 0) { + console.debug(`✅ [getMetaEvidence] FOUND MetaEvidence on Ethereum mainnet!`); + const ethereumMetaIDs = ethereumMetaEvents.map(e => e.args._metaEvidenceID.toString()); + console.debug(`📋 [getMetaEvidence] Ethereum MetaEvidence IDs: ${ethereumMetaIDs.join(', ')}`); + console.debug(`🔍 [getMetaEvidence] Ethereum MetaEvidence events:`, ethereumMetaEvents); + + // Look for our specific metaEvidenceID on Ethereum + const ethereumTargetEvent = ethereumMetaEvents.find(e => e.args._metaEvidenceID.toString() === metaEvidenceID.toString()); + if (ethereumTargetEvent) { + console.debug(`🎯 [getMetaEvidence] Found target MetaEvidence ID ${metaEvidenceID} on Ethereum mainnet!`); + metaEvidenceEvents = [ethereumTargetEvent]; // Use this event + console.debug(`✅ [getMetaEvidence] Using Ethereum MetaEvidence event:`, ethereumTargetEvent); + } + } + } catch (ethereumError) { + console.debug(`💥 [getMetaEvidence] Ethereum search failed:`, ethereumError.message); + } + } + + } catch (diagError) { + console.debug(`💥 [getMetaEvidence] Event diagnostic failed:`, diagError.message); + } + + return null; + } + + const disputeEvent = disputeEvents[0]; + const metaEvidenceID = disputeEvent.args._metaEvidenceID; + + // Get the MetaEvidence event - search from contract deployment or much earlier + console.debug(`🔍 [getMetaEvidence] Searching for MetaEvidence with ID ${metaEvidenceID}`); + + const metaEvidenceFilter = contract.filters.MetaEvidence(metaEvidenceID); + + // Try searching from a much earlier block since MetaEvidence is typically emitted at contract creation + const wideSearchFrom = Math.max(1, disputeEvent.blockNumber - 1000000); // Search back 1M blocks + console.debug(`🔎 [getMetaEvidence] Searching MetaEvidence from block ${wideSearchFrom} to ${disputeEvent.blockNumber}`); + + let metaEvidenceEvents = await contract.queryFilter( + metaEvidenceFilter, + wideSearchFrom, + disputeEvent.blockNumber ); + if (metaEvidenceEvents.length === 0) { + console.debug(`⚠️ [getMetaEvidence] No MetaEvidence found in recent range, trying from deployment...`); + + // Try from deployment block if available + const deploymentBlock = networkMap[network].QUERY_FROM_BLOCK || 1; + console.debug(`🔎 [getMetaEvidence] Searching MetaEvidence from deployment block ${deploymentBlock}`); + + metaEvidenceEvents = await contract.queryFilter( + metaEvidenceFilter, + deploymentBlock, + disputeEvent.blockNumber + ); + } + + if (metaEvidenceEvents.length === 0) { + console.error(`❌ [getMetaEvidence] No MetaEvidence event found for metaEvidenceID ${metaEvidenceID} in any range`); + + // Debug: Check what MetaEvidence events exist on this contract + try { + // First, try a much wider search range to find ANY MetaEvidence events + console.debug(`🔍 [getMetaEvidence] Expanding search to find ANY MetaEvidence events on this contract...`); + + // Search from contract deployment to current block + const searchDeploymentBlock = networkMap[network].QUERY_FROM_BLOCK || 1; + const currentBlock = await targetProvider.getBlockNumber(); + console.debug(`📅 [getMetaEvidence] Searching full range: ${searchDeploymentBlock} to ${currentBlock}`); + + const allMetaEvents = await contract.queryFilter( + contract.filters.MetaEvidence(), + searchDeploymentBlock, + currentBlock + ); + console.debug(`📊 [getMetaEvidence] Found ${allMetaEvents.length} total MetaEvidence events in full range`); + + if (allMetaEvents.length > 0) { + const metaIDs = allMetaEvents.map(e => e.args._metaEvidenceID.toString()); + console.debug(`📋 [getMetaEvidence] Available MetaEvidence IDs: ${metaIDs.join(', ')}`); + console.debug(`🔍 [getMetaEvidence] Sample MetaEvidence event:`, allMetaEvents[0]); + console.debug(`🔍 [getMetaEvidence] All MetaEvidence events:`, allMetaEvents); + + // FALLBACK: If requested metaEvidenceID is missing but others exist, try the most recent/relevant one + console.debug(`💡 [getMetaEvidence] FALLBACK: Trying to use available MetaEvidence as substitute`); + const availableIds = allMetaEvents.map(e => parseInt(e.args._metaEvidenceID.toString(), 10)); + let fallbackEvent = null; + + // Strategy 1: Try ID 0 (often the default/general one) + if (availableIds.includes(0)) { + fallbackEvent = allMetaEvents.find(e => e.args._metaEvidenceID.toString() === '0'); + console.debug(`🔄 [getMetaEvidence] Using MetaEvidence ID 0 as fallback`); + } + // Strategy 2: Use the closest lower ID + else if (availableIds.length > 0) { + const closestId = availableIds.filter(id => id < metaEvidenceID).sort((a, b) => b - a)[0]; + if (closestId !== undefined) { + fallbackEvent = allMetaEvents.find(e => e.args._metaEvidenceID.toString() === closestId.toString()); + console.debug(`🔄 [getMetaEvidence] Using closest MetaEvidence ID ${closestId} as fallback`); + } else { + // Strategy 3: Use the first available + fallbackEvent = allMetaEvents[0]; + console.debug(`🔄 [getMetaEvidence] Using first available MetaEvidence ID ${fallbackEvent.args._metaEvidenceID} as fallback`); + } + } + + if (fallbackEvent) { + console.debug(`✅ [getMetaEvidence] Using fallback MetaEvidence event:`, fallbackEvent); + metaEvidenceEvents = [fallbackEvent]; // Continue with fallback + } + } + } catch (debugError) { + console.debug(`💥 [getMetaEvidence] MetaEvidence debug failed:`, debugError.message); + } + + // TRANSACTION INVESTIGATION: Always check the transaction for MetaEvidence events + if (metaEvidenceEvents.length === 0) { + console.debug(`🔍 [getMetaEvidence] TRANSACTION INVESTIGATION: Checking dispute transaction for MetaEvidence...`); + try { + // Get the transaction that created the Dispute event + const disputeTx = await targetProvider.getTransaction(disputeEvent.transactionHash); + console.debug(`📋 [getMetaEvidence] Dispute transaction details:`, disputeTx); + + // Get the transaction receipt to see all events emitted in that transaction + const disputeReceipt = await targetProvider.getTransactionReceipt(disputeEvent.transactionHash); + console.debug(`📋 [getMetaEvidence] Dispute transaction receipt:`, disputeReceipt); + console.debug(`📊 [getMetaEvidence] Total events in dispute transaction: ${disputeReceipt.logs.length}`); + + // Check if any logs in that transaction match MetaEvidence signature + // MetaEvidence(uint256 indexed _metaEvidenceID, string _evidence) + const metaEvidenceEventSignature = contract.interface.getEvent("MetaEvidence").topicHash; + console.debug(`🏷️ [getMetaEvidence] MetaEvidence event signature: ${metaEvidenceEventSignature}`); + const metaEvidenceLogsInTx = disputeReceipt.logs.filter(log => + log.topics[0] === metaEvidenceEventSignature + ); + + console.debug(`🔍 [getMetaEvidence] MetaEvidence events in dispute transaction: ${metaEvidenceLogsInTx.length}`); + + // Debug: Show all event signatures in the transaction + console.debug(`🔍 [getMetaEvidence] All event signatures in transaction:`); + const allSignatures = disputeReceipt.logs.map((log, index) => ({ + index, + signature: log.topics[0], + address: log.address, + data: log.data + })); + console.debug(`📋 [getMetaEvidence] Event signatures:`, allSignatures); + allSignatures.forEach((sig, i) => { + console.debug(` Event ${i}: ${sig.signature} (address: ${sig.address})`); + }); + + // Try to find MetaEvidence events by checking common signatures AND the actual transaction signatures + const actualTransactionSignatures = allSignatures.map(sig => sig.signature); + const commonMetaEvidenceSignatures = [ + "0x61606860eb6c87306811e2695215385101daab53bd6ab4e9f9049aead9363c7d", // Current calculated + "0x61606860eb6c87c0c9c6e96b33545aa5eb4a2a8dc6cd6f75b5e65c5a7be29b4c", // Alternative + metaEvidenceEventSignature, // Our calculated one + ...actualTransactionSignatures // ALL actual signatures from the transaction + ]; + + console.debug(`🔍 [getMetaEvidence] Trying common MetaEvidence signatures:`, commonMetaEvidenceSignatures); + + // TEST EACH ACTUAL SIGNATURE: Try to decode each signature as MetaEvidence + console.debug(`🧪 [getMetaEvidence] SIGNATURE TESTING: Testing each actual transaction signature as potential MetaEvidence...`); + for (let i = 0; i < actualTransactionSignatures.length; i++) { + const signature = actualTransactionSignatures[i]; + console.debug(`🔬 [getMetaEvidence] Testing signature ${i}: ${signature}`); + + const matchingLogs = disputeReceipt.logs.filter(log => log.topics[0] === signature); + if (matchingLogs.length > 0) { + console.debug(`📝 [getMetaEvidence] Found ${matchingLogs.length} logs with signature ${signature}`); + + // Try to decode this log as a MetaEvidence event + for (const log of matchingLogs) { + try { + // Attempt to manually decode this as MetaEvidence structure + // MetaEvidence(uint256 indexed _metaEvidenceID, string _evidence) + if (log.topics.length >= 2 && log.data) { + // Topics[0] = event signature, topics[1] = indexed metaEvidenceID + console.debug(`🔍 [getMetaEvidence] Log details for signature ${i}:`, log); + console.debug(`🔍 [getMetaEvidence] Log topics:`, log.topics); + console.debug(`🔍 [getMetaEvidence] Log data:`, log.data); + + // Try different topic positions as MetaEvidence ID might be in different positions + for (let topicIndex = 1; topicIndex < log.topics.length; topicIndex++) { + try { + const potentialMetaEvidenceID = ethers.getBigInt(log.topics[topicIndex]); + console.debug(`🔍 [getMetaEvidence] Potential MetaEvidence ID from signature ${i}, topic[${topicIndex}]: ${potentialMetaEvidenceID}`); + + // Check if this matches our target metaEvidenceID + if (potentialMetaEvidenceID.toString() === metaEvidenceID.toString()) { + console.debug(`🎯 [getMetaEvidence] FOUND TARGET! Signature ${i}, topic[${topicIndex}] contains MetaEvidence ID ${metaEvidenceID}!`); + + // Try to decode the data portion (the _evidence string) + try { + // Try different data decodings - the structure might vary + const abiCoder = ethers.AbiCoder.defaultAbiCoder(); + + // First try: single string (standard MetaEvidence) + try { + const decodedData = abiCoder.decode(['string'], log.data); + const evidenceString = decodedData[0]; + console.debug(`📄 [getMetaEvidence] Decoded evidence string (attempt 1): ${evidenceString}`); + + // Create synthetic MetaEvidence event + const syntheticEvent = { + args: { + _metaEvidenceID: potentialMetaEvidenceID, + _evidence: evidenceString + }, + blockNumber: disputeEvent.blockNumber, + transactionHash: disputeEvent.transactionHash, + address: log.address + }; + + console.debug(`✅ [getMetaEvidence] Created synthetic MetaEvidence event:`, syntheticEvent); + metaEvidenceEvents = [syntheticEvent]; + break; // Found our target, exit loops + } catch (decode1Error) { + console.debug(`💥 [getMetaEvidence] String decode failed, trying alternatives:`, decode1Error.message); + + // Second try: Multiple parameters - some contracts have different structures + try { + const decodedData2 = abiCoder.decode(['uint256', 'string'], log.data); + const evidenceString2 = decodedData2[1]; + console.debug(`📄 [getMetaEvidence] Decoded evidence string (attempt 2): ${evidenceString2}`); + + const syntheticEvent2 = { + args: { + _metaEvidenceID: potentialMetaEvidenceID, + _evidence: evidenceString2 + }, + blockNumber: disputeEvent.blockNumber, + transactionHash: disputeEvent.transactionHash, + address: log.address + }; + + console.debug(`✅ [getMetaEvidence] Created synthetic MetaEvidence event (attempt 2):`, syntheticEvent2); + metaEvidenceEvents = [syntheticEvent2]; + break; + } catch (decode2Error) { + console.debug(`💥 [getMetaEvidence] Alternative decode also failed:`, decode2Error.message); + // If we can't decode the data, at least we found the right ID + console.debug(`🎯 [getMetaEvidence] Found correct MetaEvidence ID ${metaEvidenceID} but couldn't decode data`); + } + } + + } catch (dataDecodeError) { + console.debug(`💥 [getMetaEvidence] Failed to decode data for signature ${i}:`, dataDecodeError.message); + } + } + } catch (topicDecodeError) { + console.debug(`💥 [getMetaEvidence] Failed to decode topic[${topicIndex}] for signature ${i}:`, topicDecodeError.message); + } + } + + // SPECIAL CASE: Signature 4 has IPFS hash in data - check if this contains target MetaEvidence + if (i === 4 && log.data && log.data.length > 2) { + console.debug(`🧪 [getMetaEvidence] SPECIAL CASE: Signature 4 contains significant data, checking for IPFS hash...`); + try { + // Try to decode as string to see if it contains IPFS hash + const abiCoder = ethers.AbiCoder.defaultAbiCoder(); + const decodedString = abiCoder.decode(['string'], log.data)[0]; + console.debug(`📄 [getMetaEvidence] Decoded string from signature 4 data: "${decodedString}"`); + + // Check if this looks like an IPFS hash + if (decodedString && (decodedString.includes('ipfs') || decodedString.includes('Qm'))) { + console.debug(`🎯 [getMetaEvidence] FOUND IPFS HASH in signature 4! Creating MetaEvidence with target ID ${metaEvidenceID}`); + + // Create synthetic MetaEvidence event with target ID and found IPFS hash + const ipfsEvent = { + args: { + _metaEvidenceID: ethers.getBigInt(metaEvidenceID), + _evidence: decodedString + }, + blockNumber: disputeEvent.blockNumber, + transactionHash: disputeEvent.transactionHash, + address: log.address + }; + + console.debug(`✅ [getMetaEvidence] Created MetaEvidence event from IPFS data:`, ipfsEvent); + metaEvidenceEvents = [ipfsEvent]; + break; // Found what we need, exit + } + } catch (specialDecodeError) { + console.debug(`💥 [getMetaEvidence] Failed to decode signature 4 data as string:`, specialDecodeError.message); + + // Try alternative: Maybe the data contains the MetaEvidence ID followed by string + try { + const altAbiCoder = ethers.AbiCoder.defaultAbiCoder(); + const decodedAlt = altAbiCoder.decode(['uint256', 'string'], log.data); + const [altMetaID, altString] = decodedAlt; + console.debug(`📄 [getMetaEvidence] Alternative decode - ID: ${altMetaID}, String: "${altString}"`); + + if (altMetaID.toString() === metaEvidenceID.toString() || (altString && altString.includes('ipfs'))) { + console.debug(`🎯 [getMetaEvidence] Found match in alternative decode!`); + const altEvent = { + args: { + _metaEvidenceID: ethers.getBigInt(metaEvidenceID), + _evidence: altString + }, + blockNumber: disputeEvent.blockNumber, + transactionHash: disputeEvent.transactionHash, + address: log.address + }; + console.debug(`✅ [getMetaEvidence] Created MetaEvidence from alternative decode:`, altEvent); + metaEvidenceEvents = [altEvent]; + break; + } + } catch (altDecodeError) { + console.debug(`💥 [getMetaEvidence] Alternative decode also failed:`, altDecodeError.message); + } + } + } + } + } catch (signatureDecodeError) { + console.debug(`💥 [getMetaEvidence] Failed to decode signature ${i} as MetaEvidence:`, signatureDecodeError.message); + } + } + + // If we found the target MetaEvidence, break out of signature testing + if (metaEvidenceEvents.length > 0) break; + } + } + + // Check each signature against transaction logs + for (const signature of commonMetaEvidenceSignatures) { + const matches = disputeReceipt.logs.filter(log => log.topics[0] === signature); + if (matches.length > 0) { + console.debug(`✅ [getMetaEvidence] Found ${matches.length} events with signature ${signature}:`, matches); + } + } + + if (metaEvidenceLogsInTx.length > 0) { + console.debug(`✅ [getMetaEvidence] Found MetaEvidence in same transaction as Dispute!`); + console.debug(`📋 [getMetaEvidence] Raw MetaEvidence logs:`, metaEvidenceLogsInTx); + + // Try to decode these logs + for (const log of metaEvidenceLogsInTx) { + try { + const decodedLog = contract.interface.parseLog(log); + console.debug(`🔍 [getMetaEvidence] Decoded MetaEvidence log:`, decodedLog); + if (decodedLog.args._metaEvidenceID.toString() === metaEvidenceID.toString()) { + console.debug(`🎯 [getMetaEvidence] Found target MetaEvidence ID ${metaEvidenceID} in same transaction!`); + // Create a synthetic event object + metaEvidenceEvents = [{ + args: decodedLog.args, + blockNumber: disputeEvent.blockNumber, + transactionHash: disputeEvent.transactionHash + }]; + break; // Found it, exit the loop + } + } catch (decodeError) { + console.debug(`💥 [getMetaEvidence] Failed to decode log:`, decodeError.message); + } + } + } + } catch (txError) { + console.debug(`💥 [getMetaEvidence] Transaction investigation failed:`, txError.message); + } + } + + // If still no events after all attempts, create a generic MetaEvidence + if (metaEvidenceEvents.length === 0) { + console.debug(`💡 [getMetaEvidence] No MetaEvidence events found on contract. Creating generic fallback.`); + console.debug(`🏗️ [getMetaEvidence] This contract appears to be a non-standard arbitrable that doesn't emit MetaEvidence events.`); + + // Create a minimal generic MetaEvidence for contracts without proper metadata + const genericMetaEvidence = { + metaEvidenceJSON: { + title: `Dispute #${arbitratorDisputeID} (Non-Standard Contract)`, + description: `This dispute involves a non-standard arbitrable contract (${arbitrableAddress}) that doesn't provide MetaEvidence. Limited information is available.`, + question: "Unknown dispute question - contract doesn't provide metadata", + rulingOptions: { + type: "single-select", + titles: ["Refuse to arbitrate", "Option A", "Option B"], + descriptions: ["Invalid dispute or insufficient information", "Rule in favor of first party", "Rule in favor of second party"] + }, + category: "Non-Standard Contract", + arbitratorChainID: network, + arbitrableChainID: network, + _v: "0" + } + }; + + console.debug(`✅ [getMetaEvidence] Created generic MetaEvidence for non-standard contract`); + return genericMetaEvidence; + } + } + + const actualMetaEvidenceID = metaEvidenceEvents[0].args._metaEvidenceID.toString(); + if (actualMetaEvidenceID !== metaEvidenceID.toString()) { + console.debug(`⚠️ [getMetaEvidence] Using fallback MetaEvidence ID ${actualMetaEvidenceID} instead of requested ${metaEvidenceID}`); + } else { + console.debug(`✅ [getMetaEvidence] Found MetaEvidence event for ID ${metaEvidenceID}`); + } + + const metaEvidenceURI = metaEvidenceEvents[0].args._evidence; + console.debug(`🌐 [getMetaEvidence] Fetching MetaEvidence from URI: ${metaEvidenceURI}`); + console.debug(`🌐 [getMetaEvidence] Normalized URI: ${urlNormalize(metaEvidenceURI)}`); + + const response = await fetch(urlNormalize(metaEvidenceURI)); + console.debug(`📡 [getMetaEvidence] Fetch response status: ${response.status} ${response.statusText}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const metaEvidenceJSON = await response.json(); + console.debug(`📋 [getMetaEvidence] MetaEvidence JSON content:`, metaEvidenceJSON); + + console.log({ dispute: { metaEvidenceID, blockNumber: disputeEvent.blockNumber } }); + + // NORMALIZE FIELD NAMES: Some MetaEvidence uses 'name' instead of 'title' + if (metaEvidenceJSON.name && !metaEvidenceJSON.title) { + console.debug(`🔧 [getMetaEvidence] Normalizing 'name' field to 'title'`); + metaEvidenceJSON.title = metaEvidenceJSON.name; + } + + // Ensure title exists (fallback for UI compatibility) + if (!metaEvidenceJSON.title) { + console.debug(`🔧 [getMetaEvidence] Adding fallback title for UI compatibility`); + metaEvidenceJSON.title = `Dispute #${arbitratorDisputeID}`; + } + + console.debug(`🔧 [getMetaEvidence] Final MetaEvidence JSON after normalization:`, metaEvidenceJSON); + + // For cross-chain disputes where arbitrable is on Gnosis, ensure correct chainID + if (!metaEvidenceJSON.arbitrableChainID && network === '1') { + console.debug(`🔧 [getMetaEvidence] Adding arbitrableChainID for cross-chain dispute`); + metaEvidenceJSON.arbitrableChainID = '100'; // Gnosis chain + metaEvidenceJSON.arbitratorChainID = '1'; // Ethereum mainnet + } + + // Note: Block range parameters removed - evidence display interfaces + // should handle their own RPC optimization internally (follows Kleros Court approach) + + console.debug(`🎯 [getMetaEvidence] Successfully returning MetaEvidence for dispute ${arbitratorDisputeID}`); + return { metaEvidenceJSON }; + } catch (error) { - console.error('Error fetching meta evidence:', error); + console.error(`💥 [getMetaEvidence] Error fetching meta evidence:`, error); + console.debug(`🔍 [getMetaEvidence] Error occurred for arbitrable=${arbitrableAddress}, dispute=${arbitratorDisputeID}`); return null; } }; // Using Archon, parallel calls occasionally fail. - getMetaEvidenceParallelizeable = async (arbitrableAddress, arbitratorDisputeID) => { + getMetaEvidenceParallelizeable = async (arbitrableAddress, arbitratorDisputeID, arbitrableChainId = null) => { const { network } = this.state; + // For cross-chain disputes: if arbitrator is on Ethereum mainnet (1) but no explicit arbitrable chain ID, + // assume arbitrable is on Gnosis (100) for now + const targetNetwork = arbitrableChainId || (network === '1' ? '100' : network); + + console.debug(`🔍 [getMetaEvidenceParallelizeable] Starting search for dispute ${arbitratorDisputeID}`); + console.debug(`🌐 [getMetaEvidenceParallelizeable] Networks: arbitrator=${network}, arbitrable=${targetNetwork}`); const item = localStorage.getItem(`${network}${arbitratorDisputeID.toString()}`); if (item && item !== "undefined") { - console.debug(`Found metaevidence of ${arbitratorDisputeID} skipping fetch.`); + console.debug(`💾 [getMetaEvidenceParallelizeable] Found cached metaevidence for ${arbitratorDisputeID}`); return JSON.parse(item); } - console.debug(`Fetching ${arbitratorDisputeID}...`); + console.debug(`📡 [getMetaEvidenceParallelizeable] Fetching dispute ${arbitratorDisputeID}...`); try { - const dispute = await this.state.archon.arbitrable.getDispute( - arbitrableAddress, - networkMap[this.state.network].KLEROS_LIQUID, - arbitratorDisputeID + // Step 1: First, get the block number from DisputeCreation event on arbitrator (Ethereum) + console.debug(`🔍 [getMetaEvidenceParallelizeable] Step 1: Querying arbitrator for DisputeCreation event`); + const arbitratorContract = getContract("KlerosLiquid", networkMap[this.state.network].KLEROS_LIQUID, this.state.provider); + const disputeCreationFilter = arbitratorContract.filters.DisputeCreation(arbitratorDisputeID); + + const currentBlock = await this.state.provider.getBlockNumber(); + // Use deployment block if available, otherwise fallback to MAX_BLOCK_LOOKBACK + const deploymentBlock = networkMap[this.state.network].QUERY_FROM_BLOCK; + const arbitratorSearchFrom = deploymentBlock || Math.max(1, currentBlock - MAX_BLOCK_LOOKBACK); + + console.debug(`🔎 [getMetaEvidenceParallelizeable] Searching arbitrator blocks ${arbitratorSearchFrom} to latest`); + console.debug(`🏗️ [getMetaEvidenceParallelizeable] Using deployment block: ${deploymentBlock}, current: ${currentBlock}`); + + const disputeCreationEvents = await arbitratorContract.queryFilter(disputeCreationFilter, arbitratorSearchFrom, "latest"); + + if (disputeCreationEvents.length === 0) { + console.error(`❌ [getMetaEvidenceParallelizeable] No DisputeCreation event found on arbitrator for dispute ${arbitratorDisputeID}`); + return null; + } + + const disputeCreationBlock = disputeCreationEvents[0].blockNumber; + console.debug(`🎯 [getMetaEvidenceParallelizeable] Found DisputeCreation at block ${disputeCreationBlock}`); + console.debug(`🧾 [getMetaEvidenceParallelizeable] DisputeCreation event details:`, disputeCreationEvents[0]); + + // Step 2: Now query the arbitrable contract around that block number + console.debug(`📍 [getMetaEvidenceParallelizeable] Step 2: Querying arbitrable contract for Dispute event`); + const targetProvider = targetNetwork === network + ? this.state.provider + : new ethers.JsonRpcProvider(getReadOnlyRpcUrl({ chainId: targetNetwork })); + + console.debug(`🔗 [getMetaEvidenceParallelizeable] Target provider RPC URL: ${targetNetwork === network ? 'same network' : getReadOnlyRpcUrl({ chainId: targetNetwork })}`); + + const contract = getContract("IDisputeResolver", arbitrableAddress, targetProvider); + console.debug(`🏗️ [getMetaEvidenceParallelizeable] Created contract instance for ${arbitrableAddress} on network ${targetNetwork}`); + + const arbitratorAddr = networkMap[this.state.network].KLEROS_LIQUID; + console.debug(`⚖️ [getMetaEvidenceParallelizeable] Using arbitrator address: ${arbitratorAddr}`); + + const disputeFilter = contract.filters.Dispute( + arbitratorAddr, // arbitrator address + arbitratorDisputeID // dispute ID ); + + // Search around the dispute creation block (give some buffer for cross-chain timing) + const blockBuffer = 1000; // blocks before/after to account for cross-chain delays + const searchFromBlock = Math.max(1, disputeCreationBlock - blockBuffer); + const searchToBlock = disputeCreationBlock + blockBuffer; - const contract = EthereumInterface.getContract( - "IEvidence", - arbitrableAddress, - this.state.provider - ); + console.debug(`🔎 [getMetaEvidenceParallelizeable] Searching arbitrable blocks ${searchFromBlock} to ${searchToBlock} (±${blockBuffer} around ${disputeCreationBlock})`); + console.debug(`📊 [getMetaEvidenceParallelizeable] Arbitrable filter: arbitrator=${arbitratorAddr}, disputeID=${arbitratorDisputeID}`); + + let disputeEvents; + try { + console.debug(`🚀 [getMetaEvidenceParallelizeable] Executing queryFilter...`); + disputeEvents = await contract.queryFilter( + disputeFilter, + searchFromBlock, + searchToBlock + ); + console.debug(`📋 [getMetaEvidenceParallelizeable] Found ${disputeEvents.length} Dispute events on arbitrable contract`); + + if (disputeEvents.length > 0) { + console.debug(`📄 [getMetaEvidenceParallelizeable] First Dispute event details:`, disputeEvents[0]); + console.debug(`📋 [getMetaEvidenceParallelizeable] Event args:`, disputeEvents[0].args); + } + } catch (error) { + console.error(`💥 [getMetaEvidenceParallelizeable] Error querying Dispute events:`, error); + return null; + } + + if (disputeEvents.length === 0) { + console.error(`❌ [getMetaEvidenceParallelizeable] No Dispute event found for dispute ${arbitratorDisputeID}`); + console.debug(`🔍 [getMetaEvidenceParallelizeable] Search parameters: arbitrator=${arbitratorAddr}, disputeID=${arbitratorDisputeID}, blocks=${searchFromBlock}-${searchToBlock}`); + return null; + } + + const disputeEvent = disputeEvents[0]; + const metaEvidenceID = disputeEvent.args._metaEvidenceID; + const dispute = { metaEvidenceID, blockNumber: disputeEvent.blockNumber }; const filter = contract.filters.MetaEvidence(dispute.metaEvidenceID); + const events = await contract.queryFilter( filter, - networkMap[this.state.network].QUERY_FROM_BLOCK, + searchFromBlock, dispute.blockNumber ); @@ -614,7 +1465,7 @@ class App extends React.Component { getAppealDecision = async (arbitratorDisputeID, disputedAtBlockNumber) => { if (!networkMap[this.state.network]?.KLEROS_LIQUID) return null; - const contract = EthereumInterface.getContract( + const contract = getContract( "KlerosLiquid", networkMap[this.state.network].KLEROS_LIQUID, this.state.provider @@ -654,7 +1505,7 @@ class App extends React.Component { _round++; } - const contract = EthereumInterface.getContract( + const contract = getContract( "IDisputeResolver", arbitrableContractAddress, this.state.provider @@ -670,11 +1521,12 @@ class App extends React.Component { console.debug('DEBUG getContributions - filter:', contributionFilter); const currentBlock = await this.state.provider.getBlockNumber(); + const queryFromBlock = networkMap[this.state.network].QUERY_FROM_BLOCK; const fromBlock = searchFrom ?? Math.max( - networkMap[this.state.network].QUERY_FROM_BLOCK || 0, - currentBlock - BLOCK_SEARCH_RANGE // Search last 1M blocks to catch recent events + queryFromBlock && queryFromBlock > 0 ? queryFromBlock : currentBlock - MAX_BLOCK_LOOKBACK, + currentBlock - MAX_BLOCK_LOOKBACK // Search last 1M blocks to catch recent events ); - const toBlock = searchFrom ? searchFrom + BLOCK_SEARCH_WINDOW : "latest"; + const toBlock = searchFrom ? searchFrom + SEARCH_WINDOW_SIZE : "latest"; const events = await contract.queryFilter( contributionFilter, @@ -715,7 +1567,7 @@ class App extends React.Component { arbitrator: networkMap[this.state.network].KLEROS_LIQUID }); - const contract = EthereumInterface.getContract( + const contract = getContract( "IDisputeResolver", arbitrableContractAddress, this.state.provider @@ -732,11 +1584,12 @@ class App extends React.Component { // Ensure we search a wide enough range to catch recent events const currentBlock = await this.state.provider.getBlockNumber(); + const queryFromBlock = networkMap[this.state.network].QUERY_FROM_BLOCK; const fromBlock = searchFrom ?? Math.max( - networkMap[this.state.network].QUERY_FROM_BLOCK || 0, - currentBlock - BLOCK_SEARCH_RANGE // Search last 1M blocks to catch recent events + queryFromBlock && queryFromBlock > 0 ? queryFromBlock : currentBlock - MAX_BLOCK_LOOKBACK, + currentBlock - MAX_BLOCK_LOOKBACK // Search last 1M blocks to catch recent events ); - const toBlock = searchFrom ? searchFrom + BLOCK_SEARCH_WINDOW : "latest"; + const toBlock = searchFrom ? searchFrom + SEARCH_WINDOW_SIZE : "latest"; console.log('DEBUG getRulingFunded - block range:', { fromBlock, toBlock, currentBlock }); @@ -786,7 +1639,7 @@ class App extends React.Component { // Extract v1 contract logic to reduce complexity tryGetWithdrawableAmountV1 = async (arbitrated, arbitrableDisputeID, contributedTo) => { - const contract = EthereumInterface.getContract( + const contract = getContract( "IDisputeResolver_v1", arbitrated, this.state.provider @@ -802,7 +1655,7 @@ class App extends React.Component { }; getTotalWithdrawableAmount = async (arbitrableDisputeID, contributedTo, arbitrated) => { - const contract = EthereumInterface.getContract( + const contract = getContract( "IDisputeResolver", arbitrated, this.state.provider @@ -816,7 +1669,7 @@ class App extends React.Component { } catch { // Fallback to v1 try { - return await this.tryGetWithdrawableAmountV1(arbitrated, arbitrableDisputeID, contributedTo); + return this.tryGetWithdrawableAmountV1(arbitrated, arbitrableDisputeID, contributedTo); } catch (v1Error) { console.error('Error fetching withdrawable amount:', v1Error); return { amount: 0, ruling: contributedTo }; @@ -827,14 +1680,14 @@ class App extends React.Component { getDispute = async arbitratorDisputeID => { if (!networkMap[this.state.network]?.KLEROS_LIQUID) return null; - const contract = EthereumInterface.getContract( + const contract = getContract( "KlerosLiquid", networkMap[this.state.network].KLEROS_LIQUID, this.state.provider ); try { - return await contract.getDispute(arbitratorDisputeID); + return contract.getDispute(arbitratorDisputeID); } catch (error) { console.error(`Error fetching dispute with dispute ID ${arbitratorDisputeID}:`, error); return null @@ -858,7 +1711,7 @@ class App extends React.Component { const evidenceURI = ipfsHashEvidenceObj; - const contract = await EthereumInterface.getSignableContract( + const contract = await getSignableContract( "ArbitrableProxy", arbitrableAddress, this.state.provider @@ -869,7 +1722,7 @@ class App extends React.Component { value: ethers.parseEther(value) }); - return await tx.wait(); + return tx.wait(); } catch (error) { console.error('Error submitting evidence:', error); throw error; @@ -898,7 +1751,7 @@ class App extends React.Component { const arbitrationCost = await this.getArbitrationCost(arbitrator, arbitratorExtraData); console.debug({ arbitrationCost }) - const contract = await EthereumInterface.getSignableContract( + const contract = await getSignableContract( "ArbitrableProxy", networkMap[this.state.network].ARBITRABLE_PROXY, this.state.provider @@ -934,7 +1787,7 @@ class App extends React.Component { }; appeal = async (arbitrableAddress, arbitrableDisputeID, party, contribution) => { - const contract = await EthereumInterface.getSignableContract( + const contract = await getSignableContract( "IDisputeResolver", arbitrableAddress, this.state.provider @@ -945,7 +1798,7 @@ class App extends React.Component { value: ethers.parseEther(contribution) }) - return await tx.wait() + return tx.wait() } catch (error) { console.error("Error executing Appeal transaction: ", error) return null @@ -964,7 +1817,7 @@ class App extends React.Component { ? "IDisputeResolver_v1" : "IDisputeResolver"; - const contract = await EthereumInterface.getSignableContract( + const contract = await getSignableContract( contractName, arbitrableAddress, this.state.provider @@ -978,7 +1831,7 @@ class App extends React.Component { { value: ethers.parseEther("0") } ); - return await tx.wait(); + return tx.wait(); } catch (error) { console.log("Error executing withdrawFeesAndRewardsForAllRounds transaction: ", error) return null diff --git a/src/components/createForm.js b/src/components/createForm.js index afe252d..848d8cd 100644 --- a/src/components/createForm.js +++ b/src/components/createForm.js @@ -134,13 +134,13 @@ class CreateForm extends React.Component { }; onArrayStateVariableChange = async (event, variable, index) => { - this.setState((prevState) => ({ + this.setState(prevState => ({ [variable]: [...prevState[variable].slice(0, index), event.target.value, ...prevState[variable].slice(index + 1)], })); }; onNumberOfRulingOptionsChange = async event => { - let number = parseInt(event.target.value, 10); + const number = parseInt(event.target.value, 10); this.setState({ numberOfRulingOptions: number }); }; @@ -171,7 +171,7 @@ class CreateForm extends React.Component { return; } - let reader = new FileReader(); + const reader = new FileReader(); reader.readAsArrayBuffer(acceptedFiles[0]); reader.addEventListener("loadend", async () => { @@ -257,194 +257,246 @@ class CreateForm extends React.Component { componentDidMount = async () => { this.onSubcourtSelect("0"); const { formData } = this.props; - if (formData) this.setState({ - selectedSubcourt: parseInt(formData.selectedSubcourt, 10) && formData.selectedSubcourt, - initialNumberOfJurors: formData.initialNumberOfJurors && formData.initialNumberOfJurors, - category: formData.category && formData.category, - title: formData.title && formData.title, - description: formData.description && formData.description, - question: formData.question && formData.question, - primaryDocument: formData.primaryDocument && formData.primaryDocument, - questionType: formData.questionType.code.length > 0 && formData.questionType, - numberOfRulingOptions: formData.rulingTitles.length > 0 && formData.rulingTitles.length, - rulingTitles: formData.rulingTitles.length > 0 ? formData.rulingTitles : [], - rulingDescriptions: formData.rulingDescriptions.length > 0 ? formData.rulingDescriptions : [], - names: formData.names.length > 0 ? formData.names : [], - addresses: formData.addresses.length > 0 ? formData.addresses : [], - }); + if (formData) { + this.setState({ + selectedSubcourt: formData.selectedSubcourt ? parseInt(formData.selectedSubcourt, 10) : 0, + initialNumberOfJurors: formData.initialNumberOfJurors || "3", + category: formData.category || "", + title: formData.title || "", + description: formData.description || "", + question: formData.question || "", + primaryDocument: formData.primaryDocument || "", + questionType: formData.questionType && formData.questionType.code && formData.questionType.code.length > 0 ? formData.questionType : QuestionTypes.SINGLE_SELECT, + numberOfRulingOptions: formData.rulingTitles && formData.rulingTitles.length > 0 ? formData.rulingTitles.length : 2, + rulingTitles: formData.rulingTitles && formData.rulingTitles.length > 0 ? formData.rulingTitles : ["", ""], + rulingDescriptions: formData.rulingDescriptions && formData.rulingDescriptions.length > 0 ? formData.rulingDescriptions : [""], + names: formData.names && formData.names.length > 0 ? formData.names : [], + addresses: formData.addresses && formData.addresses.length > 0 ? formData.addresses : [], + }); + } }; - render() { - const { subcourtsLoading, subcourtDetails, network } = this.props; - - const { - title, - description, - category, - question, - validated, - questionType, - numberOfRulingOptions, - rulingTitles, - numberOfParties, - rulingDescriptions, - names, - addresses, - fileInput, - initialNumberOfJurors, - arbitrationCost, - selectedSubcourt, - summary, - primaryDocument, - uploadError, - uploading, - } = this.state; + renderNetworkError() { + return ( +

+ There is no arbitrable contract deployed in this network. + So unfortunately you can't create a dispute. + Feel free to head over GitHub issues to request this feature. +

+ ); + } - // Removed debug console.log to avoid spamming the console and impacting performance. + renderCourtAndJurorSelection() { + const { subcourtsLoading, subcourtDetails } = this.props; + const { selectedSubcourt, initialNumberOfJurors, summary } = this.state; + + return ( + + + + Court + + + {" "} + + {(subcourtsLoading && "Loading...") || + (selectedSubcourt && subcourtDetails && subcourtDetails[selectedSubcourt] && subcourtDetails[selectedSubcourt].name) || + "Please select a court"} + + + + {subcourtDetails?.map((subcourt, index) => ( + + {subcourt?.name} + + ))} + + + + + + + Number of Votes + + + + + {this.renderCategoryAndCost()} + + ); + } - if (!networkMap[network].ARBITRABLE_PROXY) return

There is no arbitrable contract deployed in this network. - So unfortunately you can't create a dispute. - Feel free to head over GitHub - issues to request this feature.

+ renderCategoryAndCost() { + const { network } = this.props; + const { category, arbitrationCost } = this.state; + + return ( + <> + + + Category (Optional) + + + + + + Arbitration Cost + + + + {arbitrationCost && `${arbitrationCost} ${networkMap[network].CURRENCY_SHORT}`} + + + + + + ); + } - return (
-
- - -

Fill up the form to

-

Create a custom dispute

- -
-
- - - - Court - - - {" "} - - {(subcourtsLoading && "Loading...") || (selectedSubcourt && subcourtDetails && subcourtDetails[selectedSubcourt] && subcourtDetails[selectedSubcourt].name) || "Please select a court"} - - + renderTitleAndDescription() { + const { title, description } = this.state; - - {subcourtDetails?.map((subcourt, index) => ( - - {subcourt?.name} - ) - )} - - - - - - - Number of Votes - - - - - - - Category (Optional) - - - - - - Arbitration Cost - - - {{arbitrationCost && arbitrationCost + " " + networkMap[network].CURRENCY_SHORT}} - - - - + return ( + <> Title - - Please enter title for the dispute, something explains it in a - nutshell. + + + Please enter title for the dispute, something explains it in a nutshell. + - Description (Optional) - + + + ); + } -
+ renderQuestionTypeSection() { + const { questionType, numberOfRulingOptions, question } = this.state; + return ( + <> Question Type - {questionType.humanReadable || "Error"} - {Object.values(QuestionTypes).map(qType => ( {qType.humanReadable} - ))} + + ))} - {(questionType.code == QuestionTypes.SINGLE_SELECT.code || questionType.code == QuestionTypes.MULTIPLE_SELECT.code) && ( - - Number of Options - - Please enter first ruling option, for example: "Yes" - - )} + {(questionType.code === QuestionTypes.SINGLE_SELECT.code || questionType.code === QuestionTypes.MULTIPLE_SELECT.code) && ( + + + Number of Options + + + Please enter first ruling option, for example: "Yes" + + + + )} - Question - - - Please enter a question. + + + Please enter a question. + - {!isNaN(numberOfRulingOptions) && (questionType.code == QuestionTypes.SINGLE_SELECT.code || questionType.code == QuestionTypes.MULTIPLE_SELECT.code) && [...Array(parseInt(numberOfRulingOptions, 10))].map((_value, index) => ( + + ); + } + + renderRulingOptionsSection() { + const { questionType, numberOfRulingOptions, rulingTitles, rulingDescriptions } = this.state; + + if (isNaN(numberOfRulingOptions) || (questionType.code !== QuestionTypes.SINGLE_SELECT.code && questionType.code !== QuestionTypes.MULTIPLE_SELECT.code)) { + return null; + } + + return ( + <> + {[...Array(parseInt(numberOfRulingOptions, 10))].map((_value, index) => ( @@ -457,7 +509,9 @@ class CreateForm extends React.Component { onChange={this.onRulingTitleChange(index)} placeholder={`Ruling option ${index + 1}`} /> - Please enter first ruling option, for example: "Yes" + + Please enter first ruling option, for example: "Yes" + @@ -471,78 +525,126 @@ class CreateForm extends React.Component { placeholder={`Ruling option ${index + 1} description`} /> - {" "} - ))} + + + ))} + + ); + } -
- - {[...Array(parseInt(numberOfParties, 10))].map((_value, index) => ( + renderPartiesSection() { + const { numberOfParties, names, addresses } = this.state; + + return ( + + {[...Array(parseInt(numberOfParties, 10))].map((_value, index) => ( + - Alias {index + 1} (Optional) - + Alias {index + 1} (Optional) - Address of Alias {index + 1} (Optional) - + Address {index + 1} (Optional) - ))} - - - - - - - - - + + ))} + + ); + } - - - - Upload the Primary Document (Optional) - - - - - - - - - -
-
); + renderFileUploadSection() { + const { fileInput, uploading, uploadError } = this.state; + + return ( + + + + Primary Document (Optional) + + {fileInput && ( +
+

Selected file: {fileInput.name}

+
+ )} +
+ +
+ ); + } + + renderSubmitSection() { + const { awaitingConfirmation, summary } = this.state; + + return ( + + + + + + ); + } + + render() { + const { network } = this.props; + const { validated, summary } = this.state; + + if (!networkMap[network].ARBITRABLE_PROXY) { + return this.renderNetworkError(); + } + + return ( +
+
+ + +

Fill up the form to

+

Create a custom dispute

+ +
+
+ {this.renderCourtAndJurorSelection()} + {this.renderTitleAndDescription()} +
+ {this.renderQuestionTypeSection()} + {this.renderRulingOptionsSection()} +
+ {this.renderPartiesSection()} +
+ {this.renderFileUploadSection()} + {this.renderSubmitSection()} +
+
+ ); } } diff --git a/src/components/crowdfundingCard.js b/src/components/crowdfundingCard.js index ee48be4..ba7ee8c 100644 --- a/src/components/crowdfundingCard.js +++ b/src/components/crowdfundingCard.js @@ -4,7 +4,7 @@ import Countdown, { zeroPad } from "react-countdown"; import styles from "components/styles/crowdfundingCard.module.css"; import { ReactComponent as Hourglass } from "assets/images/hourglass.svg"; import AlertMessage from "components/alertMessage"; -import * as realitioLibQuestionFormatter from "@reality.eth/reality-eth-lib/formatters/question"; +import { answerToBytes32 } from "@reality.eth/reality-eth-lib/formatters/question"; import DatetimePicker from "components/datetimePicker.js"; import { ethers } from "ethers"; @@ -20,54 +20,51 @@ class CrowdfundingCard extends React.Component { this.setState({ variableRulingOption: value.utcOffset(0).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).unix() }); }; + renderCountdown = props => ( + {`${zeroPad(props.days, 2)}d ${zeroPad(props.hours, 2)}h ${zeroPad(props.minutes, 2)}m`} + ); + addDecimalsToUintRuling = (currentRuling, metaEvidenceJSON) => { - return realitioLibQuestionFormatter.answerToBytes32(currentRuling, { + return answerToBytes32(currentRuling, { decimals: metaEvidenceJSON.rulingOptions.precision || 18, type: metaEvidenceJSON.rulingOptions.type, }); }; - handleFundButtonClick = async () => { - const { variable, appealCallback, rulingOptionCode, metaevidenceJSON } = this.props; - const { variableRulingOption, contribution } = this.state; - let actualRulingCode; - - // First, validate and process the input - try { - switch (variable) { - case undefined: // Not variable - actualRulingCode = rulingOptionCode; - break; - case "uint": - actualRulingCode = ethers.getBigInt(this.addDecimalsToUintRuling(variableRulingOption, metaevidenceJSON)) + 1n; - break; - case "int": { - const parsedValue = parseInt(variableRulingOption, 10); - actualRulingCode = parsedValue >= 0 ? parsedValue + 1 : parsedValue; - break; - } - case "string": - actualRulingCode = ethers.hexlify(ethers.toUtf8Bytes(variableRulingOption)); - break; - case "datetime": - actualRulingCode = variableRulingOption + 1; - break; - case "hash": - actualRulingCode = BigInt(variableRulingOption) + 1n; - break; + processRulingCode = (variable, variableRulingOption, metaevidenceJSON, rulingOptionCode) => { + switch (variable) { + case undefined: // Not variable + return rulingOptionCode; + case "uint": + return ethers.getBigInt(this.addDecimalsToUintRuling(variableRulingOption, metaevidenceJSON)) + 1n; + case "int": { + const parsedValue = parseInt(variableRulingOption, 10); + return parsedValue >= 0 ? parsedValue + 1 : parsedValue; } - } catch { - // Set error state for input validation errors - this.setState({ error: "Invalid input format. Please enter a valid number or hex string." }); - return; + case "string": + return ethers.hexlify(ethers.toUtf8Bytes(variableRulingOption)); + case "datetime": + return variableRulingOption + 1; + case "hash": + return BigInt(variableRulingOption) + 1n; + default: + throw new Error(`Unsupported variable type: ${variable}`); } + }; - // Then, execute the callback + handleFundButtonClick = async () => { + const { variable, appealCallback, rulingOptionCode, metaevidenceJSON } = this.props; + const { variableRulingOption, contribution } = this.state; + try { + const actualRulingCode = this.processRulingCode(variable, variableRulingOption, metaevidenceJSON, rulingOptionCode); await appealCallback(actualRulingCode, contribution.toString()); - } catch { - // Set error state for callback execution errors - this.setState({ error: "Transaction failed. Please check your network connection and try again." }); + } catch (error) { + if (error.message && error.message.includes('Unsupported variable type')) { + this.setState({ error: "Invalid input format. Please enter a valid number or hex string." }); + } else { + this.setState({ error: "Transaction failed. Please check your network connection and try again." }); + } } }; @@ -96,7 +93,7 @@ class CrowdfundingCard extends React.Component {
- {`${zeroPad(props.days, 2)}d ${zeroPad(props.hours, 2)}h ${zeroPad(props.minutes, 2)}m`}} /> +
{error && ( diff --git a/src/components/datetimePicker.js b/src/components/datetimePicker.js index 3a7b1c0..f7f2994 100644 --- a/src/components/datetimePicker.js +++ b/src/components/datetimePicker.js @@ -5,9 +5,6 @@ import moment from "moment"; import styles from "./styles/datetimePicker.module.css"; class DatetimePicker extends React.Component { - constructor(props) { - super(props); - } render() { const { onChange, onOk, id } = this.props; diff --git a/src/components/disputeDetails.js b/src/components/disputeDetails.js index 6f5f7f0..878675b 100644 --- a/src/components/disputeDetails.js +++ b/src/components/disputeDetails.js @@ -417,8 +417,8 @@ class DisputeDetails extends React.Component { // Helper method to check if required data is available hasRequiredData = (metaevidenceJSON, arbitratorDispute, subcourts, subcourtDetails, arbitratorDisputeDetails) => { - return metaevidenceJSON && arbitratorDispute && subcourts.length > 0 && - subcourtDetails.length > 0 && arbitratorDisputeDetails; + return metaevidenceJSON && arbitratorDispute && subcourts?.length > 0 && + subcourtDetails?.length > 0 && arbitratorDisputeDetails; }; // Helper method to render appeal card conditionally @@ -667,7 +667,7 @@ class DisputeDetails extends React.Component {
diff --git a/src/components/disputeSummary.js b/src/components/disputeSummary.js index 96bf534..5180889 100644 --- a/src/components/disputeSummary.js +++ b/src/components/disputeSummary.js @@ -1,74 +1,126 @@ -import { Row, Col, Form} from "react-bootstrap"; +import { Row, Col, Form } from "react-bootstrap"; import React from "react"; -import {ReactComponent as AttachmentSVG} from "../assets/images/attachment.svg"; -import {getReadOnlyRpcUrl} from "../ethereum/network-contract-mapping"; -import whitelistedArbitrables from "../ethereum/whitelistedArbitrables"; +import { ReactComponent as AttachmentSVG } from "../assets/images/attachment.svg"; +import { getReadOnlyRpcUrl } from "../ethereum/network-contract-mapping"; +import whitelistedArbitrables from "../ethereum/arbitrableWhitelist"; import styles from "components/styles/disputeSummary.module.css"; import ReactMarkdown from "react-markdown"; class DisputeSummary extends React.Component { - constructor(props) { - super(props); + getArbitratorConfig() { + const { arbitratorDisputeID, arbitratorAddress, arbitratorChainID, chainID, web3Provider } = this.props; + return { + disputeID: arbitratorDisputeID, + chainID, + arbitratorContractAddress: arbitratorAddress, + arbitratorJsonRpcUrl: getReadOnlyRpcUrl({ chainId: arbitratorChainID }) ?? web3Provider, + arbitratorChainID, + }; } - render() { - const { - metaevidenceJSON, - ipfsGateway, - arbitrated, - arbitratorDisputeID, - arbitratorAddress, - arbitratorChainID, + getArbitrableConfig() { + const { arbitrableChainID, arbitrated, web3Provider } = this.props; + return { + arbitrableContractAddress: arbitrated, arbitrableChainID, - chainID, // Deprecated. Use arbitrableChainID or arbitratorChainID instead. - web3Provider, - loading, - } = this.props; + arbitrableJsonRpcUrl: getReadOnlyRpcUrl({ chainId: arbitrableChainID }) ?? web3Provider, + }; + } - const injectedArgs = { - disputeID: arbitratorDisputeID, - chainID: chainID, // Deprecated. Use arbitrableChainID or arbitratorChainID instead. - arbitratorContractAddress: arbitratorAddress, - arbitratorJsonRpcUrl: getReadOnlyRpcUrl({chainId: arbitratorChainID}) ?? web3Provider, - arbitratorChainID: arbitratorChainID, - arbitrableContractAddress: arbitrated, - arbitrableChainID: arbitrableChainID, - arbitrableJsonRpcUrl: getReadOnlyRpcUrl({chainId: arbitrableChainID}) ?? web3Provider, - jsonRpcUrl: web3Provider, + getInjectedArgs() { + const { web3Provider, chainID } = this.props; + // Convert web3Provider object to URL string if needed + const jsonRpcUrl = typeof web3Provider === 'object' + ? getReadOnlyRpcUrl({ chainId: chainID }) + : web3Provider; + + // Follow Kleros Court approach: only pass essential parameters + // Do NOT pass block range parameters - let evidence interfaces handle optimization internally + const baseArgs = { + ...this.getArbitratorConfig(), + ...this.getArbitrableConfig(), + jsonRpcUrl, }; + return baseArgs; + } + + getSearchParams(injectedArgs, metaevidenceJSON) { + const { _v = "0" } = metaevidenceJSON; + if (_v === "0") { + return `${encodeURIComponent(JSON.stringify(injectedArgs))}`; + } + const _searchParams = new URLSearchParams(injectedArgs); + return `${_searchParams.toString()}`; + } + + + renderAliases(metaevidenceJSON) { + if (!metaevidenceJSON.aliases) return null; + + return ( + + {Object.entries(metaevidenceJSON.aliases).map(([key, value]) => ( + + + + Party {Object.keys(metaevidenceJSON.aliases).indexOf(key) + 1} + + {value} + + + + + + Party {Object.keys(metaevidenceJSON.aliases).indexOf(key) + 1} Address + + {key} + + + + + ))} + + ); + } + + render() { + const { metaevidenceJSON, ipfsGateway, arbitrated, arbitratorChainID, loading } = this.props; + if (metaevidenceJSON) { - let searchParams; - const {_v = "0"} = metaevidenceJSON; - if (_v === "0") { - searchParams = `${encodeURIComponent(JSON.stringify(injectedArgs))}`; - } else { - const _searchParams = new URLSearchParams(injectedArgs); - searchParams = `${_searchParams.toString()}`; - } - + const injectedArgs = this.getInjectedArgs(); + console.debug('🔍 [DisputeSummary] metaevidenceJSON:', metaevidenceJSON); + console.debug('🔍 [DisputeSummary] injectedArgs:', injectedArgs); + const searchParams = this.getSearchParams(injectedArgs, metaevidenceJSON); + console.debug('🔍 [DisputeSummary] searchParams:', searchParams); + return (

Interact with the dispute

{metaevidenceJSON.title}

-
- +
+ - {metaevidenceJSON.evidenceDisplayInterfaceURI && ( -