Skip to content

IFIF Contract

The IFIF contract is the core crowdfunding project contract that manages the complete lifecycle of investment projects, from initialization through token distribution. It implements ERC721 NFTs to represent user allocations and provides a sophisticated multi-stage sale system with automatic DEX integration.

Overview

Contract Address: contracts/src/core/IFIF.sol
License: GPL-3.0-only
Solidity Version: 0.8.19

Key Features

  • Multi-Stage Sale System: Private sale → Public sale → Success/Failure determination
  • NFT Allocation Tracking: ERC721 tokens representing purchase allocations with split/merge functionality
  • Whitelist Integration: Merkle tree-based access control for private and public sales
  • DEX Integration: Automatic pair creation and liquidity provision upon successful funding
  • EIP712 Signatures: Secure configuration updates through multi-signature validation with nonce-based replay attack prevention
  • Signature Nonce System: Independent nonce tracking per signature type prevents signature reuse and enables batch invalidation
  • UUPS Upgradeable: Admin-controlled contract upgrades (only during INIT stage)

Project Lifecycle

The IFIF contract manages projects through distinct stages:

Stage Flow

NONE → INIT → PRIVATE_SALE → PUBLIC_SALE → SALE_SUCCESSED/FAILED → CLAIM

Stage Descriptions

NONE (0)

  • Purpose: Initial uninitialized state
  • Permissions: No operations allowed
  • Transition: Manual initialization by factory

INIT (1)

  • Purpose: Project configuration and preparation
  • Permissions: Configuration updates, manual stage transitions by owner
  • Duration: Until owner manually starts private sale
  • Transition: Manual call to startPrivateSale() by project owner

PRIVATE_SALE (2)

  • Purpose: Whitelist-based early investment phase with bonus
  • Permissions: Whitelisted addresses can purchase with bonus percentage
  • Duration: privateSaleTime seconds from start
  • Requirements: Valid Merkle proof for private sale section
  • Transition: Manual call to startPublicSale() by project owner

PUBLIC_SALE (3)

  • Purpose: Open investment phase for all whitelisted participants
  • Permissions: Whitelisted addresses can purchase (no bonus)
  • Duration: publicSaleTime seconds from start
  • Requirements: Valid Merkle proof for public sale section
  • Transition: Manual call to endSales() by owner, or manual call to publicEndSales() by anyone after 60 days past scheduled end time

SALE_SUCCESSED (4)

  • Purpose: Successful funding completion
  • Trigger: Exact funding target reached (totalPurchase == fundAmount)
  • Actions: Funds transferred to project owner, NFT claiming enabled
  • Permissions: Users can claim NFTs, distributor can deposit tokens

SALE_FAILED (5)

  • Purpose: Failed funding (target not reached)
  • Trigger: Sales ended without reaching exact funding target
  • Actions: Refund mechanism enabled
  • Permissions: Purchase refunds available for all participants

CLAIM (6)

  • Purpose: Token distribution phase
  • Requirements: Successful sale completion and distributor token deposit
  • Actions: NFT conversion to project tokens based on weight allocation
  • Duration: Permanent claiming window

Core Data Structures

Config

struct Config {
    address fundToken;           // Token used for funding (USDC, USDT, etc.)
    uint256 projectId;          // Unique identifier for the project
    uint256 fundAmount;         // Target funding amount to raise
    uint256 privateSaleTime;    // Duration of private sale in seconds
    uint256 privateBonusPercent; // Bonus percentage for private sale (0-99)
    uint256 publicSaleTime;     // Duration of public sale in seconds
    uint256 desiredEndEpoch;    // Maximum project end timestamp
}

NextConfig

struct NextConfig {
    address projectOwner;           // Project owner receiving funds and controlling sales
    address distributor;            // Distributor depositing tokens and managing distribution
    uint256 distributorFeePercent;  // Percentage fee taken by distributor (0-99)
    uint256 projectOwnerFeePercent; // Percentage fee for project owner (0-99)
    uint256 platformFeePercent;     // Percentage fee for platform (0-99)
    uint256 pairPrice;              // Token price for DEX pair (scaled by 1e18)
    uint256 nonce;                  // Nonce for replay attack prevention (must match or exceed current nonce)
}

