Skip to content

Ecovault.finance-Protocol is an advanced decentralized crowdfunding platform built on the Ethereum blockchain

Notifications You must be signed in to change notification settings

olujimiAdebakin/Ecovault.finance-Protocol

Repository files navigation

🌱 EcoVault Finance Protocol

Decentralized Crowdfunding Platform for Climate & Social Impact Projects

Solidity Foundry EIP-2535 License: MIT Chainlink


📖 Table of Contents


🌍 Overview

EcoVault Finance is a next-generation decentralized crowdfunding platform built on Ethereum, designed specifically for climate action and social impact projects. By leveraging the EIP-2535 Diamond Proxy Standard, the platform achieves unparalleled modularity, upgradeability, and gas efficiency.

The Problem We Solve

Traditional crowdfunding platforms suffer from:

  • ❌ High platform fees (5-10%)
  • ❌ Geographic restrictions
  • ❌ Lack of transparency in fund usage
  • ❌ Limited donor governance
  • ❌ No verification of project completion

Our Solution

EcoVault Finance provides:

  • Zero platform fees (only gas costs)
  • Global accessibility (permissionless, borderless)
  • Milestone-based funding (pay-as-you-deliver)
  • DAO governance (donors vote on fund releases)
  • Oracle-verified evidence (proof of work completion)
  • Decentralized identity (KYC/KYB via ONCHAINID)

✨ Key Features

🏗️ Modular Architecture (EIP-2535 Diamond)

  • Upgradeable without redeploying the entire system
  • Add/remove features seamlessly
  • Gas-efficient function routing
  • Single contract address for all interactions

🆔 Decentralized Identity (ONCHAINID)

  • KYC (Know Your Customer): For individual donors
  • KYB (Know Your Business): For campaign creators
  • ERC-734/ERC-735 compliant
  • Privacy-preserving claims system
  • Trusted issuer verification

💰 Milestone-Based Funding

  • Funds locked until milestones are completed
  • Evidence submission required for each milestone
  • DAO voting on milestone approvals
  • Automatic refunds if campaign fails

🗳️ DAO Governance

  • Proportional voting power based on donations
  • On-chain voting for milestone approvals
  • Community-driven decision making
  • Tier-based membership system

🤖 Chainlink Automation

  • Automatic campaign finalization at deadline
  • Scheduled milestone reviews
  • Oracle-based evidence verification
  • AI-powered proof of work validation

🔒 Enhanced Security

  • OpenZeppelin battle-tested contracts
  • Reentrancy protection
  • Access control modifiers
  • Pull-over-push payment pattern

🌐 Multi-Token Support

  • Native ETH donations
  • Any ERC-20 token support
  • Configurable per campaign
  • Automatic token handling

🏛️ Architecture

System Design

┌─────────────────────────────────────────────────────────────┐
│                    Diamond Proxy (EIP-2535)                  │
│                  Single Entry Point Contract                 │
└─────────────────────┬───────────────────────────────────────┘
                      │
         ┌────────────┼────────────┬─────────────┐
         │            │            │             │
    ┌────▼────┐  ┌───▼────┐  ┌───▼────┐   ┌────▼─────┐
    │Identity │  │Campaign│  │Campaign│   │Management│
    │ Facet   │  │Factory │  │Instance│   │  Facet   │
    └────┬────┘  └───┬────┘  └───┬────┘   └────┬─────┘
         │           │            │             │
         └───────────┴────────────┴─────────────┘
                      │
         ┌────────────┴────────────┐
         │                         │
    ┌────▼─────┐            ┌─────▼────┐
    │ONCHAINID │            │Chainlink │
    │ Identity │            │ Oracles  │
    └──────────┘            └──────────┘

Contract Structure

Core Contracts:

  • Diamond.sol: Main proxy contract (all interactions start here)
  • LibAppStorage.sol: Shared storage library (state management)
  • CampaignInstance.sol: Individual campaign logic (minimal proxies)

Facets (Pluggable Modules):

  • IdentityFacet.sol: User registration & KYC/KYB verification
  • CampaignFactoryFacet.sol: Campaign deployment
  • CampaignManagementFacet.sol: Cross-campaign coordination, DAO voting
  • DiamondCutFacet.sol: System upgrades & facet management

Key Design Patterns:

  • Diamond Proxy: Modular upgradeable architecture
  • Minimal Proxy (EIP-1167): Gas-efficient campaign deployment
  • Diamond Storage: Conflict-free shared state
  • Pull Payment: Secure fund withdrawals

🚀 Getting Started

Prerequisites

  • Git: Version control
  • Foundry: Solidity development toolkit
    curl -L https://foundry.paradigm.xyz | bash
    foundryup
  • Node.js (optional): For frontend integration

Installation

# 1. Clone the repository
git clone https://github.com/EcoVault-Finance/core-contracts.git
cd core-contracts

# 2. Install dependencies
forge install

# 3. Build contracts
forge build

# 4. Run tests
forge test -vvv

Environment Setup

Create a .env file in the root directory:

# Network RPC URLs
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY

# Deployer Wallet
DEPLOYER_PRIVATE_KEY=0xYourPrivateKey

# Verification
ETHERSCAN_API_KEY=YOUR_ETHERSCAN_API_KEY

