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
}
}
}}
Submitted on: 2025-09-17 12:31:07
Comments
Log in to comment.
No comments yet.