Nonce Field: The nonce field in NextConfig enables signature replay attack prevention. For initialization, use nonce: 0. For updates, read the current nonce from the contract via nonces(0, projectOwner) where 0 is the NONCE_NEXT_CONFIG constant. The contract validates that the provided nonce is greater than or equal to the current nonce, then increments it to providedNonce + 1, invalidating all signatures with lower nonces.

Purchase Tracking

mapping(address user => uint256 weight) public purchaseWeights;  // Weighted allocation including bonuses
mapping(address user => uint256 amount) public purchases;        // Raw purchase amounts for refunds
mapping(uint256 tokenId => uint256 weight) public nftWeights;    // NFT token weight mapping

View Functions

Project State

stage() → Stage

Returns the current stage of the project lifecycle.

totalPurchase() → uint256

Returns the total amount purchased across all participants in funding tokens.

totalWeight() → uint256

Returns the total weight from all participant investments (including bonuses).

activeSaleEndTime() → uint256

Returns the timestamp when the current active sale ends.

token() → address

Returns the address of the deployed project token (zero if not deployed).

earningsMultiplier() → uint256

Returns the earnings multiplier for token claim calculations.

dexPair() → address

Returns the address of the created DEX liquidity pair.

owner() → address

Returns the current project owner address (from NextConfig, not contract owner).

User Data

purchaseWeights(address user) → uint256

Returns the purchase weight for a specific user (including bonuses).

purchases(address user) → uint256

Returns the purchase amount for a specific user in funding tokens.

nftWeights(uint256 tokenId) → uint256

Returns the weight associated with a specific NFT.

Nonce Management

nonces(uint8 signatureType, address signer) → uint256

Returns the current nonce value for a specific signature type and signer address.

Parameters:

  • signatureType: Type identifier (0 = NONCE_NEXT_CONFIG for NextConfig signatures)
  • signer: Address whose nonce to query (typically the project owner)

Usage: Read this value before creating update signatures to ensure proper nonce sequencing and replay attack prevention.

Nonce Constants:

  • NONCE_NEXT_CONFIG = 0: Used for NextConfig struct signatures

Nonce Behavior:

  • Initialization: Start with nonce 0 for first deployment
  • Updates: Read current nonce, include it in signature, contract increments to providedNonce + 1
  • Skipping: Using a higher nonce (e.g., 3) when current is 0 will set nonce to 4, invalidating signatures with nonces 0, 1, and 2

EIP-712 Type Hashes

CONFIG_TYPEHASH() → bytes32

Returns the EIP-712 typehash for Config struct (does not include nonce).

NEXT_CONFIG_TYPEHASH() → bytes32

Returns the EIP-712 typehash for NextConfig struct (includes nonce field for replay attack prevention).

ERC721 Metadata

name() → string

Returns the NFT collection name (format: "projectName NFT").

symbol() → string

Returns the NFT collection symbol (format: "projectSymbolN").

tokenURI(uint256 tokenId) → string

Returns the metadata URI for a specific NFT (format: "baseURI/projectId/tokenId").

Core Functions

Initialization

initialize()

function initialize(
    Config calldata config,
    NextConfig calldata nextConfig,
    bytes calldata configSignature,
    bytes calldata nextConfigSignature
) external

Purpose: Initializes a new IFIF project with configuration and EIP-712 signature validation.

Parameters:

  • config: Core project configuration (funding targets, sale durations, etc.)
  • nextConfig: Mutable configuration (stakeholders, fees, pricing, nonce)
  • configSignature: EIP-712 signature from manager validating config
  • nextConfigSignature: EIP-712 signature from manager validating nextConfig (includes nonce validation)

Access: Factory contract only during deployment

Validations:

  • Valid manager signatures for both config structs
  • Nonce validation (must use nonce: 0 for initialization)
  • Distributor has DISTRIBUTOR_ROLE
  • Total fee percentages < 100%
  • Valid bonus percentage < 100%
  • Desired end epoch in the future

Nonce Initialization: The nextConfig must include nonce: 0 for first-time project deployment. The contract validates and consumes this nonce, setting the next expected nonce to 1.

startPrivateSale()

function startPrivateSale() external isStage(Stage.INIT) onlyOwner

Purpose: Manually starts the private sale phase.

Requirements:

  • Project must be in INIT stage
  • Only project owner can call

Actions:

  • Transitions to PRIVATE_SALE stage
  • Sets activeSaleEndTime to current time + privateSaleTime
  • Emits StageUpdated event

