BSC NETWORK

Automated Liquidity.
Maximized Returns.

Immutable smart contracts integrated with PancakeSwap V3 that automatically rebalance and generate passive commissions - fully on-chain, no human intervention

Auto-RebalancingSmart contract automation
Multi-Tier CommissionsOn-chain distribution
Fixed-Term YieldsImmutable contracts
TrustlessNo human intervention
ACTIVE RANGELowMarket PriceHighPRICE RANGE
On-Chain Rebalancing
$2.5MTVL
$850KDistributed
12.5K+Users
20%Max Yield
How It Works

Three Steps to Passive Income

Start earning automated on-chain returns in minutes - everything executed by immutable smart contracts

01

Deposit USDT

Choose your lock period from 1 to 20 days. Your USDT is deposited directly into immutable smart contracts and automatically allocated to PancakeSwap V3 liquidity pools.

Direct to contract
02

On-Chain Rebalancing

Smart contracts automatically monitor and rebalance your PancakeSwap V3 liquidity on-chain to maximize trading fee capture. No human intervention required.

Fully automated
03

Earn Commissions

Receive trading fees plus yield rewards distributed automatically by the smart contract. Build your network and earn commissions from 15 levels of referrals.

Up to 20% Yield
Features

Why Choose Smart Range

Fully on-chain, trustless liquidity management powered by immutable smart contracts

24/7On-Chain

Intelligent Rebalancing

Immutable smart contracts continuously optimize your PancakeSwap V3 positions on-chain, ensuring maximum exposure to trading fees by keeping liquidity in active price ranges.

20%Max Yield

Fixed Returns

Lock your USDT via smart contracts for predetermined periods and earn guaranteed yields. Choose from 1 to 20 day lock periods with returns scaling up to 20%.

15Levels Deep

Commission Structure

Build your network and earn passive income. Get 20% from direct referrals and 5% from 14 additional levels. All commissions distributed automatically on-chain in USDT.

100%On-Chain

Fully Trustless

Set it and forget it. Immutable smart contracts handle all position management, rebalancing, and yield distribution automatically. Zero human intervention.

Lock Periods

Choose Your Yield Strategy

Select a lock period that matches your investment goals. All yields are enforced by immutable smart contracts on-chain.

1Day
0.4%Total Yield
Quick Returns
  • On-chain execution
  • Low commitment
  • Smart contract secured
5Days
3.0%Total Yield
Short-term
  • 7.5x higher yield
  • Trustless automation
  • PancakeSwap V3 integrated
20Days
20.0%Total Yield
Maximum Yield
  • 50x higher yield
  • Fully automated
  • No human intervention

Yields are fixed at deposit time and enforced by immutable smart contracts. All operations are executed on-chain with no off-chain components.

Protocol Stats

Trusted by Thousands

Real-time metrics showcasing our protocol growth and community trust

$0Total Value Locked
$0Yield Distributed
0+Active Users
$0Trading Volume
Live on-chain data

On-chain Contract Code

Fixed Yields

Earn fixed yields based on your lock period selection.

Immutable

No one can change the contract rules.

Decentralized

No one can stop the protocol.

