OTCSplitter

Description:

Smart contract deployed on Ethereum with Factory, Oracle features.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/OTCSplitter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

interface IERC20 {
    function decimals() external view returns (uint8);
    function transfer(address to, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

interface IERC20PermitLike {
    function approve(address spender, uint256 value) external returns (bool);
}

interface IAggregatorV3 {
  function latestRoundData() external view returns (
    uint80 roundId,
    int256 answer,
    uint256 startedAt,
    uint256 updatedAt,
    uint80 answeredInRound
  );
  function decimals() external view returns (uint8);
}

contract OTCSplitter {
    address public immutable GNIM;
    address public immutable GNIM_RESERVE; // holds GNIM, pre-approves this contract
    address public immutable TREASURY;     // receives 99% of payment
    address public immutable CHAINLINK_ETH_USD; // mainnet ETH/USD aggregator

    uint256 public constant COMMISSION_BPS = 100; // 1%
    uint256 public constant BPS_DENOM = 10_000;
    // Store USD with 6 decimals to avoid float issues (e.g., $1 = 1e6)
    uint8 public constant USD_DECIMALS = 6;
    // GNIM price in USD (6 decimals). $0.02 = 20000
    uint256 public constant PRICE_PER_GNIM_USD6 = 20_000;

    error InvalidAffiliate();
    error AmountTooSmall();
    error StalePrice();
    error TransferFailed();

    event Bought(address indexed buyer, address payToken, uint256 usdAmount6, uint256 gnimOut, address affiliate, uint256 commissionUsd6);

    constructor(address gnim, address gnimReserve, address treasury, address chainlinkEthUsd) {
        GNIM = gnim;
        GNIM_RESERVE = gnimReserve;
        TREASURY = treasury;
        CHAINLINK_ETH_USD = chainlinkEthUsd;
        owner = msg.sender; // add ONLY if you don't use OZ Ownable
    }

    //uint256 public minUsd6 = 1e6; // default $1
    //event MinUsd6Updated(uint256 oldValue, uint256 newValue);

    address public owner;
    modifier onlyOwner() { require(msg.sender == owner, "not owner"); _; }


    /// @notice Owner can update the minimum (must be > 0).
    /*function setMinUsd6(uint256 newMin) external onlyOwner {
        require(newMin > 0, "min must be > 0");
        emit MinUsd6Updated(minUsd6, newMin);
        minUsd6 = newMin;
    }*/


    function _usdToGnim(uint256 usdAmount6) internal pure returns (uint256 gnimOut) {
        // GNIM has 18 decimals. price: $0.02 => gnim = usd / 0.02
        // Using USD(6): gnimOut = usdAmount6 * 1e18 / 20000
        gnimOut = usdAmount6 * 1e18 / PRICE_PER_GNIM_USD6;
    }

    function _split(address token, uint256 amount, address affiliate) internal {
        uint256 commission = amount * COMMISSION_BPS / BPS_DENOM; // 1%
        uint256 toTreasury = amount - commission;
        if (affiliate == address(0)) {
            // route all to treasury if no affiliate
            toTreasury = amount;
            commission = 0;
        }
        if (token == address(0)) {
            // native ETH
            (bool ok1,) = TREASURY.call{value: toTreasury}("");
            (bool ok2,) = affiliate.call{value: commission}("");
            require(ok1 && ok2, "split eth failed");
        } else {
            require(IERC20(token).transfer(TREASURY, toTreasury), "pay to treasury failed");
            if (commission > 0) {
                require(IERC20(token).transfer(affiliate, commission), "pay affiliate failed");
            }
        }
    }

    function _sendGnim(address to, uint256 gnimAmount) internal {
        require(IERC20(GNIM).transferFrom(GNIM_RESERVE, to, gnimAmount), "gnim transferFrom failed");
    }

    function buyWithETH(uint256 usdAmount6, address receiver, address affiliate) external payable {
        //if (usdAmount6 < 10 * 1e6) revert AmountTooSmall(); // $10 minimum
        //if (usdAmount6 < minUsd6) revert AmountTooSmall();

        // Get ETH/USD
        (, int256 ans,, uint256 updatedAt,) = IAggregatorV3(CHAINLINK_ETH_USD).latestRoundData();
        require(ans > 0, "bad price");
        require(block.timestamp - updatedAt < 1 hours, "stale price");
        uint256 ethUsd = uint256(ans); // 8 decimals
        // required ETH = usd / ethUsd
        // usd (6d) * 1e18 / (ethUsd 8d) => wei
        uint256 requiredWei = usdAmount6 * 1e18 / ethUsd / 1e2;
        require(msg.value >= requiredWei, "insufficient ETH sent");
        // Split the actual received msg.value (so minor overpay gets split; excess is not refunded to keep it simple)
        _split(address(0), msg.value, affiliate);
        // Send GNIM
        _sendGnim(receiver, _usdToGnim(usdAmount6));
        emit Bought(msg.sender, address(0), usdAmount6, _usdToGnim(usdAmount6), affiliate, usdAmount6 * COMMISSION_BPS / BPS_DENOM);
    }

    function buyWithERC20(address token, uint256 usdAmount6, address receiver, address affiliate) external {
        //if (usdAmount6 < 10 * 1e6) revert AmountTooSmall();
        //if (usdAmount6 < minUsd6) revert AmountTooSmall();

        // Assume $1 per unit for USDC/USDT; token must have 6 decimals like USDC/USDT
        uint8 dec = IERC20(token).decimals();
        require(dec == 6, "expects 6-decimal stable");
        uint256 amount = usdAmount6; // 1:1
        // pull funds in
        require(IERC20(token).transferFrom(msg.sender, address(this), amount), "transferFrom buyer failed");
        // split
        _split(token, amount, affiliate);
        // send GNIM
        _sendGnim(receiver, _usdToGnim(usdAmount6));
        emit Bought(msg.sender, token, usdAmount6, _usdToGnim(usdAmount6), affiliate, usdAmount6 * COMMISSION_BPS / BPS_DENOM);
    }

    receive() external payable {}
}
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": false,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "metadata": {
      "useLiteralContent": true
    }
  }
}}

Tags:
Factory, Oracle|addr:0x8e0583cd4bf507e5abe6a53a9e7adebe6718741d|verified:true|block:23378976|tx:0x1c11797794364ef34350a06b54015e1ca48bff931b1d6ebe8192c9d3dbe8fd53|first_check:1758105065

Submitted on: 2025-09-17 12:31:07

Comments

Log in to comment.

No comments yet.