Skip to content

Yield Strategies Integration

This page is the developer reference for integrating with Stake DAO V2 yield strategies (RewardVaults). It covers the essential concepts, a quickstart guide, considerations, core interfaces, examples, deep dives into the internals, and a FAQ.

Essential Concepts

  1. RewardVault (ERC4626): The user-facing vault that accepts LP tokens and mints 1:1 shares. Shares track the same decimals as the asset.
  2. Gauge: The external staking contract the vault targets. The Accountant uses gauges to identify which vault and rewards to claim.
  3. Accountant: Manages main protocol rewards (e.g., CRV, BAL) and handles fee logic. Rewards can be claimed across multiple vaults at once.
  4. Extra rewards: Additional incentives (e.g., CVX, LDO) tracked per vault and claimed directly from the RewardVault.
  5. Harvest policy: HARVEST claims rewards on every action, CHECKPOINT tracks rewards until a harvest occurs.
  6. Strategy + Allocator + Sidecars: Internal components that route deposits/withdrawals and harvest rewards. Integrators should not call these directly.

Quickstart (TL;DR)

1. Resolve addresses

  • RewardVault address
  • gauge address (used for main rewards)
  • asset (LP token)
  • Accountant address

2. Approve and deposit

Approve the vault to spend the LP token, then call deposit() or mint() (1:1).

3. Claim rewards

  • Main rewards: Accountant.claim() using gauge addresses
  • Extra rewards: RewardVault.claim() using reward token list

Integration Considerations

  • 1:1 share ratio: assets == shares for all ERC4626 conversions. previewDeposit, previewWithdraw, convertToShares, and convertToAssets are identity functions.
  • Asset never sits in the vault: Deposits are transferred to allocation targets. IERC20(asset).balanceOf(vault) can be zero.
  • Harvest policy matters: In CHECKPOINT, rewards may be claimable only after a harvest. In HARVEST, rewards are immediately pulled.
  • Main vs extra rewards: Main rewards go through the Accountant (fees apply). Extra rewards are per-vault and fee-free.
  • Claim uses gauges: Accountant.claim() takes gauge addresses, not vault addresses.
  • Pauses/shutdowns: Protocols can be paused (deposits blocked, withdrawals allowed). A shutdown gauge pushes funds back to the vault before withdrawals.
  • Delegated calls are permissioned: deposit(account, ...) and claim(account, ...) require a ProtocolController permission (router/aggregator use case).

Core Interfaces

RewardVault (ERC4626 + extra rewards)

interface IRewardVault is IERC4626 {
    function deposit(uint256 assets, address receiver, address referrer) external returns (uint256 shares);
    function deposit(address account, address receiver, uint256 assets, address referrer) external returns (uint256 shares);
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
 
    function claim(address[] calldata tokens, address receiver) external returns (uint256[] memory amounts);
    function claim(address account, address[] calldata tokens, address receiver)
        external
        returns (uint256[] memory amounts);
 
    function getRewardTokens() external view returns (address[] memory);
    function earned(address account, address token) external view returns (uint128);
 
    function gauge() external view returns (address);
    function ACCOUNTANT() external view returns (IAccountant);
    function PROTOCOL_ID() external view returns (bytes4);
}

Accountant (main protocol rewards)

interface IAccountant {
    function claim(address[] calldata gauges, bytes[] calldata harvestData) external;
    function claim(address[] calldata gauges, bytes[] calldata harvestData, address receiver) external;
    function claim(address[] calldata gauges, address account, bytes[] calldata harvestData, address receiver) external;
 
    function harvest(address[] calldata gauges, bytes[] calldata harvestData, address receiver) external;
    function getPendingRewards(address vault, address account) external view returns (uint256);
 
    function REWARD_TOKEN() external view returns (address);
    function SCALING_FACTOR() external view returns (uint128);
}

ProtocolController (discovery + permissions)

interface IProtocolController {
    function vault(address gauge) external view returns (address);
    function asset(address gauge) external view returns (address);
    function rewardReceiver(address gauge) external view returns (address);
 
    function accountant(bytes4 protocolId) external view returns (address);
    function strategy(bytes4 protocolId) external view returns (address);
    function allocator(bytes4 protocolId) external view returns (address);
 
    function allowed(address target, address caller, bytes4 selector) external view returns (bool);
    function isPaused(bytes4 protocolId) external view returns (bool);
    function isShutdown(address gauge) external view returns (bool);
}

Examples

1. Deposit LP tokens (basic)

IERC20(asset).approve(rewardVault, amount);
uint256 shares = IRewardVault(rewardVault).deposit(amount, msg.sender);

2. Deposit with referrer + custom receiver

IERC20(asset).approve(rewardVault, amount);
address referrer = 0x1234...;
uint256 shares = IRewardVault(rewardVault).deposit(amount, receiver, referrer);

3. Withdraw (standard ERC4626)

uint256 assets = IRewardVault(rewardVault).withdraw(amount, receiver, owner);

4. Claim main rewards (no harvest)

address[] memory gauges = new address[](1);
gauges[0] = gauge;
 
IAccountant(accountant).claim(gauges, new bytes[](0), receiver);

5. Harvest + claim in one call

address[] memory gauges = new address[](1);
gauges[0] = gauge;
 
bytes[] memory harvestData = new bytes[](1);
harvestData[0] = ""; // protocol-specific, often empty
 
IAccountant(accountant).claim(gauges, harvestData, receiver);

6. Claim extra rewards from the vault

address[] memory extraTokens = IRewardVault(rewardVault).getRewardTokens();
uint256[] memory amounts = IRewardVault(rewardVault).claim(extraTokens, receiver);

