Skip to content

Router

The Stake DAO Router is the modular entrypoint that sits on top of all Stake DAO products. It lets integrators bundle deposits, withdrawals, migrations, claims, and zaps into a single transaction without interacting with downstream contracts directly.

Router At A Glance

  • Single entrypoint: Router.execute(bytes[] calls) batches actions in one transaction.
  • Modular design: Each action lives in a dedicated module.
  • Composable: Modules run in order, so later steps can rely on earlier state changes.
  • Permissioned registry: Only the Router owner can register or upgrade modules.
  • Addresses: Router and module addresses are listed on the Contract Addresses page, and the Router is deployed across multiple networks at the same address.

Permit2 Only

Why this matters:

  • Router modules execute with delegatecall, so allowances live on the Router itself.
  • New modules can be added later. A native approval would be visible to every future module.
  • Permit2 grants precise, expiring permissions that are consumed within the same transaction.

If a workflow appears to need a native approval, stop and rework it around Permit2.

How It Works

Module registry

  • Modules are registered by identifier in Router.sol.
  • Registration is permissioned (owner only).
  • Each module is executed via delegatecall.

Call format

Each call is a byte array:

  • calls[i][0]: 1-byte module identifier.
  • calls[i][1:]: ABI-encoded function selector + arguments.

The Router returns a bytes[] array aligned with the input order.

Module Catalog

IdentifierModulePurpose
0x00RouterModuleDepositDeposit ERC-4626 assets into Stake DAO reward vaults
0x01RouterModuleWithdrawWithdraw ERC-4626 assets for the caller
0x02RouterModuleClaimHarvest and claim main or extra rewards
0x03RouterModuleMigrationStakeDAOV1Migrate legacy Stake DAO vaults into new reward vaults
0x04RouterModuleMigrationCurveMigrate Curve positions into Stake DAO vaults
0x05RouterModuleMigrationYearnMigrate Yearn vault positions into Stake DAO vaults
0x06RouterModuleSDTokenFull sdToken lifecycle: mint, stake, claim, exit
0x07RouterModuleERC20ManagerPermit2 token pulls and approvals for composition
0x08RouterModuleEnsoZap between tokens using Enso before or after Stake DAO actions
0x09RouterModuleRewardsFetcherUnified claim for Merkl rewards
0x0ARouterModuleLendingManage lending positions and claim collateral rewards

Flow Recipes

1. Deposit

  1. User signs Permit2 off-chain.
  2. transferFromPermit2 moves the underlying token into the Router.
  3. deposit approves the target vault, mints reward vault shares, and credits them to msg.sender.

2. Withdraw and harvest

  1. User signs Permit2 off-chain.
  2. transferFromPermit2 moves the underlying token into the Router.
  3. withdraw burns the user's vault shares and transfers assets back to the user.
  4. Combine with another module (for example RouterModuleEnso.zap) to swap the redeemed asset.

3. Aggregate reward claims

  • Use RouterModuleRewardsFetcher to claim Merkl rewards, then route the freshly claimed tokens into staking, zapping, or vault deposits in the same transaction.

TypeScript Example (viem)

import {
  concat,
  createWalletClient,
  encodeFunctionData,
  http,
  toHex,
} from "viem";
import { mainnet } from "viem/chains";
import {
  routerAbi,
  routerModuleERC20ManagerAbi,
  routerModuleDepositAbi,
} from "./generated/abis";
 
const Module = {
  ERC20_MANAGER: 0x07,
  DEPOSIT: 0x00,
} as const;
 
const walletClient = createWalletClient({
  account,
  chain: mainnet,
  transport: http(),
});
 
const permitCall = concat([ 
  toHex(Module.ERC20_MANAGER, { size: 1 }), 
  encodeFunctionData({ 
    abi: routerModuleERC20ManagerAbi, 
    functionName: "transferFromPermit2", 
    args: [ 
      token, 
      routerAddress, 
      rewardVault, 
      assets, 
      nonce, 
      deadline, 
      signature, 
    ], 
  }), 
]); 
 
const depositCall = concat([ 
  toHex(Module.DEPOSIT, { size: 1 }), 
  encodeFunctionData({ 
    abi: routerModuleDepositAbi, 
    functionName: "deposit", 
    args: [rewardVault, assets], 
  }), 
]); 
 
const { request } = await walletClient.simulateContract({ 
  address: routerAddress, 
  abi: routerAbi, 
  functionName: "execute", 
  args: [[permitCall, depositCall]], 
}); 
 
const hash = await walletClient.writeContract(request);