Skip to content

feat: Add Community Voting System for Emergency Campaigns#156

Open
SUHANI-PAL0103 wants to merge 5 commits intoStabilityNexus:mainfrom
SUHANI-PAL0103:main
Open

feat: Add Community Voting System for Emergency Campaigns#156
SUHANI-PAL0103 wants to merge 5 commits intoStabilityNexus:mainfrom
SUHANI-PAL0103:main

Conversation

@SUHANI-PAL0103
Copy link
Copy Markdown

@SUHANI-PAL0103 SUHANI-PAL0103 commented Dec 14, 2025

Summary

Implements Phase 2 of the 4-layer fraud prevention system - Community Voting with 60% approval threshold.

Changes

New Files

  • src/lib/ergo/voting/voting_contract.ts - VotingContract class with localStorage-based voting
  • src/routes/CommunityVoting.svelte - Voting UI component with progress bars
  • src/routes/StagedWithdrawal.svelte - Staged fund release management
  • src/routes/Phase2Demo.svelte - Demo component for Phase 2 features
  • VOTING_SYSTEM_IMPLEMENTATION.md - Documentation

Modified Files

  • src/lib/common/project.ts - CRITICAL FIX: Extract emergency data from blockchain content
  • src/lib/ergo/platform.ts - Fix BigInt conversion for wallet balance
  • src/routes/NewProject.svelte - Add emergency data to all campaigns by default
  • src/routes/ProjectDetails.svelte - Integrate voting interface, block donations until approved
  • src/routes/ProjectCard.svelte - Fix BigInt balance display
  • src/routes/ProjectList.svelte - Disable test project filter by default

Features

  • ✅ 60% approval threshold with 20 minimum votes
  • ✅ Free voting (localStorage-based, no ERG cost)
  • ✅ Donation blocking until verification passes
  • ✅ Real-time vote progress tracking
  • ✅ Double-vote prevention
  • ✅ Regional/State community type option

Testing

  • Created test campaigns on mainnet
  • Verified voting interface appears after emergency data fix
  • Confirmed donation blocking works correctly

Related Issues

Implements Phase 2 of the fraud prevention roadmap.

Summary by CodeRabbit

  • New Features

    • Emergency Fundraising Platform with 4-layer fraud prevention: location anchoring, document verification, community voting, and staged fund release.
    • Direct wallet connection via Nautilus with diagnostics support.
    • Community voting system for campaign verification with 60% approval threshold.
    • Staged fund withdrawal with multi-phase release schedule and proof requirements.
    • Enhanced campaign creation for emergency-specific details and verification fields.
  • Documentation

    • Comprehensive architecture, contribution guidelines, and presentation guide added.
    • Voting system implementation and enhanced verification model documentation.
  • Improvements

    • Wallet-first balance retrieval flow.
    • Vercel deployment configuration.

✏️ Tip: You can customize this high-level summary in your review settings.

- Added geographic location anchoring (Layer 1)
- Implemented IPFS document verification (Layer 2)
- Integrated 60% community voting threshold (Layer 3)
- Designed 40-30-30% staged fund release (Layer 4)
- Created comprehensive documentation (README, ARCHITECTURE, CONTRIBUTING)
- Enhanced UI with proper contrast and validation
- Added Preview Campaign feature for demonstration
- Fixed wallet integration with Nautilus
- Removed all demo mode code
- Added MIT License

Technical improvements:
- Direct wallet balance fetching via window.ergo API
- Token creation modal with real blockchain integration
- Form validation with reactive error checking
- Emergency document upload (IPFS hash + Drive links)
- Staged withdrawal data structures in metadata

Documentation:
- Complete README with use cases and user guide
- ARCHITECTURE.md explaining system design
- CONTRIBUTING.md for open source collaboration
- Enhanced .gitignore for security
- 5-minute demo script with exact steps
- Technical Q&A preparation with honest answers
- Innovation highlights for academic presentation
- Pre-presentation checklist
- Closing statement and resource links
- Statistics and key talking points
- Elevator pitch (30 seconds)
- 7 core innovations table
- Key statistics summary
- 5-minute demo flow with timestamps
- Top 10 expected Q&A with answers
- Pre-demo checklist
- Visual aids templates
- Social media ready posts
- Competition submission template
- Lightning round answers (30/10/1 second versions)
- Updated adapter-static configuration for Vercel
- Added vercel.json with build settings
- Set output directory to build/
- Configured SPA fallback routing
Features:
- Community verification voting (60% approval, 20 votes minimum)
- Emergency data now extracted from blockchain content
- Simplified emergency data structure for box size limits
- Donation blocking until verification passes
- Free localStorage-based voting (no ERG cost)
- CommunityVoting component with progress bars
- VotingContract class for vote management
- Regional/State community type option
- Hide test projects filter disabled by default
- BigInt conversion fixes for wallet balance
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 14, 2025

Walkthrough

This PR rebrands the platform from "Bene" to "Emergency Fundraising Platform on Ergo Blockchain" and introduces a comprehensive 4-layer fraud prevention system. It adds extensive documentation (architecture, voting, enhanced verification, presentation guides), implements community voting via localStorage, introduces staged fund withdrawal with proof requirements, adds direct wallet connectivity, extends project types to include emergency metadata, and updates multiple route components to support emergency campaign flows with verification gating and multi-stage fund release.

Changes

Cohort / File(s) Change Summary
Documentation & Licensing
.gitignore, LICENSE, README.md, ARCHITECTURE.md, CONTRIBUTING.md, ENHANCED_VERIFICATION_MODEL.md, PRESENTATION_GUIDE.md, QUICK_REFERENCE.md, VOTING_SYSTEM_IMPLEMENTATION.md
Rebrands project name and scope; adds comprehensive architecture, contribution guidelines, verification model, presentation assets, and voting system documentation; updates gitignore patterns for local development and wallet files
Type Definitions
src/lib/common/project.ts
Adds public types: CampaignPhase enum, WithdrawalStage and EmergencyData interfaces; extends ProjectContent with optional emergency?: EmergencyData field
Wallet Integration
src/lib/components/DirectWalletConnect.svelte, src/lib/ergo/platform.ts
Introduces new DirectWalletConnect component for Nautilus wallet connection; refactors balance retrieval to prioritize wallet adapter over API calls with fallback support for multiple balance formats
Voting System
src/lib/ergo/voting/voting_contract.ts
Implements new VotingContract class with vote submission, double-vote prevention, 60% threshold calculation (min 20 votes), phase management, and localStorage-backed storage
Route Components – Emergency Features
src/routes/CommunityVoting.svelte, src/routes/StagedWithdrawal.svelte, src/routes/Phase2Demo.svelte
Adds three new components: community voting interface with real-time progress, staged fund withdrawal with proof submission, and Phase 2 feature demo; all integrate with VotingContract and emergency data structures
Route Components – Integration & UI Updates
src/routes/App.svelte, src/routes/NewProject.svelte, src/routes/ProjectCard.svelte, src/routes/ProjectDetails.svelte, src/routes/ProjectList.svelte
Integrates DirectWalletConnect in App layout; expands NewProject with emergency verification UI (location, documents, fund stages) and token creation modal; updates ProjectCard and ProjectDetails to display emergency status and gating; changes ProjectList default filter to show test projects
Build Configuration
svelte.config.js, vercel.json
Updates SvelteKit adapter with explicit build output, fallback routing, and precompression settings; adds Vercel deployment config with build command and framework declaration

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

  • Areas requiring extra attention:
    • src/lib/ergo/voting/voting_contract.ts — New voting logic with threshold calculations, phase transitions, and localStorage persistence; validate correctness of vote counting, minimum threshold enforcement, and double-vote prevention
    • src/routes/NewProject.svelte — Extensive form expansion with interdependent emergency fields, token creation modal orchestration, and validation chains; verify field dependencies, auto-validation logic, and token minting workflow
    • src/lib/ergo/platform.ts — Refactored balance retrieval with wallet-first flow and multi-format normalization; ensure all balance sources (BigInt, string, number) are correctly parsed and normalized to bigint output
    • src/routes/ProjectDetails.svelte — New emergency verification gating, phase-based donation blocking, and withdrawal-stage management; verify phase checks, permission gates, and state synchronization with voting results
    • src/routes/CommunityVoting.svelte & src/routes/StagedWithdrawal.svelte — New components managing critical user flows; validate prop contracts, reactive state management, and error handling paths

