Claim Rewards

To claim rewards, a staker can call RewardsManager.claimRewards

// Used to track which reward pool to claim from and whether to drip from the reward pool.
struct ClaimRewardsPoolData {
  uint16 rewardPoolId;
  bool drip;
}
  
  /// @notice Claim rewards for a specific stake pool and all reward pools and transfer rewards to `receiver_`.
  /// @dev Note that this function drips all reward pools. If you want to claim without dripping from specific reward
  /// pools, you can use one of the claimRewards functions that accepts `ClaimRewardsPoolData[] calldata
  /// claimRewardsPoolData_` as an arg.
  /// @param stakePoolId_ The ID of the stake pool to claim rewards for.
  /// @param receiver_ The address to transfer the claimed rewards to.
  function claimRewards(uint16 stakePoolId_, address receiver_) external {}

  /// @notice Claim rewards for a set of stake pools and all reward pools and transfer rewards to `receiver_`.
  /// @dev Note that this function drips all reward pools. If you want to claim without dripping from specific reward
  /// pools, you can use one of the claimRewards functions that accepts `ClaimRewardsPoolData[] calldata
  /// claimRewardsPoolData_` as an arg.
  /// @param stakePoolIds_ The IDs of the stake pools to claim rewards for.
  /// @param receiver_ The address to transfer the claimed rewards to.
  function claimRewards(uint16[] calldata stakePoolIds_, address receiver_) external {}

  /// @notice Claim rewards for a specific stake pool and set of reward pools and transfer rewards to `receiver_`.
  /// @dev Note that this function only drips and claims rewards for the reward pools specified in
  /// `claimRewardsPoolData_`. If a reward pool is omitted from `claimRewardsPoolData_`, then no rewards will be dripped
  /// or claimed for that reward pool. If drip is false, then no rewards will be dripped for that reward pool, but
  /// rewards will still be claimed.
  /// @dev The `claimRewardsPoolData_` must contain only valid reward pool IDs and no duplicates.
  /// @param stakePoolId_ The ID of the stake pool to claim rewards for.
  /// @param claimRewardsPoolData_ The reward pool IDs and whether to drip or not.
  /// @param receiver_ The address to transfer the claimed rewards to.
  function claimRewards(uint16 stakePoolId_, ClaimRewardsPoolData[] calldata claimRewardsPoolData_, address receiver_) external {}
    
  /// @notice Claim rewards for a specific set of stake pools and set of reward pools and transfer rewards to
  /// `receiver_`.
  /// @dev Note that this function only drips and claims rewards for the reward pools specified in
  /// `claimRewardsPoolData_`. If a reward pool is omitted from `claimRewardsPoolData_`, then no rewards will be dripped
  /// or claimed for that reward pool. If drip is false, then no rewards will be dripped for that reward pool, but
  /// rewards will still be claimed.
  /// @dev The `claimRewardsPoolData_` must contain only valid reward pool IDs and no duplicates.
  /// @param stakePoolIds_ The IDs of the stake pools to claim rewards for.
  /// @param claimRewardsPoolData_ The reward pool IDs and whether to drip or not.
  /// @param receiver_ The address to transfer the claimed rewards to.
  function claimRewards(
    uint16[] calldata stakePoolIds_,
    ClaimRewardsPoolData[] calldata claimRewardsPoolData_,
    address receiver_
  ) external {}

There are 4 variations of the function that allow you to specify which stake pools you want to claim rewards for and which reward pools you want to drip and claim from. Accrued rewards are sent to the receiver_ address.

Claim rewards mechanics

On claiming rewards, we follow the following steps for each reward pool:

  • Drip from the reward pool since time may have passed since the last drip.

  • Compute and update the ClaimableRewardsData for the (stake pool, reward pool) pair.

  • Update the UserRewardsData for the (stake pool, reward pool) pair.

  • Transfer the user's accruedRewards from the reward pool to the receiver_.

  • Reset accruedRewards to 0.

  • Emit a ClaimedRewards event.

To claim rewards on behalf of another user, you may use RewardsManager.claimRewardsBySig

/// @notice Returns the domain separator.
function domainSeparator() external view returns (bytes32);

/// @notice Tracks per-owner nonces for EIP-712 signed operations.
/// @param owner_ the owner of this nonce
/// @param actionKey_ typehash used for this call (i.e. CLAIM_REWARDS_BY_SIG_ALL_POOLS_TYPEHASH)
function eip712Nonces(address owner_, bytes32 actionKey_) external view returns (uint256);

function hashStakePoolIds(uint16[] calldata stakePoolIds_) external pure returns (bytes32);

bytes32 public constant CLAIM_REWARDS_BY_SIG_ALL_POOLS_TYPEHASH = keccak256("ClaimRewardsBySig(uint16[] stakePoolIds,address owner,address caller,address receiver,uint256 deadline,uint256 nonce)")

/// @notice Claim rewards for a set of stake pools on behalf of owner_ and transfer rewards to receiver_.
/// @dev This variant claims and drips all reward pools. Signature binds to (stakePoolIds, owner, caller=msg.sender, receiver, deadline, nonce).
/// @param stakePoolIds_ The IDs of the stake pools to claim rewards for.
/// @param owner_ The address whose rewards are being claimed.
/// @param receiver_ The address to receive the claimed rewards.
/// @param deadline_ The time after which the signature is no longer valid.
/// @param signature_ The owner's signature over the EIP-712 structured data.
function claimRewardsBySig(
uint16[] calldata stakePoolIds_,
address owner_,
address receiver_,
uint256 deadline_,
bytes calldata signature_
) external {

bytes32 digest_ = keccak256(
  abi.encodePacked(
    "\x19\x01",
    keccak256(
      abi.encode(domainSeparator)
    ),
    keccak256(
      abi.encode(
          CLAIM_REWARDS_BY_SIG_ALL_POOLS_TYPEHASH,
          hashStakePoolIds(stakePoolIds),
          owner_,
          msg.sender,
          receiver_,
          deadline_,
          nonce_
      )
    )
  )
);

if (!SignatureChecker.isValidSignatureNow(owner_, digest_, signature_)) revert InvalidSignature();
}