# Chainlink (Sepolia Testnet)
LINK_TOKEN_ADDRESS=0x779877A7B0D9E8603169DdbD7836e478b4624789
AUTOMATION_REGISTRAR_ADDRESS=0x9806cf6fBc89aBF286e8140C42174B94DfC62AAd
AUTOMATION_REGISTRY_ADDRESS=0x86EFBD0b6736Bed994962f9797049422A3A8E8Ad

# Identity Factory
IDENTITY_FACTORY_ADDRESS=0xYourDeployedFactoryAddress

Deployment

# Deploy to local testnet (Anvil)
anvil  # In separate terminal
forge script script/DeployDiamond.s.sol --rpc-url http://localhost:8545 --broadcast

# Deploy to Sepolia
forge script script/DeployDiamond.s.sol \
  --rpc-url $SEPOLIA_RPC_URL \
  --private-key $DEPLOYER_PRIVATE_KEY \
  --broadcast \
  --verify \
  --etherscan-api-key $ETHERSCAN_API_KEY

📡 API Documentation

Base URL

All interactions occur through the Diamond Proxy Contract:

Sepolia Testnet: 0xYourDiamondProxyAddress
Mainnet: 0xYourMainnetDiamondAddress

Authentication

  • No API keys required (blockchain-based authentication)
  • Wallet signatures authenticate all transactions
  • ONCHAINID claims authorize privileged operations

Rate Limits

  • Determined by blockchain block time (~12 seconds on Ethereum)
  • Gas limits per block (~30M gas on mainnet)
  • No external rate limiting

🔌 Smart Contract Endpoints

Identity Management

POST registerUser()

Description: Registers a new user by deploying an ONCHAINID identity contract.

Request:

// No parameters - msg.sender is the user
registerUser()

Response:

address identityContract  // Address of deployed identity contract

Events Emitted:

event IdentityRegistered(
    address indexed user,
    address indexed identityContract
)

Errors:

  • IdentityFactoryNotSet: Identity factory not configured
  • UserAlreadyRegistered: User already has an identity
  • IdentityCreationFailed: Factory call failed
  • InvalidIdentityContract: Deployed address invalid

Example (cast):

cast send $DIAMOND_ADDRESS "registerUser()" \
  --private-key $USER_PRIVATE_KEY \
  --rpc-url $SEPOLIA_RPC_URL

GET getIdentityStatus(address)

Description: Retrieves comprehensive identity status including KYC/KYB verification.

Request:

function getIdentityStatus(address user) external view returns (
    address identityContract,
    bool hasKYB,
    uint64 kybExpiry,
    address kybIssuer,
    bool hasKYC,
    uint64 kycExpiry,
    address kycIssuer,
    bool isVerified,
    uint64 linkedAt
)

Response:

{
  "identityContract": "0x1234...5678",
  "hasKYB": true,
  "kybExpiry": 1735689600,
  "kybIssuer": "0xABCD...EF00",
  "hasKYC": true,
  "kycExpiry": 1735689600,
  "kycIssuer": "0xABCD...EF00",
  "isVerified": false,
  "linkedAt": 1672531200
}

Example:

cast call $DIAMOND_ADDRESS "getIdentityStatus(address)" $USER_ADDRESS \
  --rpc-url $SEPOLIA_RPC_URL

GET isKYBVerified(address)

Description: Checks if a user has valid KYB (Know Your Business) verification.

Request:

function isKYBVerified(address user) external view returns (
    bool hasKYB,
    uint64 expiry,
    address issuer
)

Response:

{
  "hasKYB": true,
  "expiry": 1735689600,
  "issuer": "0xABCD...EF00"
}

Use Case: Called by factory before allowing campaign creation.


GET isKYCVerified(address)

Description: Checks if a user has valid KYC (Know Your Customer) verification.

Request:

function isKYCVerified(address user) external view returns (
    bool hasKYC,
    uint64 expiry,
    address issuer
)

Response:

{
  "hasKYC": true,
  "expiry": 1735689600,
  "issuer": "0xABCD...EF00"
}

Use Case: Called before accepting donations.


Campaign Creation

POST createNewCampaign(...)

Description: Deploys a new campaign instance for a KYB-verified user.

Request:

function createNewCampaign(
    string calldata title,
    string calldata description,
    uint256 goalAmount,
    address token,
    uint64 duration
) external returns (
    address campaignAddress,
    uint256 campaignId
)

Parameters:

Parameter Type Description Example
title string Campaign title (max 200 chars) "Clean Water for Rural Africa"
description string Detailed description "This project aims to..."
goalAmount uint256 Funding goal in token units 1000000000000000000000 (1000 ETH)
token address ERC20 token or address(0) for ETH 0x0000...0000 or 0x779877...
duration uint64 Campaign duration in seconds 2592000 (30 days)

Response:

{
  "campaignAddress": "0x5678...1234",
  "campaignId": 42
}

Events Emitted:

event CampaignRegistered(
    uint256 indexed campaignId,
    address indexed campaignAddress,
    address indexed creator
)

event CampaignInitialized(
    address indexed creator,
    string title,
    uint256 goalAmount,
    uint64 deadline
)