Possibly related PRs

  • Fix: Withdrawl Issue in Contract  #57: Overlapping changes to wallet adapters, multi-token support, platform contract flows, and project type structures that complement this PR's wallet and emergency-data integrations.

Poem

🐰 A fundraiser hops with glee,
Four layers guard both you and me,
Votes cascade, stages release,
Emergency aid brings souls to peace. 🌱✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main change: it introduces a Community Voting System for emergency campaigns, which is the primary feature described across multiple new files and modifications throughout the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/lib/ergo/platform.ts (1)

83-84: Type mismatch: Map<string, number> vs BigInt values.

The return type declares Map<string, number>, but the implementation stores BigInt values (lines 144, 153). BigInt and number are incompatible types in TypeScript, and this mismatch will cause type errors or unexpected behavior in consuming code.

Additionally, the balance store (from store.ts) is typed as writable<number | null> but receives a bigint on line 145.

Apply this diff to fix the type signature:

-    async get_balance(id?: string): Promise<Map<string, number>> {
-        const balanceMap = new Map<string, number>();
+    async get_balance(id?: string): Promise<Map<string, bigint>> {
+        const balanceMap = new Map<string, bigint>();

Also update src/lib/common/store.ts:

-export const balance = writable<number | null>(null);
+export const balance = writable<bigint | null>(null);
src/routes/NewProject.svelte (1)

628-643: Emergency data is intentionally simplified for blockchain submission, but critical verification fields are being lost.

The form collects emergencyLocation, hospitalAuthorityName, documentTypes, and documentDescription, along with IPFS hashes in emergencyDocuments, but only type, community, phase, and votes are included in the on-chain payload (lines 628-640). While the comment acknowledges blockchain box size constraints, the omitted fields are necessary for the community voting workflow—CommunityVoting.svelte displays documentDescription and documentHashes, but these are never transmitted. Either:

  1. Store these fields off-chain (e.g., IPFS with hash on-chain), similar to how documents are handled
  2. Remove the form inputs if they're not intended for use
  3. Document clearly that location anchoring and document types are verified client-side only
🧹 Nitpick comments (23)
.gitignore (2)

18-23: Remove duplicate .DS_Store entry.

The .DS_Store pattern appears in both the IDE section (line 18) and OS section (line 34). Consolidate to a single location to improve maintainability.

Apply this diff to remove the duplicate:

 # IDE
 .DS_Store
 .idea/
 .vscode/
 *.swp
 *.swo
 *~

 # Logs
 *.log
 npm-debug.log*
 yarn-debug.log*
 yarn-error.log*
 lerna-debug.log*

 # OS
-Thumbs.db
-.DS_Store
+Thumbs.db

Also applies to: 32-35


55-56: Clarify the purpose of .service/ directory.

The .service/ directory entry is unclear without context. Add a comment explaining what this directory contains (e.g., mock services, local service files) to help maintainers understand why it should be ignored.

For example:

 # Local Development
+# Local service mocks and data
 .service/
 local-data/
src/lib/ergo/platform.ts (1)

101-116: Consider adding a type for wallet balance response.

Using any type (line 102) reduces type safety. Consider defining an interface for the expected wallet balance formats to improve maintainability and catch issues at compile time.

interface WalletBalanceResponse {
    nanoErgs?: bigint | string | number;
    balance?: string;
    tokens?: Array<{ tokenId: string; amount: string | bigint }>;
}
src/routes/App.svelte (1)

347-352: Alternative wallet connection block appears intended for debugging.

This always-visible banner with hardcoded light-mode colors won't adapt to the app's dark theme (note the existing mode-watcher integration). The message "If the main wallet button isn't working" suggests this is a fallback for development/debugging purposes.

Consider:

  1. Making this conditional (e.g., only show when wallet connection fails or via a dev flag)
  2. Using theme-aware styles consistent with the rest of the app
  3. Removing before production if the main wallet button is stable
-    <!-- Direct Wallet Connect (Alternative Method) -->
-    <div style="background: #f0f9ff; border: 2px solid #3b82f6; padding: 1rem; margin: 1rem auto; max-width: 600px; border-radius: 0.5rem; text-align: center;">
-        <h3 style="margin: 0 0 0.5rem 0; color: #1e40af;">Alternative Wallet Connection</h3>
-        <p style="margin: 0 0 1rem 0; font-size: 0.875rem; color: #475569;">If the main wallet button isn't working, try this direct connection method:</p>
-        <DirectWalletConnect />
-    </div>
+    <!-- Direct Wallet Connect - Remove or conditionally render before production -->
+    {#if !$walletConnected}
+        <div class="alternative-wallet-banner">
+            <h3>Alternative Wallet Connection</h3>
+            <p>If the main wallet button isn't working, try this direct connection method:</p>
+            <DirectWalletConnect />
+        </div>
+    {/if}

Then add theme-aware styles in the <style> section.

src/lib/common/project.ts (1)

55-62: ProjectContent interface duplication is intentional but could be documented better.

Two separate ProjectContent interfaces exist:

  • src/lib/ergo/utils/box-size-calculator.ts (lines 30-35): Contains core fields for validation (title, description, image, link)
  • src/lib/common/project.ts (lines 55-62): Contains full project data including raw JSON and emergency field

The duplication is intentional—submit.ts uses the lighter box-size-calculator version for validation since only the core four fields are needed for size calculations. The full interface with the emergency field is used elsewhere via getProjectContent() in fetch.ts. However, this design could be clearer: consider adding a comment explaining why two interfaces exist, or unify them with optional fields to reduce confusion.

src/lib/components/DirectWalletConnect.svelte (2)

96-99: Potential runtime error when walletAddress is empty.

If connected becomes true but walletAddress is somehow an empty string, the substring calls at line 98 will produce incorrect output (e.g., ... with no address prefix). While this is unlikely given the current flow, consider adding a guard:

 {:else}
     <div class="connected-info">
         <span class="success-icon">✅</span>
-        <span class="address">{walletAddress.substring(0, 8)}...{walletAddress.substring(walletAddress.length - 6)}</span>
+        <span class="address">
+            {walletAddress.length > 14 
+                ? `${walletAddress.substring(0, 8)}...${walletAddress.substring(walletAddress.length - 6)}`
+                : walletAddress}
+        </span>
     </div>
 {/if}

35-54: Consider adding a timeout for wallet connection.

The nautilus.connect() and subsequent API calls could hang indefinitely if the wallet extension is unresponsive or the user doesn't interact with the popup. Consider wrapping these in a timeout:

// Helper to add timeout to promises
const withTimeout = <T>(promise: Promise<T>, ms: number): Promise<T> => {
    return Promise.race([
        promise,
        new Promise<T>((_, reject) => 
            setTimeout(() => reject(new Error('Connection timed out')), ms)
        )
    ]);
};

// Usage:
const nautilusApi = await withTimeout(
    (window as any).ergoConnector.nautilus.connect(), 
    30000 // 30 second timeout
);
src/routes/ProjectDetails.svelte (1)

599-652: Vote handling updates local state without persisting to blockchain.

The handleVerificationVote function updates the local project object and Svelte store, but these changes are not persisted to the blockchain. When the user refreshes the page, the voting state will be lost (only localStorage via VotingContract persists). This is documented as demo behavior, but ensure users understand this limitation.

Additionally, the error handling shows the error both in errorMessage (line 647) and via alert() (line 648), which is redundant:

         } catch (error) {
             console.error('Voting error:', error);
             errorMessage = error instanceof Error ? error.message : 'Failed to submit vote. Please try again.';
-            alert(errorMessage);
+            // errorMessage is displayed in the UI, no need for alert
         } finally {
VOTING_SYSTEM_IMPLEMENTATION.md (1)

238-247: Add language specifier to fenced code blocks for proper syntax highlighting.

Per static analysis hints, several code blocks lack language specifiers which affects rendering and syntax highlighting:

 ### Phase Transitions:
-```
+```text
 PENDING_VERIFICATION (initial)
     ↓ (votes < 20)
 UNDER_REVIEW (collecting votes)

Similarly for lines 254 and 267. This is a minor formatting improvement.

src/routes/StagedWithdrawal.svelte (1)

56-83: Direct mutation of prop objects won't trigger parent reactivity.

Directly mutating stage.status and stage.withdrawnAt (lines 71-72) modifies the prop array objects in place. While this works, it's a Svelte anti-pattern that can lead to reactivity issues - the parent component won't be notified of changes.

Consider using event dispatching or callbacks:

+import { createEventDispatcher } from "svelte";
+
+const dispatch = createEventDispatcher();
+
 async function requestWithdrawal(stage: WithdrawalStage) {
     // ...
     try {
         // ...
-        stage.status = 'withdrawn';
-        stage.withdrawnAt = Date.now();
+        dispatch('stageWithdrawn', {
+            stageNumber: stage.stageNumber,
+            status: 'withdrawn',
+            withdrawnAt: Date.now()
+        });
         // ...
     }
 }

Then the parent can handle the event and update state properly.

README.md (3)

127-131: Add language specifier to code block.

Per static analysis, this code block should have a language specifier:

 4. **Open Browser**
-```
+```text
 http://localhost:5173

---

`587-590`: **Fix empty link and bare URL.**

Static analysis flagged formatting issues:

```diff
 - **GitHub Issues:** [Report bugs or request features](https://github.com/YourUsername/emergency-fundraising-ergo/issues)
-- **Discord:** [Join our community](#) *(coming soon)*
-- **Email:** support@emergency-fundraising.io *(coming soon)*
+- **Discord:** Join our community *(coming soon)*
+- **Email:** `support@emergency-fundraising.io` *(coming soon)*

Empty links (#) and bare URLs should be avoided per markdown best practices.


516-521: Clarify Phase 2 completion status.

Lines 516-520 show checkmarks (✅) for Phase 2 items, but line 520-521 indicate ErgoScript implementation is still pending (🔄). Consider updating the checkmarks to reflect that these are "frontend ready" rather than fully complete:

 **Phase 2 (Next 3 months):** Smart Contract Enforcement
-- ✅ Implement voting logic UI (frontend ready)
-- ✅ Community voting interface with progress tracking
-- ✅ Automated stage transition UI
-- ✅ Document hash verification interface
+- ✅ Implement voting logic UI *(frontend complete)*
+- ✅ Community voting interface with progress tracking *(frontend complete)*
+- ✅ Automated stage transition UI *(frontend complete)*
+- ✅ Document hash verification interface *(frontend complete)*
 - 🔄 ErgoScript implementation (backend logic pending)
 - 🔄 Oracle integration for off-chain data

This clarifies that the backend/blockchain integration is still pending.

src/routes/CommunityVoting.svelte (4)

29-29: Potential stale state on prop changes.

votingState is initialized once at module top-level from VotingContract.getVotingState(campaignId). If campaignId prop changes, this value won't update automatically.

Consider making this reactive or reloading in onMount when the prop changes:

-    let votingState = VotingContract.getVotingState(campaignId);
+    let votingState: ReturnType<typeof VotingContract.getVotingState>;
+
+    $: {
+        votingState = VotingContract.getVotingState(campaignId);
+    }

46-57: Mutating the emergencyData prop directly may cause unexpected behavior.

Svelte props are typically treated as read-only. Mutating emergencyData.votes directly can lead to subtle bugs, especially if the parent component doesn't expect this mutation or if reactivity isn't properly triggered.

Consider using a callback prop or dispatching an event to notify the parent of vote state changes instead of direct mutation:

+    import { createEventDispatcher } from "svelte";
+    const dispatch = createEventDispatcher();
+
     function loadVotingState() {
         votingState = VotingContract.getVotingState(campaignId);
-        
-        // Update emergency data with real voting state
-        if (emergencyData.votes) {
-            emergencyData.votes.approved = votingState.approved;
-            emergencyData.votes.rejected = votingState.rejected;
-            emergencyData.votes.total = votingState.total;
-            emergencyData.votes.voters = votingState.voters;
-        } else {
-            emergencyData.votes = votingState;
-        }
+        dispatch('votingStateChanged', votingState);
     }

60-67: Race condition: checkIfUserVoted depends on $address but may run before address is available.

The function is called in onMount, but $address may still be null/undefined at that point if the wallet connection is asynchronous.

Add a reactive statement to re-check when address becomes available:

+    $: if ($address) {
+        checkIfUserVoted();
+    }
+
     function checkIfUserVoted() {
         if (!$address) return;
         
         hasVoted = VotingContract.hasUserVoted(campaignId, $address);
         if (hasVoted) {
             userVote = VotingContract.getUserVote(campaignId, $address);
         }
     }

19-19: Unused import: ErgoPlatform is instantiated but never used.

The platform variable is created but not referenced anywhere in the component.

-    import { ErgoPlatform } from "$lib/ergo/platform";
     ...
-    let platform = new ErgoPlatform();
src/routes/NewProject.svelte (3)

2563-2563: Alert used for success/error messaging is a poor UX pattern.

Using alert() for transaction results blocks the UI and doesn't allow users to copy the transaction ID easily. Consider using a toast notification or inline message instead.

Consider using a toast/snackbar component or updating the modal to show success/error states inline.


380-386: Balance validation condition may have false negatives.

The condition maxTokenAmountToSell > 0 could incorrectly skip validation when the user has a zero balance but has selected a token and entered an amount.

-        if (rewardTokenId && tokenAmountToSellPrecise > maxTokenAmountToSell && maxTokenAmountToSell > 0) {
+        if (rewardTokenId && tokenAmountToSellPrecise > 0 && tokenAmountToSellPrecise > maxTokenAmountToSell) {
             formErrors.amountExceedsBalance =
                 "Amount exceeds available balance.";
         } else {
             formErrors.amountExceedsBalance = null;
         }

119-120: Magic numbers for voting parameters should be constants or configurable.

minimumCommunityMembers: number = 20 and regionalVotingPercentage: number = 60 are hardcoded but should ideally reference the same constants used in VotingContract to ensure consistency.

Consider importing these from a shared constants file or from VotingContract:

import { VotingContract } from "$lib/ergo/voting/voting_contract";
// Then use VotingContract.MIN_VOTES_REQUIRED and VotingContract.VOTING_THRESHOLD

Note: This would require making those constants public in VotingContract.

src/lib/ergo/voting/voting_contract.ts (3)

6-6: Unused imports from @fleet-sdk/core.

ErgoAddress, OutputBuilder, TransactionBuilder, and RECOMMENDED_MIN_FEE_VALUE are imported but never used in this file. These appear to be placeholders for future on-chain implementation.

Remove unused imports to keep the code clean:

-import { ErgoAddress, OutputBuilder, TransactionBuilder, RECOMMENDED_MIN_FEE_VALUE } from "@fleet-sdk/core";
+// TODO: Import Fleet SDK when implementing on-chain voting
+// import { ErgoAddress, OutputBuilder, TransactionBuilder, RECOMMENDED_MIN_FEE_VALUE } from "@fleet-sdk/core";

107-120: getUserVote parses storage redundantly.

This method parses the same localStorage data that getVotesFromStorage handles. Consider refactoring to reuse the existing method.

     static getUserVote(campaignId: string, walletAddress: string): 'approved' | 'rejected' | null {
-        const storageKey = `${this.VOTE_STORAGE_KEY}${campaignId}`;
-        const data = localStorage.getItem(storageKey);
-        
-        if (!data) return null;
-        
-        try {
-            const votes: VoteData[] = JSON.parse(data);
-            const userVote = votes.find(v => v.voter === walletAddress);
-            return userVote ? userVote.voteType : null;
-        } catch {
-            return null;
-        }
+        const votes = this.exportVotesForBlockchain(campaignId);
+        const userVote = votes.find(v => v.voter === walletAddress);
+        return userVote ? userVote.voteType : null;
     }

33-87: submitVote accepts project parameter but only uses it for phase validation.

The project parameter is passed but underutilized. Consider if the full Project object is needed or if just the phase could be passed.

For cleaner API design:

     static async submitVote(
         campaignId: string,
-        project: Project,
+        currentPhase: CampaignPhase,
         voteType: 'approved' | 'rejected',
         walletAddress: string
     ): Promise<{ success: boolean; message: string; votingState: VotingState }> {
         ...
-            if (!project.content.emergency?.phase || 
-                (project.content.emergency.phase !== CampaignPhase.PENDING_VERIFICATION && 
-                 project.content.emergency.phase !== CampaignPhase.UNDER_REVIEW)) {
+            if (currentPhase !== CampaignPhase.PENDING_VERIFICATION && 
+                currentPhase !== CampaignPhase.UNDER_REVIEW) {
                 throw new Error("Campaign is not in verification phase");
             }

This reduces coupling and makes the API clearer about what data it actually needs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e1ac3b and 501113d.

📒 Files selected for processing (23)
  • .gitignore (1 hunks)
  • ARCHITECTURE.md (1 hunks)
  • CONTRIBUTING.md (1 hunks)
  • ENHANCED_VERIFICATION_MODEL.md (1 hunks)
  • LICENSE (1 hunks)
  • PRESENTATION_GUIDE.md (1 hunks)
  • QUICK_REFERENCE.md (1 hunks)
  • README.md (1 hunks)
  • VOTING_SYSTEM_IMPLEMENTATION.md (1 hunks)
  • src/lib/common/project.ts (2 hunks)
  • src/lib/components/DirectWalletConnect.svelte (1 hunks)
  • src/lib/ergo/platform.ts (1 hunks)
  • src/lib/ergo/voting/voting_contract.ts (1 hunks)
  • src/routes/App.svelte (2 hunks)
  • src/routes/CommunityVoting.svelte (1 hunks)
  • src/routes/NewProject.svelte (18 hunks)
  • src/routes/Phase2Demo.svelte (1 hunks)
  • src/routes/ProjectCard.svelte (5 hunks)
  • src/routes/ProjectDetails.svelte (7 hunks)
  • src/routes/ProjectList.svelte (1 hunks)
  • src/routes/StagedWithdrawal.svelte (1 hunks)
  • svelte.config.js (1 hunks)
  • vercel.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/lib/ergo/voting/voting_contract.ts (1)
src/lib/common/project.ts (1)
  • Project (104-129)
src/lib/ergo/platform.ts (1)
src/lib/common/store.ts (1)
  • balance (9-9)
src/lib/common/project.ts (1)
src/lib/ergo/utils/box-size-calculator.ts (1)
  • ProjectContent (30-35)
🪛 LanguageTool
ARCHITECTURE.md

[style] ~231-~231: The double modal “Requires verified” is nonstandard (only accepted in certain dialects). Consider “to be verified”.
Context: ...ts Voting: - ⚠️ (Planned) Requires verified community membership - ⚠️ (Planned) One...

(NEEDS_FIXED)

CONTRIBUTING.md

[style] ~456-~456: ‘Emergency situations’ might be wordy. Consider a shorter alternative.
Context: ... ### Design Principles 1. Clarity: Emergency situations need clear, simple interfaces 2. **Trus...

(EN_WORDINESS_PREMIUM_EMERGENCY_SITUATIONS)

QUICK_REFERENCE.md

[style] ~208-~208: This phrasing can be overused. Try elevating your writing with a more formal alternative.
Context: ...so communities can fork and remove fees if they want. Sustainability comes from scale, not h...

(IF_YOU_WANT)

PRESENTATION_GUIDE.md

[style] ~142-~142: For conciseness, consider replacing this expression with an adverb.
Context: ...n-chain for smart contract enforcement. Right now, the innovation is in the architectural...

(AT_THE_MOMENT)


[uncategorized] ~316-~316: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...* is acceptable - Offer to research and follow up - Redirect to what you DO know - Stay c...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[style] ~408-~408: ‘future plans’ might be wordy. Consider a shorter alternative.
Context: ...proud, be honest about current state vs future plans, and focus on the innovation of the DES...

(EN_WORDINESS_PREMIUM_FUTURE_PLANS)

🪛 markdownlint-cli2 (0.18.1)
ARCHITECTURE.md

5-5: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


28-28: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


72-72: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


79-79: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


85-85: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


95-95: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


150-150: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


161-161: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


175-175: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


188-188: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


195-195: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


203-203: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


292-292: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


317-317: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


323-323: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

ENHANCED_VERIFICATION_MODEL.md

65-65: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


66-66: Unordered list indentation
Expected: 0; Actual: 2

(MD007, ul-indent)


191-191: Bare URL used

(MD034, no-bare-urls)


201-201: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


217-217: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

CONTRIBUTING.md

9-9: Bare URL used

(MD034, no-bare-urls)


53-53: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


59-59: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


82-82: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


89-89: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


252-252: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


257-257: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


262-262: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


365-365: Bare URL used

(MD034, no-bare-urls)


368-368: Bare URL used

(MD034, no-bare-urls)


410-410: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

README.md

128-128: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


282-282: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


324-324: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


331-331: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


588-588: No empty links

(MD042, no-empty-links)


589-589: Bare URL used

(MD034, no-bare-urls)


621-621: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


623-623: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

QUICK_REFERENCE.md

6-6: Bare URL used

(MD034, no-bare-urls)


143-143: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


144-144: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


150-150: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


151-151: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


158-158: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


159-159: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


166-166: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


167-167: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


173-173: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


174-174: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


198-198: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


201-201: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


204-204: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


207-207: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


215-215: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


224-224: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


240-240: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


301-301: Bare URL used

(MD034, no-bare-urls)


321-321: No space after hash on atx style heading

(MD018, no-missing-space-atx)


325-325: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

VOTING_SYSTEM_IMPLEMENTATION.md

238-238: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


254-254: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


267-267: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


399-399: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (19)
.gitignore (1)

49-52: Good security practice for wallet and seed file protection.

The wallet and seed ignores with the "NEVER COMMIT!" warning are appropriate. The codebase integrates with external wallet libraries (wallet-svelte-component) and does not generate .key, .pem, or *.privateKey files locally, so the current patterns (*.wallet, *.seed, mnemonic.txt) are sufficient for this project. If you extend wallet functionality to generate additional key file formats in the future, add corresponding patterns to .gitignore at that time.

LICENSE (1)

1-21: LGTM!

Standard MIT License with appropriate copyright holder. The license text is complete and correctly formatted.

vercel.json (1)

1-11: Configuration looks correct for static SPA deployment.

The settings align with svelte.config.js (output to build, index.html fallback). Note that Vercel typically auto-detects SvelteKit projects, so this explicit configuration may be redundant unless you're overriding specific defaults.

QUICK_REFERENCE.md (2)

5-9: Verify the GitHub repository URL is correct.

Line 6 points to SUHANI-PAL0103/BenefactionPlatform-Ergo, but the PR is targeting StabilityNexus/BenefactionPlatform-Ergo. If this documentation is for the main project, consider updating to the organization repository URL for consistency.


1-325: Well-structured quick reference documentation.

The content provides comprehensive presentation guidance including demo flows, Q&A preparation, and checklists. The markdown formatting warnings from static analysis (bare URLs, missing code block languages) are minor stylistic issues that don't affect usability.

CONTRIBUTING.md (2)

481-506: Verify contact information before publishing.

The security and support contact details contain placeholder or potentially non-existent addresses:

  • security@emergency-fundraising.io (Line 486)
  • dev@emergency-fundraising.io (Line 505)
  • Discord link is just # (Line 504)

Ensure these channels exist and are monitored, or update to valid contact methods.


1-524: Comprehensive contributing guidelines.

The document covers prerequisites, development workflow, code style, testing, PR process, and security reporting effectively. The structure follows industry best practices for open-source contribution guides.

svelte.config.js (1)

14-21: LGTM!

The adapter configuration correctly sets up a static SPA build with index.html fallback for client-side routing. The strict: true option will catch any prerendering issues during build. Settings align with vercel.json.

ARCHITECTURE.md (1)

1-356: Excellent architecture documentation.

Comprehensive coverage of system components, data flows, security considerations, and implementation status. The honest status indicators (✅/⚠️/❌) clearly communicate what's implemented vs. planned, which is valuable for contributors and reviewers.

ENHANCED_VERIFICATION_MODEL.md (1)

1-217: Well-documented verification model.

The 4-step verification flow is clearly explained with academic justifications and practical implementation status. The talking points section is particularly useful for presentations. The privacy considerations (approximate location vs. GPS, hashes vs. raw files) demonstrate thoughtful design.

src/lib/ergo/platform.ts (1)

118-159: Robust multi-format balance handling.

The logic correctly handles various wallet balance response formats (nanoErgs object, balance string, direct bigint). The token normalization to BigInt ensures consistent handling across different wallet implementations.

src/routes/ProjectList.svelte (1)

24-24: Verify intentional default change for production.

Changing hideTestProjects from true to false means test campaigns will now be visible by default to all users. While this is helpful during development and testing of the voting system, consider whether this should be reverted before merging to production, or if there's a reason to keep test campaigns visible to end users.

src/lib/common/project.ts (2)

12-53: Well-structured types for the voting and withdrawal system.

The CampaignPhase enum, WithdrawalStage, and EmergencyData interfaces are well-designed for the fraud prevention system. The voter tracking arrays enable double-vote prevention as intended.


159-161: LGTM - Emergency data extraction.

The extraction logic correctly handles the optional emergency field, falling back to undefined when not present.

src/routes/Phase2Demo.svelte (1)

1-84: Well-organized demo component.

The demo data structures are comprehensive and correctly typed against the EmergencyData and WithdrawalStage interfaces. The toggle between voting and withdrawal modes provides a clear demonstration of Phase 2 features.

src/routes/ProjectCard.svelte (3)

33-36: BigInt to Number conversion is safe for typical balances.

The conversion Number($balance) works correctly for ERG balances since they won't exceed Number.MAX_SAFE_INTEGER (~9 quadrillion nanoERGs, far beyond circulating supply). Good fix for the BigInt display issue.


120-158: Emergency status displayed in two places.

The emergency phase is shown both in the header (lines 120-158) as status badges and in the content area (lines 258-272) as a donation-blocked alert. For PENDING_VERIFICATION, UNDER_REVIEW, and REJECTED phases, users see both indicators simultaneously.

If this is intentional (header shows campaign phase, content emphasizes donation blocking), the current approach is fine. If not, consider consolidating to reduce visual redundancy.

Also applies to: 258-272


346-381: Well-structured CSS for donation status alerts.

The new .donation-status-alert styles with .pending and .rejected variants are clean and consistent with the existing badge styling patterns.

src/lib/ergo/voting/voting_contract.ts (1)

17-23: VotingState.voters stores wallet addresses which could be PII.

Storing wallet addresses in localStorage as an array of voters could have privacy implications. While wallet addresses are pseudo-anonymous, the pattern of storing them locally and potentially exporting them should be considered.

Verify if storing voter addresses locally aligns with the project's privacy requirements and if users are informed about this data collection.

Comment thread CONTRIBUTING.md
### Reporting Bugs

**Before submitting a bug report:**
- Check if the bug has already been reported in [Issues](https://github.com/YourUsername/emergency-fundraising-ergo/issues)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update placeholder repository URL.

The Issues link contains YourUsername placeholder. Update to the actual repository URL (e.g., StabilityNexus/BenefactionPlatform-Ergo).

🤖 Prompt for AI Agents
In CONTRIBUTING.md around line 43, the Issues link uses the placeholder
`YourUsername`; update that markdown link to point to the real repository (for
example replace
`https://github.com/YourUsername/emergency-fundraising-ergo/issues` with
`https://github.com/StabilityNexus/BenefactionPlatform-Ergo/issues`) so the link
resolves correctly to the project's Issues page.

Comment on lines +189 to +196
## 🚀 Next Steps

1. **Test the new form fields** at http://localhost:5174/
2. **Navigate to "New Campaign"** tab
3. **Fill out all enhanced fields**
4. **Show your teacher** the structured verification approach
5. **Explain the anti-corruption logic** using this document

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Port number inconsistency.

Line 191 references localhost:5174, but other documentation (QUICK_REFERENCE.md) references localhost:5173. Verify which port is correct and update for consistency.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

191-191: Bare URL used

(MD034, no-bare-urls)

🤖 Prompt for AI Agents
In ENHANCED_VERIFICATION_MODEL.md around lines 189 to 196, the doc references
http://localhost:5174 which is inconsistent with QUICK_REFERENCE.md's
localhost:5173; verify the correct dev server port (check package.json scripts,
Vite/DevServer config, or QUICK_REFERENCE.md source) and update this file to use
the confirmed port so both docs match.

Comment thread PRESENTATION_GUIDE.md
Comment on lines +5 to +9
**Student:** Suhani Pal
**Institution:** [Your Institution]
**Project Type:** Hackathon/Thesis Project
**Date:** December 2025
**GitHub:** https://github.com/SUHANI-PAL0103/BenefactionPlatform-Ergo
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fill in placeholder values before merging.

The document contains several placeholders that should be completed:

  • Line 6: [Your Institution]
  • Line 9: GitHub URL points to a personal fork rather than the main repository
 **Student:** Suhani Pal  
-**Institution:** [Your Institution]  
+**Institution:** <actual institution name>  
 **Project Type:** Hackathon/Thesis Project  
 **Date:** December 2025  
-**GitHub:** https://github.com/SUHANI-PAL0103/BenefactionPlatform-Ergo
+**GitHub:** https://github.com/StabilityNexus/BenefactionPlatform-Ergo
📝 Committable suggestion

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

Suggested change
**Student:** Suhani Pal
**Institution:** [Your Institution]
**Project Type:** Hackathon/Thesis Project
**Date:** December 2025
**GitHub:** https://github.com/SUHANI-PAL0103/BenefactionPlatform-Ergo
**Student:** Suhani Pal
**Institution:** <actual institution name>
**Project Type:** Hackathon/Thesis Project
**Date:** December 2025
**GitHub:** https://github.com/StabilityNexus/BenefactionPlatform-Ergo
🤖 Prompt for AI Agents
In PRESENTATION_GUIDE.md around lines 5 to 9, replace the placeholder “[Your
Institution]” with the actual institution name on line 6 and update the GitHub
URL on line 9 to point to the canonical/main repository (not a personal fork);
also scan the file for any other placeholder values (names, dates, links) and
update them to final, accurate values before merging.

Comment thread PRESENTATION_GUIDE.md
Comment on lines +374 to +379

**Contact:**
- GitHub: @SUHANI-PAL0103
- Email: [Your Email]
- LinkedIn: [Your Profile]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Additional placeholders need completion.

 **Contact:**
 - GitHub: @SUHANI-PAL0103
-- Email: [Your Email]
-- LinkedIn: [Your Profile]
+- Email: <actual email>
+- LinkedIn: <actual profile URL>
📝 Committable suggestion

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

Suggested change
**Contact:**
- GitHub: @SUHANI-PAL0103
- Email: [Your Email]
- LinkedIn: [Your Profile]
**Contact:**
- GitHub: @SUHANI-PAL0103
- Email: <actual email>
- LinkedIn: <actual profile URL>
🤖 Prompt for AI Agents
In PRESENTATION_GUIDE.md around lines 374 to 379, the contact section contains
placeholder values; replace [Your Email] and [Your Profile] with your actual
professional email address and LinkedIn URL, and ensure the GitHub handle is
correct (e.g., @SUHANI-PAL0103) or update it to the real handle; keep the format
consistent (plain text or markdown links), verify links work, and avoid
including any personal information you don't want published.

Comment on lines +146 to +152
// Check if definitively rejected (even if more votes come, can't reach threshold)
const remainingVotes = 100 - votingState.total; // Hypothetical max votes
const maxPossibleApproval = ((votingState.approved + remainingVotes) / 100) * 100;

if (maxPossibleApproval < this.VOTING_THRESHOLD) {
return CampaignPhase.REJECTED;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Rejection calculation logic is flawed.

The remainingVotes = 100 - votingState.total assumes a maximum of 100 total votes, which is an arbitrary limit not enforced elsewhere. This could cause campaigns to be incorrectly marked as REJECTED.

The logic for determining if a campaign is "definitively rejected" needs reconsideration. With unlimited voters, a campaign can theoretically always recover:

-        // Check if definitively rejected (even if more votes come, can't reach threshold)
-        const remainingVotes = 100 - votingState.total; // Hypothetical max votes
-        const maxPossibleApproval = ((votingState.approved + remainingVotes) / 100) * 100;
-        
-        if (maxPossibleApproval < this.VOTING_THRESHOLD) {
-            return CampaignPhase.REJECTED;
-        }
+        // Only reject if enough votes have been cast AND approval is below threshold
+        // Consider adding a maximum voting period or vote cap for definitive rejection
+        if (votingState.total >= this.MIN_VOTES_REQUIRED) {
+            const approvalPercentage = (votingState.approved / votingState.total) * 100;
+            if (approvalPercentage < (100 - this.VOTING_THRESHOLD)) {
+                // If rejection percentage exceeds what's needed to block approval
+                return CampaignPhase.REJECTED;
+            }
+        }

Alternatively, document the intended behavior if 100 is the intended cap.

🤖 Prompt for AI Agents
In src/lib/ergo/voting/voting_contract.ts around lines 146-152, the rejection
check uses a hardcoded 100-vote cap (remainingVotes = 100 - votingState.total)
which is incorrect; replace that with the real source of maximum possible votes
or remove the definitive-reject branch: either (A) if the system enforces a max
voters cap, read that cap from the campaign config (e.g. campaign.maxVoters or a
constant) and compute remainingVotes = maxVoters - votingState.total and then
calculate maxPossibleApproval from that; or (B) if there is no cap, remove the
“definitively rejected” logic and only determine REJECTED when the voting period
is closed or when quorum/threshold rules based on actual votes are met;
implement the chosen approach consistently and add a comment documenting the
expectation (cap vs. unlimited voters).

Comment on lines +706 to +728
const totalVotes = (stage.votesFor || 0) + (stage.votesAgainst || 0);
const approvalRate = (stage.votesFor || 0) / totalVotes * 100;

// Check if enough votes to finalize (minimum 5 votes)
if (totalVotes >= 5) {
if (approvalRate >= 60) {
stage.status = 'approved';
stage.approvedAt = Date.now();
// Set timelock based on stage
const delays = [0, 7 * 24 * 60 * 60 * 1000, 14 * 24 * 60 * 60 * 1000]; // 0, 7, 14 days in ms
stage.timelock = Date.now() + delays[stageNumber - 1];
alert(`✅ Stage ${stageNumber} APPROVED!\nTime lock: ${delays[stageNumber - 1] / (24 * 60 * 60 * 1000)} days`);
} else if (approvalRate < 40) {
stage.status = 'rejected';
alert(`❌ Stage ${stageNumber} REJECTED by community.`);
}
}

// Update project
project = { ...project };
project_detail.set(project);

alert(`Vote recorded: ${voteType === 'approve' ? '✓ Approved' : '✕ Rejected'}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential division by zero and multiple alert() calls.

  1. Division by zero: If both votesFor and votesAgainst are 0 (shouldn't happen after vote, but defensive coding helps):
-        const approvalRate = (stage.votesFor || 0) / totalVotes * 100;
+        const approvalRate = totalVotes > 0 ? ((stage.votesFor || 0) / totalVotes) * 100 : 0;
  1. Multiple alerts: Lines 717, 720 show status-specific alerts, then line 728 shows another "Vote recorded" alert. The user sees two alerts in succession:
             if (totalVotes >= 5) {
                 if (approvalRate >= 60) {
                     stage.status = 'approved';
                     stage.approvedAt = Date.now();
                     const delays = [0, 7 * 24 * 60 * 60 * 1000, 14 * 24 * 60 * 60 * 1000];
                     stage.timelock = Date.now() + delays[stageNumber - 1];
                     alert(`✅ Stage ${stageNumber} APPROVED!\nTime lock: ${delays[stageNumber - 1] / (24 * 60 * 60 * 1000)} days`);
+                    return; // Skip generic alert
                 } else if (approvalRate < 40) {
                     stage.status = 'rejected';
                     alert(`❌ Stage ${stageNumber} REJECTED by community.`);
+                    return; // Skip generic alert
                 }
             }
             
             // ... project update code ...
             
-            alert(`Vote recorded: ${voteType === 'approve' ? '✓ Approved' : '✕ Rejected'}`);
+            // Only show generic alert if no status change alert was shown
+            if (stage.status === 'pending') {
+                alert(`Vote recorded: ${voteType === 'approve' ? '✓ Approved' : '✕ Rejected'}`);
+            }
📝 Committable suggestion

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

Suggested change
const totalVotes = (stage.votesFor || 0) + (stage.votesAgainst || 0);
const approvalRate = (stage.votesFor || 0) / totalVotes * 100;
// Check if enough votes to finalize (minimum 5 votes)
if (totalVotes >= 5) {
if (approvalRate >= 60) {
stage.status = 'approved';
stage.approvedAt = Date.now();
// Set timelock based on stage
const delays = [0, 7 * 24 * 60 * 60 * 1000, 14 * 24 * 60 * 60 * 1000]; // 0, 7, 14 days in ms
stage.timelock = Date.now() + delays[stageNumber - 1];
alert(`✅ Stage ${stageNumber} APPROVED!\nTime lock: ${delays[stageNumber - 1] / (24 * 60 * 60 * 1000)} days`);
} else if (approvalRate < 40) {
stage.status = 'rejected';
alert(`❌ Stage ${stageNumber} REJECTED by community.`);
}
}
// Update project
project = { ...project };
project_detail.set(project);
alert(`Vote recorded: ${voteType === 'approve' ? '✓ Approved' : '✕ Rejected'}`);
const totalVotes = (stage.votesFor || 0) + (stage.votesAgainst || 0);
const approvalRate = totalVotes > 0 ? ((stage.votesFor || 0) / totalVotes) * 100 : 0;
// Check if enough votes to finalize (minimum 5 votes)
if (totalVotes >= 5) {
if (approvalRate >= 60) {
stage.status = 'approved';
stage.approvedAt = Date.now();
// Set timelock based on stage
const delays = [0, 7 * 24 * 60 * 60 * 1000, 14 * 24 * 60 * 60 * 1000]; // 0, 7, 14 days in ms
stage.timelock = Date.now() + delays[stageNumber - 1];
alert(`✅ Stage ${stageNumber} APPROVED!\nTime lock: ${delays[stageNumber - 1] / (24 * 60 * 60 * 1000)} days`);
} else if (approvalRate < 40) {
stage.status = 'rejected';
alert(`❌ Stage ${stageNumber} REJECTED by community.`);
}
}
// Update project
project = { ...project };
project_detail.set(project);
alert(`Vote recorded: ${voteType === 'approve' ? '✓ Approved' : '✕ Rejected'}`);
🤖 Prompt for AI Agents
In src/routes/ProjectDetails.svelte around lines 706 to 728, calculate
totalVotes and guard against division by zero by only computing approvalRate
when totalVotes > 0 (otherwise set approvalRate = 0 or skip approval checks),
then only show one user alert: either the status-specific alert
(approved/rejected) or the generic "Vote recorded" message but not both —
implement logic to suppress the generic alert when a status alert was already
shown (e.g., track a boolean like statusAlertShown), and keep timelock
assignment as-is; ensure approvalRate uses the guarded value so no NaN/Infinity
occurs.

Comment on lines +772 to +776
// Move to next stage
const nextStage = project.content.emergency.withdrawalStages[stageNumber];
if (nextStage && nextStage.status === 'pending') {
project.content.emergency.currentStage = stageNumber + 1;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential off-by-one error in stage progression logic.

The stageNumber is 1-based, so accessing withdrawalStages[stageNumber] after withdrawing stage 1 would access index 1 (stage 2), which is correct. However, the condition nextStage.status === 'pending' may fail since stages start as 'pending' but this logic only triggers when a stage is withdrawn. Consider:

             // Move to next stage
-            const nextStage = project.content.emergency.withdrawalStages[stageNumber];
-            if (nextStage && nextStage.status === 'pending') {
-                project.content.emergency.currentStage = stageNumber + 1;
-            }
+            if (stageNumber < project.content.emergency.withdrawalStages.length) {
+                project.content.emergency.currentStage = stageNumber;
+            }

Note: currentStage semantics should be clarified - is it 0-indexed or 1-indexed? The current code sets it to stageNumber + 1 after withdrawing stage stageNumber, which could exceed array bounds for the last stage.

📝 Committable suggestion

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

Suggested change
// Move to next stage
const nextStage = project.content.emergency.withdrawalStages[stageNumber];
if (nextStage && nextStage.status === 'pending') {
project.content.emergency.currentStage = stageNumber + 1;
}
// Move to next stage
if (stageNumber < project.content.emergency.withdrawalStages.length) {
project.content.emergency.currentStage = stageNumber;
}
🤖 Prompt for AI Agents
In src/routes/ProjectDetails.svelte around lines 772–776, the progression logic
assumes stageNumber indexing but risks off-by-one and out-of-bounds when
advancing currentStage; treat stageNumber as 1-based: compute nextIndex =
stageNumber (numeric) and only access withdrawalStages[nextIndex] if nextIndex <
withdrawalStages.length; if nextStage exists and its status === 'pending' set
currentStage = stageNumber + 1 (1-based), otherwise if no next stage set
currentStage to withdrawalStages.length (or null/finished sentinel) to avoid
exceeding array bounds; add a comment clarifying that currentStage is 1-indexed.

Comment on lines +1255 to +1270
{:else}
<!-- Normal Actions (only shown if not blocked) -->
<div class="action-buttons">
{#if verificationPending}
<Alert class="bg-yellow-50 dark:bg-yellow-900/20 border-yellow-500 mb-4">
<AlertDescription>
⏳ <strong>Verification Pending:</strong> This emergency campaign is currently under community review. Donations will be enabled once verification reaches 60% approval with minimum 20 votes.
</AlertDescription>
</Alert>
{:else if verificationRejected}
<Alert class="bg-red-50 dark:bg-red-900/20 border-red-500 mb-4">
<AlertDescription>
❌ <strong>Verification Failed:</strong> This campaign did not pass community verification. Donations are disabled.
</AlertDescription>
</Alert>
{/if}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unreachable code: verificationPending alert inside else block.

The {:else} block at line 1255 only executes when the campaign is NOT in PENDING_VERIFICATION, UNDER_REVIEW, or REJECTED phases. However, lines 1258-1263 check verificationPending which would only be true for those exact phases. This alert will never be displayed:

             {:else}
                 <!-- Normal Actions (only shown if not blocked) -->
                 <div class="action-buttons">
-                    {#if verificationPending}
-                        <Alert class="bg-yellow-50 dark:bg-yellow-900/20 border-yellow-500 mb-4">
-                            <AlertDescription>
-                                ⏳ <strong>Verification Pending:</strong> ...
-                            </AlertDescription>
-                        </Alert>
-                    {:else if verificationRejected}
-                        <Alert class="bg-red-50 dark:bg-red-900/20 border-red-500 mb-4">
-                            <AlertDescription>
-                                ❌ <strong>Verification Failed:</strong> ...
-                            </AlertDescription>
-                        </Alert>
-                    {/if}
+                    <!-- Alerts moved to verification-block sections above -->

The verification alerts are already shown in the verification-block divs at lines 1227-1254.

🤖 Prompt for AI Agents
In src/routes/ProjectDetails.svelte around lines 1255 to 1270, the
verificationPending/verificationRejected Alert block is placed inside an {:else}
that only runs when the campaign is NOT in
PENDING_VERIFICATION/UNDER_REVIEW/REJECTED, so those alerts are unreachable and
duplicate the alerts in the verification-block (lines 1227–1254); remove this
unreachable verification alert block (or move its condition out of the {:else}
into the main flow) so verification messages are only rendered once in the
verification-block and the {:else} only contains actions relevant when the
campaign is not under verification/rejected.

Comment on lines +85 to +91
async function submitProof() {
if (!$walletConnected || !selectedStage) return;

if (proofHashes.length === 0) {
errorMessage = "Please add at least one proof document";
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing validation for required proofDescription field.

The UI marks "Proof Description" as required (line 362: *), but the validation only checks proofHashes:

     async function submitProof() {
         if (!$walletConnected || !selectedStage) return;

         if (proofHashes.length === 0) {
             errorMessage = "Please add at least one proof document";
             return;
         }

+        if (!proofDescription.trim()) {
+            errorMessage = "Please provide a proof description";
+            return;
+        }
+
         submittingProof = true;
📝 Committable suggestion

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

Suggested change
async function submitProof() {
if (!$walletConnected || !selectedStage) return;
if (proofHashes.length === 0) {
errorMessage = "Please add at least one proof document";
return;
}
async function submitProof() {
if (!$walletConnected || !selectedStage) return;
if (proofHashes.length === 0) {
errorMessage = "Please add at least one proof document";
return;
}
if (!proofDescription.trim()) {
errorMessage = "Please provide a proof description";
return;
}
🤖 Prompt for AI Agents
In src/routes/StagedWithdrawal.svelte around lines 85-91, the submitProof
function currently only validates proofHashes; add a check that proofDescription
is present (non-empty after trimming) and, if missing, set errorMessage to
something like "Please provide a proof description" and return. Ensure this
validation runs alongside the existing proofHashes check (order can be either
before or after) so the UI-required field prevents submission when empty.

Comment on lines +219 to +224
<div class="w-full bg-gray-200 rounded-full h-2">
<div
class="bg-green-500 h-2 rounded-full"
style="width: {(stage.votesFor / (stage.votesFor + stage.votesAgainst)) * 100}%"
></div>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Division by zero when displaying vote progress bar.

If both votesFor and votesAgainst are 0 (which can happen when stage is first requested per line 105-106), this calculation will divide by zero:

                             <div class="w-full bg-gray-200 rounded-full h-2">
                                 <div 
                                     class="bg-green-500 h-2 rounded-full"
-                                    style="width: {(stage.votesFor / (stage.votesFor + stage.votesAgainst)) * 100}%"
+                                    style="width: {(stage.votesFor + stage.votesAgainst) > 0 
+                                        ? (stage.votesFor / (stage.votesFor + stage.votesAgainst)) * 100 
+                                        : 0}%"
                                 ></div>
                             </div>
📝 Committable suggestion

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

Suggested change
<div class="w-full bg-gray-200 rounded-full h-2">
<div
class="bg-green-500 h-2 rounded-full"
style="width: {(stage.votesFor / (stage.votesFor + stage.votesAgainst)) * 100}%"
></div>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div
class="bg-green-500 h-2 rounded-full"
style="width: {(stage.votesFor + stage.votesAgainst) > 0
? (stage.votesFor / (stage.votesFor + stage.votesAgainst)) * 100
: 0}%"
></div>
</div>
🤖 Prompt for AI Agents
In src/routes/StagedWithdrawal.svelte around lines 219-224 the inline style
computes width as (stage.votesFor / (stage.votesFor + stage.votesAgainst)) * 100
which will divide by zero when both counts are 0; change it to compute a safe
percent value first (e.g. let total = stage.votesFor + stage.votesAgainst; let
percent = total > 0 ? (stage.votesFor / total) * 100 : 0) and use that percent
in the style so you never divide by zero and the progress bar shows 0% when
there are no votes.

@0xf965
Copy link
Copy Markdown
Contributor

0xf965 commented Dec 14, 2025

Hey @SUHANI-PAL0103 Could you explain a little more this idea?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants