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

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);

When to use which:

  • convertToShares / convertToAssets are generic rate conversions, both round DOWN.

  • previewWithdraw rounds UP and matches withdraw().

  • previewRedeem rounds DOWN and matches redeem().

  • For a target-asset-amount UX, prefer withdraw(assets, ...) directly over computing shares and calling redeem; withdraw accrues 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)

User enters an amount in USDC and wants exactly that much USDC out.

Example: redeem(shares, receiver, owner)

Use redeem when the user has expressed their intent in shares, most commonly "redeem all." 🔧

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)

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; owner must 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 with previewWithdraw(assets) first, then call redeemFor.

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

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?