Errors:

  • Factory_NotKYBVerified: Creator lacks KYB verification
  • Factory_NoIdentityContract: Creator has no identity
  • Factory_KYBExpired: KYB verification expired
  • Factory_ZeroGoal: Goal amount is zero
  • Factory_InvalidDuration: Duration < 1 day (86400 seconds)
  • Factory_InvalidTitle: Title is empty
  • Factory_ImplementationNotSet: Campaign logic not set by admin
  • Factory_ProxyDeploymentFailed: Minimal proxy deployment failed
  • Factory_InitializationFailed: Campaign initialization failed

Example:

cast send $DIAMOND_ADDRESS "createNewCampaign(string,string,uint256,address,uint64)" \
  "Solar Farm Initiative" \
  "Building 100MW solar farm in Sub-Saharan Africa" \
  "1000000000000000000000" \
  "0x0000000000000000000000000000000000000000" \
  "2592000" \
  --private-key $CREATOR_PRIVATE_KEY \
  --rpc-url $SEPOLIA_RPC_URL

Campaign Interactions

POST donate(uint256)

Description: Donates to a campaign (called on CampaignInstance, not Diamond).

Request:

function donate(uint256 amount) external payable

Parameters:

Parameter Type Description Notes
amount uint256 Donation amount Must match msg.value for native token

Response:

// No return value - check events

Events Emitted:

event DonationReceived(
    address indexed donor,
    uint256 amount,
    uint256 newTotalRaised
)

event DonorRegistered(
    address indexed donor,
    address indexed identity,
    uint256 totalDonations
)

event VotingPowerUpdated(
    address indexed donor,
    uint256 oldPower,
    uint256 newPower
)

Errors:

  • CampaignInstance_NotInitialized: Campaign not initialized
  • CampaignInstance_CampaignNotActive: Campaign not active
  • CampaignInstance_CampaignEnded: Deadline passed
  • CampaignInstance_InvalidAmount: Amount is zero
  • CampaignInstance_NotKYCVerified: Donor lacks KYC
  • CampaignInstance_DonationExceedsMaximum: Exceeds max limit
  • CampaignInstance_DonationBelowMinimum: Below min limit
  • CampaignInstance_NativeTokenMismatch: msg.valueamount
  • CampaignInstance_NoNativeTokensExpected: Sent ETH to ERC20 campaign

Example (Native ETH):

cast send $CAMPAIGN_ADDRESS "donate(uint256)" \
  "1000000000000000000" \
  --value 1ether \
  --private-key $DONOR_PRIVATE_KEY \
  --rpc-url $SEPOLIA_RPC_URL

Example (ERC20):

# 1. Approve campaign to spend tokens
cast send $TOKEN_ADDRESS "approve(address,uint256)" \
  $CAMPAIGN_ADDRESS \
  "10000000000000000000" \
  --private-key $DONOR_PRIVATE_KEY

# 2. Donate tokens
cast send $CAMPAIGN_ADDRESS "donate(uint256)" \
  "10000000000000000000" \
  --private-key $DONOR_PRIVATE_KEY \
  --rpc-url $SEPOLIA_RPC_URL

POST createMilestone(...)

Description: Creates a new milestone for a campaign (creator only).

Request:

function createMilestone(
    string calldata milestoneTitle,
    string calldata milestoneDescription,
    uint32 percentage,
    uint64 deadline,
    MilestoneType mType
) external returns (uint32 milestoneId)

Parameters:

Parameter Type Description Example
milestoneTitle string Milestone title "Phase 1: Solar Panel Installation"
milestoneDescription string Detailed description "Install 500 solar panels..."
percentage uint32 % of goal (basis points) 2500 (25.00%)
deadline uint64 Completion deadline (Unix) 1735689600
mType enum Milestone type 0 (FUNDING_PROPOSAL)

Milestone Types:

enum MilestoneType {
    FUNDING_PROPOSAL,    // 0: Initial funding request
    WORK_DELIVERY,       // 1: Evidence of completed work
    FINANCIAL_REPORT,    // 2: Spending transparency
    IMPACT_MEASUREMENT   // 3: Social/environmental impact
}

Response:

{
  "milestoneId": 1
}

Events Emitted:

event MilestoneCreated(
    uint32 indexed milestoneId,
    string title,
    uint32 percentage,
    uint64 deadline
)

Errors:

  • CampaignInstance_NotInitialized: Campaign not initialized
  • CampaignInstance_NotCreator: Caller not campaign creator
  • CampaignInstance_CampaignNotActive: Campaign not active
  • CampaignInstance_InvalidPercentage: Percentage = 0 or > 10000
  • CampaignInstance_CampaignEnded: Deadline in the past

Example:

cast send $CAMPAIGN_ADDRESS "createMilestone(string,string,uint32,uint64,uint8)" \
  "Phase 1: Land Acquisition" \
  "Secure 100 hectares of land for solar farm" \
  "2500" \
  "1735689600" \
  "0" \
  --private-key $CREATOR_PRIVATE_KEY \
  --rpc-url $SEPOLIA_RPC_URL

POST submitEvidence(...)

Description: Submits evidence for milestone completion (triggers oracle verification).

Request:

function submitEvidence(
    uint32 milestoneId,
    string calldata evidenceURI
) external

Parameters:

Parameter Type Description Example
milestoneId uint32 Milestone ID 1
evidenceURI string IPFS/Arweave link ipfs://QmX...abc

Events Emitted:

event EvidenceSubmitted(
    uint32 indexed milestoneId,
    string evidenceURI,
    uint64 timestamp
)

