Staking Wrappers
Convex staking wrappers let protocols and services tokenize a staked Convex position as a transferable ERC20 receipt. This is the standard integration surface for yield aggregators, lending protocols, and vaults that want to deposit into Convex while keeping a liquid, transferable representation of the position.
ConvexStakingWrapper
ConvexStakingWrapper.sol (mainnet) wraps a single Convex pool (by pid). It is an ERC20 whose supply equals the amount staked in the underlying pool. It is based on Curve's gauge wrapper implementations.
How it works
A wrapper is created for a pool id and initialized (cloned via the
WrapperFactory, see below).deposit(_amount, _to): pulls the pool's Curve LP token, deposits into the Booster and stakes into the pool's reward contract, and mints wrapper ERC20 to_to.stake(_amount, _to): same but expects the caller to already hold the Convex deposit token (use when the caller has raw deposit tokens frombooster.deposit(pid, amount, false)).withdraw(_amount): burns wrapper tokens and returns Convex deposit tokens to the caller.withdrawAndUnwrap(_amount): burns wrapper tokens and returns the underlying Curve LP token directly (unwrap via the Booster).
//create a wrapper for pool n (permissionless on mainnet)
let tx = await wrapperFactory.CreateWrapper( n )
//obtain the wrapper address from the WrapperCreated(wrapper, pid) event in the receipt
let wrapper = <address from event>
//deposit LP tokens and mint wrapper shares
await wrapper.deposit( amount, to )
//withdraw, receiving the Convex deposit token
await wrapper.withdraw( amount )
//withdraw, unwrapping to the underlying Curve LP token
await wrapper.withdrawAndUnwrap( amount )Note: CreateWrapper() is a state-changing call — it returns a transaction, not the Solidity return value. Read the wrapper address from the WrapperCreated(address _wrapper, uint256 _pid) event in the receipt (or use eth_call against a fresh clone for simulation).
Rewards
The wrapper mirrors the rewards of the underlying Convex pool. It tracks CRV and CVX rewards itself (indexes 0/1) plus any extra rewards added via addTokenReward(_token). Key functions:
earned(_account)— returns claimableEarnedData[]; note this is mutative (it checkpoints claimable rewards first); cast it as a view in your ABI when calling off-chain viaeth_call.getReward(_account)/getReward(_account, _forwardTo)— claim rewards for an account or forward them.setRewardRedirect(_to)— route rewards to an address; mutable — the account can change it or disable it withaddress(0)at any time.user_checkpoint(_account)— checkpoint an account's rewards (safe to call before reads).addTokenReward(_token)/invalidateReward(_token)— owner-managed extra reward tokens.setHook(_hook)— optionalIRewardHookthe wrapper calls after claiming (useful for compounding into the wrapper).earmarkRewards()— triggers the Booster earmark for the wrapped pool so reward balances are current.
Ownership & management
The wrapper has an owner that controls reward-token management, shutdown, and hooks. When created through the factory,
initialize(_poolId)is called by the factory, so a generic factory-created clone's owner is the factory contract — there is no wrapper-admin forwarding/ownership-handoff inside the generic factory, which limits how a protocol can manage a factory-created wrapper. Review the specialized variants below for ownership models better suited to managed integrations.The owner cannot touch user funds.
shutdown()— owner can shut the wrapper down; after shutdown, deposits are blocked and users can still withdraw.
WrapperFactory
WrapperFactory.sol clones the wrapper implementation per pool:
CreateWrapper(_pid)— permissionless on mainnet: anyone can clone the currentwrapperImplementationfor a pid and initialize it. Only implementation/ownership changes are owner-restricted.setImplementation(_imp)— owner updates the implementation used for future clones.
StashTokenWrapper (pool reward tokens for newer pools)
For some newer pools, the VirtualBalanceRewardPool's rewardToken is a StashTokenWrapper.sol facade rather than the raw incentive token. The StashTokenWrapper is a minimal facade, not an ERC20 that mints per-user receipts: it reports a balance only for the reward pool, and when the reward pool calls transfer(recipient, amount), the wrapper simply forwards the raw underlying incentive token to the recipient. So "claiming" the wrapper-credited reward means calling getReward() on the VirtualBalanceRewardPool, which unwinds through the wrapper to the raw token. Don't attempt to transferFrom or mint from a StashTokenWrapper — it has neither.
Specialized wrappers
Convex maintains purpose-built wrapper variants. They do not all share an identical surface — check each implementation before integrating:
ConvexStakingWrapperAbra— for Abracadabra Cauldron positions (abrapoolsin contracts.json)ConvexStakingWrapperFrax/ConvexStakingWrapperFraxLend— for Frax gauge / FraxLend collateralConvexStakingWrapperMorpho— for Morpho lending; the genericdeposit/stake/withdraware no-ops and it uses its owndepositFor-style entry plus collateral managementConvexStakingWrapperOhmSync— standalone wrapper for Olympus/OHM sync positions (not a subclass of the generic wrapper)CvxCrvStakingWrapper— standalone ERC20 staking wrapper over the staked cvxCRV/cvxCRV-fees position (own reward accounting; not ERC-4626)
Check contracts.json (abrapools, raripools, fraxlend, and the masterFraxWrapper/factoryAbraWrapper entries) for the listed deployed/historical instances. Note it is a deployment record, not a complete wrapper registry — generic, Morpho and OhmSync wrapper instances are not all catalogued there.
Last updated
Was this helpful?