Withdraw
Querying Redeem Amounts
This page uses cUSDC in a `WETH | USDC` or `WBTC | USDC` isolated market as the example. Use the cUSDC address for the specific market from the deployment registry for the chain you're on.
To display the amount of underlying tokens a user would receive for redeeming a given number of shares, use convertToAssets(shares):
const cUSDC = new ethers.Contract(ADDRESSES.CUSDC, CTOKEN_ABI, provider);
const shares = await cUSDC.balanceOf(userAddress);
const assetsOut = await cUSDC.convertToAssets(shares);Conversely, if you want to let the user choose how much underlying to withdraw and need to know how many shares will be burned, use previewWithdraw(assets). convertToShares(assets) is also available, but it rounds down and can under-quote the shares actually burned by withdraw().
const userInputAmount = "1000"; // 1000 USDC entered by the user
const assetsIn = ethers.parseUnits(userInputAmount, 6);
// previewWithdraw rounds UP and matches the shares the withdraw() call will actually burn
const sharesExact = await cUSDC.previewWithdraw(assetsIn);
// convertToShares rounds DOWN: useful as a generic rate quote, NOT for shares-to-burn UX
const sharesApprox = await cUSDC.convertToShares(assetsIn);convertTo* and preview* are view functions. They read the last-accrued state, while write calls such as withdraw and redeem accrue first. For wei-accurate exchange-rate reads, use the staticCall-on-exchangeRateUpdated() pattern documented in Integration Cookbook: Stale vs Fresh Reads.
When to use which:
convertToShares/convertToAssetsare generic rate conversions, both round DOWN.previewWithdrawrounds UP and matcheswithdraw().previewRedeemrounds DOWN and matchesredeem().For a target-asset-amount UX, prefer
withdraw(assets, ...)directly over computing shares and callingredeem;withdrawaccrues on-chain and hits the exact target.
Withdraw from cTokens (cUSDC example)
To withdraw USDC from cUSDC directly, use withdraw(assets, receiver, owner) if the user chose an amount in USDC, or redeem(shares, receiver, owner) if the user chose an amount in shares.
owner is the account whose shares will be burned. When the user is withdrawing their own position, owner === msg.sender and no ERC20 share allowance is required. If msg.sender !== owner, plain withdraw / redeem spend ERC20 allowance over owner's cToken shares. To use Curvance delegation instead of ERC20 allowance, use redeemFor as covered below.
Withdrawing/redeeming does not force collateral removal. These functions burn uncollateralized shares first. If the user's uncollateralized balance is insufficient, the protocol removes only the collateralized shares needed to complete the redemption, and only if the position remains healthy afterward. To intentionally redeem posted collateral, use withdrawCollateral, redeemCollateral, or redeemCollateralFor covered below.
Example: withdraw(assets, receiver, owner)
withdraw(assets, receiver, owner)User enters an amount in USDC and wants exactly that much USDC out.
Example: redeem(shares, receiver, owner)
redeem(shares, receiver, owner)Use redeem when the user has expressed their intent in shares, most commonly "redeem all." 🔧
"Redeem all" caveat: balanceOf(owner) is the raw share balance, not a guarantee that every share is currently redeemable. If shares are posted as collateral, the full redeem can revert on health checks. If the market is inside the hold period, redemption reverts. If idle liquidity is insufficient, a borrowable cToken redemption can also revert.
If your UX expresses the amount in USDC but you still prefer to call redeem, compute shares via previewWithdraw(assets) first. This gives you an asset-aware ceiling: using the same exchange rate, redeem(thoseShares) returns at least the requested asset amount after rounding. State can still move before the transaction finalizes.
Using redeemFor (for platforms withdrawing on behalf of users)
redeemFor (for platforms withdrawing on behalf of users)If your app withdraws from a user's position (e.g., a managed-vault integration), use redeemFor(shares, receiver, owner). The signature matches redeem but the authorization check switches from ERC20 allowance to the delegation system.
Key points:
No ERC20 share approval needed; delegation replaces allowance.
receiver(the USDC destination) can be your platform or any address;ownermust be the user.Note: there is no
withdrawFor. If your integration wants to pull a specific USDC amount on behalf of a user, compute shares withpreviewWithdraw(assets)first, then callredeemFor.
See Plugin Integration for the full delegation model and mass-revoke via centralRegistry.incrementApprovalIndex().
Withdrawing Collateral
When redeeming shares that are currently posted as collateral, choose the function that matches the user's intent.
These functions force the redeemed shares to come from owner's collateralPosted. If shares is greater than the owner's posted collateral, the MarketManager reverts with MarketManager__InsufficientCollateral. Plain redeem / withdraw behave differently: they consume idle shares first and only remove posted collateral when required to complete the redemption.
There is no cToken withdrawCollateralFor. If a delegated integration wants a specific underlying amount from posted collateral, compute shares with previewWithdraw(assets) first, then call redeemCollateralFor(shares, receiver, owner).
Caution: collateral removal affects your loan-to-value ratio. Removing collateral increases your loan-to-value (LTV) and can increase liquidation risk. The protocol will reject the redemption if it would push the position below its collateralization requirement, but the margin of safety is up to the integrator to surface to the user.
Hold period gotcha: posting collateral or borrowing starts a 20-minute MIN_HOLD_PERIOD. During this window, repayment, cToken transfers in the same isolated market, and redemption / withdrawal paths that call the MarketManager redeem check revert with MarketManager__MinimumHoldPeriod. The redeem path enforces this through canRedeemWithCollateralRemoval → _canRedeem → _checkTransfersAllowed → _checkHoldPeriod (MarketManagerIsolated.sol:406, :1303, :1688, :1654). Plan redemption flows so the user is not inside a fresh cooldown window.
Pre-flight checks for redemptions
Do not treat maxRedeem(owner) or maxWithdraw(owner) as final Curvance redemption prechecks. In the base ERC4626 implementation, maxRedeem(owner) returns balanceOf(owner) and maxWithdraw(owner) returns convertToAssets(balanceOf(owner)). They do not check the MarketManager hold period, market-wide redeem pause, collateral health, or available idle liquidity.
For a borrowable cToken such as cUSDC, use targeted checks before submitting:
If the redemption touches posted collateral, also run a collateral-health check before submitting. If your integration uses ProtocolReader, maxRedemptionOf(account, cToken, bufferTime) returns separate estimates for collateralized and uncollateralized shares, and hypotheticalRedemptionOf(account, cToken, redemptionShares, bufferTime) reports whether a proposed redemption would leave a liquidity deficit. These are UX helpers; the cToken transaction is still the source of truth.
These checks map to redeemPaused on the MarketManager, the public accountAssets(account) cooldown getter, and assetsHeld() on borrowable cTokens.
Last updated
Was this helpful?