startPublicSale()

function startPublicSale() external isStage(Stage.PRIVATE_SALE) onlyOwner

Purpose: Manually starts the public sale phase.

Requirements:

  • Project must be in PRIVATE_SALE stage
  • Only project owner can call
  • Funding target not yet reached

Actions:

  • Transitions to PUBLIC_SALE stage
  • Sets activeSaleEndTime to current time + publicSaleTime
  • Emits StageUpdated event

Purchase Functions

purchase()

function purchase(
    uint256 amount,
    bytes32[] calldata proof
) external isSaleStage nonReentrant

Purpose: Allows whitelisted users to purchase project allocations with funding tokens.

Parameters:

  • amount: Purchase amount in funding tokens (not ETH)
  • proof: Merkle proof for whitelist verification

Requirements:

  • Must be in PRIVATE_SALE or PUBLIC_SALE stage
  • Valid whitelist proof for current sale section (section 1 for private sale, section 2 for public sale)
  • Purchase amount > 0
  • Sale time not expired
  • Project deadline not exceeded
  • Total purchase + amount ≤ funding target

Actions:

  • Validates whitelist membership
  • Calculates weight with bonus for private sale
  • Updates purchase tracking mappings
  • Transfers funding tokens from user
  • Updates total purchase amount

Events: Emits Purchased event

NFT Management

claimNFT()

function claimNFT() external isStage(Stage.SALE_SUCCESSED)

Purpose: Claims NFT representing user's allocation after successful funding.

Requirements:

  • Caller must have valid purchase record (purchases[msg.sender] > 0)
  • Project must be in SALE_SUCCESSED stage
  • User hasn't already claimed NFT (can only be called once per user)

Actions:

  • Mints NFT with weight equal to user's purchase weight
  • Stores NFT weight in nftWeights mapping
  • Clears user's purchase and purchaseWeights data
  • Increments _currentTokenId counter

Events: Emits NFTClaimed event with user address and token ID

Note: This function can only be called once per user as it deletes their purchase data

splitNFT()

function splitNFT(
    uint256 tokenId,
    uint256[] calldata tokenWeights
) external isStage(Stage.SALE_SUCCESSED)

Purpose: Splits an existing NFT allocation into multiple smaller NFTs.

Parameters:

  • tokenId: NFT to split
  • tokenWeights: Array of weights for new NFTs (maximum 3 NFTs)

Requirements:

  • Caller must own the NFT
  • Project must be in SALE_SUCCESSED stage
  • Maximum 3 new NFTs can be created
  • Sum of weights must be ≤ original NFT weight (allows partial splits)
  • All weights must be greater than zero

Actions:

  • Burns original NFT if total weight equals original weight
  • Reduces original NFT weight if partial split (total weight < original)
  • Mints new NFTs with specified weights
  • Maintains total allocation weight

Events: Emits NFTSplitted event

Important: NFT split operations are only available during SALE_SUCCESSED stage. Once the project transitions to CLAIM stage, NFTs can only be converted to tokens via convertNFT().

mergeNFT()

function mergeNFT(uint256[] calldata tokenIds) external isStage(Stage.SALE_SUCCESSED)

Purpose: Merges multiple NFTs owned by the caller into a single NFT.

Parameters:

  • tokenIds: Array of NFT IDs to merge (maximum 3 NFTs)

Requirements:

  • Caller must own all specified NFTs
  • Project must be in SALE_SUCCESSED stage
  • Maximum 3 NFTs can be merged
  • At least 1 NFT must be provided

Actions:

  • Burns all specified NFTs
  • Mints single NFT with combined weight
  • Maintains total allocation weight

Events: Emits NFTMerged event

Important: NFT merge operations are only available during SALE_SUCCESSED stage. Once the project transitions to CLAIM stage, NFTs can only be converted to tokens via convertNFT().

Token Distribution

deposit()

function deposit(uint256 amount) external isStage(Stage.SALE_SUCCESSED) nonReentrant

Purpose: Distributor deposits tokens to enable claiming and create DEX liquidity.

Parameters:

  • amount: Total amount of tokens to deposit for distribution and liquidity

Requirements:

  • Project must be in SALE_SUCCESSED stage
  • Only distributor can call this function
  • Amount must be greater than zero