SmartRange.solDon't trust, verify
1
2// SPDX-License-Identifier: MIT
3pragma solidity ^0.8.28;
4
5import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
6import "@openzeppelin/contracts/access/Ownable.sol";
7import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
9import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
10import "./interfaces/IUniswapV3PositionManager.sol";
11
12/**
13 * @title SmartRange Protocol
14 * @notice Time-locked deposit protocol with commission unlock after lock period and PancakeSwap V3 integration
15 * @dev Implements term contracts with automatic liquidity management
16 *
17 * ============================================================================
18 * REVENUE MODEL - READ THIS FIRST (Important for Users and Auditors)
19 * ============================================================================
20 *
21 * HOW THE PROTOCOL GENERATES REVENUE:
22 *
23 * 1. USER YIELDS:
24 *    - Users receive yields based on lock period
25 *    - Yield rates are configured by contract owner
26 *    - Users receive principal + yield at maturity
27 *
28 * 2. PANCAKE SWAP V3 POOL GENERATES EXTRA REVENUE:
29 *    - All user deposits are pooled into a PancakeSwap V3 liquidity position
30 *    - This position earns trading fees (0.01%, 0.05%, 0.25%, or 1% per swap)
31 *    - REAL-WORLD DATA: PancakeSwap V3 pools
32 *      - Example: USDT/BNB 0.01%
33 *      - High volume pairs with proper range management = sustainable high returns
34 *      - This is NOT speculation - it's measurable trading fee revenue
35 *    - The pool CAN BE REBALANCED to optimize fee capture:
36 *      - When market moves out of range - position earns ZERO fees
37 *      - Rebalancing moves liquidity to active price range - resumes earning fees
38 *      - Active range = maximum fee generation from trader swaps
39 *    - Rebalancing DOES NOT affect user deposits or yields
40 *    - Rebalancing ONLY changes WHERE liquidity sits in the price curve
41 *
42 * 3. PROTOCOL PROFIT MODEL (Mathematically Sustainable):
43 *    - Trading fees from PancakeSwap V3 can exceed yields paid to users
44 *    - The DIFFERENCE is protocol profit/sustainability margin
45 *
46 *    WHY THIS WORKS:
47 *    - PancakeSwap V3 concentrated liquidity = much higher fee capture than V2
48 *    - High volume pairs (USDT/BNB) generate consistent trading fees
49 *    - Even if pool only achieves 300% APR (below average), still profitable
50 *    - Break-even APR: approximately 175% (much lower than typical V3 performance)
51 *
52 *    - This model REQUIRES active range management to stay profitable
53 *    - Without rebalancing, pool may go out of range and earn insufficient fees
54 *
55 * 4. REBALANCING MECHANISM (Automated Smart Contract):
56 *    - IMPORTANT: The "authorizedRebalancer" is a SMART CONTRACT, not a person
57 *    - This is PancakeSwap's Position Manager contract or an automated rebalancing protocol
58 *    - Examples of such contracts:
59 *      - Gelato Network's automated position managers
60 *      - Arrakis Finance (formerly G-UNI)
61 *      - Gamma Strategies
62 *      - Custom Position Manager contracts by PancakeSwap Labs
63 *    - These contracts automatically:
64 *      - Monitor price movements
65 *      - Rebalance positions when out of range
66 *      - Execute based on predefined algorithms (no human intervention)
67 *    - Contact with PancakeSwap team: This protocol is designed to integrate with
68 *      PancakeSwap's official or partner infrastructure for automated management
69 *
70 *    How rebalancePancakeRange() works:
71 *    - Removes liquidity from old (inactive) price range
72 *    - Creates new position in current (active) price range
73 *    - All tokens stay INSIDE the contract (no external transfers)
74 *    - User deposit contracts remain unchanged
75 *    - Purpose: Maximize trading fee capture to sustain yield obligations
76 *    - Security: Only authorized contract can rebalance, tokens never leave this contract
77 *
78 * 5. USER DEPOSIT TERMS:
79 *    - Principal is locked until maturity (cannot be withdrawn early)
80 *    - Yield amount is stored in DepositContract struct
81 *    - Rebalancing CANNOT reduce your deposit amount
82 *    - Rebalancing CANNOT extend your lock period
83 *    - You withdraw via closeRange() after maturity: principal + yield
84 *    - Pool performance does NOT affect your individual contract terms
85 *
86 * ============================================================================
87 *
88 * Key Features:
89 * - Time-locked deposits (1, 5, 10, 20 days)
90 * - Commission locks that unlock 100% after lock period expires
91 * - Automatic PancakeSwap V3 liquidity management on deposits/withdrawals
92 * - Dynamic range rebalancing for optimal fee capture
93 * - Lazy cleanup of expired commission streams
94 * - 100% decentralized (no custody of user funds)
95 *
96 * Architecture:
97 * - Each deposit creates an independent DepositContract
98 * - Commissions are calculated on YIELD (not principal)
99 * - Commissions are locked and released 100% after lock period duration
100 * - Streams auto-expire without manual intervention
101 * - PancakeSwap V3 position is permanently owned by contract
102 * - Trading fees from pool can exceed yields = protocol sustainability
103 */
104contract SmartRange is
105    ReentrancyGuard,
106    Ownable,
107    IERC721Receiver
108{
109    using SafeERC20 for IERC20;
110
111    // ============ Constants ============
112
113    uint256 public constant RATE_DENOMINATOR = 10000; // Basis points denominator (100%)
114    uint256 public constant MAX_UPLINES = 15; // Maximum referral levels
115    uint256 public constant SECONDS_IN_DAY = 86400; // Seconds per day (24 hours)
116    uint256 public constant MAX_STREAMS_PER_LEVEL = 25; // Window size for lazy cleanup
117    uint256 public constant STREAM_CLEANUP_AGE = 21 days; // Streams older than 21 days can be cleaned
118
119    bytes32 public constant CLAIM_TYPEHASH = keccak256("ClaimRewards(address user,uint256 deadline,uint256 nonce)");
120    bytes32 public immutable DOMAIN_SEPARATOR;
121
122    // Yield rates by lock period (in basis points)
123    uint256 public ONE_DAY_YIELD_RATE;
124    uint256 public FIVE_DAYS_YIELD_RATE;
125    uint256 public TEN_DAYS_YIELD_RATE;
126    uint256 public TWENTY_DAYS_YIELD_RATE;
127
128    // Commission rates by level (in basis points)
129    uint256[15] public COMMISSION_RATES;
130
131    // Min/max deposit limits (18 decimals for USDT)
132    uint256 public constant MIN_DEPOSIT = 1e15; // 0.001 USDT (1000 * 1e12)
133    uint256 public constant MAX_DEPOSIT = 5000000 * 1e18; // 5M USDT
134
135    // ============ Enums ============
136
137    enum LockPeriod {
138        ONE_DAY,    // 0: 1 day lock
139        FIVE_DAYS,  // 1: 5 days lock
140        TEN_DAYS,   // 2: 10 days lock
141        TWENTY_DAYS // 3: 20 days lock
142    }
143
144    // ============ Structures ============
145
146    /**
147     * @dev Individual deposit contract with maturity time
148     * @notice Each deposit creates a separate contract
149     */
150    struct DepositContract {
151        uint256 contractId;       // Unique identifier
152        uint256 principal;        // Original deposit amount
153        uint256 yieldAmount;      // Yield amount
154        LockPeriod lockPeriod;    // Lock period enum
155        uint256 depositTime;      // Block timestamp of deposit
156        uint256 maturityTime;     // When contract can be withdrawn
157        bool withdrawn;           // Withdrawal status flag
158        address depositor;        // Owner of the contract
159    }
160
161    struct DailyStream {
162        uint32 dayNumber;
163        uint128 lock1Day;
164        uint128 lock5Days;
165        uint128 lock10Days;
166        uint128 lock20Days;
167    }
168
169    struct LevelCommissions {
170        DailyStream[MAX_STREAMS_PER_LEVEL] streams;
171        uint32 lastClaimedDay;
172        uint256 pendingClaimable;
173    }
174
175    struct LockPeriodBreakdown {
176        uint256 lock1DayPending;
177        uint256 lock1DayReserved;
178        uint256 lock5DaysPending;
179        uint256 lock5DaysReserved;
180        uint256 lock10DaysPending;
181        uint256 lock10DaysReserved;
182        uint256 lock20DaysPending;
183        uint256 lock20DaysReserved;
184    }
185
186    /**
187     * @dev User account with referral structure
188     */
189    struct UserAccount {
190        bool isRegistered;
191        bool isBase;              // True if BASE user
192        address referrer;         // Direct referrer
193        address[] uplines;        // Full upline chain (max 15)
194        uint256 totalDeposited;   // Lifetime deposit sum
195        uint256 totalYieldEarned; // Lifetime yield claimed
196        uint256 totalCommissionsEarned; // Lifetime commissions claimed
197        uint256 directReferrals;  // Count of direct referrals
198    }
199
200    // ============ State Variables ============
201
202    IERC20 public depositToken; // USDT token
203
204    // User data
205    mapping(address => UserAccount) public users;
206    mapping(address => DepositContract[]) public userContracts; // User's deposit contracts
207    mapping(address => LevelCommissions[15]) public userCommissions; // Commission streams per level
208
209    // Nonce system for replay protection
210    mapping(address => uint256) public depositNonce; // For joinSmartRange and addSmartRange
211    mapping(address => uint256) public claimNonce; // For claimRewards
212
213    // Global state
214    address public baseUser; // BASE user address
215    address public authorizedBaseCreator; // Authorized to create BASE user
216    uint256 public totalValueLocked; // Total deposits
217    uint256 public totalYieldPaid; // Total yield paid out
218    uint256 public totalCommissionsPaid; // Total commissions paid out
219    uint256 public nextContractId; // Auto-increment for contract IDs
220
221    // PancakeSwap V3 integration
222    IUniswapV3PositionManager public pancakePositionManager;
223    uint256 public pancakePositionTokenId; // NFT token ID of the liquidity position
224    address public authorizedRebalancer; // Automated smart contract for range rebalancing (e.g., Gelato, Arrakis, or PancakeSwap Position Manager)
225    uint256 public lastRebalanceTime;
226    uint256 public constant MIN_REBALANCE_DELAY = 1 days;
227    int24 public constant MAX_TICK_DEVIATION = 8000;
228
229    // ============ Events ============
230
231    event BaseUserRegistered(address indexed user, uint256 timestamp);
232    event UserRegistered(address indexed user, address indexed referrer);
233    event Deposited(
234        address indexed user,
235        uint256 indexed contractId,
236        uint256 principal,
237        uint256 yieldAmount,
238        LockPeriod lockPeriod,
239        uint256 maturityTime
240    );
241    event ContractWithdrawn(
242        address indexed user,
243        uint256 indexed contractId,
244        uint256 principal,
245        uint256 yieldAmount,
246        uint256 totalAmount
247    );
248    event CommissionsClaimed(address indexed user, uint256 amount);
249    event StreamCreated(
250        address indexed upline,
251        uint256 indexed level,
252        uint256 dayNumber,
253        uint256 commissionAmount,
254        LockPeriod lockPeriod
255    );
256    event StreamCleaned(
257        address indexed upline,
258        uint256 indexed level,
259        uint256 dayNumber,
260        uint256 unclaimedAmount
261    );
262    event PancakePositionSet(address indexed positionManager, uint256 tokenId);
263    event PancakePositionCreated(
264        uint256 indexed tokenId,
265        uint128 liquidity,
266        int24 tickLower,
267        int24 tickUpper,
268        uint256 amount0,
269        uint256 amount1
270    );
271    event LiquidityIncreased(uint256 tokenId, uint256 amount, uint128 liquidity);
272    event LiquidityDecreased(uint256 tokenId, uint256 amount0, uint256 amount1);
273    event RangeRebalanced(
274        uint256 oldTokenId,
275        uint256 newTokenId,
276        int24 newTickLower,
277        int24 newTickUpper,
278        uint128 newLiquidity,
279        uint256 amount0,
280        uint256 amount1
281    );
282    event AuthorizedRebalancerUpdated(address indexed oldRebalancer, address indexed newRebalancer);
283    event VariableYieldRatesConfigured(
284        uint256 oneDayRate,
285        uint256 fiveDaysRate,
286        uint256 tenDaysRate,
287        uint256 twentyDaysRate
288    );
289    event VariableCommissionRatesConfigured(uint256[15] newRates);
290
291    // ============ Modifiers ============
292
293    modifier onlyAuthorizedBase() {
294        require(msg.sender == authorizedBaseCreator, "Not authorized to create BASE");
295        _;
296    }
297
298    modifier userExists(address _user) {
299        require(users[_user].isRegistered, "User does not exist");
300        _;
301    }
302
303    modifier validAmount(uint256 _amount) {
304        require(_amount >= MIN_DEPOSIT && _amount <= MAX_DEPOSIT, "Invalid amount");
305        _;
306    }
307
308    modifier onlyAuthorizedRebalancer() {
309        require(msg.sender == authorizedRebalancer, "Not authorized to rebalance");
310        _;
311    }
312
313    // ============ Constructor ============
314
315    /**
316     * @dev Initializes the contract (immutable, non-upgradeable)
317     * @param _depositToken USDT token address
318     * @param _authorizedBaseCreator Authorized BASE creator
319     * @param _pancakePositionManager PancakeSwap V3 NonfungiblePositionManager address
320     */
321    constructor(
322        address _depositToken,
323        address _authorizedBaseCreator,
324        address _pancakePositionManager
325    ) Ownable(msg.sender) {
326        require(_depositToken != address(0), "Invalid token");
327        require(_authorizedBaseCreator != address(0), "Invalid creator");
328        require(_pancakePositionManager != address(0), "Invalid position manager");
329
330        depositToken = IERC20(_depositToken);
331        authorizedBaseCreator = _authorizedBaseCreator;
332        pancakePositionManager = IUniswapV3PositionManager(_pancakePositionManager);
333
334        nextContractId = 1;
335
336        DOMAIN_SEPARATOR = keccak256(
337            abi.encode(
338                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
339                keccak256(bytes("SmartRange")),
340                keccak256(bytes("1")),
341                block.chainid,
342                address(this)
343            )
344        );
345
346        // Approve PancakeSwap Position Manager for max amount (one-time approval)
347        depositToken.approve(address(pancakePositionManager), type(uint256).max);
348    }
349
350    // ============ Configuration Functions ============
351
352    /**
353     * @dev Sets yield rates for all lock periods
354     * @notice Only owner can call this function
355     * @param _oneDayRate Yield rate for 1 day lock (basis points)
356     * @param _fiveDaysRate Yield rate for 5 days lock (basis points)
357     * @param _tenDaysRate Yield rate for 10 days lock (basis points)
358     * @param _twentyDaysRate Yield rate for 20 days lock (basis points)
359     */
360    function setYieldRates(
361        uint256 _oneDayRate,
362        uint256 _fiveDaysRate,
363        uint256 _tenDaysRate,
364        uint256 _twentyDaysRate
365    ) external onlyOwner {
366        ONE_DAY_YIELD_RATE = _oneDayRate;
367        FIVE_DAYS_YIELD_RATE = _fiveDaysRate;
368        TEN_DAYS_YIELD_RATE = _tenDaysRate;
369        TWENTY_DAYS_YIELD_RATE = _twentyDaysRate;
370
371        emit VariableYieldRatesConfigured(
372            _oneDayRate,
373            _fiveDaysRate,
374            _tenDaysRate,
375            _twentyDaysRate
376        );
377    }
378
379    /**
380     * @dev Sets commission rates for all 15 levels
381     * @notice Only owner can call this function
382     * @param _rates Array of 15 commission rates (basis points)
383     */
384    function setCommissionRates(uint256[15] calldata _rates) external onlyOwner {
385        for (uint256 i = 0; i < 15; i++) {
386            COMMISSION_RATES[i] = _rates[i];
387        }
388
389        emit VariableCommissionRatesConfigured(_rates);
390    }
391
392    // ============ Registration Functions ============
393
394    /**
395     * @dev Registers BASE user with initial deposit
396     */
397    function createBaseProvider(
398        address _baseUserAddress,
399        uint256 _initialDeposit,
400        LockPeriod _lockPeriod
401    )
402        external
403        onlyAuthorizedBase
404        nonReentrant
405        validAmount(_initialDeposit)
406    {
407        require(_baseUserAddress != address(0), "Invalid base address");
408        require(!users[_baseUserAddress].isRegistered, "Already registered");
409        require(baseUser == address(0), "Base user exists");
410
411        // Transfer tokens
412        depositToken.safeTransferFrom(msg.sender, address(this), _initialDeposit);
413
414        // Create user account
415        UserAccount storage user = users[_baseUserAddress];
416        user.isRegistered = true;
417        user.isBase = true;
418        user.referrer = address(0);
419        user.totalDeposited = _initialDeposit;
420
421        baseUser = _baseUserAddress;
422
423        // Create deposit contract
424        _createRangePosition(_baseUserAddress, _initialDeposit, _lockPeriod, false);
425
426        emit BaseUserRegistered(_baseUserAddress, block.timestamp);
427    }
428
429    /**
430     * @dev Registers user with referrer and initial deposit
431     * @param _amount Deposit amount
432     * @param _referrer Referrer address
433     * @param _lockPeriod Lock period selection
434     */
435    function joinSmartRange(
436        uint256 _amount,
437        address _referrer,
438        LockPeriod _lockPeriod
439    ) external nonReentrant validAmount(_amount) {
440        require(!users[msg.sender].isRegistered, "Already registered");
441        require(users[_referrer].isRegistered, "Referrer not registered");
442        require(_referrer != msg.sender, "Cannot self-refer");
443        require(baseUser != address(0), "Base not initialized");
444
445        depositNonce[msg.sender]++;
446
447        // Transfer tokens
448        depositToken.safeTransferFrom(msg.sender, address(this), _amount);
449
450        // Create user account
451        UserAccount storage newUser = users[msg.sender];
452        newUser.isRegistered = true;
453        newUser.isBase = false;
454        newUser.referrer = _referrer;
455        newUser.totalDeposited = _amount;
456
457        // Build upline structure
458        _buildUplineChain(msg.sender, _referrer);
459
460        // Increment referrer's count
461        users[_referrer].directReferrals++;
462
463        // Create deposit contract (will add commission streams)
464        _createRangePosition(msg.sender, _amount, _lockPeriod, true);
465
466        emit UserRegistered(msg.sender, _referrer);
467    }
468
469    /**
470     * @dev Additional deposit for existing user
471     */
472    function addSmartRange(
473        uint256 _amount,
474        LockPeriod _lockPeriod
475    ) external nonReentrant validAmount(_amount) userExists(msg.sender) {
476        depositNonce[msg.sender]++;
477
478        // Transfer tokens
479        depositToken.safeTransferFrom(msg.sender, address(this), _amount);
480
481        // Update total
482        users[msg.sender].totalDeposited += _amount;
483
484        // Create deposit contract
485        bool shouldAddCommissions = !users[msg.sender].isBase;
486        _createRangePosition(msg.sender, _amount, _lockPeriod, shouldAddCommissions);
487    }
488
489    // ============ Core Internal Functions ============
490
491    /**
492     * @dev Creates a deposit contract with yield and maturity
493     * @notice Automatically adds liquidity to PancakeSwap V3 position
494     * @notice Adds commission streams for uplines if applicable
495     */
496    function _createRangePosition(
497        address _depositor,
498        uint256 _amount,
499        LockPeriod _lockPeriod,
500        bool _addCommissions
501    ) private {
502        // Calculate yield and maturity
503        uint256 yieldAmount = _calculateRangeYield(_amount, _lockPeriod);
504        uint256 lockDays = _getLockDuration(_lockPeriod);
505        uint256 maturityTime = block.timestamp + (lockDays * SECONDS_IN_DAY);
506
507        // Create contract
508        uint256 contractId = nextContractId++;
509        DepositContract memory newContract = DepositContract({
510            contractId: contractId,
511            principal: _amount,
512            yieldAmount: yieldAmount,
513            lockPeriod: _lockPeriod,
514            depositTime: block.timestamp,
515            maturityTime: maturityTime,
516            withdrawn: false,
517            depositor: _depositor
518        });
519
520        userContracts[_depositor].push(newContract);
521        totalValueLocked += _amount;
522
523        // Add commission streams for uplines
524        if (_addCommissions) {
525            _distributeRewards(_depositor, yieldAmount, _lockPeriod);
526        }
527
528        // Automatically increase liquidity in PancakeSwap V3
529        _addLiquidityToPool(_amount);
530
531        emit Deposited(_depositor, contractId, _amount, yieldAmount, _lockPeriod, maturityTime);
532    }
533
534    /**
535     * @dev Adds time-locked commission streams for all uplines
536     * @notice Commissions calculated on YIELD (not principal)
537     * @notice Commissions unlock 100% after the lock period expires
538     * @notice Automatically aggregates same-day deposits
539     * @notice Triggers lazy cleanup if stream slots are full
540     */
541    function _distributeRewards(
542        address _depositor,
543        uint256 _yieldAmount,
544        LockPeriod _lockPeriod
545    ) private {
546        UserAccount storage depositorAccount = users[_depositor];
547        uint256 uplinesCount = depositorAccount.uplines.length;
548        uint256 currentDay = block.timestamp / SECONDS_IN_DAY;
549
550        // Process each commission level (0-14)
551        for (uint256 level = 0; level < MAX_UPLINES; level++) {
552            // Determine upline address for this level
553            address uplineAddress;
554
555            if (level < uplinesCount) {
556                // Direct upline at this level (reversed: closest = level 0)
557                uint256 uplineIndex = uplinesCount - 1 - level;
558                uplineAddress = depositorAccount.uplines[uplineIndex];
559            } else {
560                uplineAddress = baseUser;
561            }
562
563            if (uplineAddress == address(0)) continue;
564
565            uint256 totalCommission = (_yieldAmount * COMMISSION_RATES[level]) / RATE_DENOMINATOR;
566
567            _addRewardStream(uplineAddress, level, currentDay, totalCommission, _lockPeriod);
568        }
569    }
570
571    /**
572     * @dev Adds commission to appropriate stream bucket
573     * @notice Finds existing stream for current day or creates new one
574     * @notice Triggers lazy cleanup if all 20 slots are full
575     */
576    function _addRewardStream(
577        address _upline,
578        uint256 _level,
579        uint256 _dayNumber,
580        uint256 _commissionAmount,
581        LockPeriod _lockPeriod
582    ) private {
583        LevelCommissions storage levelComm = userCommissions[_upline][_level];
584
585        // Try to find existing stream for this day
586        int256 streamIndex = _findStreamByDay(levelComm, _dayNumber);
587
588        if (streamIndex >= 0) {
589            // Stream exists - add to appropriate bucket
590            _addToLockBucket(levelComm.streams[uint256(streamIndex)], _lockPeriod, _commissionAmount);
591        } else {
592            // Need to create new stream
593            int256 emptySlot = _findAvailableSlot(levelComm);
594
595            if (emptySlot >= 0) {
596                // Empty slot available - use it
597                _initializeStream(levelComm.streams[uint256(emptySlot)], _dayNumber, _lockPeriod, _commissionAmount);
598            } else {
599                // All slots full - trigger lazy cleanup
600                _cleanupExpiredStream(_upline, _level, levelComm, _dayNumber);
601
602                // Now find empty slot (guaranteed after cleanup)
603                emptySlot = _findAvailableSlot(levelComm);
604                require(emptySlot >= 0, "Cleanup failed");
605
606                _initializeStream(levelComm.streams[uint256(emptySlot)], _dayNumber, _lockPeriod, _commissionAmount);
607            }
608        }
609
610        emit StreamCreated(_upline, _level, _dayNumber, _commissionAmount, _lockPeriod);
611    }
612
613    /**
614     * @dev Lazy cleanup: removes oldest expired stream
615     * @notice Cleans streams older than 20 days (all buckets expired)
616     * @notice Moves unclaimed value to pendingClaimable
617     * @notice Example: Stream created day 0, 20-day lock expires day 19, cleanable from day 20
618     */
619    function _cleanupExpiredStream(
620        address _upline,
621        uint256 _level,
622        LevelCommissions storage _levelComm,
623        uint256 _currentDay
624    ) private {
625        uint256 oldestDay = type(uint256).max;
626        int256 oldestIndex = -1;
627
628        // Find oldest stream that's eligible for cleanup (20+ days old)
629        // Stream created day 0 with 20-day lock:
630        // - Locked from day 0-19 (commission is reserved, not claimable)
631        // - Day 20: unlocks 100% of commission (becomes claimable)
632        // - Day 20+: fully expired, can be cleaned
633        for (uint256 i = 0; i < MAX_STREAMS_PER_LEVEL; i++) {
634            DailyStream storage stream = _levelComm.streams[i];
635            if (stream.dayNumber > 0 && stream.dayNumber < oldestDay) {
636                // Check if fully expired (all buckets including 20-day are done)
637                if (_currentDay >= stream.dayNumber + 20) {
638                    oldestDay = stream.dayNumber;
639                    oldestIndex = int256(i);
640                }
641            }
642        }
643
644        require(oldestIndex >= 0, "No cleanable streams");
645
646        // Calculate unclaimed value from this stream
647        DailyStream storage streamToClean = _levelComm.streams[uint256(oldestIndex)];
648        uint256 unclaimed = _calculateStreamClaimable(
649            streamToClean,
650            _currentDay,
651            _levelComm.lastClaimedDay
652        );
653
654        // Move unclaimed to pendingClaimable
655        if (unclaimed > 0) {
656            _levelComm.pendingClaimable += uint128(unclaimed);
657        }
658
659        emit StreamCleaned(_upline, _level, streamToClean.dayNumber, unclaimed);
660
661        // Delete stream (free the slot)
662        delete _levelComm.streams[uint256(oldestIndex)];
663    }
664
665    /**
666     * @dev Calculates claimable amount from a single stream
667     * @notice Implements auto-expiration logic
668     * @notice Binary unlock: 0% before unlockDay, 100% on or after unlockDay
669     */
670    function _calculateStreamClaimable(
671        DailyStream storage _stream,
672        uint256 _currentDay,
673        uint256 _lastClaimedDay
674    ) private view returns (uint256 claimable) {
675        if (_stream.dayNumber == 0) return 0;
676
677        // Calculate for each lock bucket
678        claimable += _calculateBucketClaimable(_stream.lock1Day, 1, _stream.dayNumber, _currentDay, _lastClaimedDay);
679        claimable += _calculateBucketClaimable(_stream.lock5Days, 5, _stream.dayNumber, _currentDay, _lastClaimedDay);
680        claimable += _calculateBucketClaimable(_stream.lock10Days, 10, _stream.dayNumber, _currentDay, _lastClaimedDay);
681        claimable += _calculateBucketClaimable(_stream.lock20Days, 20, _stream.dayNumber, _currentDay, _lastClaimedDay);
682    }
683
684    function _calculateBucketClaimable(
685        uint128 _amountTotal,
686        uint256 _lockDays,
687        uint256 _streamDay,
688        uint256 _currentDay,
689        uint256 _lastClaimedDay
690    ) private pure returns (uint256) {
691        if (_amountTotal == 0) return 0;
692
693        uint256 unlockDay = _streamDay + _lockDays;
694
695        if (_currentDay < unlockDay) return 0;
696
697        if (_lastClaimedDay >= unlockDay) return 0;
698
699        return uint256(_amountTotal);
700    }
701
702    // ============ Withdrawal Functions ============
703
704    /**
705     * @dev Withdraws a matured deposit contract
706     * @notice Can only withdraw after maturityTime
707     * @notice Returns principal + yield in single transaction
708     * @notice Automatically decreases PancakeSwap V3 liquidity
709     */
710    function closeRange(uint256 _contractIndex) external nonReentrant userExists(msg.sender) {
711        DepositContract[] storage contracts = userContracts[msg.sender];
712        require(_contractIndex < contracts.length, "Invalid index");
713
714        DepositContract storage contractToWithdraw = contracts[_contractIndex];
715
716        // Checks
717        require(!contractToWithdraw.withdrawn, "Already withdrawn");
718        require(block.timestamp >= contractToWithdraw.maturityTime, "Not matured");
719        require(contractToWithdraw.depositor == msg.sender, "Not owner");
720
721        // Save contract data before removal (for event emission)
722        uint256 contractId = contractToWithdraw.contractId;
723        uint256 principal = contractToWithdraw.principal;
724        uint256 yieldAmount = contractToWithdraw.yieldAmount;
725        uint256 totalAmount = principal + yieldAmount;
726
727        // CRITICAL: Save TVL BEFORE decrementing for correct liquidity calculation
728        uint256 tvlBeforeWithdrawal = totalValueLocked;
729
730        // Safety check: Ensure TVL is sufficient (should never fail in normal conditions)
731        require(tvlBeforeWithdrawal >= principal, "Insufficient TVL");
732
733        // Effects - Update global state
734        users[msg.sender].totalYieldEarned += yieldAmount;
735        totalYieldPaid += yieldAmount;
736        totalValueLocked -= principal;
737
738        // Interactions - ensure sufficient balance using pre-withdrawal TVL
739        _ensureAvailableFunds(totalAmount, tvlBeforeWithdrawal);
740
741        // Remove contract from array (swap with last element and pop)
742        uint256 lastIndex = contracts.length - 1;
743        if (_contractIndex != lastIndex) {
744            contracts[_contractIndex] = contracts[lastIndex];
745        }
746        contracts.pop();
747
748        depositToken.safeTransfer(msg.sender, totalAmount);
749
750        emit ContractWithdrawn(
751            msg.sender,
752            contractId,
753            principal,
754            yieldAmount,
755            totalAmount
756        );
757    }
758
759    /**
760     * @dev Claims all unlocked commissions from time-locked streams
761     * @notice Commissions unlock 100% after their respective lock periods
762     * @notice Sums all 15 levels and includes pendingClaimable
763     * @notice Resets pendingClaimable after claim
764     * @notice Updates lastClaimedDay for each level
765     * @param deadline Timestamp until which the signature is valid
766     * @param nonce Sequential nonce for replay protection
767     * @param v ECDSA signature parameter
768     * @param r ECDSA signature parameter
769     * @param s ECDSA signature parameter
770     */
771    function claimRewards(
772        uint256 deadline,
773        uint256 nonce,
774        uint8 v,
775        bytes32 r,
776        bytes32 s
777    ) external nonReentrant userExists(msg.sender) {
778        require(block.timestamp <= deadline, "Signature expired");
779        require(nonce == claimNonce[msg.sender], "Invalid nonce");
780
781        claimNonce[msg.sender]++;
782
783        bytes32 structHash = keccak256(abi.encode(CLAIM_TYPEHASH, msg.sender, deadline, nonce));
784        bytes32 digest = keccak256(abi.encodePacked("", DOMAIN_SEPARATOR, structHash));
785        address signer = ecrecover(digest, v, r, s);
786        require(signer != address(0), "Invalid signature");
787        require(signer == msg.sender, "Invalid signature");
788
789        uint256 currentDay = block.timestamp / SECONDS_IN_DAY;
790        uint256 totalClaimable = 0;
791
792        // Calculate total claimable across all 15 levels
793        for (uint256 level = 0; level < MAX_UPLINES; level++) {
794            LevelCommissions storage levelComm = userCommissions[msg.sender][level];
795
796            // Sum all active streams
797            for (uint256 i = 0; i < MAX_STREAMS_PER_LEVEL; i++) {
798                if (levelComm.streams[i].dayNumber > 0) {
799                    totalClaimable += _calculateStreamClaimable(
800                        levelComm.streams[i],
801                        currentDay,
802                        levelComm.lastClaimedDay
803                    );
804                }
805            }
806
807            // Add pendingClaimable from cleaned streams
808            totalClaimable += levelComm.pendingClaimable;
809
810            // Update state
811            levelComm.lastClaimedDay = uint32(currentDay);
812            levelComm.pendingClaimable = 0;
813        }
814
815        require(totalClaimable > 0, "No commissions");
816
817        // Update totals
818        users[msg.sender].totalCommissionsEarned += totalClaimable;
819        totalCommissionsPaid += totalClaimable;
820
821        // Ensure sufficient liquidity and transfer
822        // Note: For commissions, TVL is not affected, so we use current TVL
823        _ensureAvailableFunds(totalClaimable, totalValueLocked);
824        depositToken.safeTransfer(msg.sender, totalClaimable);
825
826        emit CommissionsClaimed(msg.sender, totalClaimable);
827    }
828
829    // ============ PancakeSwap V3 Integration ============
830
831    /**
832     * @dev Increases liquidity in PancakeSwap V3 position
833     * @notice Called automatically on deposits
834     * @notice 100% decentralized - no operator needed
835     */
836    function _addLiquidityToPool(uint256 _amount) private {
837        require(pancakePositionTokenId > 0, "Position not set");
838
839        // Increase liquidity using entire amount
840        // Note: For single-sided liquidity (USDT only), amount1Desired = 0
841        (uint128 liquidity, uint256 amount0, ) = pancakePositionManager.increaseLiquidity(
842            IncreaseLiquidityParams({
843                tokenId: pancakePositionTokenId,
844                amount0Desired: _amount,
845                amount1Desired: 0, // Single-sided deposit
846                amount0Min: (_amount * 995) / 1000, // 0.5% slippage tolerance
847                amount1Min: 0,
848                deadline: block.timestamp
849            })
850        );
851
852        emit LiquidityIncreased(pancakePositionTokenId, amount0, liquidity);
853    }
854
855    /**
856     * @dev Ensures contract has sufficient balance for withdrawal
857     * @notice Automatically decreases Pancake liquidity if needed
858     * @param _requiredAmount Amount of tokens needed for withdrawal
859     * @param _tvlForCalculation TVL value to use for liquidity calculation (pre-withdrawal TVL)
860     */
861    function _ensureAvailableFunds(uint256 _requiredAmount, uint256 _tvlForCalculation) private {
862        uint256 contractBalance = depositToken.balanceOf(address(this));
863
864        if (contractBalance < _requiredAmount) {
865            // Need to withdraw from PancakeSwap
866            uint256 amountNeeded = _requiredAmount - contractBalance;
867
868            // Withdraw a bit more to account for calculation imprecision
869            uint256 amountToWithdraw = (amountNeeded * 1001) / 1000; // 0.1% buffer
870
871            _removeLiquidityFromPool(amountToWithdraw, _tvlForCalculation);
872        }
873    }
874
875    /**
876     * @dev Decreases liquidity from PancakeSwap V3 position
877     * @notice Correctly calculates liquidity to remove based on position state
878     * @notice Called when contract balance insufficient for withdrawal
879     * @param _amount Amount of tokens needed
880     * @param _tvlForCalculation TVL value to use for liquidity calculation (pre-withdrawal TVL)
881     */
882    function _removeLiquidityFromPool(uint256 _amount, uint256 _tvlForCalculation) private {
883        require(pancakePositionTokenId > 0, "Position not set");
884        require(_tvlForCalculation > 0, "TVL cannot be zero");
885
886        (
887            ,
888            ,
889            ,
890            ,
891            ,
892            ,
893            ,
894            uint128 totalLiquidity,
895            ,
896            ,
897            ,
898        ) = pancakePositionManager.positions(pancakePositionTokenId);
899
900        require(totalLiquidity > 0, "No liquidity in position");
901
902        // CRITICAL: Why we use TVL (totalValueLocked) as denominator for liquidity calculation:
903        //
904        // TVL = Sum of all user principals currently deposited (active contracts)
905        // Pool Balance = TVL + Accumulated Fees from PancakeSwap V3
906        //
907        // The pool contains MORE than just user principals because:
908        // 1. Users deposit principals (tracked in TVL)
909        // 2. Pool generates trading fees (from PancakeSwap swaps)
910        // 3. Protocol must pay: Principal + Yield to users
911        // 4. Yields are paid from the accumulated trading fees
912        //
913        // Example:
914        // - 10 users deposit 100K USDT each = 1M TVL
915        // - Pool generates 200K USDT in trading fees over time
916        // - Total pool balance = 1M (principals) + 200K (fees) = 1.2M USDT
917        // - Users' total obligations = 1M (principals) + 300K (promised yields) = 1.3M
918        //
919        // When removing liquidity to pay a user:
920        // - User withdraws 100K principal + 20K yield = 120K total
921        // - We calculate: liquidity_to_remove = (totalLiquidity * 120K) / 1M (TVL)
922        // - We use TVL (1M) NOT pool balance (1.2M) because:
923        //   * TVL represents the "baseline" amount that was deposited
924        //   * Fees (200K) are EXTRA revenue to cover yields (300K)
925        //   * Using TVL ensures we remove proportional liquidity based on original deposits
926        //   * This maintains the correct ratio for remaining users
927        //
928        // Why NOT use pool balance (1.2M) as denominator?
929        // - Would remove LESS liquidity than needed: (totalLiq * 120K) / 1.2M < correct amount
930        // - Over time, would leave excess liquidity in pool (inefficient)
931        // - Would mess up proportions for remaining users
932        //
933        // Why TVL is the correct denominator:
934        // - TVL represents the "share" of liquidity that belongs to user principals
935        // - Fees are distributed separately (via collect()) to cover yields
936        // - Maintains 1:1 correspondence between deposits and liquidity units
937        // - Ensures fair proportional removal for all users
938        //
939        // For auditors: This is NOT a bug. Using TVL ensures correct liquidity accounting.
940        uint128 liquidityToRemove = uint128(
941            (uint256(totalLiquidity) * _amount) / _tvlForCalculation
942        );
943
944        require(liquidityToRemove > 0, "Liquidity too small");
945
946        uint256 minUsdtAmount = (_amount * 995) / 1000;
947
948        (uint256 amount0, uint256 amount1) = pancakePositionManager.decreaseLiquidity(
949            DecreaseLiquidityParams({
950                tokenId: pancakePositionTokenId,
951                liquidity: liquidityToRemove,
952                amount0Min: minUsdtAmount,
953                amount1Min: 0,
954                deadline: block.timestamp
955            })
956        );
957
958        pancakePositionManager.collect(
959            CollectParams({
960                tokenId: pancakePositionTokenId,
961                recipient: address(this),
962                amount0Max: type(uint128).max,
963                amount1Max: type(uint128).max
964            })
965        );
966
967        emit LiquidityDecreased(pancakePositionTokenId, amount0, amount1);
968    }
969
970    // ============ Helper Functions ============
971
972    /**
973     * @dev Builds upline structure with truncation at 15 levels
974     */
975    function _buildUplineChain(address _user, address _referrer) private {
976        UserAccount storage userAccount = users[_user];
977        UserAccount storage referrerAccount = users[_referrer];
978
979        delete userAccount.uplines;
980
981        // Copy referrer's uplines with truncation
982        uint256 uplinesToCopy = referrerAccount.uplines.length;
983
984        if (uplinesToCopy >= MAX_UPLINES - 1) {
985            // Truncate: take last 14
986            uint256 startIndex = uplinesToCopy - (MAX_UPLINES - 1);
987            for (uint256 i = startIndex; i < uplinesToCopy; i++) {
988                userAccount.uplines.push(referrerAccount.uplines[i]);
989            }
990        } else {
991            // Copy all
992            for (uint256 i = 0; i < uplinesToCopy; i++) {
993                userAccount.uplines.push(referrerAccount.uplines[i]);
994            }
995        }
996
997        // Add referrer as last upline
998        userAccount.uplines.push(_referrer);
999    }
1000
1001    /**
1002     * @dev Calculates yield based on lock period
1003     */
1004    function _calculateRangeYield(uint256 _principal, LockPeriod _lockPeriod) private view returns (uint256) {
1005        uint256 rate = _getYieldRate(_lockPeriod);
1006        return (_principal * rate) / RATE_DENOMINATOR;
1007    }
1008
1009    /**
1010     * @dev Returns yield rate for lock period
1011     */
1012    function _getYieldRate(LockPeriod _lockPeriod) private view returns (uint256) {
1013        if (_lockPeriod == LockPeriod.ONE_DAY) return ONE_DAY_YIELD_RATE;
1014        if (_lockPeriod == LockPeriod.FIVE_DAYS) return FIVE_DAYS_YIELD_RATE;
1015        if (_lockPeriod == LockPeriod.TEN_DAYS) return TEN_DAYS_YIELD_RATE;
1016        if (_lockPeriod == LockPeriod.TWENTY_DAYS) return TWENTY_DAYS_YIELD_RATE;
1017        revert("Invalid lock period");
1018    }
1019
1020    /**
1021     * @dev Returns number of days for lock period
1022     */
1023    function _getLockDuration(LockPeriod _lockPeriod) private pure returns (uint256) {
1024        if (_lockPeriod == LockPeriod.ONE_DAY) return 1;
1025        if (_lockPeriod == LockPeriod.FIVE_DAYS) return 5;
1026        if (_lockPeriod == LockPeriod.TEN_DAYS) return 10;
1027        if (_lockPeriod == LockPeriod.TWENTY_DAYS) return 20;
1028        revert("Invalid lock period");
1029    }
1030
1031    /**
1032     * @dev Finds stream by day number
1033     * @return Index of stream or -1 if not found
1034     */
1035    function _findStreamByDay(
1036        LevelCommissions storage _levelComm,
1037        uint256 _dayNumber
1038    ) private view returns (int256) {
1039        for (uint256 i = 0; i < MAX_STREAMS_PER_LEVEL; i++) {
1040            if (_levelComm.streams[i].dayNumber == _dayNumber) {
1041                return int256(i);
1042            }
1043        }
1044        return -1;
1045    }
1046
1047    /**
1048     * @dev Finds empty stream slot
1049     * @return Index of empty slot or -1 if all full
1050     */
1051    function _findAvailableSlot(LevelCommissions storage _levelComm) private view returns (int256) {
1052        for (uint256 i = 0; i < MAX_STREAMS_PER_LEVEL; i++) {
1053            if (_levelComm.streams[i].dayNumber == 0) {
1054                return int256(i);
1055            }
1056        }
1057        return -1;
1058    }
1059
1060    /**
1061     * @dev Adds amount to appropriate bucket in stream
1062     */
1063    function _addToLockBucket(
1064        DailyStream storage _stream,
1065        LockPeriod _lockPeriod,
1066        uint256 _amount
1067    ) private {
1068        require(_amount <= type(uint128).max, "Amount too large");
1069
1070        if (_lockPeriod == LockPeriod.ONE_DAY) {
1071            _stream.lock1Day += uint128(_amount);
1072        } else if (_lockPeriod == LockPeriod.FIVE_DAYS) {
1073            _stream.lock5Days += uint128(_amount);
1074        } else if (_lockPeriod == LockPeriod.TEN_DAYS) {
1075            _stream.lock10Days += uint128(_amount);
1076        } else if (_lockPeriod == LockPeriod.TWENTY_DAYS) {
1077            _stream.lock20Days += uint128(_amount);
1078        }
1079    }
1080
1081    /**
1082     * @dev Creates new stream with initial values
1083     */
1084    function _initializeStream(
1085        DailyStream storage _stream,
1086        uint256 _dayNumber,
1087        LockPeriod _lockPeriod,
1088        uint256 _amount
1089    ) private {
1090        require(_amount <= type(uint128).max, "Amount too large");
1091        require(_dayNumber <= type(uint32).max, "Day too large");
1092
1093        _stream.dayNumber = uint32(_dayNumber);
1094        _stream.lock1Day = 0;
1095        _stream.lock5Days = 0;
1096        _stream.lock10Days = 0;
1097        _stream.lock20Days = 0;
1098
1099        _addToLockBucket(_stream, _lockPeriod, _amount);
1100    }
1101
1102    // ============ View Functions ============
1103
1104    /**
1105     * @dev Returns user's deposit contracts
1106     */
1107    function getRangePositions(address _user) external view returns (DepositContract[] memory) {
1108        return userContracts[_user];
1109    }
1110
1111    /**
1112     * @dev Returns user's pending commissions (total across all levels)
1113     */
1114    function getPendingRewards(address _user) external view returns (uint256) {
1115        uint256 currentDay = block.timestamp / SECONDS_IN_DAY;
1116        uint256 total = 0;
1117
1118        for (uint256 level = 0; level < MAX_UPLINES; level++) {
1119            LevelCommissions storage levelComm = userCommissions[_user][level];
1120
1121            for (uint256 i = 0; i < MAX_STREAMS_PER_LEVEL; i++) {
1122                if (levelComm.streams[i].dayNumber > 0) {
1123                    total += _calculateStreamClaimable(
1124                        levelComm.streams[i],
1125                        currentDay,
1126                        levelComm.lastClaimedDay
1127                    );
1128                }
1129            }
1130
1131            total += levelComm.pendingClaimable;
1132        }
1133
1134        return total;
1135    }
1136
1137    /**
1138     * @dev Returns commission breakdown by level
1139     */
1140    function getRewardsBreakdown(address _user) external view returns (uint256[15] memory) {
1141        uint256 currentDay = block.timestamp / SECONDS_IN_DAY;
1142        uint256[15] memory breakdown;
1143
1144        for (uint256 level = 0; level < MAX_UPLINES; level++) {
1145            LevelCommissions storage levelComm = userCommissions[_user][level];
1146
1147            for (uint256 i = 0; i < MAX_STREAMS_PER_LEVEL; i++) {
1148                if (levelComm.streams[i].dayNumber > 0) {
1149                    breakdown[level] += _calculateStreamClaimable(
1150                        levelComm.streams[i],
1151                        currentDay,
1152                        levelComm.lastClaimedDay
1153                    );
1154                }
1155            }
1156
1157            breakdown[level] += levelComm.pendingClaimable;
1158        }
1159
1160        return breakdown;
1161    }
1162
1163    function _calculateBucketLocked(
1164        uint128 _amountTotal,
1165        uint256 _lockDays,
1166        uint256 _streamDay,
1167        uint256 _currentDay
1168    ) private pure returns (uint256) {
1169        if (_amountTotal == 0) return 0;
1170
1171        uint256 unlockDay = _streamDay + _lockDays;
1172
1173        if (_currentDay >= unlockDay) return 0;
1174
1175        return uint256(_amountTotal);
1176    }
1177
1178    /**
1179     * @dev Calculates reserved commissions from a single stream
1180     * @notice Sums reserved amounts across all lock period buckets
1181     * @notice Ignores deleted streams (dayNumber = 0)
1182     *
1183     * @param _stream Storage pointer to the DailyStream struct
1184     * @param _currentDay Current day (block.timestamp / 86400)
1185     * @return reserved Total reserved commission for this stream
1186     */
1187    function _calculateStreamLocked(
1188        DailyStream storage _stream,
1189        uint256 _currentDay
1190    ) private view returns (uint256 reserved) {
1191        if (_stream.dayNumber == 0) return 0;
1192
1193        // Calculate for each lock bucket
1194        // Note: We don't need lastClaimedDay for reserved calculations
1195        reserved += _calculateBucketLocked(_stream.lock1Day, 1, _stream.dayNumber, _currentDay);
1196        reserved += _calculateBucketLocked(_stream.lock5Days, 5, _stream.dayNumber, _currentDay);
1197        reserved += _calculateBucketLocked(_stream.lock10Days, 10, _stream.dayNumber, _currentDay);
1198        reserved += _calculateBucketLocked(_stream.lock20Days, 20, _stream.dayNumber, _currentDay);
1199    }
1200
1201    /**
1202     * @dev Returns user's reserved (future/locked) commissions across all levels
1203     * @notice Reserved = commissions that will be unlocked in FUTURE days
1204     * @notice This is DIFFERENT from pending (which is available NOW)
1205     *
1206     * Key differences:
1207     * - PENDING: Available for claim right now (past + today)
1208     * - RESERVED: Will be unlocked in future (tomorrow onwards)
1209     *
1210     * Important notes:
1211     * - Reserved does NOT include pendingClaimable (that's part of PENDING)
1212     * - Reserved does NOT depend on lastClaimedDay
1213     * - Reserved is purely based on currentDay vs streamEndDay
1214     * - When a stream expires, its value moves to PENDING, not RESERVED
1215     *
1216     * Example scenario:
1217     * - Stream day 0, lock 5 days (unlocks 100% on day 5)
1218     * - Today is day 3: commission is still locked (RESERVED)
1219     * - Today is day 5+: commission is unlocked and claimable (PENDING)
1220     * - User can claim 100% of the commission only after day 5
1221     * - Before day 5: RESERVED shows total locked amount
1222     * - After day 5: PENDING shows total claimable amount
1223     *
1224     * @param _user Address of the user
1225     * @return Total reserved commissions across all 15 levels (18 decimals USDT)
1226     */
1227    function getLockedRewards(address _user) external view returns (uint256) {
1228        uint256 currentDay = block.timestamp / SECONDS_IN_DAY;
1229        uint256 totalReserved = 0;
1230
1231        // Sum reserved commissions across all 15 levels
1232        for (uint256 level = 0; level < MAX_UPLINES; level++) {
1233            LevelCommissions storage levelComm = userCommissions[_user][level];
1234
1235            // Sum all active streams
1236            for (uint256 i = 0; i < MAX_STREAMS_PER_LEVEL; i++) {
1237                if (levelComm.streams[i].dayNumber > 0) {
1238                    totalReserved += _calculateStreamLocked(
1239                        levelComm.streams[i],
1240                        currentDay
1241                    );
1242                }
1243            }
1244
1245            // Note: pendingClaimable is NOT included in reserved
1246            // pendingClaimable comes from cleaned streams and is part of PENDING
1247        }
1248
1249        return totalReserved;
1250    }
1251
1252    /**
1253     * @dev Returns reserved commissions breakdown by level (for detailed analytics)
1254     * @notice Shows how much reserved commission exists at each of the 15 levels
1255     * @notice Useful for frontend to display per-level reserved amounts
1256     *
1257     * @param _user Address of the user
1258     * @return Array of 15 values representing reserved commissions per level (18 decimals USDT)
1259     */
1260    function getLockedRewardsBreakdown(address _user) external view returns (uint256[15] memory) {
1261        uint256 currentDay = block.timestamp / SECONDS_IN_DAY;
1262        uint256[15] memory breakdown;
1263
1264        for (uint256 level = 0; level < MAX_UPLINES; level++) {
1265            LevelCommissions storage levelComm = userCommissions[_user][level];
1266
1267            for (uint256 i = 0; i < MAX_STREAMS_PER_LEVEL; i++) {
1268                if (levelComm.streams[i].dayNumber > 0) {
1269                    breakdown[level] += _calculateStreamLocked(
1270                        levelComm.streams[i],
1271                        currentDay
1272                    );
1273                }
1274            }
1275        }
1276
1277        return breakdown;
1278    }
1279
1280    function getLevelLockBreakdown(address _user, uint256 _level)
1281        external view returns (LockPeriodBreakdown memory)
1282    {
1283        require(_level < MAX_UPLINES, "Invalid level");
1284
1285        LevelCommissions storage levelComm = userCommissions[_user][_level];
1286        uint256 currentDay = block.timestamp / SECONDS_IN_DAY;
1287        uint256 lastClaimedDay = levelComm.lastClaimedDay;
1288
1289        LockPeriodBreakdown memory breakdown;
1290
1291        for (uint256 i = 0; i < MAX_STREAMS_PER_LEVEL; i++) {
1292            DailyStream storage stream = levelComm.streams[i];
1293
1294            if (stream.dayNumber > 0) {
1295                if (stream.lock1Day > 0) {
1296                    uint256 unlockDay = stream.dayNumber + 1;
1297                    if (currentDay >= unlockDay) {
1298                        if (lastClaimedDay < unlockDay) {
1299                            breakdown.lock1DayPending += uint256(stream.lock1Day);
1300                        }
1301                    } else {
1302                        breakdown.lock1DayReserved += uint256(stream.lock1Day);
1303                    }
1304                }
1305
1306                if (stream.lock5Days > 0) {
1307                    uint256 unlockDay = stream.dayNumber + 5;
1308                    if (currentDay >= unlockDay) {
1309                        if (lastClaimedDay < unlockDay) {
1310                            breakdown.lock5DaysPending += uint256(stream.lock5Days);
1311                        }
1312                    } else {
1313                        breakdown.lock5DaysReserved += uint256(stream.lock5Days);
1314                    }
1315                }
1316
1317                if (stream.lock10Days > 0) {
1318                    uint256 unlockDay = stream.dayNumber + 10;
1319                    if (currentDay >= unlockDay) {
1320                        if (lastClaimedDay < unlockDay) {
1321                            breakdown.lock10DaysPending += uint256(stream.lock10Days);
1322                        }
1323                    } else {
1324                        breakdown.lock10DaysReserved += uint256(stream.lock10Days);
1325                    }
1326                }
1327
1328                if (stream.lock20Days > 0) {
1329                    uint256 unlockDay = stream.dayNumber + 20;
1330                    if (currentDay >= unlockDay) {
1331                        if (lastClaimedDay < unlockDay) {
1332                            breakdown.lock20DaysPending += uint256(stream.lock20Days);
1333                        }
1334                    } else {
1335                        breakdown.lock20DaysReserved += uint256(stream.lock20Days);
1336                    }
1337                }
1338            }
1339        }
1340
1341        return breakdown;
1342    }
1343
1344    /**
1345     * @dev Returns user's mature (withdrawable) contracts
1346     */
1347    function getMaturedRanges(address _user) external view returns (uint256[] memory) {
1348        DepositContract[] storage contracts = userContracts[_user];
1349        uint256 matureCount = 0;
1350
1351        // Count mature contracts
1352        for (uint256 i = 0; i < contracts.length; i++) {
1353            if (!contracts[i].withdrawn && block.timestamp >= contracts[i].maturityTime) {
1354                matureCount++;
1355            }
1356        }
1357
1358        // Build array
1359        uint256[] memory matureIndices = new uint256[](matureCount);
1360        uint256 index = 0;
1361
1362        for (uint256 i = 0; i < contracts.length; i++) {
1363            if (!contracts[i].withdrawn && block.timestamp >= contracts[i].maturityTime) {
1364                matureIndices[index] = i;
1365                index++;
1366            }
1367        }
1368
1369        return matureIndices;
1370    }
1371
1372    /**
1373     * @dev Returns user's available balance (sum of all active contract principals)
1374     * @notice This represents the total deposited amount still active in the vault
1375     * @notice Contracts are removed from array when withdrawn, so we sum all existing contracts
1376     * @param _user Address of the user
1377     * @return Total principal of all active contracts (18 decimals USDT)
1378     */
1379    function getActiveLiquidity(address _user) external view returns (uint256) {
1380        DepositContract[] storage contracts = userContracts[_user];
1381        uint256 totalPrincipal = 0;
1382
1383        for (uint256 i = 0; i < contracts.length; i++) {
1384            // Since withdrawn contracts are removed from the array,
1385            // all contracts in the array are active
1386            totalPrincipal += contracts[i].principal;
1387        }
1388
1389        return totalPrincipal;
1390    }
1391
1392    /**
1393     * @dev Returns user's upline addresses
1394     * @param _user Address of the user
1395     * @return Array of upline addresses (max 15)
1396     */
1397    function getProviderUplines(address _user) external view returns (address[] memory) {
1398        return users[_user].uplines;
1399    }
1400
1401    /**
1402     * @dev Returns all active streams for a specific level
1403     * @notice Returns all 20 stream slots (empty slots have dayNumber = 0)
1404     * @param _user Address of the user
1405     * @param _level Level (0-14)
1406     * @return Array of 20 DailyStream structs
1407     */
1408    function getRewardStreams(address _user, uint256 _level) external view returns (DailyStream[MAX_STREAMS_PER_LEVEL] memory) {
1409        require(_level < MAX_UPLINES, "Invalid level");
1410        return userCommissions[_user][_level].streams;
1411    }
1412
1413    /**
1414     * @dev Returns commission metadata for a specific level
1415     * @param _user Address of the user
1416     * @param _level Level (0-14)
1417     * @return lastClaimedDay Last day user claimed commissions
1418     * @return pendingClaimable Amount from cleaned streams
1419     */
1420    function getRewardStreamMeta(address _user, uint256 _level) external view returns (
1421        uint32 lastClaimedDay,
1422        uint256 pendingClaimable
1423    ) {
1424        require(_level < MAX_UPLINES, "Invalid level");
1425        LevelCommissions storage levelComm = userCommissions[_user][_level];
1426        return (levelComm.lastClaimedDay, levelComm.pendingClaimable);
1427    }
1428
1429    /**
1430     * @dev Returns user info
1431     */
1432    function getProviderInfo(address _user) external view returns (
1433        bool isRegistered,
1434        bool isBase,
1435        address referrer,
1436        uint256 totalDeposited,
1437        uint256 activeContracts,
1438        uint256 totalYieldEarned,
1439        uint256 totalCommissionsEarned
1440    ) {
1441        UserAccount storage user = users[_user];
1442
1443        // Count active contracts
1444        uint256 active = 0;
1445        DepositContract[] storage contracts = userContracts[_user];
1446        for (uint256 i = 0; i < contracts.length; i++) {
1447            if (!contracts[i].withdrawn) active++;
1448        }
1449
1450        return (
1451            user.isRegistered,
1452            user.isBase,
1453            user.referrer,
1454            user.totalDeposited,
1455            active,
1456            user.totalYieldEarned,
1457            user.totalCommissionsEarned
1458        );
1459    }
1460
1461    // ============ PancakeSwap V3 Position Creation ============
1462
1463    /**
1464     * @dev Creates PancakeSwap V3 position with contract as permanent owner
1465     * @notice CRITICAL: This makes the position PERMANENTLY owned by contract
1466     * @notice No EOA can control or transfer this position - 100% immutable
1467     * @notice Position will be locked in contract forever (no transfer function exists)
1468     * @param token1 Address of second token (e.g., WBNB)
1469     * @param amount0 Amount of token0 (USDT) for initial liquidity
1470     * @param amount1 Amount of token1 (WBNB or other) for initial liquidity
1471     * @param tickLower Lower tick of price range
1472     * @param tickUpper Upper tick of price range
1473     * @param fee Fee tier (100 = 0.01%, 500 = 0.05%, 2500 = 0.25%, 10000 = 1%)
1474     */
1475    function initializeLiquidityPool(
1476        address token1,
1477        uint256 amount0,
1478        uint256 amount1,
1479        int24 tickLower,
1480        int24 tickUpper,
1481        uint24 fee
1482    ) external onlyOwner nonReentrant {
1483        require(pancakePositionTokenId == 0, "Position already exists");
1484        require(token1 != address(0), "Invalid token1");
1485        require(amount0 > 0, "Invalid amount0");
1486        require(tickLower < tickUpper, "Invalid tick range");
1487        require(
1488            fee == 100 || fee == 500 || fee == 2500 || fee == 10000,
1489            "Invalid fee tier"
1490        );
1491
1492        // Transfer tokens from caller (owner pays initial liquidity)
1493        depositToken.safeTransferFrom(msg.sender, address(this), amount0);
1494
1495        if (amount1 > 0) {
1496            IERC20(token1).safeTransferFrom(msg.sender, address(this), amount1);
1497            // Approve token1 for position manager
1498            IERC20(token1).approve(address(pancakePositionManager), amount1);
1499        }
1500
1501        // depositToken already approved in constructor with type(uint256).max
1502
1503        // Create position with CONTRACT as permanent owner (recipient = address(this))
1504        MintParams memory params = MintParams({
1505            token0: address(depositToken), // USDT
1506            token1: token1, // WBNB or other paired token
1507            fee: fee,
1508            tickLower: tickLower,
1509            tickUpper: tickUpper,
1510            amount0Desired: amount0,
1511            amount1Desired: amount1,
1512            amount0Min: (amount0 * 995) / 1000, // 0.5% slippage tolerance
1513            amount1Min: (amount1 * 995) / 1000,
1514            recipient: address(this), // CRITICAL: Contract owns the NFT forever
1515            deadline: block.timestamp
1516        });
1517
1518        // Mint position - NFT goes directly to contract
1519        (uint256 tokenId, uint128 liquidity, uint256 used0, uint256 used1) =
1520            pancakePositionManager.mint(params);
1521
1522        // Store position token ID
1523        pancakePositionTokenId = tokenId;
1524
1525        // Refund unused tokens to caller
1526        if (used0 < amount0) {
1527            uint256 refund = amount0 - used0;
1528            depositToken.safeTransfer(msg.sender, refund);
1529        }
1530
1531        if (amount1 > 0 && used1 < amount1) {
1532            uint256 refund = amount1 - used1;
1533            // Reset approval first
1534            IERC20(token1).approve(address(pancakePositionManager), 0);
1535            IERC20(token1).safeTransfer(msg.sender, refund);
1536        }
1537
1538        emit PancakePositionCreated(tokenId, liquidity, tickLower, tickUpper, used0, used1);
1539    }
1540
1541    /**
1542     * @dev Sets the authorized rebalancer contract address
1543     * @notice CRITICAL: This should be an AUTOMATED SMART CONTRACT, not an EOA (Externally Owned Account)
1544     * @notice Examples of valid rebalancer contracts:
1545     *         - Gelato Network's automated position managers
1546     *         - Arrakis Finance position managers
1547     *         - Gamma Strategies contracts
1548     *         - PancakeSwap Labs' official position management contracts
1549     * @notice The rebalancer contract will automatically:
1550     *         - Monitor price movements on-chain
1551     *         - Rebalance positions based on predefined algorithms
1552     *         - Execute without human intervention
1553     * @notice Only owner can set this address (one-time setup)
1554     * @notice Rebalancer contract CANNOT withdraw tokens to external addresses
1555     * @notice Rebalancer can ONLY change the position range (tickLower, tickUpper)
1556     * @notice All tokens remain locked in THIS contract during and after rebalancing
1557     * @param _rebalancer Address of the automated rebalancing smart contract
1558     */
1559    function setAuthorizedRebalancer(address _rebalancer) external onlyOwner {
1560        require(_rebalancer != address(0), "Invalid rebalancer address");
1561        uint256 codeSize;
1562        assembly {
1563            codeSize := extcodesize(_rebalancer)
1564        }
1565        require(codeSize > 0, "Rebalancer must be a contract");
1566        address oldRebalancer = authorizedRebalancer;
1567        authorizedRebalancer = _rebalancer;
1568        emit AuthorizedRebalancerUpdated(oldRebalancer, _rebalancer);
1569    }
1570
1571    /**
1572     * @dev ERC721 Receiver implementation - allows contract to receive NFTs
1573     * @notice Only accepts NFTs from PancakeSwap Position Manager
1574     * @notice This is required for the contract to receive the position NFT
1575     */
1576    function onERC721Received(
1577        address,
1578        address,
1579        uint256 tokenId,
1580        bytes calldata
1581    ) external view override returns (bytes4) {
1582        require(msg.sender == address(pancakePositionManager), "Invalid sender");
1583        if (pancakePositionTokenId > 0) {
1584            require(tokenId == pancakePositionTokenId, "Invalid token");
1585        }
1586        return IERC721Receiver.onERC721Received.selector;
1587    }
1588
1589    // ============ Range Rebalancing ============
1590
1591    /**
1592     * @dev Rebalances PancakeSwap V3 position to a new price range to maximize fee capture
1593     * @notice THIS IS HOW THE PROTOCOL GENERATES REVENUE
1594     * @notice Called by AUTOMATED SMART CONTRACT (not a person)
1595     * @notice User deposits, yields, and maturities remain unchanged
1596     * @notice Only changes WHERE liquidity is positioned in PancakeSwap V3 to capture more trading fees
1597     * @notice Tokens NEVER leave the contract - they stay locked inside
1598     *
1599     * WHY THIS EXISTS (Mathematical Sustainability):
1600     * - Protocol earns trading fees from PancakeSwap V3
1601     * - When price moves out of range - position earns ZERO fees - must rebalance
1602     * - Rebalancing keeps position in active range - maintains fee generation - protocol stays sustainable
1603     *
1604     * WHO CALLS THIS (Automated Contract):
1605     * - NOT a person or centralized operator
1606     * - An automated smart contract (Gelato, Arrakis, Gamma, or PancakeSwap Labs contracts)
1607     * - These contracts monitor prices and rebalance algorithmically
1608     * - No human decision-making or intervention required
1609     *
1610     * Security guarantees:
1611     * - Position NFT remains owned by THIS contract (address(this))
1612     * - All tokens stay within THIS contract (no external transfers)
1613     * - User DepositContracts are unaffected (principal, yield, maturity unchanged)
1614     * - Only authorized rebalancer CONTRACT can call this function
1615     * - Rebalancer CANNOT withdraw tokens or modify user contracts
1616     * - Users can only withdraw via closeRange() and claimRewards()
1617     *
1618     * How it works:
1619     * 1. Removes ALL liquidity from old position (inactive range)
1620     * 2. Collects tokens back to THIS contract (address(this))
1621     * 3. Creates NEW position with new tick range (active range)
1622     * 4. Adds all collected liquidity to new position
1623     * 5. Updates pancakePositionTokenId to new NFT
1624     * 6. Old position is abandoned (empty NFT remains in contract)
1625     *
1626     * Example use case:
1627     * - Current range: 250-300 USDT/BNB (position created 1 month ago)
1628     * - Market moves to 400 USDT/BNB (out of range = earning 0 fees)
1629     * - Automated contract detects this and calls rebalancePancakeRange()
1630     * - New range: 380-420 USDT/BNB (active range = earning high APR again)
1631     * - Result: Protocol maintains fee generation to sustain operations
1632     *
1633     * @param newTickLower Lower tick boundary of new range
1634     * @param newTickUpper Upper tick boundary of new range
1635     */
1636    function rebalancePancakeRange(
1637        int24 newTickLower,
1638        int24 newTickUpper
1639    ) external onlyAuthorizedRebalancer nonReentrant {
1640        require(pancakePositionTokenId > 0, "No position to rebalance");
1641        require(newTickLower < newTickUpper, "Invalid tick range");
1642        require(block.timestamp >= lastRebalanceTime + MIN_REBALANCE_DELAY, "Rebalance too soon");
1643
1644        uint256 oldTokenId = pancakePositionTokenId;
1645
1646        // Step 1: Get current position info
1647        (
1648            ,
1649            ,
1650            address token0,
1651            address token1,
1652            uint24 fee,
1653            int24 oldTickLower,
1654            int24 oldTickUpper,
1655            uint128 liquidity,
1656            ,
1657            ,
1658            ,
1659        ) = pancakePositionManager.positions(oldTokenId);
1660
1661        require(liquidity > 0, "No liquidity to rebalance");
1662
1663        int24 currentMidTick = (oldTickLower + oldTickUpper) / 2;
1664        require(newTickLower >= currentMidTick - MAX_TICK_DEVIATION, "New range too far below");
1665        require(newTickUpper <= currentMidTick + MAX_TICK_DEVIATION, "New range too far above");
1666        require(newTickUpper - newTickLower >= 2000, "Range too narrow");
1667
1668        // Step 2: Remove ALL liquidity from old position
1669        (uint256 amount0Removed, ) = pancakePositionManager.decreaseLiquidity(
1670            DecreaseLiquidityParams({
1671                tokenId: oldTokenId,
1672                liquidity: liquidity,
1673                amount0Min: (uint256(uint128(liquidity)) * 995) / 1000,
1674                amount1Min: 0,
1675                deadline: block.timestamp
1676            })
1677        );
1678
1679        // Step 3: Collect tokens from old position to contract
1680        (uint256 collected0, uint256 collected1) = pancakePositionManager.collect(
1681            CollectParams({
1682                tokenId: oldTokenId,
1683                recipient: address(this), // Tokens stay in contract
1684                amount0Max: type(uint128).max,
1685                amount1Max: type(uint128).max
1686            })
1687        );
1688
1689        // Step 4: Approve tokens if needed (token0 already approved in constructor)
1690        if (collected1 > 0) {
1691            IERC20(token1).approve(address(pancakePositionManager), collected1);
1692        }
1693
1694        // Step 5: Create NEW position with new range
1695        MintParams memory params = MintParams({
1696            token0: token0,
1697            token1: token1,
1698            fee: fee, // Keep same fee tier
1699            tickLower: newTickLower, // NEW RANGE
1700            tickUpper: newTickUpper, // NEW RANGE
1701            amount0Desired: collected0,
1702            amount1Desired: collected1,
1703            amount0Min: (collected0 * 995) / 1000, // 0.5% slippage
1704            amount1Min: collected1 > 0 ? (collected1 * 995) / 1000 : 0,
1705            recipient: address(this), // Contract remains owner
1706            deadline: block.timestamp
1707        });
1708
1709        (uint256 newTokenId, uint128 newLiquidity, uint256 used0, uint256 used1) =
1710            pancakePositionManager.mint(params);
1711
1712        // Step 6: Update state - new position becomes active
1713        pancakePositionTokenId = newTokenId;
1714        lastRebalanceTime = block.timestamp;
1715
1716        // Step 7: Clean up approval if token1 was used
1717        if (collected1 > 0) {
1718            IERC20(token1).approve(address(pancakePositionManager), 0);
1719        }
1720
1721        // Note: Old position (oldTokenId) is now empty and abandoned
1722        // It cannot be burned because contract doesn't implement burn logic
1723        // This is intentional - old NFT stays in contract forever (harmless)
1724
1725        emit RangeRebalanced(
1726            oldTokenId,
1727            newTokenId,
1728            newTickLower,
1729            newTickUpper,
1730            newLiquidity,
1731            used0,
1732            used1
1733        );
1734    }
1735
1736}

