Crypto fund profit distribution system

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Crypto fund profit distribution system
Complex
~5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1217
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1046
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Crypto Fund Profit Distribution System Development

When a fund realizes profit and needs to distribute it among LPs, simple "divide proportional to shares" doesn't work. Different LPs entered at different times and prices, some requested partial redemptions, others received interim distributions, manager accumulated performance fees. Profit distribution system must be mathematically exact, auditable, and resistant to manipulation. Let's see how this is done correctly.

Profit Distribution Models

Before writing contracts, choose the fundamental share accounting model:

Share-based (tokenized shares)

LPs receive ERC-20 shares on entry. Profit reflects through NAV per share growth — one share is worth more, but share count doesn't change. Profit distribution means either share value growth (capital appreciation) or dividend payout with NAV per share decrease.

Pros: EIP-4626 standard, integrates with DeFi easily. Cons: complexity with partial exits and interim distributions.

Point-based (accumulative points)

Each LP accumulates "points" proportional to time and deposit amount. Profit divides proportional to points. Suits yield funds with regular payouts.

// Pattern "rewards per token" — standard for point-based
contract ProfitDistributor {
    uint256 public rewardPerShareStored;
    uint256 public lastUpdateTime;
    uint256 public totalShares;

    mapping(address => uint256) public rewardPerSharePaid;
    mapping(address => uint256) public pendingRewards;
    mapping(address => uint256) public shares;

    modifier updateReward(address account) {
        rewardPerShareStored = rewardPerShare();
        lastUpdateTime = block.timestamp;
        if (account != address(0)) {
            pendingRewards[account] = earned(account);
            rewardPerSharePaid[account] = rewardPerShareStored;
        }
        _;
    }

    function earned(address account) public view returns (uint256) {
        return shares[account]
            * (rewardPerShare() - rewardPerSharePaid[account])
            / 1e18
            + pendingRewards[account];
    }
}

This pattern ("Synthetix staking rewards") — proven way to efficiently calculate accumulated rewards without iterating all LPs.

Complex Cases: What breaks simple systems

Entry and exit mid-period

If LP entered mid-quarter, they shouldn't receive profit for the period before entry. Conversely, on mid-period exit they should get their share of accrued but not distributed profit.

Solution: snapshot-based distribution. Each state change (LP entry/exit) creates checkpoint with current rewardPerShare. On calculation we use difference between current and checkpoint value:

function _updateCheckpoint(address lp) internal {
    uint256 currentRPS = rewardPerShareStored;
    uint256 lpShares = shares[lp];
    uint256 lastRPS = checkpoints[lp].rewardPerShare;

    if (lpShares > 0 && currentRPS > lastRPS) {
        uint256 accrued = lpShares * (currentRPS - lastRPS) / PRECISION;
        checkpoints[lp].pendingReward += accrued;
    }
    checkpoints[lp].rewardPerShare = currentRPS;
}

Performance fee with High Water Mark

Performance fee (typically 20%) should be taken only on new profit — above previous HWM. This protects LPs from double fee after drawdown and recovery.

Nuance: is HWM per-LP or global? Two approaches:

Global HWM — one HWM for entire fund. Simple, but unjust: LP who entered at peak doesn't pay fee until fund returns above that peak, even if for new LP it's profit.

Per-LP HWM — each LP has own HWM = NAV per share at their entry. More fair, but harder to implement and gas-costly with many LPs.

mapping(address => uint256) public lpHighWaterMark; // NAV per share at LP entry

function calculatePerformanceFee(address lp, uint256 currentNAVPerShare)
    public view returns (uint256 feeAmount)
{
    uint256 hwm = lpHighWaterMark[lp];
    if (currentNAVPerShare <= hwm) return 0;

    uint256 profitPerShare = currentNAVPerShare - hwm;
    uint256 lpShareBalance = shares[lp];

    // fee = profit_per_share * lp_shares * fee_rate / PRECISION
    feeAmount = profitPerShare * lpShareBalance * performanceFeeRate
        / (PRECISION * 10000);
}

Hurdle rate

Some funds take performance fee only if returns exceeded benchmark (hurdle rate, e.g. 8% annually). Implemented as additional threshold above HWM:

function isAboveHurdle(address lp, uint256 currentNAVPerShare) public view returns (bool) {
    uint256 entryNAV = lpEntryNAV[lp];
    uint256 entryTime = lpEntryTime[lp];
    uint256 elapsed = block.timestamp - entryTime;

    // Hurdle rate = entryNAV * (1 + annualRate * elapsed / YEAR)
    uint256 hurdleNAV = entryNAV + entryNAV * hurdleRate * elapsed / (10000 * 365 days);
    return currentNAVPerShare > hurdleNAV;
}