event OracleVerificationRequested(
    uint256 indexed campaignId,
    uint32 indexed milestoneId,
    address indexed oracle,
    bytes32 requestId
)

Errors:

  • CampaignInstance_NotInitialized: Campaign not initialized
  • CampaignInstance_NotCreator: Caller not creator
  • CampaignInstance_CampaignNotActive: Campaign not active
  • CampaignInstance_MilestoneNotFound: Invalid milestone ID
  • CampaignInstance_InvalidMilestoneStatus: Milestone already processed

Example:

cast send $CAMPAIGN_ADDRESS "submitEvidence(uint32,string)" \
  "1" \
  "ipfs://QmXa1b2c3d4e5f6g7h8i9j0k..." \
  --private-key $CREATOR_PRIVATE_KEY \
  --rpc-url $SEPOLIA_RPC_URL

POST finalizeCampaign()

Description: Finalizes campaign after deadline (anyone can call, typically Chainlink Automation).

Request:

function finalizeCampaign() external

Response:

// No return value - check events

Events Emitted:

event CampaignStateUpdated(
    CampaignState oldState,
    CampaignState newState
)

event CampaignSuccessful(
    uint256 totalRaised,
    uint256 goalAmount
)
// OR
event CampaignFailed(
    uint256 totalRaised,
    uint256 goalAmount
)

Logic:

  • If totalRaised >= goalAmountCampaignState.Successful
  • If totalRaised < goalAmountCampaignState.Failed (refunds enabled)

Errors:

  • CampaignInstance_NotInitialized: Campaign not initialized
  • CampaignInstance_DeadlineNotReached: Deadline not yet passed
  • CampaignInstance_AlreadyFinalized: Campaign already finalized

Example:

cast send $CAMPAIGN_ADDRESS "finalizeCampaign()" \
  --rpc-url $SEPOLIA_RPC_URL

POST claimRefund()

Description: Claims refund if campaign failed (pull payment pattern).

Request:

function claimRefund() external

Response:

// No return value - check events

Events Emitted:

event RefundClaimed(
    address indexed donor,
    uint256 amount
)

Errors:

  • CampaignInstance_NotInitialized: Campaign not initialized
  • CampaignInstance_RefundNotAvailable: Campaign not in Failed state
  • CampaignInstance_NoContribution: Caller has no contribution

Example:

cast send $CAMPAIGN_ADDRESS "claimRefund()" \
  --private-key $DONOR_PRIVATE_KEY \
  --rpc-url $SEPOLIA_RPC_URL

POST batchRefund(address[])

Description: Batch refunds multiple donors (creator only, gas optimization).

Request:

function batchRefund(address[] calldata recipients) external

Parameters:

Parameter Type Description Example
recipients address[] Array of donor addresses [0xAlice, 0xBob, 0xCharlie]

Response:

// No return value - check events per recipient

Events Emitted:

event RefundClaimed(
    address indexed donor,
    uint256 amount
)
// Emitted once per successful refund

Errors:

  • CampaignInstance_NotInitialized: Campaign not initialized
  • CampaignInstance_NotCreator: Caller not creator
  • CampaignInstance_RefundNotAvailable: Campaign not Failed

Example:

cast send $CAMPAIGN_ADDRESS "batchRefund(address[])" \
  "[0x1111...,0x2222...,0x3333...]" \
  --private-key $CREATOR_PRIVATE_KEY \
  --rpc-url $SEPOLIA_RPC_URL

DAO Voting

POST submitVote(...)

Description: Submits a vote for milestone approval (DAO members only).

Request:

function submitVote(
    uint256 campaignId,
    uint32 milestoneId,
    bool approve
) external

Parameters:

Parameter Type Description Example
campaignId uint256 Campaign ID 42
milestoneId uint32 Milestone ID 1
approve bool Approve or reject true

Response:

// No return value - check events

Events Emitted:

event DAOVoteRegistered(
    uint256 indexed campaignId,
    uint32 indexed milestoneId,
    address indexed voter,
    uint256 votingPower,
    bool approved
)

Voting Power Calculation:

votingPower = totalDonations[voter]  // Proportional to all donations

Errors:

  • Coordinator_NoIdentityContract: Voter has no identity
  • Coordinator_NotKYBVerified: Voter not KYB verified
  • Coordinator_KYBExpired: Voter's KYB expired
  • Coordinator_CampaignNotFound: Invalid campaign ID
  • Coordinator_CampaignNotActive: Campaign not active
  • Coordinator_AlreadyVoted: Voter already voted on this milestone

Example:

cast send $DIAMOND_ADDRESS "submitVote(uint256,uint32,bool)" \
  "42" \
  "1" \
  "true" \
  --private-key $DONOR_PRIVATE_KEY \
  --rpc-url $SEPOLIA_RPC_URL

POST aggregateVotes(...)

Description: Aggregates votes and submits result to campaign (governance only).

Request:

function aggregateVotes(
    uint256 campaignId,
    uint32 milestoneId
) external

Response:

// No return value - check events

Events Emitted:

event DAOVoteAggregated(
    uint256 indexed campaignId,
    uint32 indexed milestoneId,
    uint256 totalApproval,
    uint256 totalVotes,
    bool approved
)

Approval Logic:

approvalPercentage = (approvalVotes * 10000) / totalVotes
approved = approvalPercentage > 5000  // > 50%