Contract Ownership Renounced

This contract is permanently locked. No one can modify or upgrade it.

Immutable Forever

What does this mean?

The "owner" is the only one who could update this contract. By renouncing ownership, the owner key was permanently destroyed.

No one can change it

Not the developers, not hackers, not anyone. The contract code will run exactly as written forever.

Your funds are safe

The rules are set in stone. Your deposits and earnings will always work exactly as the code defines.

Blockchain Proof

This is recorded on BNB Smart Chain and can be verified by anyone

Event RecordedOwnershipTransferred
Previous Owner0x1b31...5e74
New Owner
0x0000...0000NULL (No One)
Block Number23,914,923
DateDecember 1, 2025
Verify on BscScan

Frequently Asked Questions

Roadmap

Building the Future

Our vision for decentralized yield optimization and ecosystem growth

Q1 2026

Foundation

Completed
  • Intelligent Range Rebalancing
  • BSC Network
  • PancakeSwap V3 Integration
Q3 2026

Innovation

Planned
  • Token Launch & Airdrop
  • Smart Wallet Integration
Q4 2026

Ecosystem

Planned
  • Mobile Applications
  • NFT Benefits Program
Future

Continuous Innovation

... More innovations coming ...

Ready to Start

Ready to Maximize Your Returns?

Join thousands of users earning passive income through automated liquidity management. Start with as little as $100 USDT.

Automated Yield
Up to 20%
Referral Levels
15 Levels