Actions:

  1. Calculates fee distributions (project owner, platform, liquidity)
  2. Transfers fees to respective parties
  3. Deploys project token via factory
  4. Creates DEX pair and adds liquidity
  5. Transitions to CLAIM stage
  6. Sets earningsMultiplier for proportional token claiming

claim()

function claim() external isStage(Stage.CLAIM)

Purpose: Allows users to claim tokens directly from their purchase weights.

Requirements:

  • Project must be in CLAIM stage
  • Caller must have purchase weights > 0
  • User hasn't already claimed tokens

Actions:

  • Calculates token amount based on user's purchase weight and earnings multiplier
  • Clears user's purchase and purchaseWeights data
  • Transfers calculated token amount to user

Events: Emits Claimed event

Note: Alternative to NFT flow - users can claim directly without first claiming NFT

convertNFT()

function convertNFT(uint256 tokenId) external isStage(Stage.CLAIM)

Purpose: Converts NFT to project tokens based on weight allocation.

Parameters:

  • tokenId: NFT ID representing allocation

Requirements:

  • Caller must own the NFT
  • Project must be in CLAIM stage
  • Distributor must have deposited tokens

Calculations:

  • Token amount = calculated using NFT weight, earnings multiplier, and pair price

Actions:

  • Calculates token amount based on NFT weight
  • Burns converted NFT
  • Transfers calculated token amount to user

Events: Emits NFTConverted event

Note: Alternative to direct claim - users first claim NFT, then convert it to tokens

Refund System

refund()

function refund() external isStage(Stage.SALE_FAILED)

Purpose: Refunds purchase amount for failed projects.

Requirements:

  • Caller must have valid purchase record
  • Project must be in SALE_FAILED stage
  • User hasn't already claimed refund

Actions:

  • Transfers purchase amount back to caller in funding tokens
  • Clears user's purchase records
  • Updates refund tracking

Events: Emits Refunded event

Administrative Functions

endSales()

function endSales() external onlyOwner

Purpose: Manually ends the current sale phase and determines project outcome.

Requirements:

  • Must be in PRIVATE_SALE or PUBLIC_SALE stage
  • Only project owner can call

Logic:

  • If totalPurchase == fundAmount: transitions to SALE_SUCCESSED, transfers funds to owner
  • If totalPurchase != fundAmount: transitions to SALE_FAILED

publicEndSales()

function publicEndSales() external

Purpose: Emergency function to end stuck sales after 60 days past scheduled end.

Requirements:

  • Must be 60+ days past activeSaleEndTime
  • Anyone can call this function

Logic: Same as endSales() but with emergency timing validation

updateConfig()

function updateConfig(
    NextConfig calldata nextConfig,
    bytes[] calldata signatures
) external

Purpose: Updates mutable project configuration with multi-signature validation and nonce-based replay attack prevention.

Parameters:

  • nextConfig: New configuration values including current or higher nonce
  • signatures: Array of exactly 3 signatures [manager, current distributor, current project owner]

Requirements:

  • Must have exactly 3 signatures
  • Cannot be called during CLAIM stage
  • Nonce must be greater than or equal to current nonce value
  • Valid signatures from:
    1. Manager (validates with manager role)
    2. Current distributor (validates against current distributor address)
    3. Current project owner (validates against current owner address)
  • New distributor must have DISTRIBUTOR_ROLE
  • Total fee percentages < 100%

Nonce Workflow:

  1. Read current nonce: uint256 currentNonce = ifif.nonces(0, projectOwner)
  2. Include nonce in nextConfig: nextConfig.nonce = currentNonce (or higher to skip nonces)
  3. Create signatures with nonce-aware digest
  4. Contract validates nonce ≥ current, then sets nonce to providedNonce + 1
  5. All signatures with nonces ≤ providedNonce become invalid

Security: Multi-signature validation with nonce-based replay attack prevention ensures only fresh, authorized updates are processed

Events: Emits NonceConsumed(0, projectOwner, providedNonce) to track nonce usage

DEX Integration

Automatic Liquidity Provision

When distributor calls deposit() after successful funding:

  1. Fee Distribution: Calculates and distributes fees to project owner and platform
  2. Token Deployment: Factory deploys project token with calculated mint amount
  3. Pair Creation: Creates DEX pair for fundToken/projectToken
  4. Liquidity Addition: Adds calculated liquidity amounts to the pair
  5. Earnings Setup: Calculates earningsMultiplier for proportional token distribution