Errors:

  • Coordinator_NotGovernance: Caller not governance address
  • Coordinator_VotingNotComplete: No votes submitted

Example:

cast send $DIAMOND_ADDRESS "aggregateVotes(uint256,uint32)" \
  "42" \
  "1" \
  --private-key $GOVERNANCE_PRIVATE_KEY \
  --rpc-url $SEPOLIA_RPC_URL

View Functions

GET getVotingPower(address)

Description: Returns donor's total voting power across all campaigns.

Request:

function getVotingPower(address donor) external view returns (uint256)

Response:

{
  "votingPower": "5000000000000000000000"
}

Example:

cast call $DIAMOND_ADDRESS "getVotingPower(address)" $DONOR_ADDRESS \
  --rpc-url $SEPOLIA_RPC_URL

GET getTotalDonations(address)

Description: Returns donor's total donations across all campaigns.

Request:

function getTotalDonations(address donor) external view returns (uint256)

Response:

{
  "totalDonations": "5000000000000000000000"
}

GET getDonorTier(address)

Description: Returns donor's community tier.

Request:

function getDonorTier(address donor) external view returns (uint32)

Response:

{
  "tier": 3
}

Tier Levels:

Tier Name Total Donations Benefits
0 None < 10 ETH Basic access
1 Bronze ≥ 10 ETH Priority support
2 Silver ≥ 100 ETH Exclusive events
3 Gold ≥ 1,000 ETH Governance role
4 Platinum ≥ 10,000 ETH Advisory board

GET getCampaignAddress(uint256)

Description: Returns campaign address by ID.

Request:

function getCampaignAddress(uint256 campaignId) external view returns (address)

Response:

{
  "campaignAddress": "0x5678...1234"
}

💡 Usage Examples

Complete Campaign Lifecycle

#!/bin/bash

# Configuration
DIAMOND="0xYourDiamondAddress"
CREATOR_KEY="0xCreatorPrivateKey"
DONOR_KEY="0xDonorPrivateKey"
RPC="$SEPOLIA_RPC_URL"

# ========================================
# Step 1: Register Creator Identity
# ========================================
echo "Registering creator identity..."
cast send $DIAMOND "registerUser()" \
  --private-key $CREATOR_KEY \
  --rpc-url $RPC

# ========================================
# Step 2: Admin Verifies KYB (Off-chain)
# ========================================
# Trusted issuer adds KYB claim to creator's identity contract
# (This happens off-chain via the claim issuer's interface)

# ========================================
# Step 3: Create Campaign
# ========================================
echo "Creating campaign..."
CAMPAIGN_TX=$(cast send $DIAMOND "createNewCampaign(string,string,uint256,address,uint64)" \
  "Ocean Cleanup Initiative" \
  "Remove 10,000 tons of plastic from Pacific Ocean" \
  "1000000000000000000000" \
  "0x0000000000000000000000000000000000000000" \
  "2592000" \
  --private-key $CREATOR_KEY \
  --rpc-url $RPC \
  --json)

# Extract campaign address from logs
CAMPAIGN=$(echo $CAMPAIGN_TX | jq -r '.logs[0].topics[2]')
echo "Campaign deployed at: $CAMPAIGN"

# ========================================
# Step 4: Create Milestones
# ========================================
echo "Creating milestones..."

# Milestone 1: Equipment Purchase (25%)
cast send $CAMPAIGN "createMilestone(string,string,uint32,uint64,uint8)" \
  "Equipment Procurement" \
  "Purchase ocean cleanup vessels and equipment" \
  "2500" \
  "$(date -d '+30 days' +%s)" \
  "0" \
  --private-key $CREATOR_KEY \
  --rpc-url $RPC

# Milestone 2: Operations (50%)
cast send $CAMPAIGN "createMilestone(string,string,uint32,uint64,uint8)" \
  "Cleanup Operations" \
  "6-month ocean cleanup operations" \
  "5000" \
  "$(date -d '+180 days' +%s)" \
  "1" \
  --private-key $CREATOR_KEY \
  --rpc-url $RPC

# Milestone 3: Impact Report (25%)
cast send $CAMPAIGN "createMilestone(string,string,uint32,uint64,uint8)" \
  "Impact Measurement" \
  "Third-party verified impact assessment" \
  "2500" \
  "$(date -d '+210 days' +%s)" \
  "3" \
  --private-key $CREATOR_KEY \
  --rpc-url $RPC

# ========================================
# Step 5: Donor Registration & Donation
# ========================================
echo "Registering donor..."
cast send $DIAMOND "registerUser()" \
  --private-key $DONOR_KEY \
  --rpc-url $RPC

# Wait for KYC verification (off-chain)
sleep 5

echo "Making donation..."
cast send $CAMPAIGN "donate(uint256)" \
  "100000000000000000000" \
  --value 100ether \
  --private-key $DONOR_KEY \
  --rpc-url $RPC

# ========================================
# Step 6: Submit Evidence for Milestone 1
# ========================================
echo "Submitting evidence..."
cast send $CAMPAIGN "submitEvidence(uint32,string)" \
  "1" \
  "ipfs://QmEquipmentPurchaseReceipts..." \
  --private-key $CREATOR_KEY \
  --rpc-url $RPC

