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
| Identifier | Module | Purpose |
|---|---|---|
0x00 | RouterModuleDeposit | Deposit ERC-4626 assets into Stake DAO reward vaults |
0x01 | RouterModuleWithdraw | Withdraw ERC-4626 assets for the caller |
0x02 | RouterModuleClaim | Harvest and claim main or extra rewards |
0x03 | RouterModuleMigrationStakeDAOV1 | Migrate legacy Stake DAO vaults into new reward vaults |
0x04 | RouterModuleMigrationCurve | Migrate Curve positions into Stake DAO vaults |
0x05 | RouterModuleMigrationYearn | Migrate Yearn vault positions into Stake DAO vaults |
0x06 | RouterModuleSDToken | Full sdToken lifecycle: mint, stake, claim, exit |
0x07 | RouterModuleERC20Manager | Permit2 token pulls and approvals for composition |
0x08 | RouterModuleEnso | Zap between tokens using Enso before or after Stake DAO actions |
0x09 | RouterModuleRewardsFetcher | Unified claim for Merkl rewards |
0x0A | RouterModuleLending | Manage lending positions and claim collateral rewards |
Flow Recipes
1. Deposit
- User signs Permit2 off-chain.
transferFromPermit2moves the underlying token into the Router.depositapproves the target vault, mints reward vault shares, and credits them tomsg.sender.
2. Withdraw and harvest
- User signs Permit2 off-chain.
transferFromPermit2moves the underlying token into the Router.withdrawburns the user's vault shares and transfers assets back to the user.- Combine with another module (for example
RouterModuleEnso.zap) to swap the redeemed asset.
3. Aggregate reward claims
- Use
RouterModuleRewardsFetcherto 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);