CreatorFeeShare

0x10e88fc30e5abbb1d6dc4a648e1679dc2c826e43

Verification
Unverified
v0.8.26+commit.8a97fa7a
Type
Contract
4,329 bytes
ABI entries
36
17 read · 5 write
License
none

Contract information

Address
0x10e88fc30e5abbb1d6dc4a648e1679dc2c826e43
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x6cbd55a063…fa8a879c34

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (17)

BPS_DENOMINATOR()uint256
CREATOR_BPS()uint256
claimBalance()uint256
creator()address
creatorClaimable()uint256
excessWeth()uint256
factory()address
hook()address
isSolvent()bool
outstanding()uint256
poolManager()address
protocolClaimable()uint256
totalAssets()uint256
totalCreatorClaimed()uint256
totalDeposited()uint256
totalProtocolClaimed()uint256
weth()address

Events (3)

CreatorClaimedDepositedProtocolClaimed

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "factory_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "creator_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "hook_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "weth_",
        "type": "address"
      },
      {
        "internalType": "contract IPoolManager",
        "name": "poolManager_",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "Insolvent",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NothingToClaim",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnauthorizedDepositor",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnauthorizedUnlockCallback",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnexpectedClaimRedemption",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnsupportedTransferBehavior",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddressNotAllowed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroValueNotAllowed",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "caller",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "creator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "CreatorClaimed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "creatorAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "protocolAmount",
        "type": "uint256"
      }
    ],
    "name": "Deposited",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "caller",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "ProtocolClaimed",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "BPS_DENOMINATOR",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "CREATOR_BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "claim",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "creatorAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "protocolAmount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "claimBalance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "claimCreator",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "claimProtocol",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creator",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "creatorClaimable",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "depositClaim",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "excessWeth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "factory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "hook",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "isSolvent",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "outstanding",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "poolManager",
    "outputs": [
      {
        "internalType": "contract IPoolManager",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "protocolClaimable",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalAssets",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalCreatorClaimed",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalDeposited",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalProtocolClaimed",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "unlockCallback",
    "outputs": [
      {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";

import {IFactoryFeeSource} from "./interfaces/IPumpV4.sol";

/**
 * @title CreatorFeeShare
 * @notice Immutable per-token PoolManager-claim custody with a fixed 50/50 split.
 * @dev The hook is the only depositor. Anyone may trigger claims, but creator
 *      funds always go to the constructor-fixed creator and protocol funds
 *      always go to the factory's current feeCollector.
 */
contract CreatorFeeShare is ReentrancyGuard, IUnlockCallback {
    using SafeERC20 for IERC20;

    uint256 public constant CREATOR_BPS = 5_000;
    uint256 public constant BPS_DENOMINATOR = 10_000;

    address public immutable factory;
    address public immutable creator;
    address public immutable hook;
    address public immutable weth;
    IPoolManager public immutable poolManager;

    uint256 public creatorClaimable;
    uint256 public protocolClaimable;
    uint256 public totalDeposited;
    uint256 public totalCreatorClaimed;
    uint256 public totalProtocolClaimed;
    uint256 private redeemingClaimAmount;

    event Deposited(uint256 amount, uint256 creatorAmount, uint256 protocolAmount);
    event CreatorClaimed(address indexed caller, address indexed creator, uint256 amount);
    event ProtocolClaimed(address indexed caller, address indexed recipient, uint256 amount);

    error ZeroAddressNotAllowed();
    error ZeroValueNotAllowed();
    error UnauthorizedDepositor();
    error UnsupportedTransferBehavior();
    error Insolvent();
    error NothingToClaim();
    error UnauthorizedUnlockCallback();
    error UnexpectedClaimRedemption();

    constructor(address factory_, address creator_, address hook_, address weth_, IPoolManager poolManager_) {
        if (
            factory_ == address(0) || creator_ == address(0) || hook_ == address(0) || weth_ == address(0)
                || address(poolManager_) == address(0)
        ) {
            revert ZeroAddressNotAllowed();
        }
        factory = factory_;
        creator = creator_;
        hook = hook_;
        weth = weth_;
        poolManager = poolManager_;
    }

    function depositClaim(uint256 amount) external nonReentrant {
        if (msg.sender != hook) revert UnauthorizedDepositor();
        if (amount == 0) revert ZeroValueNotAllowed();

        _recordDeposit(amount);
    }

    function unlockCallback(bytes calldata data) external returns (bytes memory) {
        if (msg.sender != address(poolManager)) revert UnauthorizedUnlockCallback();
        uint256 amount = abi.decode(data, (uint256));
        if (amount == 0 || amount != redeemingClaimAmount) revert UnexpectedClaimRedemption();

        redeemingClaimAmount = 0;
        uint256 claimId = uint256(uint160(weth));
        poolManager.burn(address(this), claimId, amount);
        poolManager.take(Currency.wrap(weth), address(this), amount);
        return abi.encode(amount);
    }

    function totalAssets() public view returns (uint256) {
        return IERC20(weth).balanceOf(address(this)) + claimBalance();
    }

    function claimBalance() public view returns (uint256) {
        return poolManager.balanceOf(address(this), uint256(uint160(weth)));
    }

    function _recordDeposit(uint256 amount) private {
        uint256 creatorAmount = amount / 2;
        uint256 protocolAmount = amount - creatorAmount;
        creatorClaimable += creatorAmount;
        protocolClaimable += protocolAmount;
        totalDeposited += amount;
        _assertClaimBacked();

        emit Deposited(amount, creatorAmount, protocolAmount);
    }

    function claimCreator() external nonReentrant returns (uint256 amount) {
        amount = _claimCreator(msg.sender);
    }

    function claimProtocol() external nonReentrant returns (uint256 amount) {
        amount = _claimProtocol(msg.sender);
    }

    function claim() external nonReentrant returns (uint256 creatorAmount, uint256 protocolAmount) {
        creatorAmount = creatorClaimable;
        protocolAmount = protocolClaimable;
        if (creatorAmount == 0 && protocolAmount == 0) revert NothingToClaim();

        address recipient = IFactoryFeeSource(factory).feeCollector();
        if (protocolAmount != 0 && recipient == address(0)) revert ZeroAddressNotAllowed();

        creatorClaimable = 0;
        protocolClaimable = 0;
        totalCreatorClaimed += creatorAmount;
        totalProtocolClaimed += protocolAmount;

        _redeemClaims(creatorAmount + protocolAmount);
        IERC20 token = IERC20(weth);
        if (creatorAmount != 0) {
            token.safeTransfer(creator, creatorAmount);
            emit CreatorClaimed(msg.sender, creator, creatorAmount);
        }
        if (protocolAmount != 0) {
            token.safeTransfer(recipient, protocolAmount);
            emit ProtocolClaimed(msg.sender, recipient, protocolAmount);
        }
        _assertClaimBacked();
    }

    function outstanding() public view returns (uint256) {
        return creatorClaimable + protocolClaimable;
    }

    function isSolvent() external view returns (bool) {
        return claimBalance() >= outstanding();
    }

    function excessWeth() external view returns (uint256) {
        return IERC20(weth).balanceOf(address(this));
    }

    function _claimCreator(address caller) private returns (uint256 amount) {
        amount = creatorClaimable;
        if (amount == 0) revert NothingToClaim();
        creatorClaimable = 0;
        totalCreatorClaimed += amount;
        _payout(creator, amount);
        _assertClaimBacked();
        emit CreatorClaimed(caller, creator, amount);
    }

    function _claimProtocol(address caller) private returns (uint256 amount) {
        amount = protocolClaimable;
        if (amount == 0) revert NothingToClaim();
        address recipient = IFactoryFeeSource(factory).feeCollector();
        if (recipient == address(0)) revert ZeroAddressNotAllowed();

        protocolClaimable = 0;
        totalProtocolClaimed += amount;
        _payout(recipient, amount);
        _assertClaimBacked();
        emit ProtocolClaimed(caller, recipient, amount);
    }

    function _payout(address recipient, uint256 amount) private {
        _redeemClaims(amount);
        IERC20(weth).safeTransfer(recipient, amount);
    }

    function _redeemClaims(uint256 amount) private {
        if (redeemingClaimAmount != 0) revert UnexpectedClaimRedemption();
        IERC20 token = IERC20(weth);
        uint256 beforeBalance = token.balanceOf(address(this));
        redeemingClaimAmount = amount;
        bytes memory result = poolManager.unlock(abi.encode(amount));
        if (redeemingClaimAmount != 0 || abi.decode(result, (uint256)) != amount) {
            revert UnexpectedClaimRedemption();
        }
        if (token.balanceOf(address(this)) - beforeBalance != amount) revert UnsupportedTransferBehavior();
    }

    function _assertClaimBacked() private view {
        if (claimBalance() < outstanding()) revert Insolvent();
    }
}
Chain explorer6006msChain node84ms