# ========================================
# Step 7: DAO Voting
# ========================================
echo "Voting on milestone..."
cast send $DIAMOND "submitVote(uint256,uint32,bool)" \
  "$(cast call $DIAMOND 'getCampaignAddress(uint256)' $CAMPAIGN)" \
  "1" \
  "true" \
  --private-key $DONOR_KEY \
  --rpc-url $RPC

# ========================================
# Step 8: Aggregate Votes (Governance)
# ========================================
echo "Aggregating votes..."
cast send $DIAMOND "aggregateVotes(uint256,uint32)" \
  "$(cast call $DIAMOND 'getCampaignAddress(uint256)' $CAMPAIGN)" \
  "1" \
  --private-key $GOVERNANCE_KEY \
  --rpc-url $RPC

# ========================================
# Step 9: Withdraw Milestone Funds
# ========================================
echo "Withdrawing milestone funds..."
cast send $CAMPAIGN "withdrawMilestoneFunds(uint32)" \
  "1" \
  --private-key $CREATOR_KEY \
  --rpc-url $RPC

echo "Campaign lifecycle complete!"

Frontend Integration (JavaScript/TypeScript)

import { ethers } from 'ethers';

// Contract ABIs
import DiamondABI from './abis/Diamond.json';
import CampaignInstanceABI from './abis/CampaignInstance.json';

// Configuration
const DIAMOND_ADDRESS = '0xYourDiamondAddress';
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();

// Initialize contracts
const diamond = new ethers.Contract(DIAMOND_ADDRESS, DiamondABI, signer);

// ========================================
// User Registration
// ========================================
async function registerUser() {
  try {
    const tx = await diamond.registerUser();
    await tx.wait();
    console.log('User registered successfully');
    return tx;
  } catch (error) {
    console.error('Registration failed:', error);
    throw error;
  }
}

// ========================================
// Check KYB Status
// ========================================
async function checkKYBStatus(userAddress) {
  try {
    const [hasKYB, expiry, issuer] = await diamond.isKYBVerified(userAddress);
    
    return {
      verified: hasKYB,
      expiresAt: new Date(expiry * 1000),
      issuer: issuer,
      isExpired: expiry > 0 && Date.now() / 1000 > expiry
    };
  } catch (error) {
    console.error('KYB check failed:', error);
    return { verified: false };
  }
}

// ========================================
// Create Campaign
// ========================================
async function createCampaign(campaignData) {
  const { title, description, goalAmount, token, durationDays } = campaignData;
  
  try {
    // Convert duration to seconds
    const duration = durationDays * 24 * 60 * 60;
    
    // Create campaign
    const tx = await diamond.createNewCampaign(
      title,
      description,
      ethers.utils.parseEther(goalAmount),
      token || ethers.constants.AddressZero, // address(0) for ETH
      duration
    );
    
    const receipt = await tx.wait();
    
    // Parse CampaignRegistered event
    const event = receipt.events?.find(e => e.event === 'CampaignRegistered');
    const campaignId = event?.args?.campaignId;
    const campaignAddress = event?.args?.campaignAddress;
    
    console.log(`Campaign created: ID=${campaignId}, Address=${campaignAddress}`);
    
    return { campaignId, campaignAddress, receipt };
  } catch (error) {
    console.error('Campaign creation failed:', error);
    throw error;
  }
}

// ========================================
// Donate to Campaign
// ========================================
async function donate(campaignAddress, amount, isNativeToken = true) {
  const campaign = new ethers.Contract(campaignAddress, CampaignInstanceABI, signer);
  
  try {
    const amountWei = ethers.utils.parseEther(amount);
    
    let tx;
    if (isNativeToken) {
      // Native ETH donation
      tx = await campaign.donate(amountWei, { value: amountWei });
    } else {
      // ERC20 donation (requires prior approval)
      tx = await campaign.donate(amountWei);
    }
    
    const receipt = await tx.wait();
    console.log('Donation successful');
    
    return receipt;
  } catch (error) {
    console.error('Donation failed:', error);
    throw error;
  }
}

// ========================================
// Create Milestone
// ========================================
async function createMilestone(campaignAddress, milestoneData) {
  const campaign = new ethers.Contract(campaignAddress, CampaignInstanceABI, signer);
  const { title, description, percentage, deadlineDays, type } = milestoneData;
  
  try {
    // Calculate deadline timestamp
    const deadline = Math.floor(Date.now() / 1000) + (deadlineDays * 24 * 60 * 60);
    
    // Convert percentage to basis points (25% = 2500)
    const basisPoints = percentage * 100;
    
    const tx = await campaign.createMilestone(
      title,
      description,
      basisPoints,
      deadline,
      type // 0=FUNDING_PROPOSAL, 1=WORK_DELIVERY, 2=FINANCIAL_REPORT, 3=IMPACT_MEASUREMENT
    );
    
    const receipt = await tx.wait();
    
    // Parse MilestoneCreated event
    const event = receipt.events?.find(e => e.event === 'MilestoneCreated');
    const milestoneId = event?.args?.milestoneId;
    
    console.log(`Milestone created: ID=${milestoneId}`);
    
    return { milestoneId, receipt };
  } catch (error) {
    console.error('Milestone creation failed:', error);
    throw error;
  }
}

