Skip to content

[Feature] Build a dispute resolution system for pool members to report and resolve conflicts (missed deposits, unfair penalties) #208

Description

@Sendi0011

Getting Started

  1. Fork the repository: https://github.com/JointSave-org/Joint_Save
  2. Clone your fork:
git clone https://github.com/<your-username>/Joint_Save.git
cd Joint_Save
  1. Create a new branch:
git checkout -b feat/dispute-resolution

Overview

When a pool member misses a deposit or disputes a penalty applied by the admin, there is currently no formal mechanism to handle the conflict. The admin has unilateral power to remove members and apply penalties, with no appeals process. This creates trust issues, especially in larger pools where members may not know each other personally.

Checked frontend/app/dashboard/group/[id]/GroupClient.tsx — the admin actions (emergency_withdraw, pause, remove_member) are directly callable. There is no dispute table in Supabase. The pool_activity table records actions but has no dispute/resolution concept.

This issue adds a dispute resolution system where members can file disputes against admin actions or other members, other members can vote to resolve the dispute, and resolutions are recorded on-chain and off-chain.

Requirements

Database Changes

  • New table disputes:

    • id (uuid, PK, default gen_random_uuid())
    • pool_id (text, not null)
    • filer_address (text, not null) — the member filing the dispute
    • target_address (text, nullable) — the member/admin being disputed against (null for admin action disputes)
    • dispute_type (text, not null): missed_deposit, unfair_penalty, admin_abuse, member_misconduct, other
    • description (text, not null, max 2000 chars)
    • evidence_urls (jsonb, default '[]') — optional links to evidence
    • status (text, not null, default 'open'): open, voting, resolved_upheld, resolved_dismissed, expired
    • resolution (text, nullable) — explanation of the resolution
    • votes_for (integer, default 0)
    • votes_against (integer, default 0)
    • resolved_by (text, nullable) — address of the resolving voter
    • resolved_at (timestamptz, nullable)
    • created_at (timestamptz, default now())
    • expires_at (timestamptz, not null) — 72 hours from creation
    • RLS: pool members can read disputes for their pools; filer can insert/update their own; authenticated users can update vote counts
  • New table dispute_votes:

    • dispute_id (uuid, FK to disputes)
    • voter_address (text)
    • vote (boolean) — true = uphold, false = dismiss
    • created_at (timestamptz)
    • PK: (dispute_id, voter_address)
    • RLS: pool members can read; authenticated users can insert their own vote

API Changes

  • POST /api/disputes — file a new dispute (body: pool_id, target_address, dispute_type, description, evidence_urls)
    • Validates filer is a member of the pool
    • Validates no active dispute already exists for the same type+target in the same pool
    • Creates dispute with 72-hour expiry
  • GET /api/disputes?pool_id=X — list disputes for a pool, sorted by created_at DESC
  • POST /api/disputes/[id]/vote — vote on a dispute (body: vote: boolean)
    • Validates voter is a member, not the filer, not the target, hasn't already voted
    • Increments votes_for or votes_against
    • If votes_for >= 50% of pool members: auto-resolve as resolved_upheld
    • If votes_against >= 50%: auto-resolve as resolved_dismissed
  • PUT /api/disputes/[id]/resolve — admin manually resolves (body: resolution: string, outcome: 'upheld'|'dismissed')
    • Only pool admin can call
    • Required when voting ends in a tie
  • POST /api/disputes/expire — cron endpoint to expire disputes past their expires_at

Frontend Changes

  • New tab in group detail page: "Disputes" (alongside Members, Activity, Chat)
    • Lists all disputes for the pool with status badges
    • "File Dispute" button opens a dialog
    • Each dispute card shows: filer, target, type, description, vote counts, time remaining, status
  • New component: frontend/components/disputes/file-dispute-dialog.tsx
    • Dropdown for dispute type
    • Text area for description (2000 char limit with counter)
    • Optional evidence URL inputs (max 3)
    • Submit button (disabled until required fields filled)
  • New component: frontend/components/disputes/dispute-card.tsx
    • Shows dispute details with vote buttons
    • "Upvote to Uphold" / "Downvote to Dismiss" buttons
    • Visual progress bar showing vote split
    • Time remaining countdown
    • Resolution text when resolved
  • New component: frontend/components/disputes/dispute-resolution-banner.tsx
    • Banner shown at top of group page when a dispute has been resolved
    • Brief summary of the outcome

Activity Logging

  • When a dispute is filed: insert into pool_activity with activity_type: 'dispute_filed'
  • When a dispute is resolved: insert with activity_type: 'dispute_resolved' and resolution details in metadata

Acceptance Criteria

  • Members can file disputes with type, description, and optional evidence
  • Only pool members can view and vote on disputes for their pool
  • Filers and targets cannot vote on their own disputes
  • Voting automatically resolves disputes when 50% threshold is reached
  • Disputes expire after 72 hours without resolution
  • Admin can manually resolve tied disputes
  • Dispute activity is logged in the pool activity feed
  • No duplicate active disputes for the same type+target in the same pool
  • Frontend renders dispute list, file dialog, and vote cards correctly
  • Mobile: dispute filing and voting UI is fully responsive

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third CampaignfeatureNew functionality to addhigh-complexityLarge scope, multiple systems/files. Needs planning

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions