Robinhood MainnetChain ID: 4663
Contract detail

GammaRewards

GammaRewards is a verified Robinhood Chain smart contract at 0x80acb8de…7dc0b774 with 0 indexed transactions.

Address type
Smart contract
Transactions
0
History coverage
No indexed activity found
Native balance
0 ETH
Balance observed at
Block #35598242
Emitted events
2
Verification
Verified
Compiler
v0.8.26+commit.8a97fa7a
Verification match
partial
Creation transaction
0xd380b48b…d47720d6
Created at block
#28433869
Language
solidity
EVM version
cancun
License
none
Compiler optimization
Enabled · 200 runs
Verified contract

Contract source code

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

/*
    Gamma — Rewards · diamond-hands holder yield (V4)

    The token launchpad on Robinhood Chain.
    Holders earn real stock · liquidity locked for life · no owner keys.

    Web   https://gammahood.xyz
    X     https://x.com/gammahood

    Deployed immutably on Robinhood Chain (chainId 4663).
*/

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

/**
 * @title GammaRewards
 * @notice Cumulative Merkle distributor for holder stock-dividends.
 *
 *         The indexer computes each holder's diamond-hands weight (balance x holding-duration)
 *         off-chain and publishes a Merkle root of CUMULATIVE amounts. Because amounts are
 *         cumulative, a holder claims ONCE and receives everything owed to date — no per-cycle
 *         claiming. A keeper can call `claim` on anyone's behalf (it always pays the account,
 *         never the caller), so rewards can be pushed automatically while holders keep the option
 *         to claim themselves in one click.
 */
contract GammaRewards is Ownable2Step {
    using SafeERC20 for IERC20;

    struct Distribution {
        bytes32 root;
        uint64 updatedAt;
        uint64 epoch;
    }

    /// @notice One cumulative root per reward token (the stock, e.g. AAPL).
    mapping(address rewardToken => Distribution) public distributions;
    /// @notice Cumulative amount already claimed per (rewardToken, account).
    mapping(address rewardToken => mapping(address account => uint256)) public claimed;

    /// @notice The indexer/keeper allowed to publish roots.
    address public updater;

    event UpdaterSet(address indexed updater);
    event RootUpdated(address indexed rewardToken, bytes32 root, uint64 epoch);
    event Claimed(address indexed rewardToken, address indexed account, uint256 amount);

    error NotUpdater();
    error InvalidProof();
    error NothingToClaim();

    constructor(address owner_, address updater_) Ownable(owner_) {
        updater = updater_;
        emit UpdaterSet(updater_);
    }

    function setUpdater(address u) external onlyOwner {
        updater = u;
        emit UpdaterSet(u);
    }

    function updateRoot(address rewardToken, bytes32 root) external {
        if (msg.sender != updater) revert NotUpdater();
        Distribution storage d = distributions[rewardToken];
        d.root = root;
        d.updatedAt = uint64(block.timestamp);
        d.epoch += 1;
        emit RootUpdated(rewardToken, root, d.epoch);
    }

    /// @notice Amount currently claimable by `account` for `rewardToken`, given its cumulative
    ///         entry in the latest root. Used by the frontend to show a single number.
    function claimable(address rewardToken, address account, uint256 cumulativeAmount)
        external
        view
        returns (uint256)
    {
        uint256 already = claimed[rewardToken][account];
        return cumulativeAmount > already ? cumulativeAmount - already : 0;
    }

    /**
     * @notice Claim everything owed. Permissionless: anyone (a keeper) can trigger it, funds
     *         always go to `account`. Leaf = keccak256(bytes.concat(keccak256(abi.encode(
     *         account, cumulativeAmount)))) — the OpenZeppelin StandardMerkleTree encoding.
     */
    function claim(address rewardToken, address account, uint256 cumulativeAmount, bytes32[] calldata proof)
        external
    {
        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(account, cumulativeAmount))));
        if (!MerkleProof.verify(proof, distributions[rewardToken].root, leaf)) revert InvalidProof();

        uint256 already = claimed[rewardToken][account];
        if (cumulativeAmount <= already) revert NothingToClaim();

        uint256 amount = cumulativeAmount - already;
        claimed[rewardToken][account] = cumulativeAmount;
        IERC20(rewardToken).safeTransfer(account, amount);
        emit Claimed(rewardToken, account, amount);
    }
}