Robinhood MainnetChain ID: 4663
Contract detail

Cc0StakingEscrow

Live Robinhood Chain account facts for 0xe4542b52…eeef35b2 with at least 14 observed transactions. The history total is updating as indexing continues.

Address type
Smart contract
Transactions
14+ observed
First indexed block
#904242
Latest indexed block
#972785
History coverage
Recent activity available; total is catching up
Indexed token transfers
2
ERC-20 transfers
9
Emitted events
3
Verification
Verified
Compiler
v0.8.28+commit.7893614a
Verification match
partial
Creation transaction
0x55728618…a971dc81
Created at block
#904242
Language
solidity
EVM version
cancun
License
none
Compiler optimization
Enabled · 200 runs
Related activity

Transactions

Verified contract

Contract source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {IClankerFeeLocker} from "./interfaces/IClankerFeeLocker.sol";

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable, Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/**
 * @title Cc0StakingEscrow
 * @author cc0.company
 * @notice Chain-local sink for the 15% protocol "staking" slice on chains with NO bridge path to Base
 *         yet (first use: Robinhood Chain, an Arbitrum-Orbit L2 — the OP Standard Bridge trick used by
 *         {Cc0EthStakingForwarder} on Ethereum L1 does not exist there).
 *
 * @dev cc0strategy staking ($cc0company) lives on BASE. On a chain where the 15% cannot be bridged to
 *      the Base {Cc0Staking} pool trustlessly at deploy time, this escrow is wired as the factory's
 *      enforced staking recipient and simply PARKS the accrued WETH until a bridge route exists:
 *
 *        1. Launch trading fees accrue in the chain's {ClankerFeeLocker} with this contract as the
 *           fee owner of the 15% slice.
 *        2. `sync()` (permissionless) pulls the accrued WETH out of the locker into the escrow, so the
 *           parked balance is plainly visible on-chain (`weth.balanceOf(escrow)`).
 *        3. Once a credible bridge adapter to the Base feeder exists, the owner wires it with
 *           `setTarget()` — ONE-TIME, must be a contract. It cannot be changed afterwards, which caps
 *           the owner's power at a single irreversible routing decision (mirroring the forwarder's
 *           immutable `baseFeeder`).
 *        4. `forward()` (permissionless once wired) pushes ALL held WETH to the target — the caller
 *           gains nothing, same trust model as {Cc0EthStakingForwarder.forward}.
 *
 *      The WETH is intentionally NOT unwrapped here (unlike the L1 forwarder): the future adapter picks
 *      the bridge's expected asset form. No ETH path, no receive() — WETH in, WETH out, one direction.
 *
 *      Owner powers, complete list: `setTarget` (once) and `rescueERC20` for tokens that are NOT the
 *      reward WETH. There is NO way for the owner to extract the parked WETH to an EOA of their choice
 *      directly; the only exit is the one-time target, which is public and event-logged.
 */
contract Cc0StakingEscrow is Ownable2Step, ReentrancyGuard {
    using SafeERC20 for IERC20;

    /// @notice This chain's ClankerFeeLocker; the escrow is the fee owner of the 15% slice.
    IClankerFeeLocker public immutable feeLocker;
    /// @notice Canonical WETH on this chain (the reward asset the locker accrues).
    IERC20 public immutable weth;

    /// @notice Future bridge adapter toward the Base staking pool. Zero until wired; settable ONCE.
    address public target;

    event Synced(uint256 amount);
    event TargetSet(address indexed target);
    event Forwarded(address indexed target, uint256 amount);
    event ExcessRescued(address indexed token, address indexed to, uint256 amount);

    error ZeroAddress();
    error TargetAlreadySet();
    error TargetNotAContract();
    error TargetNotSet();
    error NothingToForward();
    error CannotRescueWeth();

    constructor(address _feeLocker, address _weth, address _owner) Ownable(_owner) {
        if (_feeLocker == address(0) || _weth == address(0)) revert ZeroAddress();
        feeLocker = IClankerFeeLocker(_feeLocker);
        weth = IERC20(_weth);
    }

    /**
     * @notice Permissionless. Pull the accrued WETH (the 15% staking slice) out of the fee locker so
     *         it is parked HERE, visibly, instead of scattered as locker credit. Safe no-op when
     *         nothing is waiting.
     */
    function sync() external nonReentrant {
        uint256 available = feeLocker.availableFees(address(this), address(weth));
        if (available == 0) return;
        uint256 before = weth.balanceOf(address(this));
        feeLocker.claim(address(this), address(weth));
        emit Synced(weth.balanceOf(address(this)) - before);
    }

    /**
     * @notice Wire the bridge adapter that will carry the parked WETH toward the Base staking pool.
     *         ONE-TIME and irreversible — the single routing decision the owner is trusted with.
     *         Must be a deployed contract (an EOA target would let the owner pocket the rewards).
     */
    function setTarget(address _target) external onlyOwner {
        if (target != address(0)) revert TargetAlreadySet();
        if (_target == address(0)) revert ZeroAddress();
        if (_target.code.length == 0) revert TargetNotAContract();
        target = _target;
        emit TargetSet(_target);
    }

    /**
     * @notice Permissionless once the target is wired: drain the locker slice and push ALL held WETH
     *         to the target. The destination is fixed — the caller gains nothing.
     */
    function forward() external nonReentrant {
        address to = target;
        if (to == address(0)) revert TargetNotSet();
        if (feeLocker.availableFees(address(this), address(weth)) > 0) {
            feeLocker.claim(address(this), address(weth));
        }
        uint256 bal = weth.balanceOf(address(this));
        if (bal == 0) revert NothingToForward();
        weth.safeTransfer(to, bal);
        emit Forwarded(to, bal);
    }

    /**
     * @notice Recover an unrelated token sent here by mistake. WETH (the parked reward asset) is
     *         excluded — it can only ever leave via `forward()` to the one-time target.
     */
    function rescueERC20(address token, address to, uint256 amount) external onlyOwner {
        if (token == address(weth)) revert CannotRescueWeth();
        if (to == address(0)) revert ZeroAddress();
        IERC20(token).safeTransfer(to, amount);
        emit ExcessRescued(token, to, amount);
    }

    /// @notice WETH still waiting in the locker (not yet pulled here). Total parked = this + balanceOf.
    function availableInLocker() external view returns (uint256) {
        return feeLocker.availableFees(address(this), address(weth));
    }

    /// @notice Total 15%-slice WETH under this escrow's control (parked + still in the locker).
    function totalParked() external view returns (uint256) {
        return weth.balanceOf(address(this)) + feeLocker.availableFees(address(this), address(weth));
    }
}