// ========================================
// Submit Evidence
// ========================================
async function submitEvidence(campaignAddress, milestoneId, evidenceURI) {
  const campaign = new ethers.Contract(campaignAddress, CampaignInstanceABI, signer);
  
  try {
    const tx = await campaign.submitEvidence(milestoneId, evidenceURI);
    const receipt = await tx.wait();
    
    console.log('Evidence submitted successfully');
    return receipt;
  } catch (error) {
    console.error('Evidence submission failed:', error);
    throw error;
  }
}

// ========================================
// Vote on Milestone
// ========================================
async function voteOnMilestone(campaignId, milestoneId, approve) {
  try {
    const tx = await diamond.submitVote(campaignId, milestoneId, approve);
    const receipt = await tx.wait();
    
    console.log(`Vote submitted: ${approve ? 'Approved' : 'Rejected'}`);
    return receipt;
  } catch (error) {
    console.error('Voting failed:', error);
    throw error;
  }
}

// ========================================
// Get Donor Info
// ========================================
async function getDonorInfo(donorAddress) {
  try {
    const [votingPower, totalDonations, tier] = await Promise.all([
      diamond.getVotingPower(donorAddress),
      diamond.getTotalDonations(donorAddress),
      diamond.getDonorTier(donorAddress)
    ]);
    
    return {
      votingPower: ethers.utils.formatEther(votingPower),
      totalDonations: ethers.utils.formatEther(totalDonations),
      tier: tier.toNumber(),
      tierName: getTierName(tier.toNumber())
    };
  } catch (error) {
    console.error('Failed to get donor info:', error);
    throw error;
  }
}

function getTierName(tier) {
  const tiers = ['None', 'Bronze', 'Silver', 'Gold', 'Platinum'];
  return tiers[tier] || 'Unknown';
}

// ========================================
// React Hook Example
// ========================================
import { useState, useEffect } from 'react';

function useCampaignData(campaignAddress) {
  const [campaign, setCampaign] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    async function fetchCampaignData() {
      try {
        const contract = new ethers.Contract(
          campaignAddress,
          CampaignInstanceABI,
          provider
        );
        
        const [
          creator,
          title,
          description,
          goalAmount,
          totalRaised,
          deadline,
          state,
          tokenAddress
        ] = await Promise.all([
          contract.getCreator(),
          contract.title(),
          contract.description(),
          contract.getGoalAmount(),
          contract.getTotalRaised(),
          contract.getDeadline(),
          contract.getCampaignState(),
          contract.getTokenAddress()
        ]);
        
        setCampaign({
          address: campaignAddress,
          creator,
          title,
          description,
          goalAmount: ethers.utils.formatEther(goalAmount),
          totalRaised: ethers.utils.formatEther(totalRaised),
          deadline: new Date(deadline * 1000),
          state: ['Active', 'Paused', 'Successful', 'Failed', 'Cancelled'][state],
          isNativeToken: tokenAddress === ethers.constants.AddressZero,
          tokenAddress,
          progress: (totalRaised / goalAmount) * 100
        });
      } catch (error) {
        console.error('Failed to fetch campaign data:', error);
      } finally {
        setLoading(false);
      }
    }
    
    fetchCampaignData();
  }, [campaignAddress]);
  
  return { campaign, loading };
}

// Export functions
export {
  registerUser,
  checkKYBStatus,
  createCampaign,
  donate,
  createMilestone,
  submitEvidence,
  voteOnMilestone,
  getDonorInfo,
  useCampaignData
};

🛠️ Technologies Used

Technology Purpose Version
Solidity Smart contract language 0.8.20+
Foundry Development framework Latest
EIP-2535 Diamond Modular architecture Standard
OpenZeppelin Security libraries 5.0.0
Chainlink Automation Automated operations v2.0
Chainlink Oracles External data feeds Latest
ONCHAINID Decentralized identity ERC-734/735
ERC-1167 Minimal proxy clones Standard

Key Dependencies

[dependencies]
# OpenZeppelin Contracts
forge-std = { git = "https://github.com/foundry-rs/forge-std", tag = "v1.7.0" }
openzeppelin-contracts = { git = "https://github.com/OpenZeppelin/openzeppelin-contracts", tag = "v5.0.0" }

# Chainlink
chainlink = { git = "https://github.com/smartcontractkit/chainlink", tag = "v2.0.0" }

# ONCHAINID
onchainid = { git = "https://github.com/onchain-id/solidity", tag = "v4.0.0" }

🧪 Testing

Run Tests

# Run all tests
forge test

# Run with verbosity (show logs)
forge test -vvv

# Run specific test file
forge test --match-path test/CampaignInstance.t.sol

# Run specific test function
forge test --match-test testDonation

# Run with gas reporting
forge test --gas-report

# Run with coverage
forge coverage

# Generate coverage report (HTML)
forge coverage --report lcov
genhtml lcov.info -o coverage

Test Structure

test/
├── unit/
│   ├── Diamond.t.sol           # Diamond proxy tests
│   ├── IdentityFacet.t.sol     # Identity management tests
│   ├── CampaignFactory.t.sol   # Campaign creation tests
│   └── CampaignInstance.t.sol  # Campaign lifecycle tests
├── integration/
│   ├── EndToEnd.t.sol          # Full campaign lifecycle
│   ├── DAOVoting.t.sol         # Governance tests
│   └── OracleIntegration.t.sol # Oracle verification tests
├── fork/
│   └── SepoliaFork.t.sol       # Mainnet fork tests
└── helpers/
    └── TestHelpers.sol         # Shared test utilities

