Source Code
Latest 1 from a total of 1 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Set Merkle Root | 42907457 | 40 days ago | IN | 0 ETH | 0.00000065 |
Cross-Chain Transactions
Loading...
Loading
Contract Name:
POHRewards
Compiler Version
v0.8.24+commit.e11b9ed9
Optimization Enabled:
Yes with 200 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// 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/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
/**
* @title POHRewards — Proof of Planet Token Distribution
* @notice Distributes POH tokens from the Community Rewards pool (50% of supply)
* using weekly merkle trees with 24hr timelock and reputation-based vesting.
*
* Flow:
* 1. Backend computes weekly rewards → builds merkle tree
* 2. Owner stages root via setMerkleRoot() — 24hr timelock begins
* 3. After 24hrs, owner calls activateMerkleRoot() to finalize
* 4. Users call claim() with merkle proof
* 5. Immediate portion → savings wallet (or msg.sender)
* 6. Vesting portion → locked in contract until unlock time
* 7. After vesting period, user calls releaseVested() to receive tokens
*
* Vesting tiers (calculated off-chain by backend):
* - New miners (<6mo reputation): 25% immediate / 75% vests 180 days
* - Veterans (6mo+ reputation): 75% immediate / 25% vests 30 days
*
* Funded by transferring Community Rewards tokens to this contract.
*/
contract POHRewards is Ownable {
using SafeERC20 for IERC20;
// ── Constants ────────────────────────────────────────────────────────
uint256 public constant TIMELOCK_DURATION = 24 hours;
// ── State ────────────────────────────────────────────────────────────
IERC20 public immutable token;
/// @notice Current epoch number (incremented each time a merkle root is activated)
uint256 public currentEpoch;
/// @notice Merkle root for each epoch
mapping(uint256 => bytes32) public merkleRoots;
/// @notice Whether an address has claimed for a given epoch
mapping(uint256 => mapping(address => bool)) public hasClaimed;
/// @notice Savings wallet (cold storage / Ledger) for each user
mapping(address => address) public savingsWallet;
/// @notice Total tokens distributed (immediate + released vesting)
uint256 public totalDistributed;
// ── Timelock ─────────────────────────────────────────────────────────
/// @notice Pending merkle root awaiting timelock expiry
bytes32 public pendingRoot;
/// @notice Timestamp when the pending root was staged
uint256 public pendingRootTimestamp;
// ── Vesting ──────────────────────────────────────────────────────────
struct VestingInfo {
uint256 amount;
uint256 unlocksAt;
bool released;
}
/// @notice Vesting entries per user per epoch
mapping(address => mapping(uint256 => VestingInfo)) public vesting;
/// @notice Total tokens currently locked in vesting across all users
uint256 public totalVesting;
// ── Events ───────────────────────────────────────────────────────────
event MerkleRootStaged(bytes32 root, uint256 activatesAt);
event MerkleRootActivated(uint256 indexed epoch, bytes32 root);
event MerkleRootCancelled(bytes32 root);
event RewardsClaimed(
uint256 indexed epoch,
address indexed claimant,
address indexed recipient,
uint256 immediateAmount,
uint256 vestingAmount,
uint256 vestingUnlocksAt
);
event VestingReleased(
uint256 indexed epoch,
address indexed user,
address indexed recipient,
uint256 amount
);
event SavingsWalletSet(address indexed user, address indexed wallet);
// ── Constructor ──────────────────────────────────────────────────────
constructor(address _token) Ownable(msg.sender) {
require(_token != address(0), "Rewards: token is zero");
token = IERC20(_token);
}
// ── User: Set savings wallet ─────────────────────────────────────────
/**
* @notice Set a savings wallet address where claimed rewards will be sent.
* This allows mining phones (hot wallet) to claim without ever
* holding valuable tokens — they go straight to cold storage.
* @param _wallet The destination address (Ledger, hardware wallet, etc.)
* Set to address(0) to clear and receive at msg.sender.
*/
function setSavingsWallet(address _wallet) external {
savingsWallet[msg.sender] = _wallet;
emit SavingsWalletSet(msg.sender, _wallet);
}
// ── Owner: Stage merkle root (24hr timelock) ─────────────────────────
/**
* @notice Stage a new merkle root. It cannot be activated until 24 hours
* have passed, giving the community time to verify the tree.
* Leaf format: keccak256(abi.encode(address, claimableNow, vestingAmount, vestingDuration))
* @param _root The merkle root for this epoch's reward tree
*/
function setMerkleRoot(bytes32 _root) external onlyOwner {
require(_root != bytes32(0), "Rewards: empty root");
require(pendingRoot == bytes32(0), "Rewards: root already pending");
pendingRoot = _root;
pendingRootTimestamp = block.timestamp;
emit MerkleRootStaged(_root, block.timestamp + TIMELOCK_DURATION);
}
// ── Owner: Activate merkle root after timelock ───────────────────────
/**
* @notice Activate the pending merkle root after the 24hr timelock has expired.
* Increments the epoch and makes the root available for claims.
*/
function activateMerkleRoot() external onlyOwner {
require(pendingRoot != bytes32(0), "Rewards: no pending root");
require(
block.timestamp >= pendingRootTimestamp + TIMELOCK_DURATION,
"Rewards: timelock not expired"
);
currentEpoch++;
merkleRoots[currentEpoch] = pendingRoot;
emit MerkleRootActivated(currentEpoch, pendingRoot);
pendingRoot = bytes32(0);
pendingRootTimestamp = 0;
}
// ── Owner: Cancel pending merkle root ────────────────────────────────
/**
* @notice Cancel a staged merkle root before it is activated.
* Used if an error is discovered during the timelock window.
*/
function cancelPendingRoot() external onlyOwner {
require(pendingRoot != bytes32(0), "Rewards: no pending root");
emit MerkleRootCancelled(pendingRoot);
pendingRoot = bytes32(0);
pendingRootTimestamp = 0;
}
// ── User: Claim rewards ──────────────────────────────────────────────
/**
* @notice Claim rewards for a specific epoch using a merkle proof.
* Immediate portion is sent to savings wallet (or msg.sender).
* Vesting portion is locked until vestingDuration elapses.
* @param _epoch The epoch to claim for
* @param _claimableNow Immediate reward amount
* @param _vestingAmount Amount locked for vesting
* @param _vestingDuration Seconds until vesting unlocks (0 if no vesting)
* @param _proof The merkle proof path
*/
function claim(
uint256 _epoch,
uint256 _claimableNow,
uint256 _vestingAmount,
uint256 _vestingDuration,
bytes32[] calldata _proof
) external {
require(_epoch > 0 && _epoch <= currentEpoch, "Rewards: invalid epoch");
require(!hasClaimed[_epoch][msg.sender], "Rewards: already claimed");
require(_claimableNow > 0 || _vestingAmount > 0, "Rewards: zero amount");
// Verify merkle proof (double-hash to match OpenZeppelin StandardMerkleTree)
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(
msg.sender, _claimableNow, _vestingAmount, _vestingDuration
))));
require(
MerkleProof.verify(_proof, merkleRoots[_epoch], leaf),
"Rewards: invalid proof"
);
hasClaimed[_epoch][msg.sender] = true;
address recipient = savingsWallet[msg.sender];
if (recipient == address(0)) {
recipient = msg.sender;
}
uint256 vestingUnlocksAt;
// Send immediate portion
if (_claimableNow > 0) {
totalDistributed += _claimableNow;
token.safeTransfer(recipient, _claimableNow);
}
// Record vesting portion
if (_vestingAmount > 0) {
vestingUnlocksAt = block.timestamp + _vestingDuration;
vesting[msg.sender][_epoch] = VestingInfo({
amount: _vestingAmount,
unlocksAt: vestingUnlocksAt,
released: false
});
totalVesting += _vestingAmount;
}
emit RewardsClaimed(
_epoch, msg.sender, recipient,
_claimableNow, _vestingAmount, vestingUnlocksAt
);
}
// ── User: Batch claim multiple epochs ────────────────────────────────
/**
* @notice Claim rewards for multiple epochs in a single transaction.
* @param _epochs Array of epoch numbers
* @param _claimableNows Array of immediate amounts per epoch
* @param _vestingAmounts Array of vesting amounts per epoch
* @param _vestingDurations Array of vesting durations per epoch
* @param _proofs Array of merkle proofs per epoch
*/
function claimBatch(
uint256[] calldata _epochs,
uint256[] calldata _claimableNows,
uint256[] calldata _vestingAmounts,
uint256[] calldata _vestingDurations,
bytes32[][] calldata _proofs
) external {
require(_epochs.length == _claimableNows.length, "Rewards: length mismatch");
require(_epochs.length == _vestingAmounts.length, "Rewards: length mismatch");
require(_epochs.length == _vestingDurations.length, "Rewards: length mismatch");
require(_epochs.length == _proofs.length, "Rewards: length mismatch");
address recipient = savingsWallet[msg.sender];
if (recipient == address(0)) {
recipient = msg.sender;
}
uint256 totalImmediate;
uint256 totalVestingAdded;
for (uint256 i = 0; i < _epochs.length; i++) {
uint256 epoch = _epochs[i];
uint256 claimableNow = _claimableNows[i];
uint256 vestingAmount = _vestingAmounts[i];
uint256 vestingDuration = _vestingDurations[i];
require(epoch > 0 && epoch <= currentEpoch, "Rewards: invalid epoch");
require(!hasClaimed[epoch][msg.sender], "Rewards: already claimed");
require(claimableNow > 0 || vestingAmount > 0, "Rewards: zero amount");
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(
msg.sender, claimableNow, vestingAmount, vestingDuration
))));
require(
MerkleProof.verify(_proofs[i], merkleRoots[epoch], leaf),
"Rewards: invalid proof"
);
hasClaimed[epoch][msg.sender] = true;
totalImmediate += claimableNow;
uint256 vestingUnlocksAt;
if (vestingAmount > 0) {
vestingUnlocksAt = block.timestamp + vestingDuration;
vesting[msg.sender][epoch] = VestingInfo({
amount: vestingAmount,
unlocksAt: vestingUnlocksAt,
released: false
});
totalVestingAdded += vestingAmount;
}
emit RewardsClaimed(
epoch, msg.sender, recipient,
claimableNow, vestingAmount, vestingUnlocksAt
);
}
totalDistributed += totalImmediate;
totalVesting += totalVestingAdded;
if (totalImmediate > 0) {
token.safeTransfer(recipient, totalImmediate);
}
}
// ── User: Release vested tokens ──────────────────────────────────────
/**
* @notice Release vested tokens for a specific epoch after the vesting
* period has elapsed. Tokens go to savings wallet if set.
* @param _epoch The epoch whose vesting to release
*/
function releaseVested(uint256 _epoch) external {
VestingInfo storage v = vesting[msg.sender][_epoch];
require(v.amount > 0, "Rewards: no vesting");
require(!v.released, "Rewards: already released");
require(block.timestamp >= v.unlocksAt, "Rewards: still vesting");
v.released = true;
uint256 amount = v.amount;
totalVesting -= amount;
totalDistributed += amount;
address recipient = savingsWallet[msg.sender];
if (recipient == address(0)) {
recipient = msg.sender;
}
token.safeTransfer(recipient, amount);
emit VestingReleased(_epoch, msg.sender, recipient, amount);
}
// ── User: Batch release vested tokens ────────────────────────────────
/**
* @notice Release vested tokens for multiple epochs in one transaction.
* @param _epochs Array of epochs whose vesting to release
*/
function releaseVestedBatch(uint256[] calldata _epochs) external {
address recipient = savingsWallet[msg.sender];
if (recipient == address(0)) {
recipient = msg.sender;
}
uint256 totalAmount;
for (uint256 i = 0; i < _epochs.length; i++) {
VestingInfo storage v = vesting[msg.sender][_epochs[i]];
require(v.amount > 0, "Rewards: no vesting");
require(!v.released, "Rewards: already released");
require(block.timestamp >= v.unlocksAt, "Rewards: still vesting");
v.released = true;
totalAmount += v.amount;
emit VestingReleased(_epochs[i], msg.sender, recipient, v.amount);
}
totalVesting -= totalAmount;
totalDistributed += totalAmount;
token.safeTransfer(recipient, totalAmount);
}
// ── View: Contract token balance ─────────────────────────────────────
function rewardsRemaining() external view returns (uint256) {
return token.balanceOf(address(this));
}
// ── View: Available balance (excludes locked vesting) ────────────────
function rewardsAvailable() external view returns (uint256) {
return token.balanceOf(address(this)) - totalVesting;
}
}// 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);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)
pragma solidity >=0.6.2;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
/**
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value) external returns (bool);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)
pragma solidity >=0.4.16;
import {IERC165} from "../utils/introspection/IERC165.sol";// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)
pragma solidity >=0.4.16;
import {IERC20} from "../token/ERC20/IERC20.sol";// 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);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}// 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;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/Hashes.sol)
pragma solidity ^0.8.20;
/**
* @dev Library of standard hash functions.
*
* _Available since v5.1._
*/
library Hashes {
/**
* @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*
* NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
*/
function commutativeKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return a < b ? efficientKeccak256(a, b) : efficientKeccak256(b, a);
}
/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function efficientKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32 value) {
assembly ("memory-safe") {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol)
// This file was procedurally generated from scripts/generate/templates/MerkleProof.js.
pragma solidity ^0.8.20;
import {Hashes} from "./Hashes.sol";
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the Merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates Merkle trees that are safe
* against this attack out of the box.
*
* IMPORTANT: Consider memory side-effects when using custom hashing functions
* that access memory in an unsafe way.
*
* NOTE: This library supports proof verification for merkle trees built using
* custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving
* leaf inclusion in trees built using non-commutative hashing functions requires
* additional logic that is not supported by this library.
*/
library MerkleProof {
/**
*@dev The multiproof provided is not valid.
*/
error MerkleProofInvalidMultiproof();
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in memory with the default hashing function.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in memory with the default hashing function.
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in memory with a custom hashing function.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProof(proof, leaf, hasher) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in memory with a custom hashing function.
*/
function processProof(
bytes32[] memory proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with the default hashing function.
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with the default hashing function.
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with a custom hashing function.
*/
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProofCalldata(proof, leaf, hasher) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leaves & pre-images are assumed to be sorted.
*
* This version handles proofs in calldata with a custom hashing function.
*/
function processProofCalldata(
bytes32[] calldata proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in memory with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in memory with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in memory with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProof(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in memory with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in calldata with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in calldata with the default hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* This version handles multiproofs in calldata with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* This version handles multiproofs in calldata with a custom hashing function.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
* and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
* validating the leaves elsewhere.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}{
"evmVersion": "cancun",
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"_token","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":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"epoch","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"root","type":"bytes32"}],"name":"MerkleRootActivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"root","type":"bytes32"}],"name":"MerkleRootCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"root","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"activatesAt","type":"uint256"}],"name":"MerkleRootStaged","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":"uint256","name":"epoch","type":"uint256"},{"indexed":true,"internalType":"address","name":"claimant","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"immediateAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"vestingAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"vestingUnlocksAt","type":"uint256"}],"name":"RewardsClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"wallet","type":"address"}],"name":"SavingsWalletSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"epoch","type":"uint256"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"VestingReleased","type":"event"},{"inputs":[],"name":"TIMELOCK_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"activateMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cancelPendingRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_epoch","type":"uint256"},{"internalType":"uint256","name":"_claimableNow","type":"uint256"},{"internalType":"uint256","name":"_vestingAmount","type":"uint256"},{"internalType":"uint256","name":"_vestingDuration","type":"uint256"},{"internalType":"bytes32[]","name":"_proof","type":"bytes32[]"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_epochs","type":"uint256[]"},{"internalType":"uint256[]","name":"_claimableNows","type":"uint256[]"},{"internalType":"uint256[]","name":"_vestingAmounts","type":"uint256[]"},{"internalType":"uint256[]","name":"_vestingDurations","type":"uint256[]"},{"internalType":"bytes32[][]","name":"_proofs","type":"bytes32[][]"}],"name":"claimBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"currentEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"hasClaimed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"merkleRoots","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingRootTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_epoch","type":"uint256"}],"name":"releaseVested","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_epochs","type":"uint256[]"}],"name":"releaseVestedBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardsAvailable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardsRemaining","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"savingsWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_root","type":"bytes32"}],"name":"setMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_wallet","type":"address"}],"name":"setSavingsWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalDistributed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalVesting","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":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"vesting","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"unlocksAt","type":"uint256"},{"internalType":"bool","name":"released","type":"bool"}],"stateMutability":"view","type":"function"}]Contract Creation Code
60a060405234801562000010575f80fd5b5060405162001b3238038062001b3283398101604081905262000033916200011f565b33806200005a57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b6200006581620000d0565b506001600160a01b038116620000be5760405162461bcd60e51b815260206004820152601660248201527f526577617264733a20746f6b656e206973207a65726f00000000000000000000604482015260640162000051565b6001600160a01b03166080526200014e565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f6020828403121562000130575f80fd5b81516001600160a01b038116811462000147575f80fd5b9392505050565b6080516119a1620001915f395f818161035a0152818161088801528181610c8b01528181610de301528181611157015281816112ee015261138c01526119a15ff3fe608060405234801561000f575f80fd5b5060043610610153575f3560e01c806378c196f3116100bf578063c9ad09ac11610079578063c9ad09ac14610315578063df9a97591461031e578063e7a7250a14610331578063efca2eed14610339578063f2fde38b14610342578063fc0c546a14610355575f80fd5b806378c196f3146102915780637c17357d146102995780637cb64759146102a257806381735dc9146102b5578063873f6f9e146102c85780638da5cb5b14610305575f80fd5b806353212f8d1161011057806353212f8d146102325780635463c3e814610245578063715018a61461025857806371c5ecb114610260578063750588cf1461027f5780637667180814610288575f80fd5b806310089a8214610157578063143ee5b9146101615780632e100ff9146101bf5780632ff2ec35146101d2578063356efb5f146101da5780634623c81e1461021a575b5f80fd5b61015f61037c565b005b61019d61016f36600461161d565b600860209081525f928352604080842090915290825290208054600182015460029092015490919060ff1683565b6040805193845260208401929092521515908201526060015b60405180910390f35b61015f6101cd36600461168d565b610419565b61015f6108be565b6102026101e8366004611773565b60046020525f90815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020016101b6565b6102246201518081565b6040519081526020016101b6565b61015f610240366004611773565b6109dd565b61015f61025336600461178c565b610a33565b61015f610db9565b61022461026e3660046117f1565b60026020525f908152604090205481565b61022460065481565b61022460015481565b610224610dcc565b61022460095481565b61015f6102b03660046117f1565b610e59565b61015f6102c3366004611808565b610f4c565b6102f56102d6366004611847565b600360209081525f928352604080842090915290825290205460ff1681565b60405190151581526020016101b6565b5f546001600160a01b0316610202565b61022460075481565b61015f61032c3660046117f1565b611184565b610224611369565b61022460055481565b61015f610350366004611773565b6113ff565b6102027f000000000000000000000000000000000000000000000000000000000000000081565b61038461143c565b6006546103d35760405162461bcd60e51b815260206004820152601860248201527714995dd85c991cce881b9bc81c195b991a5b99c81c9bdbdd60421b60448201526064015b60405180910390fd5b7ffa43becbd161ace8d670cca2291138105cce796bb4900baa36652ee1daff2b5060065460405161040691815260200190565b60405180910390a15f6006819055600755565b8887146104385760405162461bcd60e51b81526004016103ca90611871565b8885146104575760405162461bcd60e51b81526004016103ca90611871565b8883146104765760405162461bcd60e51b81526004016103ca90611871565b8881146104955760405162461bcd60e51b81526004016103ca90611871565b335f908152600460205260409020546001600160a01b0316806104b55750335b5f805f5b8c811015610846575f8e8e838181106104d4576104d46118a8565b9050602002013590505f8d8d848181106104f0576104f06118a8565b9050602002013590505f8c8c8581811061050c5761050c6118a8565b9050602002013590505f8b8b86818110610528576105286118a8565b9050602002013590505f8411801561054257506001548411155b6105875760405162461bcd60e51b81526020600482015260166024820152750a4caeec2e4c8e67440d2dcecc2d8d2c840cae0dec6d60531b60448201526064016103ca565b5f84815260036020908152604080832033845290915290205460ff16156105eb5760405162461bcd60e51b815260206004820152601860248201527714995dd85c991cce88185b1c9958591e4818db185a5b595960421b60448201526064016103ca565b5f8311806105f857505f82115b61063b5760405162461bcd60e51b815260206004820152601460248201527314995dd85c991cce881e995c9bc8185b5bdd5b9d60621b60448201526064016103ca565b6040805133602082015290810184905260608101839052608081018290525f9060a00160408051601f19818403018152828252805160209182012090830152016040516020818303038152906040528051906020012090506106ff8b8b888181106106a8576106a86118a8565b90506020028101906106ba91906118bc565b808060200260200160405190810160405280939291908181526020018383602002808284375f9201829052508a81526002602052604090205492508591506114689050565b6107445760405162461bcd60e51b81526020600482015260166024820152752932bbb0b932399d1034b73b30b634b210383937b7b360511b60448201526064016103ca565b5f8581526003602090815260408083203384529091529020805460ff191660011790556107718489611916565b97505f83156107e4576107848342611916565b6040805160608101825286815260208082018481525f838501818152338252600884528582208d835290935293909320915182559151600182015590516002909101805460ff191691151591909117905590506107e18489611916565b97505b60408051868152602081018690529081018290526001600160a01b038b1690339088907feab2f81188e80bcf0ee55c4b1576bf6346f8ab686461a2987f3223058bb2e57e9060600160405180910390a45050600190940193506104b992505050565b508160055f8282546108589190611916565b925050819055508060095f8282546108709190611916565b909155505081156108af576108af6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016848461147d565b50505050505050505050505050565b6108c661143c565b6006546109105760405162461bcd60e51b815260206004820152601860248201527714995dd85c991cce881b9bc81c195b991a5b99c81c9bdbdd60421b60448201526064016103ca565b620151806007546109219190611916565b4210156109705760405162461bcd60e51b815260206004820152601d60248201527f526577617264733a2074696d656c6f636b206e6f74206578706972656400000060448201526064016103ca565b60018054905f61097f83611929565b9091555050600654600180545f9081526002602090815260409182902084905591549051928352917fc22277889f95a802079d088cbf4733d802140bb409452e05c6538b4d720ce3ab910160405180910390a25f6006819055600755565b335f8181526004602052604080822080546001600160a01b0319166001600160a01b03861690811790915590519092917f0e3ba6ed0a117297ff8e68c95e5110d0c0090f925cbca0295dac793b0923397b91a350565b5f86118015610a4457506001548611155b610a895760405162461bcd60e51b81526020600482015260166024820152750a4caeec2e4c8e67440d2dcecc2d8d2c840cae0dec6d60531b60448201526064016103ca565b5f86815260036020908152604080832033845290915290205460ff1615610aed5760405162461bcd60e51b815260206004820152601860248201527714995dd85c991cce88185b1c9958591e4818db185a5b595960421b60448201526064016103ca565b5f851180610afa57505f84115b610b3d5760405162461bcd60e51b815260206004820152601460248201527314995dd85c991cce881e995c9bc8185b5bdd5b9d60621b60448201526064016103ca565b6040805133602082015290810186905260608101859052608081018490525f9060a00160408051601f1981840301815282825280516020918201209083015201604051602081830303815290604052805190602001209050610bdf8383808060200260200160405190810160405280939291908181526020018383602002808284375f9201829052508c81526002602052604090205492508591506114689050565b610c245760405162461bcd60e51b81526020600482015260166024820152752932bbb0b932399d1034b73b30b634b210383937b7b360511b60448201526064016103ca565b5f8781526003602090815260408083203384528252808320805460ff1916600117905560049091529020546001600160a01b031680610c605750335b5f8715610cb2578760055f828254610c789190611916565b90915550610cb290506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016838a61147d565b8615610d5e57610cc28642611916565b905060405180606001604052808881526020018281526020015f151581525060085f336001600160a01b03166001600160a01b031681526020019081526020015f205f8b81526020019081526020015f205f820151815f0155602082015181600101556040820151816002015f6101000a81548160ff0219169083151502179055509050508660095f828254610d589190611916565b90915550505b60408051898152602081018990529081018290526001600160a01b0383169033908b907feab2f81188e80bcf0ee55c4b1576bf6346f8ab686461a2987f3223058bb2e57e9060600160405180910390a4505050505050505050565b610dc161143c565b610dca5f6114d4565b565b6040516370a0823160e01b81523060048201525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015610e30573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e549190611941565b905090565b610e6161143c565b80610ea45760405162461bcd60e51b815260206004820152601360248201527214995dd85c991cce88195b5c1d1e481c9bdbdd606a1b60448201526064016103ca565b60065415610ef45760405162461bcd60e51b815260206004820152601d60248201527f526577617264733a20726f6f7420616c72656164792070656e64696e6700000060448201526064016103ca565b60068190554260078190557f5dbcef3d946df491fac91405a97aec117288db65639576d908912cc02f36f051908290610f31906201518090611916565b6040805192835260208301919091520160405180910390a150565b335f908152600460205260409020546001600160a01b031680610f6c5750335b5f805b8381101561111a57335f90815260086020526040812081878785818110610f9857610f986118a8565b9050602002013581526020019081526020015f2090505f815f015411610ff65760405162461bcd60e51b8152602060048201526013602482015272526577617264733a206e6f2076657374696e6760681b60448201526064016103ca565b600281015460ff16156110475760405162461bcd60e51b815260206004820152601960248201527814995dd85c991cce88185b1c9958591e481c995b19585cd959603a1b60448201526064016103ca565b80600101544210156110945760405162461bcd60e51b8152602060048201526016602482015275526577617264733a207374696c6c2076657374696e6760501b60448201526064016103ca565b60028101805460ff1916600117905580546110af9084611916565b92506001600160a01b038416338787858181106110ce576110ce6118a8565b905060200201357f5b659ec90a29a3254b32b5b01abbf5c000f33d6e0ac08e03e824d0c64c2a9431845f015460405161110991815260200190565b60405180910390a450600101610f6f565b508060095f82825461112c9190611958565b925050819055508060055f8282546111449190611916565b9091555061117e90506001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016838361147d565b50505050565b335f908152600860209081526040808320848452909152902080546111e15760405162461bcd60e51b8152602060048201526013602482015272526577617264733a206e6f2076657374696e6760681b60448201526064016103ca565b600281015460ff16156112325760405162461bcd60e51b815260206004820152601960248201527814995dd85c991cce88185b1c9958591e481c995b19585cd959603a1b60448201526064016103ca565b806001015442101561127f5760405162461bcd60e51b8152602060048201526016602482015275526577617264733a207374696c6c2076657374696e6760501b60448201526064016103ca565b60028101805460ff191660011790558054600980548291905f906112a4908490611958565b925050819055508060055f8282546112bc9190611916565b9091555050335f908152600460205260409020546001600160a01b0316806112e15750335b6113156001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016828461147d565b806001600160a01b0316336001600160a01b0316857f5b659ec90a29a3254b32b5b01abbf5c000f33d6e0ac08e03e824d0c64c2a94318560405161135b91815260200190565b60405180910390a450505050565b6009546040516370a0823160e01b81523060048201525f91906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa1580156113d1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113f59190611941565b610e549190611958565b61140761143c565b6001600160a01b03811661143057604051631e4fbdf760e01b81525f60048201526024016103ca565b611439816114d4565b50565b5f546001600160a01b03163314610dca5760405163118cdaa760e01b81523360048201526024016103ca565b5f826114748584611523565b14949350505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526114cf908490611567565b505050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f81815b845181101561155d5761155382868381518110611546576115466118a8565b60200260200101516115d3565b9150600101611527565b5090505b92915050565b5f8060205f8451602086015f885af180611586576040513d5f823e3d81fd5b50505f513d9150811561159d5780600114156115aa565b6001600160a01b0384163b155b1561117e57604051635274afe760e01b81526001600160a01b03851660048201526024016103ca565b5f8183106115ed575f8281526020849052604090206115fb565b5f8381526020839052604090205b9392505050565b80356001600160a01b0381168114611618575f80fd5b919050565b5f806040838503121561162e575f80fd5b61163783611602565b946020939093013593505050565b5f8083601f840112611655575f80fd5b50813567ffffffffffffffff81111561166c575f80fd5b6020830191508360208260051b8501011115611686575f80fd5b9250929050565b5f805f805f805f805f8060a08b8d0312156116a6575f80fd5b8a3567ffffffffffffffff808211156116bd575f80fd5b6116c98e838f01611645565b909c509a5060208d01359150808211156116e1575f80fd5b6116ed8e838f01611645565b909a50985060408d0135915080821115611705575f80fd5b6117118e838f01611645565b909850965060608d0135915080821115611729575f80fd5b6117358e838f01611645565b909650945060808d013591508082111561174d575f80fd5b5061175a8d828e01611645565b915080935050809150509295989b9194979a5092959850565b5f60208284031215611783575f80fd5b6115fb82611602565b5f805f805f8060a087890312156117a1575f80fd5b86359550602087013594506040870135935060608701359250608087013567ffffffffffffffff8111156117d3575f80fd5b6117df89828a01611645565b979a9699509497509295939492505050565b5f60208284031215611801575f80fd5b5035919050565b5f8060208385031215611819575f80fd5b823567ffffffffffffffff81111561182f575f80fd5b61183b85828601611645565b90969095509350505050565b5f8060408385031215611858575f80fd5b8235915061186860208401611602565b90509250929050565b60208082526018908201527f526577617264733a206c656e677468206d69736d617463680000000000000000604082015260600190565b634e487b7160e01b5f52603260045260245ffd5b5f808335601e198436030181126118d1575f80fd5b83018035915067ffffffffffffffff8211156118eb575f80fd5b6020019150600581901b3603821315611686575f80fd5b634e487b7160e01b5f52601160045260245ffd5b8082018082111561156157611561611902565b5f6001820161193a5761193a611902565b5060010190565b5f60208284031215611951575f80fd5b5051919050565b818103818111156115615761156161190256fea2646970667358221220a5727a3acf8f025116b33d90aa20b1fd3a5c4d49ef553b4c233c1a22eaa09bc664736f6c63430008180033000000000000000000000000280ddb8b67ad8cf791d370fe59227d19e989fb07
Deployed Bytecode
0x608060405234801561000f575f80fd5b5060043610610153575f3560e01c806378c196f3116100bf578063c9ad09ac11610079578063c9ad09ac14610315578063df9a97591461031e578063e7a7250a14610331578063efca2eed14610339578063f2fde38b14610342578063fc0c546a14610355575f80fd5b806378c196f3146102915780637c17357d146102995780637cb64759146102a257806381735dc9146102b5578063873f6f9e146102c85780638da5cb5b14610305575f80fd5b806353212f8d1161011057806353212f8d146102325780635463c3e814610245578063715018a61461025857806371c5ecb114610260578063750588cf1461027f5780637667180814610288575f80fd5b806310089a8214610157578063143ee5b9146101615780632e100ff9146101bf5780632ff2ec35146101d2578063356efb5f146101da5780634623c81e1461021a575b5f80fd5b61015f61037c565b005b61019d61016f36600461161d565b600860209081525f928352604080842090915290825290208054600182015460029092015490919060ff1683565b6040805193845260208401929092521515908201526060015b60405180910390f35b61015f6101cd36600461168d565b610419565b61015f6108be565b6102026101e8366004611773565b60046020525f90815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020016101b6565b6102246201518081565b6040519081526020016101b6565b61015f610240366004611773565b6109dd565b61015f61025336600461178c565b610a33565b61015f610db9565b61022461026e3660046117f1565b60026020525f908152604090205481565b61022460065481565b61022460015481565b610224610dcc565b61022460095481565b61015f6102b03660046117f1565b610e59565b61015f6102c3366004611808565b610f4c565b6102f56102d6366004611847565b600360209081525f928352604080842090915290825290205460ff1681565b60405190151581526020016101b6565b5f546001600160a01b0316610202565b61022460075481565b61015f61032c3660046117f1565b611184565b610224611369565b61022460055481565b61015f610350366004611773565b6113ff565b6102027f000000000000000000000000280ddb8b67ad8cf791d370fe59227d19e989fb0781565b61038461143c565b6006546103d35760405162461bcd60e51b815260206004820152601860248201527714995dd85c991cce881b9bc81c195b991a5b99c81c9bdbdd60421b60448201526064015b60405180910390fd5b7ffa43becbd161ace8d670cca2291138105cce796bb4900baa36652ee1daff2b5060065460405161040691815260200190565b60405180910390a15f6006819055600755565b8887146104385760405162461bcd60e51b81526004016103ca90611871565b8885146104575760405162461bcd60e51b81526004016103ca90611871565b8883146104765760405162461bcd60e51b81526004016103ca90611871565b8881146104955760405162461bcd60e51b81526004016103ca90611871565b335f908152600460205260409020546001600160a01b0316806104b55750335b5f805f5b8c811015610846575f8e8e838181106104d4576104d46118a8565b9050602002013590505f8d8d848181106104f0576104f06118a8565b9050602002013590505f8c8c8581811061050c5761050c6118a8565b9050602002013590505f8b8b86818110610528576105286118a8565b9050602002013590505f8411801561054257506001548411155b6105875760405162461bcd60e51b81526020600482015260166024820152750a4caeec2e4c8e67440d2dcecc2d8d2c840cae0dec6d60531b60448201526064016103ca565b5f84815260036020908152604080832033845290915290205460ff16156105eb5760405162461bcd60e51b815260206004820152601860248201527714995dd85c991cce88185b1c9958591e4818db185a5b595960421b60448201526064016103ca565b5f8311806105f857505f82115b61063b5760405162461bcd60e51b815260206004820152601460248201527314995dd85c991cce881e995c9bc8185b5bdd5b9d60621b60448201526064016103ca565b6040805133602082015290810184905260608101839052608081018290525f9060a00160408051601f19818403018152828252805160209182012090830152016040516020818303038152906040528051906020012090506106ff8b8b888181106106a8576106a86118a8565b90506020028101906106ba91906118bc565b808060200260200160405190810160405280939291908181526020018383602002808284375f9201829052508a81526002602052604090205492508591506114689050565b6107445760405162461bcd60e51b81526020600482015260166024820152752932bbb0b932399d1034b73b30b634b210383937b7b360511b60448201526064016103ca565b5f8581526003602090815260408083203384529091529020805460ff191660011790556107718489611916565b97505f83156107e4576107848342611916565b6040805160608101825286815260208082018481525f838501818152338252600884528582208d835290935293909320915182559151600182015590516002909101805460ff191691151591909117905590506107e18489611916565b97505b60408051868152602081018690529081018290526001600160a01b038b1690339088907feab2f81188e80bcf0ee55c4b1576bf6346f8ab686461a2987f3223058bb2e57e9060600160405180910390a45050600190940193506104b992505050565b508160055f8282546108589190611916565b925050819055508060095f8282546108709190611916565b909155505081156108af576108af6001600160a01b037f000000000000000000000000280ddb8b67ad8cf791d370fe59227d19e989fb0716848461147d565b50505050505050505050505050565b6108c661143c565b6006546109105760405162461bcd60e51b815260206004820152601860248201527714995dd85c991cce881b9bc81c195b991a5b99c81c9bdbdd60421b60448201526064016103ca565b620151806007546109219190611916565b4210156109705760405162461bcd60e51b815260206004820152601d60248201527f526577617264733a2074696d656c6f636b206e6f74206578706972656400000060448201526064016103ca565b60018054905f61097f83611929565b9091555050600654600180545f9081526002602090815260409182902084905591549051928352917fc22277889f95a802079d088cbf4733d802140bb409452e05c6538b4d720ce3ab910160405180910390a25f6006819055600755565b335f8181526004602052604080822080546001600160a01b0319166001600160a01b03861690811790915590519092917f0e3ba6ed0a117297ff8e68c95e5110d0c0090f925cbca0295dac793b0923397b91a350565b5f86118015610a4457506001548611155b610a895760405162461bcd60e51b81526020600482015260166024820152750a4caeec2e4c8e67440d2dcecc2d8d2c840cae0dec6d60531b60448201526064016103ca565b5f86815260036020908152604080832033845290915290205460ff1615610aed5760405162461bcd60e51b815260206004820152601860248201527714995dd85c991cce88185b1c9958591e4818db185a5b595960421b60448201526064016103ca565b5f851180610afa57505f84115b610b3d5760405162461bcd60e51b815260206004820152601460248201527314995dd85c991cce881e995c9bc8185b5bdd5b9d60621b60448201526064016103ca565b6040805133602082015290810186905260608101859052608081018490525f9060a00160408051601f1981840301815282825280516020918201209083015201604051602081830303815290604052805190602001209050610bdf8383808060200260200160405190810160405280939291908181526020018383602002808284375f9201829052508c81526002602052604090205492508591506114689050565b610c245760405162461bcd60e51b81526020600482015260166024820152752932bbb0b932399d1034b73b30b634b210383937b7b360511b60448201526064016103ca565b5f8781526003602090815260408083203384528252808320805460ff1916600117905560049091529020546001600160a01b031680610c605750335b5f8715610cb2578760055f828254610c789190611916565b90915550610cb290506001600160a01b037f000000000000000000000000280ddb8b67ad8cf791d370fe59227d19e989fb0716838a61147d565b8615610d5e57610cc28642611916565b905060405180606001604052808881526020018281526020015f151581525060085f336001600160a01b03166001600160a01b031681526020019081526020015f205f8b81526020019081526020015f205f820151815f0155602082015181600101556040820151816002015f6101000a81548160ff0219169083151502179055509050508660095f828254610d589190611916565b90915550505b60408051898152602081018990529081018290526001600160a01b0383169033908b907feab2f81188e80bcf0ee55c4b1576bf6346f8ab686461a2987f3223058bb2e57e9060600160405180910390a4505050505050505050565b610dc161143c565b610dca5f6114d4565b565b6040516370a0823160e01b81523060048201525f907f000000000000000000000000280ddb8b67ad8cf791d370fe59227d19e989fb076001600160a01b0316906370a0823190602401602060405180830381865afa158015610e30573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e549190611941565b905090565b610e6161143c565b80610ea45760405162461bcd60e51b815260206004820152601360248201527214995dd85c991cce88195b5c1d1e481c9bdbdd606a1b60448201526064016103ca565b60065415610ef45760405162461bcd60e51b815260206004820152601d60248201527f526577617264733a20726f6f7420616c72656164792070656e64696e6700000060448201526064016103ca565b60068190554260078190557f5dbcef3d946df491fac91405a97aec117288db65639576d908912cc02f36f051908290610f31906201518090611916565b6040805192835260208301919091520160405180910390a150565b335f908152600460205260409020546001600160a01b031680610f6c5750335b5f805b8381101561111a57335f90815260086020526040812081878785818110610f9857610f986118a8565b9050602002013581526020019081526020015f2090505f815f015411610ff65760405162461bcd60e51b8152602060048201526013602482015272526577617264733a206e6f2076657374696e6760681b60448201526064016103ca565b600281015460ff16156110475760405162461bcd60e51b815260206004820152601960248201527814995dd85c991cce88185b1c9958591e481c995b19585cd959603a1b60448201526064016103ca565b80600101544210156110945760405162461bcd60e51b8152602060048201526016602482015275526577617264733a207374696c6c2076657374696e6760501b60448201526064016103ca565b60028101805460ff1916600117905580546110af9084611916565b92506001600160a01b038416338787858181106110ce576110ce6118a8565b905060200201357f5b659ec90a29a3254b32b5b01abbf5c000f33d6e0ac08e03e824d0c64c2a9431845f015460405161110991815260200190565b60405180910390a450600101610f6f565b508060095f82825461112c9190611958565b925050819055508060055f8282546111449190611916565b9091555061117e90506001600160a01b037f000000000000000000000000280ddb8b67ad8cf791d370fe59227d19e989fb0716838361147d565b50505050565b335f908152600860209081526040808320848452909152902080546111e15760405162461bcd60e51b8152602060048201526013602482015272526577617264733a206e6f2076657374696e6760681b60448201526064016103ca565b600281015460ff16156112325760405162461bcd60e51b815260206004820152601960248201527814995dd85c991cce88185b1c9958591e481c995b19585cd959603a1b60448201526064016103ca565b806001015442101561127f5760405162461bcd60e51b8152602060048201526016602482015275526577617264733a207374696c6c2076657374696e6760501b60448201526064016103ca565b60028101805460ff191660011790558054600980548291905f906112a4908490611958565b925050819055508060055f8282546112bc9190611916565b9091555050335f908152600460205260409020546001600160a01b0316806112e15750335b6113156001600160a01b037f000000000000000000000000280ddb8b67ad8cf791d370fe59227d19e989fb0716828461147d565b806001600160a01b0316336001600160a01b0316857f5b659ec90a29a3254b32b5b01abbf5c000f33d6e0ac08e03e824d0c64c2a94318560405161135b91815260200190565b60405180910390a450505050565b6009546040516370a0823160e01b81523060048201525f91906001600160a01b037f000000000000000000000000280ddb8b67ad8cf791d370fe59227d19e989fb0716906370a0823190602401602060405180830381865afa1580156113d1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906113f59190611941565b610e549190611958565b61140761143c565b6001600160a01b03811661143057604051631e4fbdf760e01b81525f60048201526024016103ca565b611439816114d4565b50565b5f546001600160a01b03163314610dca5760405163118cdaa760e01b81523360048201526024016103ca565b5f826114748584611523565b14949350505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526114cf908490611567565b505050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f81815b845181101561155d5761155382868381518110611546576115466118a8565b60200260200101516115d3565b9150600101611527565b5090505b92915050565b5f8060205f8451602086015f885af180611586576040513d5f823e3d81fd5b50505f513d9150811561159d5780600114156115aa565b6001600160a01b0384163b155b1561117e57604051635274afe760e01b81526001600160a01b03851660048201526024016103ca565b5f8183106115ed575f8281526020849052604090206115fb565b5f8381526020839052604090205b9392505050565b80356001600160a01b0381168114611618575f80fd5b919050565b5f806040838503121561162e575f80fd5b61163783611602565b946020939093013593505050565b5f8083601f840112611655575f80fd5b50813567ffffffffffffffff81111561166c575f80fd5b6020830191508360208260051b8501011115611686575f80fd5b9250929050565b5f805f805f805f805f8060a08b8d0312156116a6575f80fd5b8a3567ffffffffffffffff808211156116bd575f80fd5b6116c98e838f01611645565b909c509a5060208d01359150808211156116e1575f80fd5b6116ed8e838f01611645565b909a50985060408d0135915080821115611705575f80fd5b6117118e838f01611645565b909850965060608d0135915080821115611729575f80fd5b6117358e838f01611645565b909650945060808d013591508082111561174d575f80fd5b5061175a8d828e01611645565b915080935050809150509295989b9194979a5092959850565b5f60208284031215611783575f80fd5b6115fb82611602565b5f805f805f8060a087890312156117a1575f80fd5b86359550602087013594506040870135935060608701359250608087013567ffffffffffffffff8111156117d3575f80fd5b6117df89828a01611645565b979a9699509497509295939492505050565b5f60208284031215611801575f80fd5b5035919050565b5f8060208385031215611819575f80fd5b823567ffffffffffffffff81111561182f575f80fd5b61183b85828601611645565b90969095509350505050565b5f8060408385031215611858575f80fd5b8235915061186860208401611602565b90509250929050565b60208082526018908201527f526577617264733a206c656e677468206d69736d617463680000000000000000604082015260600190565b634e487b7160e01b5f52603260045260245ffd5b5f808335601e198436030181126118d1575f80fd5b83018035915067ffffffffffffffff8211156118eb575f80fd5b6020019150600581901b3603821315611686575f80fd5b634e487b7160e01b5f52601160045260245ffd5b8082018082111561156157611561611902565b5f6001820161193a5761193a611902565b5060010190565b5f60208284031215611951575f80fd5b5051919050565b818103818111156115615761156161190256fea2646970667358221220a5727a3acf8f025116b33d90aa20b1fd3a5c4d49ef553b4c233c1a22eaa09bc664736f6c63430008180033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000280ddb8b67ad8cf791d370fe59227d19e989fb07
-----Decoded View---------------
Arg [0] : _token (address): 0x280Ddb8b67Ad8cf791D370FE59227d19e989Fb07
-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 000000000000000000000000280ddb8b67ad8cf791d370fe59227d19e989fb07
Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 32 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.