Payout Mechanism: Tokens vs native

Reinvestment (default): profit automatically increases NAV per share. LP gets no payout — his share value grows. Gas efficient, requires no on-chain transactions.

Cash distribution: manager initiates distribution, LPs claim their payouts. Two patterns:

Pull pattern (recommended): contract holds accumulated rewards, LPs claim themselves. Manager doesn't know all LP addresses, no gas exhaustion risk with many LPs.

function claimRewards() external updateReward(msg.sender) {
    uint256 reward = pendingRewards[msg.sender];
    require(reward > 0, "Nothing to claim");
    pendingRewards[msg.sender] = 0;
    IERC20(rewardToken).safeTransfer(msg.sender, reward);
    emit RewardClaimed(msg.sender, reward);
}

Push pattern — manager sends to all LPs. Problem: if LP is smart contract that reverts on token receipt, entire transaction fails. Or gas limit exhausts at 1000+ LPs. Use only for small LP count and after whitelist check.

Merkle-based distribution at scale

With hundreds and thousands of LPs — Merkle drop pattern. Manager calculates payouts off-chain, builds Merkle tree, publishes root on-chain. Each LP claims their payout with proof:

bytes32 public merkleRoot;

function claimMerkle(
    uint256 index,
    address account,
    uint256 amount,
    bytes32[] calldata merkleProof
) external {
    require(!isClaimed(index), "Already claimed");

    bytes32 node = keccak256(abi.encodePacked(index, account, amount));
    require(MerkleProof.verify(merkleProof, merkleRoot, node), "Invalid proof");

    _setClaimed(index);
    IERC20(rewardToken).safeTransfer(account, amount);
    emit Claimed(index, account, amount);
}

This gives O(log N) gas for claim regardless of recipient count. Uniswap used this pattern for UNI distribution.

Tax and Regulatory Reporting

Each distribution must generate report for LP: date, amount, type (return of capital / capital gain / ordinary income). Income classification differs by jurisdiction.

Minimal data set for each distribution event:

  • Timestamp and block number
  • LP address and identifier (KYC data stored off-chain)
  • Amount in tokens and USD equivalent at distribution
  • NAV per share before and after distribution
  • Accumulated fee retained by manager

Hash each distribution report published on-chain — LPs can verify off-chain data wasn't altered.

Security and Audit

Reentrancy — classic vulnerability on payouts. Always zero pendingRewards[msg.sender] before transfer, not after. OpenZeppelin ReentrancyGuard as additional protection layer.

Precision loss on division. All calculations in fixed-point arithmetic at 1e18 scale. Store rewardPerShare with maximum precision, round down on payouts (benefiting contract, not LP — otherwise contract depletes early).

Oracle manipulation on performance fee calculation. If fee calculated from NAV per share, and NAV depends on DEX prices — flash loan attack can temporarily raise NAV, trigger settleFee, grab fee, restore NAV. Protection: TWAP instead of spot prices, cooldown between NAV updates and fee settlements.

Manager centralization. Manager with updateNAV and settleFee rights has great power. Minimize via timelock (operations take effect in 24-48 hours) and multi-sig (3-of-5 for fee settlement).

Testing

Invariant tests via Foundry (Invariant Testing):

  • sum(lpShares) == totalSupply() — always
  • sum(pendingRewards) <= contractBalance(rewardToken) — contract cannot be insolvent
  • After any deposit/withdraw/distribute sequence — earned() must be correct
// Foundry invariant test
contract ProfitDistributorInvariant is Test {
    ProfitDistributor distributor;

    function invariant_solvency() public {
        uint256 totalPending = 0;
        for (uint i = 0; i < lps.length; i++) {
            totalPending += distributor.earned(lps[i]);
        }
        assertLe(totalPending, IERC20(rewardToken).balanceOf(address(distributor)));
    }
}

Development Process

Design (3-5 days). Choose model (share-based vs point-based), fee structure, per-LP vs global HWM, payout mechanism. Formalize system invariants.

Smart contract development (7-10 days). Vault, distributor, fee module. Unit tests + fuzz tests + invariant tests on Foundry.

Off-chain components (3-5 days). NAV calculator, Merkle tree builder, claim scripts.

Audit (1-2 weeks). External audit is mandatory for any system managing others' money.

Deploy and monitoring (2-3 days). Graduated rollout, real-time invariant monitoring.