> For the complete documentation index, see [llms.txt](https://docs.curvance.com/app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.curvance.com/app/developer-docs/quick-start-guides/loans-and-collateral/withdraw.md).

# 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)`:

```js
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()`.

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

{% hint style="warning" %}
`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](/app/developer-docs/quick-start-guides/integration-cookbook.md#stale-vs-fresh-reads).
{% endhint %}

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

```js
const cUSDC       = new ethers.Contract(ADDRESSES.CUSDC, CTOKEN_ABI, signer);
const userAddress = await signer.getAddress();

const usdcOut     = ethers.parseUnits(userInputAmount, 6); // user types "1000"

// Optional: preview the exact shares that will be burned
const sharesBurned = await cUSDC.previewWithdraw(usdcOut);

// Withdraw USDC; receiver gets the USDC, owner has shares burned
const tx = await cUSDC.withdraw(usdcOut, userAddress, userAddress);
await tx.wait();
```

#### Example: `redeem(shares, receiver, owner)`

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

```js
const cUSDC       = new ethers.Contract(ADDRESSES.CUSDC, CTOKEN_ABI, signer);
const userAddress = await signer.getAddress();

// Redeem all: burn the user's entire share balance
const allShares = await cUSDC.balanceOf(userAddress);
const tx        = await cUSDC.redeem(allShares, userAddress, userAddress);
await tx.wait();
```

{% hint style="warning" %}
**"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.
{% endhint %}

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.

```js
// The user must have pre-delegated your platform:
//   cUSDC.setDelegateApproval(<yourPlatformAddress>, true)
const tx = await cUSDC.redeemFor(sharesToRedeem, yourPlatformAddress, userAddress);
await tx.wait();
```

**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](/app/developer-docs/quick-start-guides/plugin-integration.md) 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.

```js
// User withdraws an exact amount of USDC from their own posted collateral.
// This path uses ERC20 share allowance if msg.sender != owner.
const tx1 = await cUSDC.withdrawCollateral(usdcOut, userAddress, userAddress);
await tx1.wait();

// User redeems an exact number of their own posted collateral shares.
// No ERC20 share approval is needed when msg.sender == owner.
const tx2 = await cUSDC.redeemCollateral(shares, userAddress, userAddress);
await tx2.wait();

// Platform redeems collateral shares on behalf of a delegated user.
const tx3 = await cUSDC.redeemCollateralFor(shares, yourPlatformAddress, userAddress);
await tx3.wait();
```

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

{% hint style="warning" %}
**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.
{% endhint %}

{% hint style="info" %}
**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.
{% endhint %}

***

### 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:

```js
const marketManager = new ethers.Contract(
  ADDRESSES.MARKET_MANAGER,
  MARKET_MANAGER_ABI,
  provider
);

const cUSDC = new ethers.Contract(ADDRESSES.USDC_CTOKEN, CTOKEN_ABI, signer);
const owner = await signer.getAddress();
const MIN_HOLD_PERIOD = 20n * 60n;

// Market-wide redemption pause. Public getter returns 2 when paused.
const redeemPaused = await marketManager.redeemPaused();
if (redeemPaused === 2n) {
  throw new Error("Redemptions are paused for this market");
}

// Account cooldown. accountAssets(owner) returns the cooldown timestamp.
const latestBlock = await provider.getBlock("latest");
if (latestBlock === null) throw new Error("Could not load latest block");

const cooldownStarted = await marketManager.accountAssets(owner);
const cooldownEnds = cooldownStarted + MIN_HOLD_PERIOD;
if (cooldownEnds > BigInt(latestBlock.timestamp)) {
  throw new Error("Account is still inside the 20-minute hold period");
}

// Balance and available idle-liquidity checks.
const sharesToRedeem = await cUSDC.balanceOf(owner);
if (sharesToRedeem === 0n) throw new Error("No cUSDC shares to redeem");

const assetsOut = await cUSDC.previewRedeem(sharesToRedeem);
const idleAssets = await cUSDC.assetsHeld();
if (assetsOut > idleAssets) {
  throw new Error("Not enough idle USDC liquidity for this redemption");
}
```

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.

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.curvance.com/app/developer-docs/quick-start-guides/loans-and-collateral/withdraw.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
