VenaMiningV2
0x1dda64bd76165400ad929d4d94e0d8285288d37b
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
6,440 bytes
ABI entries
45
22 read · 9 write
License
none
Contract information
- Address
- 0x1dda64bd76165400ad929d4d94e0d8285288d37b
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0x3b8C25CA63…369d25fF4c
- Creation tx
- 0xbd325abebc…5597797295
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (22)
ACC_PRECISION() → uint256
accRewardPerPower() → uint256
fundVestPeriod() → uint256
fundingOperator() → address
getUserInfo(address) → uint256, uint256[], uint256
isActive() → bool
lastRewardTime() → uint256
minPoolToStart() → uint256
onERC721Received(address, address, uint256, bytes) → bytes4
owner() → address
pendingRewards(address) → uint256
pickaxe() → address
poolBalance() → uint256
rewardPerSecond() → uint256
stakedBy(uint256) → address
stakedTokenIds(address) → uint256[]
startTime() → uint256
started() → bool
totalClaimed() → uint256
totalFunded() → uint256
totalPower() → uint256
venaToken() → address
Events (9)
FundVestPeriodSetFundedFundingOperatorSetMinPoolToStartSetOwnershipTransferredRewardsClaimedStakedStartedUnstaked
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_venaToken",
"type": "address"
},
{
"internalType": "address",
"name": "_pickaxe",
"type": "address"
},
{
"internalType": "address",
"name": "initialOwner",
"type": "address"
},
{
"internalType": "address",
"name": "_fundingOperator",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnableInvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "period",
"type": "uint256"
}
],
"name": "FundVestPeriodSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newRewardPerSecond",
"type": "uint256"
}
],
"name": "Funded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "FundingOperatorSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "minAmount",
"type": "uint256"
}
],
"name": "MinPoolToStartSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "RewardsClaimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "power",
"type": "uint256"
}
],
"name": "Staked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "startTime",
"type": "uint256"
}
],
"name": "Started",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Unstaked",
"type": "event"
},
{
"inputs": [],
"name": "ACC_PRECISION",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "accRewardPerPower",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "claimRewards",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "fundRewards",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "fundVestPeriod",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "fundingOperator",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "getUserInfo",
"outputs": [
{
"internalType": "uint256",
"name": "power",
"type": "uint256"
},
{
"internalType": "uint256[]",
"name": "stakedIds",
"type": "uint256[]"
},
{
"internalType": "uint256",
"name": "pending",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "isActive",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lastRewardTime",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "minPoolToStart",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "onERC721Received",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "pendingRewards",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pickaxe",
"outputs": [
{
"internalType": "contract PickaxeNFT",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolBalance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "rewardPerSecond",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "period",
"type": "uint256"
}
],
"name": "setFundVestPeriod",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "setFundingOperator",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "minAmount",
"type": "uint256"
}
],
"name": "setMinPoolToStart",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "stakeNFT",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "stakedBy",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "stakedTokenIds",
"outputs": [
{
"internalType": "uint256[]",
"name": "",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "startTime",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "started",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalClaimed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalFunded",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalPower",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "unstakeNFT",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "venaToken",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./PickaxeNFT.sol";
/**
* @title VenaMiningV2 (Robinhood Chain)
* @notice Buyback-fed staking pool — no fixed 250M upfront allocation.
* Treasury / keeper calls `fundRewards()` after each ETH→VENA buyback.
* New deposits vest into `rewardPerSecond` over `fundVestPeriod`.
*/
contract VenaMiningV2 is IERC721Receiver, Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
uint256 public constant ACC_PRECISION = 1e18;
IERC20 public immutable venaToken;
PickaxeNFT public immutable pickaxe;
/// @notice Address allowed to top up the pool (treasury keeper wallet).
address public fundingOperator;
/// @notice Minimum pool balance before staking opens.
uint256 public minPoolToStart;
/// @notice Each buyback vests into emission over this window.
uint256 public fundVestPeriod;
bool public started;
uint256 public startTime;
uint256 public rewardPerSecond;
uint256 public lastRewardTime;
uint256 public accRewardPerPower;
uint256 public totalPower;
uint256 public totalFunded;
uint256 public totalClaimed;
struct UserInfo {
uint256 totalPower;
uint256 rewardDebt;
uint256 pendingHarvest;
uint256[] stakedIds;
}
mapping(address => UserInfo) private _users;
mapping(uint256 => address) public stakedBy;
event Started(uint256 startTime);
event Funded(address indexed from, uint256 amount, uint256 newRewardPerSecond);
event Staked(address indexed user, uint256 indexed tokenId, uint256 power);
event Unstaked(address indexed user, uint256 indexed tokenId);
event RewardsClaimed(address indexed user, uint256 amount);
event FundingOperatorSet(address indexed operator);
event FundVestPeriodSet(uint256 period);
event MinPoolToStartSet(uint256 minAmount);
constructor(
address _venaToken,
address _pickaxe,
address initialOwner,
address _fundingOperator
) Ownable(initialOwner) {
require(_venaToken != address(0) && _pickaxe != address(0), "Zero address");
venaToken = IERC20(_venaToken);
pickaxe = PickaxeNFT(_pickaxe);
fundingOperator = _fundingOperator;
minPoolToStart = 1_000 * 1e18;
fundVestPeriod = 7 days;
}
function setFundingOperator(address operator) external onlyOwner {
require(operator != address(0), "Zero address");
fundingOperator = operator;
emit FundingOperatorSet(operator);
}
function setFundVestPeriod(uint256 period) external onlyOwner {
require(period >= 1 hours, "Period too short");
fundVestPeriod = period;
emit FundVestPeriodSet(period);
}
function setMinPoolToStart(uint256 minAmount) external onlyOwner {
minPoolToStart = minAmount;
emit MinPoolToStartSet(minAmount);
}
/// @notice Top up the pool after a treasury buyback. Opens staking on first qualifying deposit.
function fundRewards(uint256 amount) external nonReentrant {
require(
msg.sender == fundingOperator || msg.sender == owner(),
"Not funding role"
);
require(amount > 0, "Zero amount");
venaToken.safeTransferFrom(msg.sender, address(this), amount);
totalFunded += amount;
if (!started) {
require(
venaToken.balanceOf(address(this)) >= minPoolToStart,
"Below min pool"
);
started = true;
startTime = block.timestamp;
lastRewardTime = block.timestamp;
emit Started(block.timestamp);
} else {
_updatePool();
}
rewardPerSecond += amount / fundVestPeriod;
emit Funded(msg.sender, amount, rewardPerSecond);
}
function stakeNFT(uint256 tokenId) external nonReentrant {
require(started, "Mining not started");
require(pickaxe.ownerOf(tokenId) == msg.sender, "Not owner");
require(stakedBy[tokenId] == address(0), "Already staked");
_updatePool();
UserInfo storage user = _users[msg.sender];
_harvest(user);
uint256 power = pickaxe.getMiningPower(tokenId);
user.totalPower += power;
user.rewardDebt = user.totalPower * accRewardPerPower / ACC_PRECISION;
user.stakedIds.push(tokenId);
totalPower += power;
stakedBy[tokenId] = msg.sender;
pickaxe.safeTransferFrom(msg.sender, address(this), tokenId);
emit Staked(msg.sender, tokenId, power);
}
function unstakeNFT(uint256 tokenId) external nonReentrant {
require(stakedBy[tokenId] == msg.sender, "Not staked");
_updatePool();
UserInfo storage user = _users[msg.sender];
_harvest(user);
uint256 power = pickaxe.getMiningPower(tokenId);
user.totalPower -= power;
user.rewardDebt = user.totalPower * accRewardPerPower / ACC_PRECISION;
_removeStakedId(user, tokenId);
totalPower -= power;
stakedBy[tokenId] = address(0);
pickaxe.safeTransferFrom(address(this), msg.sender, tokenId);
emit Unstaked(msg.sender, tokenId);
}
function claimRewards() external nonReentrant {
require(started, "Mining not started");
_updatePool();
UserInfo storage user = _users[msg.sender];
_harvest(user);
uint256 pending = user.pendingHarvest;
require(pending > 0, "Nothing to claim");
user.pendingHarvest = 0;
totalClaimed += pending;
venaToken.safeTransfer(msg.sender, pending);
emit RewardsClaimed(msg.sender, pending);
}
/// @dev Frontend compatibility alias.
function isActive() external view returns (bool) {
return started;
}
function pendingRewards(address user) public view returns (uint256) {
UserInfo storage u = _users[user];
if (!started || u.totalPower == 0) return u.pendingHarvest;
uint256 acc = accRewardPerPower;
if (block.timestamp > lastRewardTime && totalPower > 0) {
uint256 rate = _effectiveRewardPerSecond();
uint256 elapsed = block.timestamp - lastRewardTime;
acc += elapsed * rate * ACC_PRECISION / totalPower;
}
uint256 accumulated = u.totalPower * acc / ACC_PRECISION;
return u.pendingHarvest + accumulated - u.rewardDebt;
}
function stakedTokenIds(address user) external view returns (uint256[] memory) {
return _users[user].stakedIds;
}
/// @dev Frontend compatibility — matches legacy `getUserInfo` shape.
function getUserInfo(address account)
external
view
returns (uint256 power, uint256[] memory stakedIds, uint256 pending)
{
UserInfo storage u = _users[account];
return (u.totalPower, u.stakedIds, pendingRewards(account));
}
function poolBalance() external view returns (uint256) {
return venaToken.balanceOf(address(this));
}
function onERC721Received(address, address, uint256, bytes calldata)
external
pure
returns (bytes4)
{
return IERC721Receiver.onERC721Received.selector;
}
function _effectiveRewardPerSecond() internal view returns (uint256) {
uint256 bal = venaToken.balanceOf(address(this));
if (bal == 0) return 0;
uint256 maxRate = bal / 1 days;
return rewardPerSecond > maxRate ? maxRate : rewardPerSecond;
}
function _updatePool() internal {
if (!started || block.timestamp <= lastRewardTime || totalPower == 0) {
return;
}
uint256 rate = _effectiveRewardPerSecond();
if (rate == 0) return;
uint256 elapsed = block.timestamp - lastRewardTime;
accRewardPerPower += elapsed * rate * ACC_PRECISION / totalPower;
lastRewardTime = block.timestamp;
}
function _harvest(UserInfo storage user) internal {
if (user.totalPower == 0) return;
uint256 accumulated = user.totalPower * accRewardPerPower / ACC_PRECISION;
uint256 pending = accumulated - user.rewardDebt;
if (pending > 0) user.pendingHarvest += pending;
user.rewardDebt = accumulated;
}
function _removeStakedId(UserInfo storage user, uint256 tokenId) internal {
uint256 len = user.stakedIds.length;
for (uint256 i; i < len; ++i) {
if (user.stakedIds[i] == tokenId) {
user.stakedIds[i] = user.stakedIds[len - 1];
user.stakedIds.pop();
return;
}
}
}
}
Chain explorer1612msChain node72ms