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);
}
}