IFIFv2 Contract
The IFIFv2 contract is an enhanced version of the core IFIF crowdfunding project contract that extends the base functionality with advanced features including vested fund claiming, enhanced purchase controls, dynamic distributor configuration, and improved token distribution mechanisms. It maintains full compatibility with the base IFIF interface while adding sophisticated timing and vesting capabilities.
Overview
Contract Address: contracts/src/core/IFIFv2.sol
License: GPL-3.0-only
Solidity Version: 0.8.19
Key Enhancements Over IFIF v1
- Automatic Stage Transitions: Eliminates manual stage control with time-based automation
- Enhanced Purchase Controls: Hard caps, soft caps, and minimum weight validations
- Vested Fund Claiming: Time-based fund release for project owners with nonce-based replay attack prevention
- Dynamic Distributor Configuration: Flexible fee structures for different distributors with independent nonce tracking
- Improved Token Claiming: Vesting schedules for gradual token release to investors
- Asset Sweep Functionality: Recovery of unclaimed assets after expiration periods
- Multi-signature Enhancements: Optimized signature requirements for general config updates (2 vs 3 signatures) with nonce validation
- Enhanced Nonce System: Additional signature types with independent nonce sequences for FundClaimConfig and DistributorConfig
Enhanced Data Structures
Config_v2
struct Config_v2 {
uint256 privateSaleStartEpoch; // Timestamp when private sale begins automatically
uint256 purchaseHardCapPercent; // Maximum purchase limit as % of funding goal (0-100)
uint256 purchaseSoftCap; // Minimum purchase amount required per transaction
uint256 minNFTWeight; // Minimum weight required for NFT split operations
}Note: Config_v2 is stored as internal _config_v2 and is not directly accessible via view functions. The configuration is validated during initialization and used internally for purchase validations and automatic stage transitions.
FundClaimConfig
struct FundClaimConfig {
uint256 startEpoch; // When project owner can start claiming funds
uint256 endEpoch; // When fund vesting period ends
uint256 periodLength; // Duration of each vesting period (minimum 1 day)
uint256 nonce; // Nonce for replay attack prevention (must match or exceed current nonce)
}Nonce Field: The nonce field enables signature replay attack prevention for fund claim configuration updates. Read the current nonce via nonces(1, projectOwner) where 1 is the NONCE_FUND_CLAIM_CONFIG constant. Use nonce: 0 for first-time configuration.
DistributorConfig
struct DistributorConfig {
uint256 distributorFeePercent; // Fee percentage for distributor (0-100)
uint256 projectOwnerFeePercent; // Fee percentage for project owner (0-100)
uint256 platformFeePercent; // Fee percentage for platform (0-100)
uint256 nonce; // Nonce for replay attack prevention (must match or exceed current nonce)
}Nonce Field: The nonce field enables signature replay attack prevention for distributor configuration updates. Read the current nonce via nonces(2, distributor) where 2 is the NONCE_DISTRIBUTOR_CONFIG constant. Each distributor has their own independent nonce sequence. Use nonce: 0 for first-time configuration of a distributor.
ClaimVesting
struct ClaimVesting {
uint256 lastTime; // Timestamp of last successful claim
uint256 leftAmount; // Remaining amount available for claiming
}Project Lifecycle (v2 Enhanced)
IFIFv2 maintains the same core lifecycle as IFIF v1 but adds automatic transitions and enhanced configuration:
Enhanced Stage Flow
NONE → INIT → PRIVATE_SALE → PUBLIC_SALE → SALE_SUCCESSED/FAILED → CLAIM
↓ ↓ ↓ ↓
Time-based Time-based Enhanced Enhanced
Transition Transition Deposit Vesting
(on purchase) (on purchase) (multi-dist) (claims)Enhanced Stage Features
INIT (1) - Enhanced
- Automatic Transition: Transitions to PRIVATE_SALE when
purchase()is called afterprivateSaleStartEpoch - Configuration: Enhanced with timing controls and purchase limits
- Deprecations:
startPrivateSale()andstartPublicSale()are deprecated and will revert
PRIVATE_SALE (2) - Enhanced
- Automatic Transition: Transitions to PUBLIC_SALE when
purchase()is called after private sale duration expires - Purchase Validation: Enforces soft cap minimums and hard cap maximums
- Timing Control: Based on v2 configuration timestamps rather than manual triggers
PUBLIC_SALE (3) - Enhanced
- Reduced Grace Period: 30-day grace period for
publicEndSales()(vs 60 days in v1) - Hard Cap Enforcement: Continued enforcement of individual purchase limits
SALE_SUCCESSED (4) - Enhanced
- Fund Retention: Funds remain in contract for vesting claims (no immediate transfer)
- Multiple Deposits: Supports multiple distributor deposits with custom fee structures
- Vesting Setup: Prepares for both owner fund claiming and investor token claiming
CLAIM (6) - Enhanced
- Vesting Claims: Time-based gradual release for both funds and tokens
- Configuration Window: Claiming parameters can be set during early CLAIM stage
- Sweep Protection: 6-month window before unclaimed assets can be recovered
Enhanced View Functions
V2-Specific State
version() → string
Returns "2" to identify this as the enhanced v2 implementation.
sweept() → bool
Returns whether unclaimed assets have been swept by the project owner.
totalLiquidityAmount() → uint256
Returns the total amount of project tokens available for investor claims.
claimStartEpoch() → uint256
Returns the timestamp when the investor claiming period begins.
claimEndEpoch() → uint256
Returns the timestamp when the investor claiming period ends.
claimPeriodLength() → uint256
Returns the duration of each vesting period in seconds (minimum 1 day).
Vesting State
claimVestings(bytes32 vestingKey) → (uint256 lastTime, uint256 leftAmount)
Returns vesting information for a specific vesting schedule identified by the key.
distributorConfigs(address distributor) → (uint256, uint256, uint256)
Returns the fee configuration for a specific distributor (distributor%, owner%, platform%).
Nonce Management (V2 Enhanced)
nonces(uint8 signatureType, address signer) → uint256
Returns the current nonce value for a specific signature type and signer address.
V2 Signature Types:
NONCE_NEXT_CONFIG = 0: For NextConfig struct signatures (inherited from IFIF v1)NONCE_FUND_CLAIM_CONFIG = 1: For FundClaimConfig struct signatures (v2 specific)NONCE_DISTRIBUTOR_CONFIG = 2: For DistributorConfig struct signatures (v2 specific, per-distributor)
Key Differences from V1:
- V2 adds two additional signature types with independent nonce sequences
- DistributorConfig nonces are tracked per distributor address, not per project owner
- Each signature type maintains its own independent nonce counter
Usage Examples:
// Read NextConfig nonce (same as v1)
uint256 nextConfigNonce = ififv2.nonces(0, projectOwner);
// Read FundClaimConfig nonce (v2 specific)
uint256 fundClaimNonce = ififv2.nonces(1, projectOwner);
// Read DistributorConfig nonce (v2 specific, per distributor)
uint256 distributorNonce = ififv2.nonces(2, distributorAddress);EIP-712 Type Hashes
CONFIG_V2_TYPEHASH() → bytes32
Returns the EIP-712 typehash for Config_v2 struct signature verification (does not include nonce).
FUNDCLAIM_CONFIG_TYPEHASH() → bytes32
Returns the EIP-712 typehash for FundClaimConfig struct signature verification (includes nonce field).
DISTRIBUTOR_CONFIG_TYPEHASH() → bytes32
Returns the EIP-712 typehash for DistributorConfig struct signature verification (includes nonce field).
Enhanced Core Functions
V2 Initialization
initialize_v2()
function initialize_v2(
Config_v2 calldata config_v2,
bytes calldata configV2Signature
) externalPurpose: Initializes version 2 enhanced features with timing controls and purchase limits.
Parameters:
config_v2: Enhanced configuration with automatic timing and purchase constraintsconfigV2Signature: EIP-712 signature from manager validating v2 configuration
Access: Factory contract only during deployment (reinitializer version 2)
Validations:
- Private sale start time must be in the future
- Hard cap percentage must be ≤ 100%
- Valid manager signature for v2 configuration
Actions:
- Sets v2 configuration parameters
- Calculates hard cap amount based on funding goal
- Emits
ConfigV2Updatedevent
Enhanced Configuration Management
updateFundClaimConfig()
function updateFundClaimConfig(
FundClaimConfig calldata fundClaimConfig,
bytes calldata fundClaimConfigSignature
) external onlyOwnerPurpose: Establishes vesting schedule for project owner fund withdrawals with nonce-based replay attack prevention.
Parameters:
fundClaimConfig: Vesting parameters for fund claiming including noncefundClaimConfigSignature: Manager signature authorizing configuration (includes nonce validation)
Requirements:
- Only project owner can call
- Must be called before fund claiming starts
- Nonce must be greater than or equal to current nonce for FundClaimConfig
- Start epoch must be in the future
- End epoch must be after start epoch
- Period length must be at least 1 day
- Valid manager signature
Nonce Workflow:
- Read current nonce:
uint256 currentNonce = ififv2.nonces(1, projectOwner)(1 = NONCE_FUND_CLAIM_CONFIG) - Include nonce in fundClaimConfig:
fundClaimConfig.nonce = currentNonce - Create manager signature with nonce-aware digest
- Contract validates nonce ≥ current, then sets nonce to
providedNonce + 1
Events: Emits FundClaimConfigUpdated event and NonceConsumed(1, projectOwner, providedNonce) event
updateDistributorConfig()
function updateDistributorConfig(
address distributor,
DistributorConfig calldata distributorConfig,
bytes[] calldata signatures
) external onlyOwnerPurpose: Updates fee distribution configuration for a specific distributor with nonce-based replay attack prevention.
Parameters:
distributor: Address of distributor to configuredistributorConfig: New fee structure parameters including noncesignatures: Array of exactly 3 signatures [manager, distributor, project owner]
Multi-signature Validation with Nonce:
- Nonce validation: Validates provided nonce against distributor's current nonce
- Manager signature: Validates configuration parameters
- Distributor signature: Confirms distributor consent to new terms
- Project owner signature: Confirms owner approval of fee structure
Requirements:
- Nonce must be greater than or equal to current nonce for this distributor
- Total fee percentages must be < 100%
- Distributor must have DISTRIBUTOR_ROLE
- All three signatures must be valid
Nonce Workflow:
- Read distributor's current nonce:
uint256 currentNonce = ififv2.nonces(2, distributorAddress)(2 = NONCE_DISTRIBUTOR_CONFIG) - Include nonce in distributorConfig:
distributorConfig.nonce = currentNonce - Create three signatures with nonce-aware digest
- Contract validates nonce ≥ current, then sets nonce to
providedNonce + 1
Important: Each distributor has their own independent nonce sequence. Updating one distributor's configuration does not affect other distributors' nonces.
Events: Emits DistributorConfigUpdated event and NonceConsumed(2, distributorAddress, providedNonce) event
updateClaimConfig()
function updateClaimConfig(
uint256 startEpoch,
uint256 periodLength
) external onlyFactoryPurpose: Configures investor token claiming schedule during CLAIM stage.
Parameters:
startEpoch: When investors can start claiming tokensperiodLength: Duration of each vesting period (minimum 1 day)
Access: Factory contract only during CLAIM stage initialization
Automatic Calculations:
claimEndEpoch = startEpoch + 60 days(2-month claiming window)sweepableEpoch = claimEndEpoch + 180 days(6-month grace before sweep)
Enhanced Purchase System
purchase() - Enhanced
function purchase(
uint256 amount,
bytes32[] calldata proof
) externalEnhanced Features:
- Automatic Stage Transitions: Moves from INIT → PRIVATE_SALE → PUBLIC_SALE based on timestamps
- Soft Cap Validation: Purchase amount must meet minimum threshold
- Hard Cap Enforcement: Individual purchase limits based on percentage of funding goal
- Smart Timing: Uses v2 configuration for precise timing control
Enhanced Validations:
- Amount ≥
purchaseSoftCap(minimum purchase requirement) - User's total purchases + amount ≤ hard cap limit
- Automatic stage progression based on configured timestamps
Vested Fund Management
claimFund()
function claimFund() external onlyOwnerPurpose: Allows project owner to claim vested funds according to configured schedule.
Requirements:
- Only project owner can call
- Project must be in SALE_SUCCESSED stage
- Fund claim configuration must be set
- Current time must be after vesting start
Vesting Logic:
- Calculates claimable amount based on time elapsed and vesting schedule
- Updates vesting state to prevent double claiming
- Transfers calculated amount to project owner
- Maintains remaining vested balance for future claims
Events: Emits FundClaimed event with claimed amount
Enhanced Token Distribution
deposit() - Enhanced
function deposit(uint256 amount) externalEnhanced Features:
- Distributor-specific Fees: Uses individual distributor configurations instead of global settings
- Multi-deposit Support: Allows multiple deposits from different distributors
- Extended Stage Support: Can be called in both SALE_SUCCESSED and CLAIM stages
- Timing Validation: Prevents deposits after claiming starts
Requirements:
- Minimum deposit of 1 ether
- Caller must be a configured distributor (has fee configuration)
- Must be before claim start time if in CLAIM stage
claim() - Enhanced
function claim() externalEnhanced Features:
- Vested Claiming: Gradual token release based on configured vesting schedule
- Timing Controls: Respects claim start/end epochs and sweep status
- Progressive Release: Calculates claimable amounts using time-based vesting
- State Management: Updates vesting state and cleans up fully claimed positions
Vesting Calculations:
- Uses purchase weight and earnings multiplier for base amount
- Applies time-based vesting to determine currently claimable portion
- Updates user's vesting state after each claim
convertNFT() - Enhanced
function convertNFT(uint256 tokenId) externalEnhanced Features:
- NFT Vesting: Separate vesting schedule for each NFT
- Partial Conversions: Allows gradual conversion based on vesting schedule
- Smart Burning: Burns NFT only when fully vested and claimed
- Weight Preservation: Maintains NFT weight throughout vesting period
Asset Recovery
sweep()
function sweep() external onlyFactoryPurpose: Recovers unclaimed assets after extended grace period.
Access: Factory contract only
Requirements:
- Can only be called by factory contract
- Must be at least 6 months after claim period ends
- Cannot be called if already swept
- Project must be in CLAIM stage
Actions:
- Transfers all remaining project tokens to factory
- Transfers any remaining fund tokens to factory
- Sets sweep flag to prevent future sweeps
- Factory can then redistribute assets to project owner
Timeline:
- Claiming period: 2 months from claim start
- Grace period: 6 months after claiming ends
- Total window: 8 months before sweep eligibility
Enhanced Sales Termination
endSales() - Enhanced
function endSales() external onlyFactoryPurpose: Factory-controlled sales termination for v2 projects.
Access: Factory contract only (vs owner-controlled in v1)
Enhanced Behavior:
- Funds remain in contract for vesting claims (vs immediate transfer in v1)
- Prepares project for vested fund claiming by project owner
- Centralizes project lifecycle management through factory
publicEndSales()
function publicEndSales() externalPurpose: Emergency sales termination after extended delay.
Access: Anyone can call after 30-day grace period (vs 60 days in v1)
Enhanced Behavior: Same fund retention logic as endSales()
Enhanced NFT Operations
splitNFT() - Enhanced
function splitNFT(
uint256 tokenId,
uint256[] calldata tokenWeights
) externalEnhanced Validation:
- All new NFT weights must meet
minNFTWeightrequirement from v2 configuration - Prevents creation of NFTs below minimum operational threshold
- Maintains existing split logic from base IFIF contract
Deprecated Functions
Manual Stage Control
function startPrivateSale() external // Reverts with Deprecated()
function startPublicSale() external // Reverts with Deprecated()Reason: V2 uses automatic stage transitions based on configured timestamps, eliminating the need for manual stage control.
Reduced Signature Requirements
updateConfig() - Simplified
function updateConfig(
NextConfig calldata nextConfig_,
bytes[] calldata signatures
) externalSimplified Requirements:
- Only 2 signatures required (vs 3 in v1): [manager, project owner]
- Distributor signature no longer required for general config updates
- Nonce validation required (same as v1, using NONCE_NEXT_CONFIG type)
- Cannot be called during CLAIM stage (additional protection)
Nonce Workflow (Same as V1):
- Read current nonce:
uint256 currentNonce = ififv2.nonces(0, projectOwner) - Include nonce in nextConfig:
nextConfig.nonce = currentNonce - Create two signatures (manager + project owner) with nonce-aware digest
- Contract validates nonce ≥ current, then sets nonce to
providedNonce + 1
Events: Emits ConfigUpdated event and NonceConsumed(0, projectOwner, providedNonce) event
Enhanced Security Features
Multi-signature Authorization with Nonce Validation
- Distributor Configuration: Requires manager + distributor + owner signatures with distributor's nonce (3 signatures)
- Fund Claim Configuration: Requires manager signature with project owner's nonce for vesting setup (1 signature)
- Config Updates: Simplified to manager + owner signatures with project owner's nonce (2 vs 3 in v1)
- Independent Nonce Sequences: Each signature type (NextConfig, FundClaimConfig, DistributorConfig) maintains separate nonce counters
- Per-Distributor Nonces: DistributorConfig nonces are tracked per distributor address, not per project
Timing-based Security
- Automatic Transitions: Eliminates manual timing manipulation
- Vesting Protection: Prevents premature fund/token claims
- Grace Periods: Extended recovery windows for various operations
Enhanced Validation
- Purchase Limits: Hard and soft caps prevent abuse
- Minimum Weights: Prevents spam NFT creation
- Configuration Validation: Comprehensive parameter checking
Events (V2 Enhanced)
New V2 Events
event ConfigV2Updated(Config_v2 config_v2);
event FundClaimConfigUpdated(FundClaimConfig fundClaimConfig);
event FundClaimed(uint256 amount);
event DistributorConfigUpdated(address indexed distributor, DistributorConfig distributorConfig);
event ClaimConfigUpdated(uint256 startEpoch, uint256 endEpoch, uint256 sweepEpoch, uint256 periodLength);
event Sweept(uint256 fundAmount, uint256 tokenAmount);
event NonceConsumed(uint8 indexed signatureType, address indexed signer, uint256 nonce); // Inherited from SignatureNonceManagerV2 Nonce Events
IFIFv2 emits NonceConsumed events for all three signature types:
- Type 0 (NONCE_NEXT_CONFIG): When
updateConfig()is called with project owner's signature - Type 1 (NONCE_FUND_CLAIM_CONFIG): When
updateFundClaimConfig()is called with project owner's nonce - Type 2 (NONCE_DISTRIBUTOR_CONFIG): When
updateDistributorConfig()is called with distributor's nonce
Each event tracks which signature type was used, who signed it, and what nonce value was consumed.
Enhanced Existing Events
- All base IFIF events are maintained with same functionality
- Enhanced with additional context where applicable
Integration Examples
V2 Project Initialization
// V2 projects are deployed through the same factory but require additional initialization
const factory = await ethers.getContractAt("IFIFFactory", factoryAddress);
// First deploy the base project (same as v1)
const deployTx = await factory.deploy(
baseConfig,
nextConfig,
configSignature,
nextConfigSignature,
projectName,
projectSymbol,
paymentToken
);
// Get the deployed project address
const receipt = await deployTx.wait();
const projectAddress = receipt.logs[0].address; // Extract from deployment logs
const projectV2 = await ethers.getContractAt("IFIFv2", projectAddress);
// V2 configuration with timing and limits
const config_v2 = {
privateSaleStartEpoch: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
purchaseHardCapPercent: 10, // Max 10% of funding goal per user
purchaseSoftCap: ethers.parseEther("100"), // Minimum 100 USDC per purchase
minNFTWeight: ethers.parseEther("50") // Minimum NFT weight for operations
};
// Initialize v2 features (separate call)
const initV2Tx = await projectV2.initialize_v2(config_v2, v2ConfigSignature);Enhanced Purchase Flow
const projectV2 = await ethers.getContractAt("IFIFv2", projectAddress);
// Note: V2 config is internal, not directly accessible via view function
// Check current stage and validate purchase amount against known limits
const currentStage = await projectV2.stage();
const userPurchases = await projectV2.purchases(userAddress);
// Execute purchase (stages transition automatically based on timestamps)
const purchaseTx = await projectV2.purchase(purchaseAmount, merkleProof);Vested Fund Claiming
// Project owner sets up fund claiming vesting with nonce
const projectOwner = await projectV2.owner();
const currentNonce = await projectV2.nonces(1, projectOwner); // 1 = NONCE_FUND_CLAIM_CONFIG
const fundClaimConfig = {
startEpoch: Math.floor(Date.now() / 1000) + 86400, // 1 day from now
endEpoch: Math.floor(Date.now() / 1000) + 86400 * 180, // 6 months vesting
periodLength: 86400 * 7, // Weekly claims
nonce: currentNonce // Include current nonce for replay attack prevention
};
// Create manager signature with nonce-aware digest
const managerSignature = await createFundClaimConfigSignature(
projectV2.address,
fundClaimConfig
);
const updateTx = await projectV2.updateFundClaimConfig(
fundClaimConfig,
managerSignature
);
// Check that nonce was incremented
const newNonce = await projectV2.nonces(1, projectOwner);
console.log(`Nonce incremented: ${currentNonce} -> ${newNonce}`);
// Project owner claims vested funds periodically
const claimTx = await projectV2.claimFund();Enhanced Token Claiming
// Configure investor token claiming (called by factory during CLAIM stage)
const claimStartEpoch = Math.floor(Date.now() / 1000) + 86400; // 1 day from now
const periodLength = 86400 * 7; // Weekly vesting
// Note: updateClaimConfig is called by factory, not directly by users
const factory = await ethers.getContractAt("IFIFFactory", factoryAddress);
await factory.updateClaimConfig(projectAddress, claimStartEpoch, periodLength);
// Investors claim vested tokens
const userClaimTx = await projectV2.connect(investor).claim();
// Or convert NFT with vesting
const convertTx = await projectV2.connect(nftOwner).convertNFT(tokenId);Distributor Configuration with Per-Distributor Nonces
// Each distributor has their own independent nonce sequence
const distributorAddress = "0x...";
const currentNonce = await projectV2.nonces(2, distributorAddress); // 2 = NONCE_DISTRIBUTOR_CONFIG
const distributorConfig = {
distributorFeePercent: 3,
projectOwnerFeePercent: 5,
platformFeePercent: 2,
nonce: currentNonce // Use distributor's current nonce
};
// Create three signatures with nonce-aware digest
const signatures = await createDistributorConfigSignatures(
projectV2.address,
distributorAddress,
distributorConfig
);
const updateTx = await projectV2.updateDistributorConfig(
distributorAddress,
distributorConfig,
signatures // [manager, distributor, project owner]
);
// Verify distributor's nonce was incremented
const newNonce = await projectV2.nonces(2, distributorAddress);
console.log(`Distributor nonce: ${currentNonce} -> ${newNonce}`);
// Important: Other distributors' nonces are unaffected
const otherDistributor = "0x...";
const otherNonce = await projectV2.nonces(2, otherDistributor);
console.log(`Other distributor nonce unchanged: ${otherNonce}`);Asset Recovery
// After 6 months grace period, factory can sweep unclaimed assets
const factory = await ethers.getContractAt("IFIFFactory", factoryAddress);
const sweepTx = await factory.sweep(projectAddress);
// Note: After sweep, factory receives the assets and can handle redistribution
// The specific redistribution mechanism depends on factory implementationTesting Coverage
IFIFv2 maintains comprehensive test coverage including:
- V2 Initialization: Enhanced configuration validation and signature verification
- Automatic Transitions: Time-based stage progression testing
- Vesting Mechanisms: Fund and token vesting calculations and claims
- Enhanced Purchases: Hard cap, soft cap, and timing validations
- Multi-signature Operations: Distributor configuration and reduced signature requirements
- Asset Recovery: Sweep functionality and timing validations
- Backward Compatibility: Ensures v1 functionality remains intact where applicable
Migration from V1 to V2
Key Differences
- Manual → Automatic: Stage transitions become time-based
- Immediate → Vested: Fund transfers become vesting-based claims
- Global → Individual: Distributor configurations become address-specific
- Basic → Enhanced: Purchase controls add caps and minimums
Deployment Considerations
- V2 projects require additional configuration parameters
- Enhanced timing precision requires careful timestamp management
- Vesting schedules need proper configuration for both funds and tokens
- Multi-signature requirements may need workflow adjustments
For detailed implementation examples and migration guides, see the Examples section.