Example Test

// test/unit/CampaignInstance.t.sol
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/CampaignInstance.sol";

contract CampaignInstanceTest is Test {
    CampaignInstance campaign;
    address creator = address(0x1);
    address donor = address(0x2);
    
    function setUp() public {
        campaign = new CampaignInstance(1, address(this));
        
        vm.prank(creator);
        campaign.initializeCampaign(
            creator,
            1,
            address(this),
            "Test Campaign",
            "Description",
            1000 ether,
            address(0),
            30 days
        );
    }
    
    function testDonation() public {
        vm.deal(donor, 10 ether);
        
        vm.prank(donor);
        campaign.donate{value: 10 ether}(10 ether);
        
        assertEq(campaign.getTotalRaised(), 10 ether);
        assertEq(campaign.getDonorBalance(donor), 10 ether);
    }
    
    function testRefundOnFailedCampaign() public {
        // Donate
        vm.deal(donor, 10 ether);
        vm.prank(donor);
        campaign.donate{value: 10 ether}(10 ether);
        
        // Fast forward past deadline
        vm.warp(block.timestamp + 31 days);
        
        // Finalize (should fail - not enough funds)
        campaign.finalizeCampaign();
        
        // Claim refund
        vm.prank(donor);
        campaign.claimRefund();
        
        assertEq(donor.balance, 10 ether);
        assertEq(campaign.getDonorBalance(donor), 0);
    }
}

🤝 Contributing

We welcome contributions from the community! Here's how to get involved:

Contribution Workflow

  1. Fork the Repository

    git clone https://github.com/YourUsername/ecovault-finance.git
    cd ecovault-finance
  2. Create a Feature Branch

    git checkout -b feature/awesome-feature
  3. Make Your Changes

    • Write clean, documented code
    • Follow Solidity style guide
    • Add/update tests
    • Update documentation
  4. Test Your Changes

    forge test
    forge fmt
  5. Commit Your Changes

    git commit -m "feat: add awesome feature"

    Commit Message Format:

    • feat: New feature
    • fix: Bug fix
    • docs: Documentation update
    • refactor: Code refactoring
    • test: Test updates
    • chore: Maintenance tasks
  6. Push and Create PR

    git push origin feature/awesome-feature

    Open a Pull Request with:

    • Clear description of changes
    • Link to related issues
    • Test results
    • Breaking changes (if any)

Development Guidelines

Code Style:

  • Follow Solidity Style Guide
  • Use forge fmt before committing
  • Keep functions under 50 lines
  • Add NatSpec comments

Testing:

  • Write tests for new features
  • Maintain >80% code coverage
  • Include edge cases
  • Test gas optimization claims

Security:

Reporting Issues

Found a bug? Have a feature request?

  1. Search existing issues to avoid duplicates
  2. Create a new issue with:
    • Clear title
    • Detailed description
    • Steps to reproduce (for bugs)
    • Expected vs actual behavior
    • Environment details

Code Review Process

  1. Automated checks must pass (tests, linting)
  2. At least 2 core team approvals required
  3. Security review for critical changes
  4. Documentation must be updated
  5. Merge after all feedback addressed

📄 License

This project is licensed under the MIT License.

MIT License

Copyright (c) 2025 EcoVault Finance

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

👥 Team & Contact

Core Team

EcoVault Finance Development Team

  • Building the future of decentralized impact funding
  • Passionate about blockchain, sustainability, and social impact

Connect With Us

Security

Found a security vulnerability? Please DO NOT open a public issue.

Report security issues to: security@ecovault.finance

We take security seriously and will respond within 48 hours.


🗺️ Roadmap

Phase 1: MVP (Q1 2025) ✅

  • Diamond proxy architecture
  • Identity management (ONCHAINID)
  • Campaign creation & donations
  • Milestone-based funding
  • Basic DAO voting

Phase 2: Enhanced Features (Q2 2025) 🚧

  • Chainlink Automation integration
  • Oracle-based evidence verification
  • Advanced voting mechanisms
  • Mobile app (React Native)
  • Frontend dashboard

Phase 3: Scalability (Q3 2025) 📋

  • Layer 2 deployment (Optimism/Arbitrum)
  • Cross-chain bridge
  • ZK rollup voting
  • Enhanced analytics
  • API for third-party integrations

Phase 4: Ecosystem Growth (Q4 2025) 🌱

  • Grant programs
  • Impact NFTs
  • Carbon credit integration
  • Partnership integrations
  • Developer SDK

📊 Project Stats

GitHub stars GitHub forks GitHub issues GitHub pull requests Lines of code


🙏 Acknowledgments

Built with support from:

  • OpenZeppelin: Security library foundations
  • Chainlink: Oracle and automation infrastructure
  • ONCHAINID: Decentralized identity standard
  • Foundry: Development toolkit
  • Ethereum Community: Ongoing support and feedback

Special thanks to all contributors who have helped shape this project.


📚 Additional Resources


Built with ❤️ for a sustainable future

⭐ Star us on GitHub | 🐦 Follow on Twitter | 💬 Join Discord

About

Ecovault.finance-Protocol is an advanced decentralized crowdfunding platform built on the Ethereum blockchain

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published