Skip to main content
PulseScanner.io

Address

0x9dc97ca4fb3a023f8d0033d5e29338ddf58abc57
Current Holdings
$0.00
TXs sent
not counted
First Active
2026-04-07
block 26,225,391
Last Active
164 days ago
block 26,227,735
Funded By
not identified

Net worth historyi

No net-worth snapshots recorded yet
exact matchGeniusBatchBuyersolc 0.8.34+commit.80d5c536runtime exact · creation exact
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * @title GeniusBatchBuyer V2 — ArtistTokenFactory Edition
 *
 * @dev Two functions changed from V1:
 *
 *   _purchaseToken: Previously bought various ecosystem tokens.
 *                   Now always buys eDAI and sends it directly to the
 *                   ArtistTokenFactory (streamingRewardsAddress).
 *                   Factory accepts raw eDAI transfers — no function call needed.
 *
 *   _fundStreamingPool: Previously called fundPool() on StreamingRewards.
 *                       Now a no-op (eDAI already sent in _purchaseToken).
 *                       Kept for call-chain compatibility.
 *
 * @dev Everything else is identical to V1:
 *   - Three PLS taxes (oracle, mefi, mysteryBox) before purchase
 *   - Health-based batch allocation (repurposed for future multi-factory support)
 *   - Token inventory system (tracks eDAI purchased and released)
 *   - Batch performance analytics
 *   - Auto-discovery and evolution engine
 *
 * @dev Post-deploy setup:
 *   1. setEcosystemContracts(artistFactoryAddress)
 *      — points streamingRewardsAddress at the factory
 *   2. No other changes needed — factory accepts raw eDAI
 */

interface IERC20Extended is IERC20 {
    function decimals() external view returns (uint8);
}

interface IDEXRouter {
    function WPLS() external pure returns (address);
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable;
    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function getAmountsOut(uint amountIn, address[] calldata path)
        external view returns (uint[] memory amounts);
}

interface IWPLS is IERC20Extended {
    function deposit() external payable;
}

interface IStreamingRewards {
    function getAllSupportedTokens() external view returns (address[] memory);
    function getPoolInfo(address token) external view returns (
        uint256 balance,
        uint256 totalDeposited,
        uint256 totalDistributed,
        uint256 distributionRate,
        bool isActive,
        uint256 nextDistribution,
        uint8 decimals
    );
    function fundPool(address token, uint256 amount) external;
    function isTokenSupported(address tokenAddress) external view returns (bool);
}