7. Claim both main + extra in the same transaction (contract example)

function claimAll(address rewardVault, address accountant, address gauge, address receiver) external {
    address[] memory gauges = new address[](1);
    gauges[0] = gauge;
 
    IAccountant(accountant).claim(gauges, new bytes[](0), receiver);
 
    address[] memory extraTokens = IRewardVault(rewardVault).getRewardTokens();
    IRewardVault(rewardVault).claim(extraTokens, receiver);
}

8. Read the harvest policy (HARVEST vs CHECKPOINT)

IStrategy.HarvestPolicy policy = RewardVault(rewardVault).POLICY();

9. Compute full claimable main rewards

getPendingRewards() only returns rewards accrued since the last checkpoint-triggering action. To compute the total:

claimable=pending+(vaultIntegraluserIntegral)×balanceSCALING_FACTOR\text{claimable} = \text{pending} + \frac{(\text{vaultIntegral} - \text{userIntegral}) \times \text{balance}}{\text{SCALING\_FACTOR}}
Accountant.VaultData memory v = accountant.vaults(rewardVault);
Accountant.AccountData memory a = accountant.accounts(rewardVault, user);
uint256 pending = accountant.getPendingRewards(rewardVault, user);
uint256 scaling = accountant.SCALING_FACTOR();
 
uint256 claimable = pending + (v.integral - a.integral) * a.balance / scaling;

10. Discover vaults via ProtocolController

address vault = IProtocolController(controller).vault(gauge);
address asset = IProtocolController(controller).asset(gauge);
address rewardReceiver = IProtocolController(controller).rewardReceiver(gauge);

11. Address book (on-chain constants)

import { CurveEthereum } from "@stake-dao/address-book";
 
address accountant = CurveEthereum.ACCOUNTANT;
address protocolController = CurveEthereum.PROTOCOL_CONTROLLER;

Deep Dives

System architecture

Stake DAO V2 separates user entrypoints from protocol-specific logic:

  • RewardVault: ERC4626 entrypoint + extra rewards accounting
  • Accountant: main rewards + fee logic + cross-vault aggregation
  • Strategy: protocol-specific deposit/withdraw/harvest executor
  • Allocator: determines where assets should go (locker vs sidecars)
  • Sidecars: optional adapters for extra yield sources
  • RewardReceiver: forwards extra rewards into the vault

Integrators should call RewardVault and Accountant only.

Stake DAO V2 strategies architecture

Reward model: main vs extra rewards

Main rewards (Accountant):

  • Single reward token per protocol (e.g., CRV for Curve)
  • Distributed via integral accounting (SCALING_FACTOR = 1e27)
  • Subject to protocol fee and harvest fee

Extra rewards (RewardVault):

  • Stored per vault, each token has its own distribution schedule
  • Fee-free
  • Claimable via RewardVault.claim()

Harvest policy semantics

PolicyWhen rewards are pulledClaim timingFees
HARVESTEvery user actionImmediately claimableProtocol fee only
CHECKPOINTOn explicit harvestClaimable if Accountant holds balanceProtocol + harvest fee reserved

In CHECKPOINT, reward integrals update only when new rewards exceed MIN_MEANINGFUL_REWARDS (1e18) to avoid precision loss.

Deposit/withdraw flow (vault perspective)

  1. Vault checkpoints extra rewards for the receiver.
  2. Allocator returns target addresses + amounts.
  3. Vault transfers assets to targets.
  4. Strategy deposits/withdraws into the underlying protocol.
  5. Accountant checkpoints balances and main rewards.

RewardReceiver (extra rewards)

Extra rewards emitted by gauges are sent to a dedicated RewardReceiver:

  • Validates reward tokens against the vault
  • Calls RewardVault.depositRewards() to start a 7-day distribution period
  • Can forward legacy rewards to older reward receivers if configured

Pauses and shutdowns

  • Paused protocol: deposits blocked, withdrawals allowed.
  • Shutdown gauge: strategy can be shutdown to move assets back to the vault. Users then withdraw directly from the vault.

Building a new protocol integration (advanced)

If you are adding a new yield source to Stake DAO V2:

  1. Define a protocol ID (bytes4 of keccak256("PROTOCOL")).
  2. Implement a Strategy for deposit/withdraw/harvest.
  3. Implement an Allocator for target selection.
  4. Implement Sidecars if additional yield sources are needed.
  5. Register components and vaults in the ProtocolController.

FAQ

What is the difference between main and extra rewards?

Main rewards are pooled in the Accountant and subject to protocol/harvest fees. Extra rewards are distributed directly by each RewardVault and are fee-free.

Why does the vault not hold the LP token?

Deposits are immediately allocated to the locker/sidecars for yield. The vault only tracks balances and rewards.

How do I know which policy a vault uses?

Read RewardVault.POLICY() and compare against IStrategy.HarvestPolicy.

I called getPendingRewards but claimable is higher. Why?

getPendingRewards(vault, user) only covers rewards accrued since the last checkpoint. Use the integral formula to compute the full claimable amount.

Do I need to harvest before claiming?

Only in CHECKPOINT mode if the Accountant does not yet hold the reward tokens. In HARVEST mode, claim immediately after any vault action.

Can I batch claims across multiple vaults?

Yes. Accountant.claim() accepts arrays of gauges, enabling multi-vault claims in one call.

Are vault shares transferable?

Yes. RewardVault shares are standard ERC20 tokens, and transfers trigger reward checkpoints for both sides.

Are extra rewards auto-compounded?

No. Extra rewards are distributed to users; you can re-deposit manually if you want compounding.

Where can I find vault addresses?

Use the address book, the strategies API endpoint, or the Contract Addresses page filters.