// SPDX-License-Identifier: MIT pragma solidity 0.8.28; interface IERC20 { function balanceOf(address) external view returns (uint256); function transfer(address, uint256) external returns (bool); } interface IUniversalRouter { function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable; } /// @title APEX Revenue Split on Arc /// @notice Every USDC this contract receives (payments for houses, paid API calls, anything on Arc) can leave in exactly /// two ways, both fixed at deployment: half to the USDC faucet pot, half swapped for APEX on the Arc pool and sent /// to the drip vault. Nobody can withdraw. `receive()` only records, so a buyer's payment can never revert because /// of the pool. The operator only cranks `split()` and supplies the minimum APEX out (sandwich protection). contract ApexRevenueSplit { struct PoolKey { address currency0; address currency1; uint24 fee; int24 tickSpacing; address hooks; } struct ExactInputSingleParams { PoolKey poolKey; bool zeroForOne; uint128 amountIn; uint128 amountOutMinimum; bytes hookData; } address payable public immutable usdcFaucet; address public immutable vault; IERC20 public immutable apex; IUniversalRouter public immutable router; uint24 public immutable fee; int24 public immutable tickSpacing; address public operator; uint256 public receivedTotal; uint256 public toPotTotal; uint256 public toBuybackTotal; uint256 public apexToVaultTotal; uint256 public splits; event Received(address indexed from, uint256 amount); event Split(uint256 toPot, uint256 usdcSpent, uint256 apexToVault); event OperatorSet(address indexed operator); constructor(address payable usdcFaucet_, address vault_, address apex_, address router_, uint24 fee_, int24 tickSpacing_, address op) { require(usdcFaucet_ != address(0) && vault_ != address(0) && apex_ != address(0) && router_ != address(0) && op != address(0), "args"); usdcFaucet = usdcFaucet_; vault = vault_; apex = IERC20(apex_); router = IUniversalRouter(router_); fee = fee_; tickSpacing = tickSpacing_; operator = op; emit OperatorSet(op); } receive() external payable { receivedTotal += msg.value; emit Received(msg.sender, msg.value); } function setOperator(address op) external { require(msg.sender == operator && op != address(0), "operator"); operator = op; emit OperatorSet(op); } function pending() external view returns (uint256) { return address(this).balance; } function split(uint256 minApexOut) external { require(msg.sender == operator, "not operator"); uint256 bal = address(this).balance; require(bal > 0, "empty"); uint256 half = bal / 2; uint256 buy = bal - half; (bool ok, ) = usdcFaucet.call{value: half}(""); require(ok, "pot transfer"); bytes memory actions = abi.encodePacked(uint8(0x06), uint8(0x0c), uint8(0x0f)); // SWAP_EXACT_IN_SINGLE, SETTLE_ALL, TAKE_ALL bytes[] memory params = new bytes[](3); params[0] = abi.encode(ExactInputSingleParams(PoolKey(address(0), address(apex), fee, tickSpacing, address(0)), true, uint128(buy), uint128(minApexOut), bytes(""))); params[1] = abi.encode(address(0), buy); params[2] = abi.encode(address(apex), minApexOut); bytes[] memory inputs = new bytes[](1); inputs[0] = abi.encode(actions, params); router.execute{value: buy}(abi.encodePacked(uint8(0x10)), inputs, block.timestamp + 120); // V4_SWAP uint256 got = apex.balanceOf(address(this)); require(got >= minApexOut && got > 0, "apex out"); require(apex.transfer(vault, got), "vault transfer"); toPotTotal += half; toBuybackTotal += buy; apexToVaultTotal += got; splits += 1; emit Split(half, buy, got); } }