Liquidity Calculations

// Fee calculations from deposit amount
uint256 projectOwnerShare = shareAmount(amount, distributorFee, projectOwnerFee);
uint256 platformShare = shareAmount(amount, distributorFee, platformFee);
uint256 liquidityAmount = amount - projectOwnerShare - platformShare;
 
// Token amounts for DEX pair
uint256 pairTokenAmount = pairLiquidityAmount * pairPrice / 1e18;
uint256 totalTokenMint = pairTokenAmount * 2 + (pairTokenAmount * 2 / 1000); // Double for liquidity + users, plus 0.1% buffer

Supported DEX Protocols

  • Uniswap V2: Primary DEX integration
  • Compatible Forks: Any Uniswap V2-compatible DEX
  • Pair Structure: FundToken/ProjectToken pairs (not ETH pairs)

Security Features

Access Control

  • Role-based permissions: Factory, owner, and admin-specific functions
  • Stage-based restrictions: Operations only available in appropriate stages
  • Ownership validation: NFT ownership required for user actions

Signature Verification & Replay Attack Prevention

  • EIP712 compliance: Standardized signature format for all configuration updates
  • Manager signature requirement: Manager signatures required for config validation
  • Nonce-based replay protection: Independent nonce tracking per signature type prevents signature reuse
  • SignatureNonceManager inheritance: Provides unified nonce management system across all signature types
  • Nonce skipping capability: Allows intentional invalidation of multiple pending signatures by using higher nonces
  • Sequential validation: Enforces nonce ≥ current nonce, then increments to providedNonce + 1
  • Event transparency: NonceConsumed events provide audit trail of nonce usage

Reentrancy Protection

  • Critical functions protected: All token transfer operations use reentrancy guards
  • State updates first: Balance updates before external calls
  • Safe transfer patterns: Uses SafeTransferLib for secure transfers

Input Validation

  • Comprehensive checks: All function parameters validated
  • Custom errors: Gas-efficient error reporting
  • Edge case handling: Prevents division by zero and overflow conditions

Events

Core Events

event Purchased(address indexed user, uint256 amount);
event Refunded(address indexed user, uint256 amount);
event NFTClaimed(address indexed user, uint256 indexed tokenId);
event NFTSplitted(uint256 indexed tokenId, uint256[] newTokenIds);
event NFTMerged(uint256[] tokenIds, uint256 indexed newTokenId);
event Deposited(uint256 amount);
event Claimed(address indexed user, uint256 amount);
event NFTConverted(address indexed user, uint256 indexed tokenId, uint256 amount);
event ConfigUpdated(address newProjectOwner, address newDistributor);
event StageUpdated(Stage newStage);
event DexPairCreated(address indexed pair, address indexed token, address indexed fundToken);
event NonceConsumed(uint8 indexed signatureType, address indexed signer, uint256 nonce);

Nonce Management Event

The NonceConsumed event is emitted whenever a nonce is successfully validated and incremented:

  • signatureType: The type of signature (0 for NONCE_NEXT_CONFIG)
  • signer: The address whose nonce was consumed (typically project owner)
  • nonce: The nonce value that was consumed (before increment)

This event provides transparency for nonce usage and helps with off-chain tracking and debugging.

Integration Examples

Frontend Integration

// Project lifecycle management
const project = await ethers.getContractAt("IFIF", projectAddress);
 
// Check current stage and nonce state
const currentStage = await project.stage();
const projectOwner = await project.owner();
const currentNonce = await project.nonces(0, projectOwner); // 0 = NONCE_NEXT_CONFIG
console.log(`Current nonce: ${currentNonce}`);
 
// Start private sale (project owner only)
if (currentStage === 1) { // INIT
  const startPrivateTx = await project.startPrivateSale();
}
 
// Start public sale (project owner only)
if (currentStage === 2) { // PRIVATE_SALE
  const startPublicTx = await project.startPublicSale();
}
 
// Purchase during sale with funding token (not ETH)
const fundToken = await ethers.getContractAt("ERC20", fundTokenAddress);
await fundToken.approve(projectAddress, purchaseAmount);
const purchase = await project.purchase(purchaseAmount, merkleProof);
 