contract GeniusBatchBuyer is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    // ============ CONSTANTS ============
    address public constant WPLS           = 0xA1077a294dDE1B09bB078844df40758a5D0f9a27;
    address public constant EDAI           = 0xefD766cCb38EaF1dfd701853BFCe31359239F305;
    address public constant MYFI_TOKEN     = 0x11D8C297BAEEA38E00F1a0FB7b288219a69c6522; // ⚠️  UPDATE TO MYFI ADDRESS BEFORE DEPLOY
    address public constant PULSEX_ROUTER  = 0x165C3410fC91EF562C50559f7d2289fEbed552d9;
    address public constant ORACLE_ADDRESS = 0x17e7b189982D8Df2539d059b46467a09a7bCB91D;

    uint256 public constant SYNC_INTERVAL        = 3600;
    uint256 public constant MAX_TOKENS_PER_QUERY = 50;

    // ============ TAX CONFIGURATION ============
    uint256 public oracleTaxBps     = 200;
    uint256 public myfiTaxBps       = 200;
    uint256 public mysteryBoxTaxBps = 200;

    address public myfiAddress;
    address public mysteryBoxAddress;

    // ============ USD HEALTH THRESHOLDS ============

    uint256 public constant CRITICAL_HEALTH  = 20;
    uint256 public constant LOW_HEALTH       = 40;
    uint256 public constant MEDIUM_HEALTH    = 60;
    uint256 public constant GOOD_HEALTH      = 80;
    uint256 public constant EXCELLENT_HEALTH = 100;

    uint256 public constant CRITICAL_RELEASE_THRESHOLD = 20;
    uint256 public constant LOW_RELEASE_THRESHOLD      = 40;
    uint256 public constant MEDIUM_RELEASE_THRESHOLD   = 60;
    uint256 public constant BUFFER_RELEASE_THRESHOLD   = 80;
    uint256 public constant MIN_HOLD_TIME = 1800;
    uint256 public constant MAX_HOLD_TIME = 86400;

    // ============ PRICE STATE ============

    // ============ CORE STATE ============
    IDEXRouter public immutable router;

    address public keyTokenAddress;
    address public streamingRewardsAddress; // V2: points at ArtistTokenFactory

    // V2: Dual-token purchase destinations
    // factoryAddress receives eDAI → used for artist token minting + creator payments
    // jackpotAddress receives MYFI → funds weekly streaming competition prizes
    // streamingRewardsAddress is kept for IStreamingRewards discovery compat
    address public factoryAddress;   // ArtistTokenFactory — receives eDAI
    address public jackpotAddress;   // StreamingRewardsV6 — receives MYFI

    // Split of purchase allocation between eDAI (factory) and MYFI (jackpot)
    // Default 70% eDAI / 30% MEFI. Must sum to 10000.
    uint256 public edaiSplitBps  = 7000; // 70% → factory as eDAI
    uint256 public myfiSplitBps  = 3000; // 30% → jackpot as MYFI

    bool public useStreamingRewards   = true;
    bool public autoDiscoveryEnabled  = true;
    bool public healthBasedAllocation = true;
    bool public batchesInitialized    = false;
    bool public autoReleaseEnabled    = true;

    address[] public batch1Tokens;
    address[] public batch2Tokens;
    address[] public batch3Tokens;

    // ============ BATCH PERFORMANCE TRACKING ============
    mapping(uint8 => uint256) public batchSuccessRates;
    mapping(uint8 => uint256) public batchExecutionCounts;
    mapping(uint8 => uint256) public batchTotalVolume;
    mapping(uint8 => uint256) public batchLastExecution;

    // ============ TOKEN INVENTORY SYSTEM ============
    struct TokenInventory {
        uint256 balance;
        uint256 reservedAmount;
        uint256 lastReleaseTime;
        uint256 totalPurchased;
        uint256 totalReleased;
        bool    autoReleaseEnabled;
    }

    mapping(address => TokenInventory) public tokenInventories;
    address[] public inventoryTokens;
    uint256 public lastInventoryCheck;

    mapping(address => uint256) public poolHealthScores;
    mapping(address => uint256) public lastHealthUpdate;
    mapping(address => uint256) public tokenPurchaseCount;
    mapping(address => uint256) public totalTokensPurchased;

    uint256 public lastSyncTime;
    uint256 public syncCount;
    uint256 public discoveryAttempts;

    struct SystemStats {
        uint256 totalBatches;
        uint256 totalVolumePLS;
        uint256 totalTaxPaid;
        uint256 successfulSwaps;
        uint256 failedSwaps;
        uint256 poolsFunded;
        uint256 tokensDiscovered;
        uint256 lastOperationTime;
        uint256 tokensStored;
        uint256 tokensReleased;
    }

    SystemStats public stats;

    // ============ EVENTS ============
    event GeniusActivated(uint256 timestamp, address keyToken);
    event EcosystemDiscovered(string component, address contractAddress, uint256 attempt);
    event TokenBatchOptimized(uint8 indexed batchId, uint256 tokenCount, uint256 avgHealth);
    event HealthBasedPurchase(address indexed token, uint256 plsAmount, uint256 tokensReceived, uint256 healthScore);
    event OracleTaxPayment(uint256 plsAmount, uint256 timestamp);
    event MyfiTaxPayment(uint256 plsAmount, uint256 timestamp);
    event MysteryBoxTaxPayment(uint256 plsAmount, uint256 timestamp);
    event StreamingPoolFunded(address indexed token, uint256 amount, uint256 newBalance);
    event BatchExecutionComplete(uint8 indexed batchId, uint256 plsProcessed, uint256 tokensAffected);
    event EmergencyMode(string reason, bool active);
    event TokensStored(address indexed token, uint256 amount, uint256 totalInventory);
    event TokensAutoReleased(address indexed token, uint256 amount, uint256 healthScore, string reason);
    event InventoryRebalanced(uint256 tokensChecked, uint256 tokensReleased, uint256 totalValue);
    event EmergencyRelease(address indexed token, uint256 amount, string reason);
    event BatchPerformanceUpdate(uint8 indexed batchId, uint256 executionCount, uint256 successRate, string performanceGrade);
    event BatchMilestone(uint8 indexed batchId, uint256 milestone, uint256 totalVolume);
    event TaxConfigUpdated(uint256 oracleBps, uint256 mefiBps, uint256 mysteryBoxBps);
    event TaxAddressUpdated(address mefi, address mysteryBox);
    // V2
    event EdaiBoughtAndSent(uint256 plsSpent, uint256 edaiReceived, address factory);
    event MyfiBoughtAndSent(uint256 plsSpent, uint256 mefiReceived, address jackpot);

    // ============ CONSTRUCTOR ============
    constructor(
        address _keyToken,
        address _streamingRewards,
        address _myfiAddress,
        address _mysteryBoxAddress
    ) Ownable(msg.sender) {
        router = IDEXRouter(PULSEX_ROUTER);
        lastSyncTime = block.timestamp;

        if (_keyToken != address(0)) {
            keyTokenAddress = _keyToken;
            emit EcosystemDiscovered("KEY_TOKEN", _keyToken, ++discoveryAttempts);
        }

        if (_streamingRewards != address(0)) {
            streamingRewardsAddress = _streamingRewards;
            emit EcosystemDiscovered("STREAMING_REWARDS", _streamingRewards, ++discoveryAttempts);
        }

        if (_myfiAddress != address(0)) myfiAddress = _myfiAddress;
        if (_mysteryBoxAddress != address(0)) mysteryBoxAddress = _mysteryBoxAddress;

        _initializeEmergencyFallback();
        emit GeniusActivated(block.timestamp, msg.sender);
    }

    // ============ CORE BATCH FUNCTIONS ============

    function buyBatch1() external payable nonReentrant {
        _authenticateAndActivate();
        require(msg.value > 0, "NP");
        _evolveIfNeeded();
        _executeBatch(1, msg.value);
    }

    function buyBatch2() external payable nonReentrant {
        _authenticateAndActivate();
        require(msg.value > 0, "NP");
        _evolveIfNeeded();
        _executeBatch(2, msg.value);
    }

    function buyBatch3() external payable nonReentrant {
        _authenticateAndActivate();
        require(msg.value > 0, "NP");
        _evolveIfNeeded();
        _executeBatch(3, msg.value);
    }

    // ============ AUTHENTICATION ============

    function _authenticateAndActivate() internal {
        if (keyTokenAddress == address(0) && autoDiscoveryEnabled) {
            keyTokenAddress = msg.sender;
            emit EcosystemDiscovered("KEY_TOKEN", msg.sender, ++discoveryAttempts);
            emit GeniusActivated(block.timestamp, msg.sender);
        }

        require(
            msg.sender == keyTokenAddress ||
            msg.sender == owner() ||
            hasRole("OPERATOR", msg.sender),
            "A"
        );
    }

    // ============ EVOLUTION ENGINE ============

    function _evolveIfNeeded() internal {
        if (block.timestamp - lastSyncTime < SYNC_INTERVAL) return;
        _discoverEcosystem();
        if (autoReleaseEnabled) _performIntelligentReleases();
        lastSyncTime = block.timestamp;
        syncCount++;
    }


    // ============ PRICE FEED ============




    // ============ ECOSYSTEM DISCOVERY ============

    function _discoverEcosystem() internal {
        if (streamingRewardsAddress == address(0) && autoDiscoveryEnabled) {
            _discoverStreamingRewards();
        }
        if (!batchesInitialized && streamingRewardsAddress != address(0)) {
            _initializeFromStreamingRewards();
        }
    }

    function _discoverStreamingRewards() internal {
        address[] memory candidates = _getStreamingRewardsCandidates();
        for (uint i = 0; i < candidates.length; i++) {
            if (candidates[i] != address(0) && _validateStreamingRewards(candidates[i])) {
                streamingRewardsAddress = candidates[i];
                emit EcosystemDiscovered("STREAMING_REWARDS", candidates[i], ++discoveryAttempts);
                break;
            }
        }
    }

    // ============ TOKEN INTELLIGENCE & BATCH OPTIMIZATION ============



    function _distributePrioritizedTokens(address[] memory tokens) internal {
        uint256 criticalCount = 0;
        uint256 mediumCount   = 0;
        uint256 healthyCount  = 0;

        for (uint i = 0; i < tokens.length; i++) {
            uint256 health = poolHealthScores[tokens[i]];
            if (health <= LOW_HEALTH)         { batch1Tokens.push(tokens[i]); criticalCount++; }
            else if (health <= MEDIUM_HEALTH) { batch2Tokens.push(tokens[i]); mediumCount++;   }
            else                              { batch3Tokens.push(tokens[i]); healthyCount++;  }
        }

        if (batch1Tokens.length == 0) { batch1Tokens.push(EDAI); batch1Tokens.push(MYFI_TOKEN); }
        if (batch2Tokens.length == 0) { batch2Tokens.push(EDAI); batch2Tokens.push(MYFI_TOKEN); }
        if (batch3Tokens.length == 0) { batch3Tokens.push(EDAI); batch3Tokens.push(MYFI_TOKEN); }

        emit TokenBatchOptimized(1, batch1Tokens.length, 0);
        emit TokenBatchOptimized(2, batch2Tokens.length, 0);
        emit TokenBatchOptimized(3, batch3Tokens.length, 0);
    }




    // ============ BATCH EXECUTION ============

    function _executeBatch(uint8 batchId, uint256 plsAmount) internal {
        uint256 remaining = plsAmount;

        if (oracleTaxBps > 0) {
            uint256 tax = (plsAmount * oracleTaxBps) / 10000;
            if (tax > 0 && tax <= remaining) {
                remaining -= tax;
                _payTax(ORACLE_ADDRESS, tax);
                emit OracleTaxPayment(tax, block.timestamp);
            }
        }

        if (myfiTaxBps > 0 && myfiAddress != address(0)) {
            uint256 tax = (plsAmount * myfiTaxBps) / 10000;
            if (tax > 0 && tax <= remaining) {
                remaining -= tax;
                _payTax(myfiAddress, tax);
                emit MyfiTaxPayment(tax, block.timestamp);
            }
        }

        if (mysteryBoxTaxBps > 0 && mysteryBoxAddress != address(0)) {
            uint256 tax = (plsAmount * mysteryBoxTaxBps) / 10000;
            if (tax > 0 && tax <= remaining) {
                remaining -= tax;
                _payTax(mysteryBoxAddress, tax);
                emit MysteryBoxTaxPayment(tax, block.timestamp);
            }
        }

        address[] memory tokens = _getBatchTokens(batchId);
        require(tokens.length > 0, "E");

        uint256[] memory allocations = _calculateIntelligentAllocations(tokens, remaining, batchId);

        uint256 successfulPurchases = 0;
        for (uint i = 0; i < tokens.length; i++) {
            if (allocations[i] > 0 && _executePurchase(tokens[i], allocations[i])) {
                successfulPurchases++;
            }
        }

        _updateIntelligence(batchId, plsAmount, successfulPurchases);

        uint256 rate = batchSuccessRates[batchId];
        string memory grade;
        if      (rate >= 90) grade = "EXCELLENT";
        else if (rate >= 70) grade = "GOOD";
        else if (rate >= 50) grade = "FAIR";
        else if (rate >= 30) grade = "POOR";
        else                 grade = "CRITICAL";

        emit BatchPerformanceUpdate(batchId, batchExecutionCounts[batchId], rate, grade);
        if (batchExecutionCounts[batchId] % 25 == 0) {
            emit BatchMilestone(batchId, batchExecutionCounts[batchId], batchTotalVolume[batchId]);
        }
        emit BatchExecutionComplete(batchId, plsAmount, successfulPurchases);
    }

    function _payTax(address recipient, uint256 amount) internal {
        (bool success, ) = recipient.call{value: amount}("");
        if (success) {
            stats.totalTaxPaid += amount;
        } else {
            (bool ok,) = payable(owner()).call{value: amount}("");
            require(ok, "X");
        }
    }

    function _getBiasFactor(uint8 batchId, uint256 health) internal pure returns (uint256) {
        if (health <= CRITICAL_HEALTH) return batchId == 1 ? 300 : 200;

        if (batchId == 1) {
            return health <= LOW_HEALTH ? 300 : 75;
        } else if (batchId == 2) {
            if (health <= LOW_HEALTH)    return 150;
            if (health <= MEDIUM_HEALTH) return 300;
            return 75;
        } else {
            if (health <= LOW_HEALTH)    return 125;
            if (health <= MEDIUM_HEALTH) return 150;
            return 300;
        }
    }

    function _calculateIntelligentAllocations(
        address[] memory tokens,
        uint256 totalPLS,
        uint8   batchId
    ) internal view returns (uint256[] memory) {
        uint256[] memory allocations = new uint256[](tokens.length);

        // V2: For EDAI/MYFI tokens, use the configured split ratio
        // For any other tokens (legacy), fall back to health-based
        bool hasSplitTokens = false;
        for (uint i = 0; i < tokens.length; i++) {
            if (tokens[i] == EDAI || tokens[i] == MYFI_TOKEN) {
                hasSplitTokens = true;
                break;
            }
        }

        if (hasSplitTokens) {
            uint256 allocated = 0;
            for (uint i = 0; i < tokens.length; i++) {
                if (tokens[i] == EDAI) {
                    allocations[i] = (totalPLS * edaiSplitBps) / 10000;
                    allocated += allocations[i];
                } else if (tokens[i] == MYFI_TOKEN) {
                    allocations[i] = (totalPLS * myfiSplitBps) / 10000;
                    allocated += allocations[i];
                }
            }
            // Any dust from rounding goes to the last EDAI token
            uint256 dust = totalPLS - allocated;
            if (dust > 0) {
                for (uint i = 0; i < tokens.length; i++) {
                    if (tokens[i] == EDAI) { allocations[i] += dust; break; }
                }
            }
            return allocations;
        }

        // Legacy health-based allocation
        if (!healthBasedAllocation) {
            uint256 plsPerToken = totalPLS / tokens.length;
            for (uint i = 0; i < tokens.length; i++) allocations[i] = plsPerToken;
            return allocations;
        }

        uint256 totalInversePriority = 0;
        uint256[] memory priorities  = new uint256[](tokens.length);

        for (uint i = 0; i < tokens.length; i++) {
            uint256 health = poolHealthScores[tokens[i]];
            uint256 baseWeight = health > 0 ? (200 - health) : 100;
            priorities[i]      = (baseWeight * _getBiasFactor(batchId, health)) / 100;
            totalInversePriority += priorities[i];
        }

        if (totalInversePriority > 0) {
            uint256 allocated = 0;
            for (uint i = 0; i < tokens.length - 1; i++) {
                allocations[i] = (totalPLS * priorities[i]) / totalInversePriority;
                allocated += allocations[i];
            }
            allocations[tokens.length - 1] = totalPLS - allocated;
        }

        return allocations;
    }

    function _executePurchase(address token, uint256 plsAmount) internal returns (bool) {
        uint256 tokensReceived = _purchaseToken(token, plsAmount);
        if (tokensReceived > 0) {
            _storeTokensInInventory(EDAI, tokensReceived); // track eDAI in inventory
            tokenPurchaseCount[token]++;
            totalTokensPurchased[token] += tokensReceived;
            emit HealthBasedPurchase(token, plsAmount, tokensReceived, poolHealthScores[token]);
            return true;
        }
        return false;
    }

    // ============ V2: CORE PURCHASE FUNCTION ============

    /**
     * @dev V2: Routes purchase based on token parameter.
     *      EDAI  → buys eDAI, sends to factoryAddress (artist token engine)
     *      MYFI  → buys MYFI, sends to jackpotAddress (weekly prize pool)
     *      Both destinations accept raw transfers — no function call needed.
     *
     *      Split is controlled by edaiSplitBps / myfiSplitBps.
     *      Batch tokens are initialised as [EDAI, MYFI_TOKEN] so the
     *      existing health allocation naturally splits each batch.
     */
    function _purchaseToken(address token, uint256 plsAmount) internal returns (uint256) {
        bool isEdai = (token == EDAI);
        bool isMefi = (token == MYFI_TOKEN);
        if (!isEdai && !isMefi) return 0; // unknown token — skip

        address[] memory path = new address[](2);
        path[0] = WPLS;
        path[1] = token;

        uint256 balanceBefore = IERC20(token).balanceOf(address(this));

        try router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: plsAmount}(
            0, path, address(this), block.timestamp + 300
        ) {
            uint256 received = IERC20(token).balanceOf(address(this)) - balanceBefore;
            if (received > 0) {
                if (isEdai && factoryAddress != address(0)) {
                    IERC20(EDAI).safeTransfer(factoryAddress, received);
                    emit EdaiBoughtAndSent(plsAmount, received, factoryAddress);
                } else if (isMefi && jackpotAddress != address(0)) {
                    IERC20(MYFI_TOKEN).safeTransfer(jackpotAddress, received);
                    emit MyfiBoughtAndSent(plsAmount, received, jackpotAddress);
                }
            }
            stats.successfulSwaps++;
            return received;
        } catch {
            stats.failedSwaps++;
            return 0;
        }
    }

    // ============ INVENTORY MANAGEMENT ============

    function _distributeTokensIntelligently(address token, uint256 amount) internal {
        _storeTokensInInventory(token, amount);
        uint256 healthScore = poolHealthScores[token];

        if (healthScore <= CRITICAL_RELEASE_THRESHOLD) {
            _executeAutoRelease(token, "CRITICAL_HEALTH");
        } else if (healthScore <= LOW_RELEASE_THRESHOLD) {
            uint256 releaseAmount = tokenInventories[token].balance * 75 / 100;
            if (releaseAmount > 0) _executePartialRelease(token, releaseAmount, "LOW_HEALTH");
        }
    }

    function _storeTokensInInventory(address token, uint256 amount) internal {
        TokenInventory storage inv = tokenInventories[token];
        if (inv.balance == 0) {
            inventoryTokens.push(token);
            inv.autoReleaseEnabled = true;
        }
        inv.balance        += amount;
        inv.totalPurchased += amount;
        inv.lastReleaseTime = block.timestamp;
        stats.tokensStored += amount;
        emit TokensStored(token, amount, inv.balance);
    }

    function _performIntelligentReleases() internal {
        for (uint i = 0; i < inventoryTokens.length && i < 20; i++) {
            address token = inventoryTokens[i];
            TokenInventory storage inv = tokenInventories[token];
            if (!inv.autoReleaseEnabled || inv.balance == 0) continue;

            uint256 releaseAmount = _calculateOptimalRelease(
                token,
                poolHealthScores[token],
                block.timestamp - inv.lastReleaseTime
            );
            if (releaseAmount > 0) _executePartialRelease(token, releaseAmount, "AUTO_EVOLUTION");
        }
    }

    function _calculateOptimalRelease(
        address token,
        uint256 healthScore,
        uint256 timeSinceLastRelease
    ) internal view returns (uint256) {
        uint256 available = tokenInventories[token].balance;
        if (available == 0) return 0;
        if (timeSinceLastRelease >= MAX_HOLD_TIME) return available;
        if (timeSinceLastRelease < MIN_HOLD_TIME && healthScore > CRITICAL_RELEASE_THRESHOLD) return 0;

        if      (healthScore <= CRITICAL_RELEASE_THRESHOLD) return available;
        else if (healthScore <= LOW_RELEASE_THRESHOLD)      return available * 75 / 100;
        else if (healthScore <= MEDIUM_RELEASE_THRESHOLD)   return available * 50 / 100;
        else if (healthScore <= BUFFER_RELEASE_THRESHOLD)   return available * 25 / 100;
        return 0;
    }

    function _executePartialRelease(address token, uint256 amount, string memory reason) internal {
        TokenInventory storage inv = tokenInventories[token];
        require(inv.balance >= amount, "B");

        inv.balance         -= amount;
        inv.totalReleased   += amount;
        inv.lastReleaseTime  = block.timestamp;
        stats.tokensReleased += amount;

        uint256 healthScore = poolHealthScores[token];

        if (useStreamingRewards && _fundStreamingPool(token, amount)) {
            stats.poolsFunded++;
            emit TokensAutoReleased(token, amount, healthScore, reason);
            return;
        }

        IERC20(token).safeTransfer(owner(), amount);
        emit TokensAutoReleased(token, amount, healthScore, "TO_OWNER");
    }

    function _executeAutoRelease(address token, string memory reason) internal {
        uint256 fullBalance = tokenInventories[token].balance;
        if (fullBalance > 0) _executePartialRelease(token, fullBalance, reason);
    }

    // ============ V2: CONVERT TOKENS TO eDAI/MEFI AND ROUTE ============

    /**
     * @dev Converts any ERC20 token received (e.g. farming fees) into eDAI and MEFI
     *      using a two-hop path: token → WPLS → eDAI, then splits per edaiSplitBps.
     *      Called by _executePartialRelease when inventory tokens need routing.
     *      WPLS is used as intermediary because every token has a WPLS pair on PulseX.
     */
    function _fundStreamingPool(address token, uint256 amount) internal returns (bool) {
        if (amount == 0) return false;
        if (factoryAddress == address(0) && jackpotAddress == address(0)) return false;

        // Special case: if token is already eDAI, skip swap and route directly
        if (token == EDAI) {
            return _routeEdai(amount);
        }

        // Special case: if token is WPLS, skip first hop
        if (token == WPLS) {
            return _wplsToEdaiAndRoute(amount);
        }

        // General case: token → WPLS → eDAI (two-hop)
        uint256 wplsBefore = IERC20(WPLS).balanceOf(address(this));

        // Approve router for input token
        IERC20(token).approve(address(router), amount);

        address[] memory path1 = new address[](2);
        path1[0] = token;
        path1[1] = WPLS;

        try router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            amount, 0, path1, address(this), block.timestamp + 300
        ) {
            uint256 wplsReceived = IERC20(WPLS).balanceOf(address(this)) - wplsBefore;
            if (wplsReceived == 0) return false;
            return _wplsToEdaiAndRoute(wplsReceived);
        } catch {
            IERC20(token).approve(address(router), 0);
            return false;
        }
    }

    /**
     * @dev Swaps WPLS → eDAI then splits to factory and jackpot per edaiSplitBps.
     */
    function _wplsToEdaiAndRoute(uint256 wplsAmount) internal returns (bool) {
        uint256 edaiBefore = IERC20(EDAI).balanceOf(address(this));

        IWPLS(WPLS).approve(address(router), wplsAmount);

        address[] memory path = new address[](2);
        path[0] = WPLS;
        path[1] = EDAI;

        try router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            wplsAmount, 0, path, address(this), block.timestamp + 300
        ) {
            uint256 edaiReceived = IERC20(EDAI).balanceOf(address(this)) - edaiBefore;
            if (edaiReceived == 0) return false;
            return _routeEdai(edaiReceived);
        } catch {
            IWPLS(WPLS).approve(address(router), 0);
            return false;
        }
    }

    /**
     * @dev Routes eDAI to factory and buys MYFI for jackpot per edaiSplitBps.
     *      eDAI split → factory (raw transfer)
     *      MYFI split → swap eDAI→MYFI → jackpot (raw transfer)
     */
    function _routeEdai(uint256 edaiAmount) internal returns (bool) {
        if (edaiAmount == 0) return false;

        uint256 forFactory = (edaiAmount * edaiSplitBps) / 10000;
        uint256 forJackpot = edaiAmount - forFactory;

        // eDAI → factory (raw transfer, factory reads live balance)
        if (forFactory > 0 && factoryAddress != address(0)) {
            IERC20(EDAI).safeTransfer(factoryAddress, forFactory);
            emit EdaiBoughtAndSent(0, forFactory, factoryAddress);
        }

        // eDAI → MYFI → jackpot
        if (forJackpot > 0 && jackpotAddress != address(0)) {
            uint256 mefiBefore = IERC20(MYFI_TOKEN).balanceOf(address(this));

            IERC20(EDAI).approve(address(router), forJackpot);

            address[] memory path = new address[](2);
            path[0] = EDAI;
            path[1] = MYFI_TOKEN;

            try router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
                forJackpot, 0, path, address(this), block.timestamp + 300
            ) {
                uint256 mefiReceived = IERC20(MYFI_TOKEN).balanceOf(address(this)) - mefiBefore;
                if (mefiReceived > 0) {
                    IERC20(MYFI_TOKEN).safeTransfer(jackpotAddress, mefiReceived);
                    emit MyfiBoughtAndSent(0, mefiReceived, jackpotAddress);
                }
            } catch {
                IERC20(EDAI).approve(address(router), 0);
                // Swap failed — send eDAI to factory as fallback rather than lose it
                if (forJackpot > 0 && factoryAddress != address(0)) {
                    IERC20(EDAI).safeTransfer(factoryAddress, forJackpot);
                    emit EdaiBoughtAndSent(0, forJackpot, factoryAddress);
                }
            }
        }

        return true;
    }

    // ============ INTELLIGENCE & ANALYTICS ============

    function _updateIntelligence(uint8 batchId, uint256 plsAmount, uint256 successfulPurchases) internal {
        stats.totalBatches      += 1;
        stats.totalVolumePLS    += plsAmount;
        stats.lastOperationTime  = block.timestamp;

        batchExecutionCounts[batchId]++;
        batchTotalVolume[batchId]   += plsAmount;
        batchLastExecution[batchId]  = block.timestamp;

        address[] memory batchTokens = _getBatchTokens(batchId);
        uint256 batchSuccessRate = batchTokens.length > 0
            ? (successfulPurchases * 100) / batchTokens.length : 0;
        batchSuccessRates[batchId] = batchSuccessRate;


    }

    function _initializeFromStreamingRewards() internal {
        try IStreamingRewards(streamingRewardsAddress).getAllSupportedTokens()
        returns (address[] memory supportedTokens) {
            if (supportedTokens.length > 0) {
                _distributePrioritizedTokens(supportedTokens);
                batchesInitialized = true;
            }
        } catch {
            // V2: Factory doesn't implement IStreamingRewards — use EDAI as default batch token
            batch1Tokens.push(EDAI);
            batch2Tokens.push(EDAI);
            batch3Tokens.push(EDAI);
            batchesInitialized = true;
        }
    }

    // ============ DISCOVERY CANDIDATES ============

    function _getStreamingRewardsCandidates() internal pure returns (address[] memory) {
        address[] memory c = new address[](3);
        c[0] = 0x3f629eE74E1523e1E27742b0dE4706fAC838456F;
        return c;
    }

    function _validateStreamingRewards(address candidate) internal view returns (bool) {
        if (candidate == address(0) || candidate.code.length == 0) return false;
        try IStreamingRewards(candidate).getAllSupportedTokens() returns (address[] memory tokens) {
            return tokens.length > 0;
        } catch { return false; }
    }

    // ============ UTILITIES ============

    function _getBatchTokens(uint8 batchId) internal view returns (address[] memory) {
        if (batchId == 1) return batch1Tokens;
        if (batchId == 2) return batch2Tokens;
        if (batchId == 3) return batch3Tokens;
        revert("D");
    }

    function _initializeEmergencyFallback() internal {
        // V2: Two purchase targets — eDAI for factory, MYFI for jackpot
        // Each batch gets both so the split logic applies on every call
        batch1Tokens.push(EDAI);
        batch1Tokens.push(MYFI_TOKEN);
        batch2Tokens.push(EDAI);
        batch2Tokens.push(MYFI_TOKEN);
        batch3Tokens.push(EDAI);
        batch3Tokens.push(MYFI_TOKEN);
    }

    bytes32 private constant OPERATOR_ROLE = keccak256("OPERATOR");

    function hasRole(string memory role, address account) internal view returns (bool) {
        if (keccak256(abi.encodePacked(role)) == OPERATOR_ROLE) return account == owner();
        return false;
    }

    // ============ PUBLIC INVENTORY MANAGEMENT ============

    function checkAndReleaseTokens() external {
        require(block.timestamp - lastInventoryCheck >= 1800, "F");

        uint256 tokensChecked      = 0;
        uint256 tokensReleased_    = 0;
        uint256 totalValueReleased = 0;

        for (uint i = 0; i < inventoryTokens.length && i < MAX_TOKENS_PER_QUERY; i++) {
            address token = inventoryTokens[i];
            TokenInventory storage inv = tokenInventories[token];
            if (!inv.autoReleaseEnabled || inv.balance == 0) continue;

            tokensChecked++;
            uint256 releaseAmount = _calculateOptimalRelease(
                token,
                poolHealthScores[token],
                block.timestamp - inv.lastReleaseTime
            );

            if (releaseAmount > 0) {
                _executePartialRelease(token, releaseAmount, _getHealthReason(poolHealthScores[token]));
                tokensReleased_++;
                totalValueReleased += releaseAmount;
            }
        }

        lastInventoryCheck = block.timestamp;
        emit InventoryRebalanced(tokensChecked, tokensReleased_, totalValueReleased);
    }

    function _getHealthReason(uint256 healthScore) internal pure returns (string memory) {
        if (healthScore <= CRITICAL_RELEASE_THRESHOLD) return "CRITICAL_HEALTH";
        if (healthScore <= LOW_RELEASE_THRESHOLD)      return "LOW_HEALTH";
        if (healthScore <= MEDIUM_RELEASE_THRESHOLD)   return "MEDIUM_HEALTH";
        if (healthScore <= BUFFER_RELEASE_THRESHOLD)   return "BUFFER_HEALTH";
        return "MAINTENANCE_RELEASE";
    }

    // ============ ADMIN ============

    function setEcosystemContracts(address _streamingRewards) external onlyOwner {
        if (_streamingRewards != address(0)) {
            streamingRewardsAddress = _streamingRewards;
            emit EcosystemDiscovered("STREAMING_REWARDS", _streamingRewards, ++discoveryAttempts);
        }
    }

    /// @notice Set the ArtistTokenFactory address — receives eDAI purchases.
    function setFactoryAddress(address _factory) external onlyOwner {
        require(_factory != address(0), "Z");
        factoryAddress = _factory;
    }

    /// @notice Set the StreamingRewardsV6 address — receives MYFI purchases for jackpot.
    function setJackpotAddress(address _jackpot) external onlyOwner {
        require(_jackpot != address(0), "Z");
        jackpotAddress = _jackpot;
    }

    /// @notice Set the eDAI/MEFI purchase split. Must sum to 10000.
    ///         e.g. 7000/3000 = 70% eDAI to factory, 30% MYFI to jackpot.
    function setPurchaseSplit(uint256 _edaiBps, uint256 _mefiBps) external onlyOwner {
        require(_edaiBps + _mefiBps == 10000, "S");
        edaiSplitBps = _edaiBps;
        myfiSplitBps = _mefiBps;
    }

    function setMyfiAddress(address _mefi) external onlyOwner {
        require(_mefi != address(0), "Z");
        emit TaxAddressUpdated(_mefi, mysteryBoxAddress);
        myfiAddress = _mefi;
    }

    function setMysteryBoxAddress(address _mysteryBox) external onlyOwner {
        require(_mysteryBox != address(0), "Z");
        emit TaxAddressUpdated(myfiAddress, _mysteryBox);
        mysteryBoxAddress = _mysteryBox;
    }

    function setOracleTax(uint256 _bps) external onlyOwner {
        require(_bps <= 500, "M");
        oracleTaxBps = _bps;
        emit TaxConfigUpdated(oracleTaxBps, myfiTaxBps, mysteryBoxTaxBps);
    }

    function setMyfiTax(uint256 _bps) external onlyOwner {
        require(_bps <= 500, "M");
        myfiTaxBps = _bps;
        emit TaxConfigUpdated(oracleTaxBps, myfiTaxBps, mysteryBoxTaxBps);
    }

    function setMysteryBoxTax(uint256 _bps) external onlyOwner {
        require(_bps <= 500, "M");
        mysteryBoxTaxBps = _bps;
        emit TaxConfigUpdated(oracleTaxBps, myfiTaxBps, mysteryBoxTaxBps);
    }

    function forceEvolution() external onlyOwner {
        lastSyncTime = 0;
        _evolveIfNeeded();
    }


    function toggleIntelligence(bool _healthBased, bool _autoDiscovery, bool _autoRelease) external onlyOwner {
        healthBasedAllocation = _healthBased;
        autoDiscoveryEnabled  = _autoDiscovery;
        autoReleaseEnabled    = _autoRelease;
    }

    function manualReleaseToken(address token, uint256 amount) external onlyOwner {
        require(tokenInventories[token].balance >= amount, "B");
        _executePartialRelease(token, amount, "MANUAL_OVERRIDE");
    }

    function emergencyReleaseAll() external onlyOwner {
        for (uint i = 0; i < inventoryTokens.length; i++) {
            address token   = inventoryTokens[i];
            uint256 balance = tokenInventories[token].balance;
            if (balance > 0) {
                _executePartialRelease(token, balance, "EMERGENCY_RELEASE");
                emit EmergencyRelease(token, balance, "ADMIN_TRIGGERED");
            }
        }
    }

    function setTokenAutoRelease(address token, bool enabled) external onlyOwner {
        tokenInventories[token].autoReleaseEnabled = enabled;
    }

    function emergencyWithdraw(address token) external onlyOwner {
        if (token == address(0)) {
            (bool ok,) = payable(owner()).call{value: address(this).balance}("");
            require(ok, "X");
        } else {
            IERC20(token).safeTransfer(owner(), IERC20(token).balanceOf(address(this)));
        }
    }

    // ============ VIEW FUNCTIONS ============






    function getEcosystemStatus() external view returns (
        address keyToken,
        address streamingRewards,
        address mefi,
        address mysteryBox,
        bool    autoDiscovery,
        bool    healthAllocation,
        uint256 discoveryAttempts_
    ) {
        return (
            keyTokenAddress, streamingRewardsAddress,
            myfiAddress, mysteryBoxAddress,
            autoDiscoveryEnabled, healthBasedAllocation, discoveryAttempts
        );
    }

    function getTaxConfig() external view returns (
        uint256 oracleBps,
        uint256 mefiBps,
        uint256 mysteryBoxBps,
        uint256 totalTaxBps,
        uint256 purchaseBps
    ) {
        totalTaxBps = oracleTaxBps + myfiTaxBps + mysteryBoxTaxBps;
        purchaseBps = 10000 - totalTaxBps;
        return (oracleTaxBps, myfiTaxBps, mysteryBoxTaxBps, totalTaxBps, purchaseBps);
    }

    /**
     * @notice Returns the current eDAI/MEFI purchase split and destinations.
     *         Frontend shows this on the tokenomics/about page.
     */
    function getPurchaseSplit() external view returns (
        uint256 edaiBps,
        uint256 mefiBps_,
        address factory_,
        address jackpot_
    ) {
        return (edaiSplitBps, myfiSplitBps, factoryAddress, jackpotAddress);
    }



    receive() external payable {}
}