For the complete documentation index, see llms.txt. This page is also available as Markdown.

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

  1. A wrapper is created for a pool id and initialized (cloned via the WrapperFactory, see below).

  2. 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.

  3. stake(_amount, _to): same but expects the caller to already hold the Convex deposit token (use when the caller has raw deposit tokens from booster.deposit(pid, amount, false)).

  4. withdraw(_amount): burns wrapper tokens and returns Convex deposit tokens to the caller.

  5. 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 claimable EarnedData[]; note this is mutative (it checkpoints claimable rewards first); cast it as a view in your ABI when calling off-chain via eth_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 with address(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) — optional IRewardHook the 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 current wrapperImplementation for 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 (abrapools in contracts.json)

  • ConvexStakingWrapperFrax / ConvexStakingWrapperFraxLend — for Frax gauge / FraxLend collateral

  • ConvexStakingWrapperMorpho — for Morpho lending; the generic deposit/stake/withdraw are no-ops and it uses its own depositFor-style entry plus collateral management

  • ConvexStakingWrapperOhmSync — 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?