// Claim NFT after successful funding
if (currentStage === 4) { // SALE_SUCCESSED
  const claimNFT = await project.claimNFT();
}
 
// Distributor deposits tokens to enable claiming
if (currentStage === 4) { // SALE_SUCCESSED
  const token = await ethers.getContractAt("ERC20", tokenAddress);
  await token.approve(projectAddress, depositAmount);
  const deposit = await project.deposit(depositAmount);
}
 
// Convert NFT to tokens during CLAIM phase (NFT flow)
if (currentStage === 6) { // CLAIM
  // Option 1: Claim NFT first, then convert to tokens
  const claimNFT = await project.claimNFT(); // Must be in SALE_SUCCESSED stage
  // ... later when stage becomes CLAIM ...
  const convert = await project.convertNFT(tokenId);
 
  // Option 2: Direct token claiming without NFT (during CLAIM stage)
  const directClaim = await project.claim(); // Direct token claim from purchase weights
}
 
// Split NFT allocation
const split = await project.splitNFT(
  tokenId,
  [weight1, weight2] // Weight values, not amounts
);
 
// Update project configuration with nonce-based replay attack prevention
const currentNonce = await project.nonces(0, projectOwner); // Read current nonce
 
const newNextConfig = {
  projectOwner: projectOwner,
  distributor: newDistributor,
  distributorFeePercent: 3,
  projectOwnerFeePercent: 5,
  platformFeePercent: 2,
  pairPrice: ethers.parseEther("0.1"),
  nonce: currentNonce // Use current nonce or higher to skip nonces
};
 
// Create EIP-712 signatures with nonce-aware digest
const signatures = await createUpdateConfigSignatures(project.address, newNextConfig);
 
// Update configuration (nonce will be incremented to currentNonce + 1)
const updateTx = await project.updateConfig(newNextConfig, signatures);
 
// After update, nonce is now currentNonce + 1
const newNonce = await project.nonces(0, projectOwner);
console.log(`Nonce after update: ${newNonce}`); // currentNonce + 1
 
// Advanced: Skip multiple nonces to invalidate pending signatures
const skipToNonce = currentNonce + 5; // Skip to nonce 5
newNextConfig.nonce = skipToNonce;
// Create signatures and update...
// This will set nonce to 6, invalidating all signatures with nonces 0-5

Query Functions

// Check project stage and metrics
Stage currentStage = ifif.stage();
uint256 totalPurchased = ifif.totalPurchase();
uint256 totalWeights = ifif.totalWeight();
uint256 activeSaleEnd = ifif.activeSaleEndTime();
 
// Get project owner (from NextConfig, not traditional contract owner)
address projectOwner = ifif.owner(); // Returns _nextConfig.projectOwner
 
// Get user purchase information
uint256 userWeight = ifif.purchaseWeights(userAddress);
uint256 userPurchase = ifif.purchases(userAddress);
 
// Get NFT information
uint256 nftWeight = ifif.nftWeights(tokenId);
 
// ERC721 metadata functions
string memory collectionName = ifif.name(); // Returns "projectName NFT"
string memory collectionSymbol = ifif.symbol(); // Returns "projectSymbolN"
string memory nftMetadataURI = ifif.tokenURI(tokenId); // Returns "baseURI/projectId/tokenId"
 
// Check if claiming is active
address projectToken = ifif.token();
uint256 earningsMultiplier = ifif.earningsMultiplier();
address dexPair = ifif.dexPair();

Testing Coverage

The IFIF contract maintains 100% test coverage including:

  • Lifecycle transitions: All stage changes and validations
  • Purchase scenarios: Valid and invalid purchase attempts
  • NFT operations: Split, merge, and transfer functionality
  • Token claiming: Proportional distribution calculations
  • Refund mechanisms: Failed project recovery
  • Security tests: Access control and reentrancy protection
  • Edge cases: Boundary conditions and error scenarios

Deployment Considerations

Gas Optimization

  • Clone pattern: Deployed via factory using LibClone for efficiency
  • Packed structs: Optimized storage layout for gas savings
  • Custom errors: Gas-efficient error reporting

Network Compatibility

  • EVM compatible: Works on any Ethereum-compatible network
  • DEX integration: Requires compatible Uniswap V2-style DEX
  • Block time considerations: Timestamp validations account for network block times

For detailed implementation examples and integration patterns, see the Examples section.