Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
},
"@openzeppelin/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
"
},
"@openzeppelin/contracts/utils/Pausable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
"
},
"@openzeppelin/contracts/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
"
},
"contracts/VANcoinStaking.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
/**
* @title VANcoinStaking
* @dev Simplified staking contract for VANcoin with PFTL wallet integration (V2)
*
* Features:
* - Fixed 3-month staking period
* - 1.0x point multiplier (simplified)
* - PFTL wallet self-registration for airdrops
* - Enhanced analytics for staker summaries
* - PFTL address required for withdrawal
*/
contract VANcoinStaking is Ownable, ReentrancyGuard, Pausable {
IERC20 public immutable vanToken;
// Constants - Simplified for V2
uint256 public constant STAKE_DURATION_MONTHS = 3;
uint256 public constant SECONDS_PER_DAY = 86400;
uint256 public constant DAYS_PER_MONTH = 30; // Simplified to 30 days per month
uint256 public constant TOTAL_STAKE_DURATION = STAKE_DURATION_MONTHS * DAYS_PER_MONTH * SECONDS_PER_DAY;
// Fixed 1.0x multiplier (no scaling)
uint256 public constant POINTS_MULTIPLIER = 1000; // 1.0x represented as 1000 for precision
uint256 public constant MULTIPLIER_PRECISION = 1000;
// Staking position structure - simplified
struct StakePosition {
uint256 amount; // Amount of tokens staked
uint256 startTime; // When stake was created
uint256 endTime; // When stake can be withdrawn (always startTime + TOTAL_STAKE_DURATION)
uint256 lastPointUpdate; // Last time points were calculated
uint256 accruedPoints; // Total points accrued so far
bool withdrawn; // Whether stake has been withdrawn
}
// PFTL wallet integration
mapping(address => string) public ethToPftl; // ETH address -> PFTL address
mapping(string => address) public pftlToEth; // PFTL address -> ETH address
mapping(address => bool) public hasPftlAddress; // Quick check if user has PFTL set
// User data
mapping(address => StakePosition[]) public userPositions;
mapping(address => uint256) public totalUserPoints;
mapping(address => uint256) public totalUserStaked; // New: track total staked per user
// Enhanced tracking for analytics
address[] public allStakers; // List of all stakers
mapping(address => bool) public isStaker; // Quick check if address is a staker
mapping(address => uint256) public stakerIndex; // Index in allStakers array
// Global stats
uint256 public totalStaked;
uint256 public totalPositions;
uint256 public totalPointsIssued;
uint256 public totalStakers;
// Events
event Staked(
address indexed user,
uint256 indexed positionId,
uint256 amount,
uint256 endTime
);
event Withdrawn(
address indexed user,
uint256 indexed positionId,
uint256 amount,
uint256 finalPoints
);
event PointsUpdated(
address indexed user,
uint256 indexed positionId,
uint256 pointsEarned,
uint256 totalPoints
);
event PftlAddressSet(
address indexed ethAddress,
string indexed pftlAddress
);
// Staker summary struct for analytics
struct StakerSummary {
address ethAddress;
string pftlAddress;
uint256 totalStaked;
uint256 totalPoints;
uint256 activePositions;
bool canWithdrawAny;
}
constructor(address _vanToken, address _initialOwner) Ownable(_initialOwner) {
require(_vanToken != address(0), "Invalid token address");
vanToken = IERC20(_vanToken);
}
/**
* @dev Validate PFTL address format (XRPL-like)
* @param pftlAddress The PFTL address to validate
* @return valid Whether the address is valid
*/
function isValidPftlAddress(string memory pftlAddress) public pure returns (bool valid) {
bytes memory addr = bytes(pftlAddress);
// Check length (XRPL addresses are typically 25-35 characters)
if (addr.length < 25 || addr.length > 35) {
return false;
}
// Check if starts with 'r' (like XRP addresses)
if (addr[0] != 0x72) { // 0x72 is 'r' in ASCII
return false;
}
// Basic character validation (alphanumeric, case-sensitive)
for (uint256 i = 1; i < addr.length; i++) {
bytes1 char = addr[i];
if (!(
(char >= 0x30 && char <= 0x39) || // 0-9
(char >= 0x41 && char <= 0x5A) || // A-Z
(char >= 0x61 && char <= 0x7A) // a-z
)) {
return false;
}
}
return true;
}
/**
* @dev Set PFTL wallet address for airdrop registration
* @param pftlAddress The PFTL wallet address (XRPL format)
*/
function setPftl(string calldata pftlAddress) external {
require(bytes(pftlAddress).length > 0, "PFTL address cannot be empty");
require(isValidPftlAddress(pftlAddress), "Invalid PFTL address format");
// If user already has a PFTL address, clear the old mapping
string memory oldPftl = ethToPftl[msg.sender];
if (bytes(oldPftl).length > 0) {
delete pftlToEth[oldPftl];
}
// Set new mappings (allowing duplicate PFTL addresses)
ethToPftl[msg.sender] = pftlAddress;
pftlToEth[pftlAddress] = msg.sender;
hasPftlAddress[msg.sender] = true;
emit PftlAddressSet(msg.sender, pftlAddress);
}
/**
* @dev Get PFTL address by ETH address
* @param ethAddress The Ethereum address
* @return pftlAddress The associated PFTL address
*/
function getPftlByEth(address ethAddress) external view returns (string memory pftlAddress) {
return ethToPftl[ethAddress];
}
/**
* @dev Get ETH address by PFTL address
* @param pftlAddress The PFTL address
* @return ethAddress The associated Ethereum address
*/
function getEthByPftl(string calldata pftlAddress) external view returns (address ethAddress) {
return pftlToEth[pftlAddress];
}
/**
* @dev Get all ETH <> PFTL associations in bulk
* @return ethAddresses Array of ETH addresses that have PFTL set
* @return pftlAddresses Array of corresponding PFTL addresses
*/
function getAllEthPftlAssociations() external view returns (
address[] memory ethAddresses,
string[] memory pftlAddresses
) {
// Count addresses with PFTL set
uint256 count = 0;
for (uint256 i = 0; i < allStakers.length; i++) {
if (hasPftlAddress[allStakers[i]]) {
count++;
}
}
// Create arrays with correct size
ethAddresses = new address[](count);
pftlAddresses = new string[](count);
// Fill arrays with associations
uint256 index = 0;
for (uint256 i = 0; i < allStakers.length; i++) {
address staker = allStakers[i];
if (hasPftlAddress[staker]) {
ethAddresses[index] = staker;
pftlAddresses[index] = ethToPftl[staker];
index++;
}
}
return (ethAddresses, pftlAddresses);
}
/**
* @dev Get all ETH addresses that have PFTL set
* @return addresses Array of ETH addresses with PFTL associations
*/
function getAllEthAddressesWithPftl() external view returns (address[] memory addresses) {
// Count addresses with PFTL set
uint256 count = 0;
for (uint256 i = 0; i < allStakers.length; i++) {
if (hasPftlAddress[allStakers[i]]) {
count++;
}
}
// Create array with correct size
addresses = new address[](count);
// Fill array
uint256 index = 0;
for (uint256 i = 0; i < allStakers.length; i++) {
address staker = allStakers[i];
if (hasPftlAddress[staker]) {
addresses[index] = staker;
index++;
}
}
return addresses;
}
/**
* @dev Calculate points earned for a position up to current time
* @param user User address
* @param positionId Position index
* @return points Points earned since last update
*/
function calculatePendingPoints(address user, uint256 positionId)
public
view
returns (uint256 points)
{
require(positionId < userPositions[user].length, "Invalid position");
StakePosition storage position = userPositions[user][positionId];
if (position.withdrawn) {
return 0;
}
uint256 currentTime = block.timestamp;
uint256 lastUpdate = position.lastPointUpdate;
// Don't accrue points beyond the end time
if (currentTime > position.endTime) {
currentTime = position.endTime;
}
if (currentTime <= lastUpdate) {
return 0;
}
uint256 daysPassed = (currentTime - lastUpdate) / SECONDS_PER_DAY;
if (daysPassed == 0) {
return 0;
}
// Simplified: Points = days * amount * 1.0x multiplier
points = (daysPassed * position.amount * POINTS_MULTIPLIER) / MULTIPLIER_PRECISION;
return points;
}
/**
* @dev Update points for a specific position
* @param user User address
* @param positionId Position index
*/
function updatePositionPoints(address user, uint256 positionId) public {
require(positionId < userPositions[user].length, "Invalid position");
uint256 pendingPoints = calculatePendingPoints(user, positionId);
if (pendingPoints > 0) {
StakePosition storage position = userPositions[user][positionId];
position.accruedPoints += pendingPoints;
position.lastPointUpdate = block.timestamp;
// Don't update beyond end time
if (position.lastPointUpdate > position.endTime) {
position.lastPointUpdate = position.endTime;
}
totalUserPoints[user] += pendingPoints;
totalPointsIssued += pendingPoints;
emit PointsUpdated(user, positionId, pendingPoints, position.accruedPoints);
}
}
/**
* @dev Update points for all user positions
* @param user User address
*/
function updateAllUserPoints(address user) public {
uint256 positionCount = userPositions[user].length;
for (uint256 i = 0; i < positionCount; i++) {
updatePositionPoints(user, i);
}
}
/**
* @dev Stake VANcoin tokens for 3 months
* @param amount Amount of tokens to stake
*/
function stake(uint256 amount)
external
nonReentrant
whenNotPaused
{
require(amount > 0, "Amount must be greater than 0");
// Transfer tokens from user
require(vanToken.transferFrom(msg.sender, address(this), amount), "Transfer failed");
// Calculate end time (fixed 3 months)
uint256 endTime = block.timestamp + TOTAL_STAKE_DURATION;
// Create new position
StakePosition memory newPosition = StakePosition({
amount: amount,
startTime: block.timestamp,
endTime: endTime,
lastPointUpdate: block.timestamp,
accruedPoints: 0,
withdrawn: false
});
userPositions[msg.sender].push(newPosition);
uint256 positionId = userPositions[msg.sender].length - 1;
// Update user tracking
totalUserStaked[msg.sender] += amount;
// Add to stakers list if first time staking
if (!isStaker[msg.sender]) {
allStakers.push(msg.sender);
stakerIndex[msg.sender] = allStakers.length - 1;
isStaker[msg.sender] = true;
totalStakers++;
}
// Update global stats
totalStaked += amount;
totalPositions++;
emit Staked(msg.sender, positionId, amount, endTime);
}
/**
* @dev Withdraw staked tokens after 3 months (requires PFTL address)
* @param positionId Position index to withdraw
*/
function withdraw(uint256 positionId)
external
nonReentrant
whenNotPaused
{
require(positionId < userPositions[msg.sender].length, "Invalid position");
require(hasPftlAddress[msg.sender], "Must set PFTL address before withdrawal");
StakePosition storage position = userPositions[msg.sender][positionId];
require(!position.withdrawn, "Position already withdrawn");
require(block.timestamp >= position.endTime, "Staking period not ended");
// Update points one final time
updatePositionPoints(msg.sender, positionId);
uint256 amount = position.amount;
uint256 finalPoints = position.accruedPoints;
// Mark as withdrawn
position.withdrawn = true;
// Update user tracking
totalUserStaked[msg.sender] -= amount;
// Update global stats
totalStaked -= amount;
// Transfer tokens back to user
require(vanToken.transfer(msg.sender, amount), "Transfer failed");
emit Withdrawn(msg.sender, positionId, amount, finalPoints);
}
/**
* @dev Get user's position count
* @param user User address
* @return count Number of positions
*/
function getUserPositionCount(address user) external view returns (uint256) {
return userPositions[user].length;
}
/**
* @dev Get detailed position information
* @param user User address
* @param positionId Position index
* @return position StakePosition struct
* @return pendingPoints Points pending since last update
* @return canWithdraw Whether position can be withdrawn
*/
function getPositionDetails(address user, uint256 positionId)
external
view
returns (
StakePosition memory position,
uint256 pendingPoints,
bool canWithdraw
)
{
require(positionId < userPositions[user].length, "Invalid position");
position = userPositions[user][positionId];
pendingPoints = calculatePendingPoints(user, positionId);
canWithdraw = !position.withdrawn &&
block.timestamp >= position.endTime &&
hasPftlAddress[user];
}
/**
* @dev Get user's total points (including pending)
* @param user User address
* @return totalPoints Total points earned
*/
function getUserTotalPoints(address user) external view returns (uint256 totalPoints) {
totalPoints = totalUserPoints[user];
// Add pending points from all positions
uint256 positionCount = userPositions[user].length;
for (uint256 i = 0; i < positionCount; i++) {
totalPoints += calculatePendingPoints(user, i);
}
}
/**
* @dev Get total number of stakers
* @return count Number of unique stakers
*/
function getStakerCount() external view returns (uint256 count) {
return totalStakers;
}
/**
* @dev Get all stakers with their totals (for analytics)
* @return summaries Array of StakerSummary structs
*/
function getAllStakersWithTotals() external view returns (StakerSummary[] memory summaries) {
summaries = new StakerSummary[](allStakers.length);
for (uint256 i = 0; i < allStakers.length; i++) {
address staker = allStakers[i];
uint256 positionCount = userPositions[staker].length;
uint256 activePositions = 0;
uint256 totalPoints = totalUserPoints[staker];
bool canWithdrawAny = false;
// Count active positions and calculate total points including pending
for (uint256 j = 0; j < positionCount; j++) {
StakePosition storage position = userPositions[staker][j];
if (!position.withdrawn) {
activePositions++;
totalPoints += calculatePendingPoints(staker, j);
// Check if any position can be withdrawn
if (block.timestamp >= position.endTime && hasPftlAddress[staker]) {
canWithdrawAny = true;
}
}
}
summaries[i] = StakerSummary({
ethAddress: staker,
pftlAddress: ethToPftl[staker],
totalStaked: totalUserStaked[staker],
totalPoints: totalPoints,
activePositions: activePositions,
canWithdrawAny: canWithdrawAny
});
}
return summaries;
}
/**
* @dev Get contract statistics
* @return stats Array containing [totalStaked, totalPositions, totalPointsIssued, totalStakers]
*/
function getContractStats() external view returns (uint256[4] memory stats) {
stats[0] = totalStaked;
stats[1] = totalPositions;
stats[2] = totalPointsIssued;
stats[3] = totalStakers;
}
// Admin functions
/**
* @dev Pause the contract (emergency only)
*/
function pause() external onlyOwner {
_pause();
}
/**
* @dev Unpause the contract
*/
function unpause() external onlyOwner {
_unpause();
}
/**
* @dev Emergency function to recover accidentally sent tokens (not VAN tokens)
* @param token Token address
* @param amount Amount to recover
*/
function recoverToken(address token, uint256 amount) external onlyOwner {
require(token != address(vanToken), "Cannot recover staked VAN tokens");
IERC20(token).transfer(owner(), amount);
}
/**
* @dev Emergency function to recover accidentally sent ETH
*/
function recoverETH() external onlyOwner {
payable(owner()).transfer(address(this).balance);
}
}
"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}
}}
Submitted on: 2025-11-07 11:19:21
Comments
Log in to comment.
No comments yet.