Overview
SwapModule is a helper contract that lets curators swap a subvault’s assets under onchain guardrails. Each module serves one subvault. The subvault pushes assets into the module, an authorized caller swaps them through an approved router or a CoW Swap limit order, and the subvault pulls the output back.
Every swap is checked against an external price oracle, so the curator cannot accept a price worse than a configured tolerance.
Roles
SwapModule uses MellowACL, so roles are granted on the module itself. Three roles are granted to token or router addresses, not to people:
Role IDs follow the pattern
keccak256("utils.SwapModule.<ROLE>").
Initialization
The module is deployed through a factory and initialized with:Moving assets
Only the configured subvault can call these. In practice the curator triggers them through
Subvault.call, so the subvault’s verifier must allow those calls, and the approve that precedes a push. For native ETH, pushAssets must be called with msg.value == value.
Price check
Before any swap or limit order,checkParams validates the Params struct:
tokenInholdsTOKEN_IN_ROLEandtokenOutholdsTOKEN_OUT_ROLE.amountInis non-zero and not more than the module’s balance oftokenIn.tokenIndiffers fromtokenOut, anddeadlinehas not passed.minAmountOutis at least the oracle value ofamountIn, adjusted by the multiplier:
evaluate converts with getAssetPrice from the oracle and adjusts for token decimals. Native ETH is priced as WETH. The multiplier is the per-pair value set with setCustomMultiplier(tokenIn, tokenOut, m), or defaultMultiplier if none is set. Multipliers must stay between 0.9e8 and 1.1e8. For example, 0.995e8 allows a result up to 0.5% below the oracle value.
Failed checks revert with Forbidden(reason), where reason names the failing check.
Router swaps
CALLER_ROLE.
- Runs
checkParams(params)and checks thatrouterholdsROUTER_ROLE. - For ERC20 input, approves
routerfor exactlyamountIn, calls it withdata, then resets the approval to 0. For native ETH input, calls the router withamountInas value. - Measures the module’s
tokenOutbalance increase and reverts if it is belowminAmountOut. - Emits
Swap(params, router, amountOut).
pullAssets.
CoW Swap limit orders
For CoW Swap, the module signs orders onchain with pre-signatures.setCowswapApproval(asset, amount): approves the CoW Swap vault relayer to spendasset. The asset must holdTOKEN_IN_ROLEand cannot be native ETH.createLimitOrder(params, order, orderUid): runscheckParamsandcheckCowswapOrder, then callssetPreSignature(orderUid, true)on the settlement contract.invalidateOrder(orderUid): cancels an order on the settlement contract.
checkCowswapOrder requires the order to be a sell order that uses ERC20 balances, with the module as receiver. Its sell token, buy token, sell amount, buy amount and validTo must match params, and orderUid must match the order hash. ETH is not supported in limit orders.
Configuration functions
Events
Price sources
The oracle must implementgetAssetPrice(address) as AaveOracle does. For assets without a Chainlink feed, Mellow uses custom AaveOracle deployments backed by PermissionedChainlinkOracle feeds.
Deployments
SwapModule addresses for live vaults are listed on the deployment pages, for example Lido Earn and strETH.