Skip to main content
PulseScanner.io

Address

0xe4374ad7eeb1bd69cf8ea9bba71412e2a94c28f6
Current Holdings
$0.00
TXs sent
not counted
First Active
2025-09-23
block 24,580,976
Last Active
36 days ago
block 27,246,930
Funded By
0x4a45…3b21

Net worth historyi

213 snapshots · to block 27,475,412coverage change 26 Augcoverage change 27 Augcoverage change 27 Augcoverage change 27 Augcoverage change 27 Aug
partial matchTreasurysolc 0.8.20+commit.a1b79de6runtime partial · creation partial
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {console} from "forge-std/console.sol";
import {IPoolV3} from "../Lending/interfaces/IPoolV3.sol";

// Interface for tokens with underlying
interface ITokenWithUnderlying {
    function underlyingToken() external view returns (address);
}

// Staking contract interface
interface IStakingContract {
    function notifyRewardAmount(address _rewardsToken, uint256 reward) external;
}

// Interface for WPLS unwrapping
interface IWPLS {
    function withdraw(uint256 amount) external;
}

interface IDEXRouter {
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapExactTokensForETH(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function getAmountsOut(
        uint amountIn,
        address[] calldata path
    ) external view returns (uint[] memory amounts);
}

contract Treasury is Ownable {
    using SafeERC20 for IERC20;
    // Distribution addresses
    address public dev1;
    address public dev2;
    address public buyBurnContract = 0xBd48026E337f1419EC97F780b2045eb0ef2E0467;
    address public stakingContract;
    address public reserveWallet = 0x0f1062EFd80a07e84D37CeA24046635e264a9D86;

    // Distribution percentages (basis points, 10000 = 100%)
    uint256 public profitSharePercentage = 6000; // 60% of distributable
    uint256 public dev1Percentage = 2000; // 20% of distributable
    uint256 public dev2Percentage = 500; // 5% of distributable
    uint256 public buyBurnPercentage = 500; // 5% of distributable
    uint256 public reservePercentage = 1000; // 10% - stays in contract for bad debt coverage

    // Time lock for distributions
    uint256 public distribution_duration;
    uint256 public lastDistribution;

    // Global switch for buy burn behavior
    bool public sendToOwnerMode; // false = use buy burn, true = send to owner

    // DEX Router for swaps (hardcoded PulseChain router)
    IDEXRouter public constant dexRouter =
        IDEXRouter(0x165C3410fC91EF562C50559f7d2289fEbed552d9);
    IDEXRouter public constant backupRouter =
        IDEXRouter(0x98bf93ebf5c380C0e6Ae8e192A7e2AE08edAcc02);

    address public constant PLS = 0xA1077a294dDE1B09bB078844df40758a5D0f9a27;

    // Whitelisted tokens for staking rewards
    mapping(address => bool) public whitelistedTokens;
    address[] public whitelistedTokensList;

    // Events
    event FeesDistributed(address tokenDistributed, uint256 totalUnderlying);

    event StakingContractUpdated(
        address indexed oldContract,
        address indexed newContract
    );

    event TokenWhitelisted(address indexed token);

    event TokenRemovedFromWhitelist(address indexed token);

    event FallbackToOwner(
        address indexed token,
        uint256 amount,
        address indexed owner
    );

    constructor() Ownable(msg.sender) {}

    /**
     * @notice Initializes the Treasury contract
     * @param _dev1 Address for dev1 distributions
     * @param _dev2 Address for dev2 distributions
     * @param _stakingContract Address of the staking contract
     
     */
    function initialize(
        address _dev1,
        address _dev2,
        address _stakingContract
    ) external onlyOwner {
        dev1 = _dev1;
        dev2 = _dev2;
        stakingContract = _stakingContract;
    }

    receive() external payable {}

    /**
     * @notice Distributes fees for all whitelisted tokens that have balances
     * @dev Processes all available pool tokens and updates last distribution timestamp
     */
    function distributeAllFees() public {
        require(
            block.timestamp >= lastDistribution + distribution_duration,
            "Distribution cooldown not met"
        );
        uint256 lngt = whitelistedTokensList.length;
        for (uint256 i; i < lngt; i++) {
            _distributeFees(IPoolV3(whitelistedTokensList[i]));
        }

        lastDistribution = block.timestamp;
    }

    /**
     * @notice Internal function that handles the core distribution logic
     * @dev Calculates and distributes fees based on percentages, reserves are what's left in contract
     * @param poolToken The pool token to distribute fees from
     */
    function _distributeFees(IPoolV3 poolToken) internal {
        uint256 poolTokenBalance = IERC20(poolToken).balanceOf(address(this));
        if (poolTokenBalance == 0) return;

        // Calculate distribution amounts from total balance
        uint256 reserveAmount = (poolTokenBalance * reservePercentage) / 10000;
        uint256 profitAmount = (poolTokenBalance * profitSharePercentage) /
            10000;
        uint256 dev1Amount = (poolTokenBalance * dev1Percentage) / 10000;
        uint256 dev2Amount = (poolTokenBalance * dev2Percentage) / 10000;
        uint256 buyBurnAmount = (poolTokenBalance * buyBurnPercentage) / 10000;

        uint256 underlyingReceived = (poolToken).redeem(
            buyBurnAmount,
            address(this),
            address(this)
        );
        if (underlyingReceived == 0) return;

        IStakingContract(stakingContract).notifyRewardAmount(
            address(poolToken),
            profitAmount
        );

        poolToken.transfer(dev1, dev1Amount);
        poolToken.transfer(dev2, dev2Amount);
        poolToken.transfer(reserveWallet, reserveAmount);

        // Use global switch to determine buy burn behavior
        if (sendToOwnerMode) {
            IERC20(poolToken.underlyingToken()).transfer(
                owner(),
                underlyingReceived
            );
        } else {
            _swapToPLSAndSend(poolToken.underlyingToken(), underlyingReceived);
        }

        emit FeesDistributed(poolToken.underlyingToken(), underlyingReceived);
    }
    /**
     * @notice Swaps underlying tokens to native PLS and sends to buy burn contract
     * @dev Handles WPLS unwrapping and DEX swaps with fallback mechanisms
     * @param underlyingTokenAddress The underlying token to swap
     * @param amount Amount of underlying to swap
     */

    function _swapToPLSAndSend(
        address underlyingTokenAddress,
        uint256 amount
    ) internal {
        if (underlyingTokenAddress == PLS) {
            IWPLS(PLS).withdraw(amount);
            buyBurnContract.call{value: amount}("");
        } else {
            address[] memory path = new address[](2);
            path[0] = underlyingTokenAddress;
            path[1] = PLS;

            try
                dexRouter.swapExactTokensForETH(
                    amount,
                    0,
                    path,
                    address(buyBurnContract),
                    block.timestamp + 300
                )
            {
                return;
            } catch {
                try
                    backupRouter.swapExactTokensForETH(
                        amount,
                        0,
                        path,
                        address(buyBurnContract),
                        block.timestamp + 300
                    )
                {
                    // Backup swap succeeded
                    return;
                } catch {
                    // Both routers failed, send tokens to owner
                    IERC20(underlyingTokenAddress).transfer(owner(), amount);
                    emit FallbackToOwner(
                        underlyingTokenAddress,
                        amount,
                        owner()
                    );
                }
            }
        }
    }

    /**
     * @notice Updates dev1 address
     * @param _dev1 New dev1 address
     */
    function setDev1(address _dev1) external onlyOwner {
        require(_dev1 != address(0), "Invalid dev1 address");
        dev1 = _dev1;
    }

    /**
     * @notice Updates dev2 address
     * @param _dev2 New dev2 address
     */
    function setDev2(address _dev2) external onlyOwner {
        require(_dev2 != address(0), "Invalid dev2 address");
        dev2 = _dev2;
    }

    /**
     * @notice Updates profit share percentage
     * @param _profitSharePercentage New profit share percentage (basis points)
     */
    function setProfitSharePercentage(
        uint256 _profitSharePercentage
    ) external onlyOwner {
        profitSharePercentage = _profitSharePercentage;
    }

    /**
     * @notice Updates dev1 percentage
     * @param _dev1Percentage New dev1 percentage (basis points)
     */
    function setDev1Percentage(uint256 _dev1Percentage) external onlyOwner {
        dev1Percentage = _dev1Percentage;
    }

    /**
     * @notice Updates dev2 percentage
     * @param _dev2Percentage New dev2 percentage (basis points)
     */
    function setDev2Percentage(uint256 _dev2Percentage) external onlyOwner {
        dev2Percentage = _dev2Percentage;
    }

    /**
     * @notice Updates buy burn percentage
     * @param _buyBurnPercentage New buy burn percentage (basis points)
     */
    function setBuyBurnPercentage(
        uint256 _buyBurnPercentage
    ) external onlyOwner {
        buyBurnPercentage = _buyBurnPercentage;
    }

    /**
     * @notice Updates reserve percentage
     * @param _reservePercentage New reserve percentage (basis points)
     */
    function setReservePercentage(
        uint256 _reservePercentage
    ) external onlyOwner {
        reservePercentage = _reservePercentage;
    }

    /**
     * @notice Updates reserve wallet address
     * @param _reserveWallet New reserve wallet address
     */
    function setReserveWallet(address _reserveWallet) external onlyOwner {
        require(_reserveWallet != address(0), "Invalid reserve wallet address");
        reserveWallet = _reserveWallet;
    }

    /**
     * @notice Updates buy burn contract address
     * @param _buyBurnContract New buy burn contract address (can be zero to disable)
     */
    function setBuyBurnContract(address _buyBurnContract) external onlyOwner {
        buyBurnContract = _buyBurnContract;
    }

    /**
     * @notice Sets the staking contract address
     * @param _stakingContract Address of the staking contract
     */
    function setStakingContract(address _stakingContract) external onlyOwner {
        require(
            _stakingContract != address(0),
            "Invalid staking contract address"
        );
        address oldContract = stakingContract;
        stakingContract = _stakingContract;

        uint256 length = whitelistedTokensList.length;
        for (uint256 i; i < length; i++) {
            IERC20(whitelistedTokensList[i]).approve(
                address(_stakingContract),
                type(uint256).max
            );
            address underlying = ITokenWithUnderlying(whitelistedTokensList[i])
                .underlyingToken();
            require(underlying != address(0), "Invalid token address");
            IERC20(underlying).approve(address(dexRouter), type(uint256).max);
            IERC20(underlying).approve(
                address(backupRouter),
                type(uint256).max
            );
        }

        emit StakingContractUpdated(oldContract, _stakingContract);
    }

    /**
     * @notice Sets the distribution duration (cooldown period between distributions)
     * @param _distributionDuration New distribution duration in seconds
     */
    function setDistributionDuration(
        uint256 _distributionDuration
    ) external onlyOwner {
        require(_distributionDuration != 0, "Invalid distribution duration");
        distribution_duration = _distributionDuration;
    }

    /**
     * @notice Toggle between sending to buy burn contract or directly to owner
     * @param _sendToOwnerMode True to send to owner, false to use buy burn mechanism
     */
    function setSendToOwnerMode(bool _sendToOwnerMode) external onlyOwner {
        sendToOwnerMode = _sendToOwnerMode;
    }
    function sadaasasasasasd() public view returns (uint256 bb) {
        bb = 233231;
    }
    uint256 lelf = 1312;
    /**
     * @notice Adds a token to the whitelist for staking distributions
     * @param token Token address to whitelist
     */
    function addWhitelistedToken(address token) public onlyOwner {
        require(!whitelistedTokens[token], "Token already whitelisted");

        address underlying = ITokenWithUnderlying(token).underlyingToken();
        require(underlying != address(0), "Invalid token address");
        // Approve both routers
        IERC20(underlying).approve(address(dexRouter), type(uint256).max);
        IERC20(underlying).approve(address(backupRouter), type(uint256).max);
        IERC20(token).approve(address(stakingContract), type(uint256).max);

        whitelistedTokens[token] = true;
        whitelistedTokensList.push(token);
        emit TokenWhitelisted(token);
    }

    /**
     * @notice Removes a token from the whitelist
     * @param token Token address to remove
     */
    function removeWhitelistedToken(address token) external onlyOwner {
        whitelistedTokens[token] = false;

        // Remove from array
        for (uint256 i = 0; i < whitelistedTokensList.length; i++) {
            if (whitelistedTokensList[i] == token) {
                whitelistedTokensList[i] = whitelistedTokensList[
                    whitelistedTokensList.length - 1
                ];
                whitelistedTokensList.pop();
                break;
            }
        }
        emit TokenRemovedFromWhitelist(token);
    }

    /**
     * @notice Adds multiple tokens to the whitelist
     * @param tokens Array of token addresses to whitelist
     */
    function addMultipleWhitelistedTokens(
        address[] calldata tokens
    ) external onlyOwner {
        for (uint256 i = 0; i < tokens.length; i++) {
            if (tokens[i] != address(0) && !whitelistedTokens[tokens[i]]) {
                addWhitelistedToken(tokens[i]);
            }
        }
    }

    /**
     * @notice Allows owner to withdraw any token from the contract
     * @param token Token address to withdraw
     * @param amount Amount to withdraw
     */
    function withdrawaToken(address token, uint256 amount) external onlyOwner {
        IERC20(token).safeTransfer(owner(), amount);
    }

    /**
     * @notice Allows owner to withdraw native PLS from the contract
     * @param amount Amount of native PLS to withdraw
     */
    function withdrawaNative(uint256 amount) external onlyOwner {
        (bool success, ) = owner().call{value: amount}("");
        require(success, "Native PLS transfer failed");
    }

    /**
     * @notice View function to check when next distribution is available
     * @return Time until next distribution is available (0 if available now)
     */
    function timeUntilNextDistribution() external view returns (uint256) {
        uint256 nextDistribution = lastDistribution + distribution_duration;
        if (block.timestamp >= nextDistribution) {
            return 0;
        }
        return nextDistribution - block.timestamp;
    }

    /**
     * @notice View function to preview distribution amounts
     * @param poolToken The pool token to preview distribution for
     * @return profitAmount Amount that would go to profit share
     * @return dev1Amount Amount that would go to dev1
     * @return dev2Amount Amount that would go to dev2
     * @return buyBurnAmount Amount that would be used for buy burn
     * @return reserveAmount Amount that would remain in contract as reserves
     */
    function previewDistribution(
        address poolToken
    )
        external
        view
        returns (
            uint256 profitAmount,
            uint256 dev1Amount,
            uint256 dev2Amount,
            uint256 buyBurnAmount,
            uint256 reserveAmount
        )
    {
        uint256 poolTokenBalance = IERC20(poolToken).balanceOf(address(this));
        if (poolTokenBalance == 0) {
            return (0, 0, 0, 0, 0);
        }

        // Use hardcoded percentages matching _distributeFees function
        reserveAmount = (poolTokenBalance * reservePercentage) / 10000;
        profitAmount = (poolTokenBalance * profitSharePercentage) / 10000;
        dev1Amount = (poolTokenBalance * dev1Percentage) / 10000;
        dev2Amount = (poolTokenBalance * dev2Percentage) / 10000;
        buyBurnAmount = (poolTokenBalance * buyBurnPercentage) / 10000;
    }

    /**
     * @notice Gets all whitelisted tokens
     * @return Array of whitelisted token addresses
     */
    function getWhitelistedTokens() external view returns (address[] memory) {
        return whitelistedTokensList;
    }

    /**
     * @notice Gets the count of whitelisted tokens
     * @return Number of whitelisted tokens
     */
    function getWhitelistedTokensCount() external view returns (uint256) {
        return whitelistedTokensList.length;
    }

    /**
     * @notice Checks if a token is whitelisted
     * @param token Token address to check
     * @return True if token is whitelisted
     */
    function isWhitelisted(address token) external view returns (bool) {
        return whitelistedTokens[token];
    }
}