function _hashStakePoolIds(uint16[] calldata stakePoolIds_) internal pure returns (bytes32) {
  // Encode each stake pool id as a full 32-byte word to satisfy EIP-712 array hashing semantics.
  uint256 stakePoolCount_ = stakePoolIds_.length;
  bytes32[] memory stakePoolIdsEncoded_ = new bytes32[](stakePoolCount_);
  for (uint256 i = 0; i < stakePoolCount_; i++) {
    stakePoolIdsEncoded_[i] = bytes32(uint256(stakePoolIds_[i]));
  }
  return keccak256(abi.encodePacked(stakePoolIdsEncoded_));
}
  • The owner must sign a digest that matches the above.

  • Fetch Domain Separator via external domainSeparator() getter and nonce via external eip712Nonces(owner, actionKey)(actionKey is the relevant typehash) getter. Both are exposed in IRewardsManager.

  • The above claimRewardsBySig function is truncated to only show the relevant structure of the digest. hashStakePoolIds is a public exposed function that encodes each stakePoolId as a 32-byte word before hashing to satisfy EIP-712 array semantics.

To specify which reward pools to drip and claim from, you can use a similar claimRewardsBySig function with a slightly different set of typehashes and an additional claimRewardsPoolData_ arg in the signature. Its important that the signature has structured the data correctly before hashing to satisfy EIP-712 semantics. hashStakePoolIds and hashClaimRewardsPoolData are public exposed functions on IRewardsManager that can help you construct this part of the signature.


function hashStakePoolIds(uint16[] calldata stakePoolIds_) external pure returns (bytes32);

function hashClaimRewardsPoolData(ClaimRewardsPoolData[] calldata claimRewardsPoolData_)
  external
  pure
  returns (bytes32);
  
bytes32 public constant CLAIM_REWARDS_POOL_DATA_TYPEHASH =
  keccak256("ClaimRewardsPoolData(uint16 rewardPoolId,bool drip)");

bytes32 public constant CLAIM_REWARDS_BY_SIG_SELECTED_POOLS_TYPEHASH = keccak256(
  "ClaimRewardsBySig(uint16[] stakePoolIds,ClaimRewardsPoolData[] claimRewardsPoolData,address owner,address caller,address receiver,uint256 deadline,uint256 nonce)ClaimRewardsPoolData(uint16 rewardPoolId,bool drip)"
);

/// @notice Claim rewards for a set of stake pools on behalf of `owner_`, authorized by signature.
/// @dev This variant claims and drips specified reward pools.
/// @param stakePoolIds_ The IDs of the stake pools to claim rewards for.
/// @param claimRewardsPoolData_ The reward pool IDs and whether to drip before claiming each one.
/// @param owner_ The address whose rewards are being claimed.
/// @param receiver_ The address to receive the claimed rewards.
/// @param deadline_ The time after which the signature is no longer valid.
/// @param signature_ The owner's signature over the EIP-712 structured data.
function claimRewardsBySig(
  uint16[] calldata stakePoolIds_,
  ClaimRewardsPoolData[] calldata claimRewardsPoolData_,
  address owner_,
  address receiver_,
  uint256 deadline_,
  bytes calldata signature_
) external {

bytes32 digest_ = keccak256(
  abi.encodePacked(
    "\x19\x01",
    keccak256(
      abi.encode(domainSeparator)
    ),
    keccak256(
      abi.encode(
          CLAIM_REWARDS_BY_SIG_SELECTED_POOLS_TYPEHASH,
          hashStakePoolIds(stakePoolIds),
          _hashClaimRewardsPoolData(claimRewardsPoolData_)
          owner_,
          msg.sender,
          receiver_,
          deadline_,
          nonce_
      )
    )
  )
);

if (!SignatureChecker.isValidSignatureNow(owner_, digest_, signature_)) revert InvalidSignature();
}

function _hashStakePoolIds(uint16[] calldata stakePoolIds_) internal pure returns (bytes32) {
  // Encode each stake pool id as a full 32-byte word to satisfy EIP-712 array hashing semantics.
  uint256 stakePoolCount_ = stakePoolIds_.length;
  bytes32[] memory stakePoolIdsEncoded_ = new bytes32[](stakePoolCount_);
  for (uint256 i = 0; i < stakePoolCount_; i++) {
    stakePoolIdsEncoded_[i] = bytes32(uint256(stakePoolIds_[i]));
  }
  return keccak256(abi.encodePacked(stakePoolIdsEncoded_));
}


function _hashClaimRewardsPoolData(ClaimRewardsPoolData[] calldata claimRewardsPoolData_)
  internal
  pure
  returns (bytes32)
{
  uint256 length_ = claimRewardsPoolData_.length;
  if (length_ == 0) return keccak256("");

  bytes32[] memory elementHashes_ = new bytes32[](length_);
  for (uint256 i = 0; i < length_; i++) {
    elementHashes_[i] = keccak256(
      abi.encode(
        CLAIM_REWARDS_POOL_DATA_TYPEHASH, claimRewardsPoolData_[i].rewardPoolId, claimRewardsPoolData_[i].drip
      )
    );
  }

  return keccak256(abi.encodePacked(elementHashes_));
}

Last updated