> For the complete documentation index, see [llms.txt](https://docs.maple.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.maple.finance/technical-resources/pools/withdrawal-manager.md).

# Withdrawal Manager

The queue-based `MapleWithdrawalManager` handles exits from a Pool. Lenders submit redemption requests, the contract escrows their shares in a FIFO queue, and when a request is processed it exchanges the shares for assets at the current exit rate and sends them to the owner. The November 2026 release ships v2.0.1, whose only functional change is a permission check on `removeSharesById`. Storage is unchanged, so the upgrade is an implementation swap with no migrator. Full interface: [WithdrawalManagerQueue](/technical-resources/interfaces/withdrawal-manager-queue.md).

### Withdrawal Request

Any user holding shares can perform a withdrawal request.

* Each withdrawal request has a unique identifier assigned to it.
* A user may have multiple active withdrawal requests at the same time. Requests are tracked per user and can be adjusted or removed by id.
* When making the request the user specifies the amount of shares that should be redeemed and that amount of shares is then transferred to the withdrawal manager where it remains in custody.
* Once the withdrawal is processed the request is deleted and the user can again make another one.
* The user can also adjust an existing withdrawal request, but only in a downwards manner (by returning some of the shares locked).
* Additionally, the Pool Delegate, the Operational Admin or a registered redeemer can also remove any withdrawal request from the queue. This lets them unblock the queue if a request cannot be processed.

*NOTE*: Withdrawal requests can only be submitted in the form of redemption requests (indicating the number of shares to redeem), `requestWithdraw()` and `withdraw()` functions are disabled.

### Withdrawal Queue (FIFO)

The withdrawal queue contains all pending withdrawal requests and maintains their ordering from first to last. Whenever the Pool Delegate, the Operational Admin or a registered redeemer processes withdrawal requests, they are processed in that order (**FIFO**). This means earlier requests are processed before later requests. If the Pool does not hold enough liquidity to redeem every share the call is asked to process, the call reverts with `WM:PR:LOW_LIQUIDITY` and processes nothing. Requests the call does not reach stay in the queue to be processed later.

### Request Processing

The Pool Delegate, the Operational Admin or a registered redeemer can at any time process any amount of shares that are pending redemption. The amount of shares to be processed is specified in advance, and as many redemption requests as possible will be processed using that amount of shares. The requests are processed based on their position in the queue (first to last), and using the current exchange rate.

*NOTE*: The `processRedemptions()` function has a reentrancy guard since it performs external calls.

*NOTE*: The amount of shares must be specified and are assumed to be a reasonable amount where out of gas issues don't occur.

### Exchange Rate

When a withdrawal request is processed, the amount of assets withdrawn is calculated by converting the requested shares into assets using the current exchange rate. This means that the exchange rate for different users' withdrawal requests may vary depending on when the requests are processed. If any loan or strategy is impaired, `unrealizedLosses` lowers the value of each share until the impairment is removed or the loss is realised. A request processed while an impairment is open therefore receives fewer assets than one processed after the impairment is removed. See [Exchange Rates](/technical-resources/pools/exchange-rates.md).

The formula for calculating the exchange rate of shares to assets is defined as:

`exchangeRate = (totalAssets - unrealizedLosses) / totalSupply`

### Partial Redemptions

If the shares left to process run out partway through a request, that request is processed partially (*note this is only applicable to the last request being processed in the queue*). This means that only a portion of the shares from the original withdrawal request will be redeemed. The remainder keeps its original position in the queue and is processed first on the next call. The number of shares processed from a request is:

`processedShares = min(requestShares, remainingSharesToProcess)`

### Adjust or Cancel a Request (removeSharesById)

The request owner can reduce or cancel a specific pending request at any time before it is processed by calling `removeSharesById(requestId, sharesToRemove)`. The checks run in this order:

1. The request exists (`WM:RSBI:INVALID_REQUEST`).
2. The caller owns it (`WM:RSBI:NOT_OWNER`).
3. `sharesToRemove` is non-zero and no more than the request's shares.
4. The caller holds the `WM:removeSharesById` permission for this Pool in the Pool Permission Manager (`WM:RSBI:NOT_ALLOWED`). This check is new in v2.0.1. It brings `removeSharesById` in line with `removeShares`, which the Pool already checks against `P:removeShares`, so both ways of cancelling a request pass through the same permission check. The check passes on a public Pool and for a Lender on the Pool's allowlist. Otherwise a private Pool denies the call, a `POOL_LEVEL` Pool checks the Pool-wide bitmap, and a `FUNCTION_LEVEL` Pool checks the bitmap for this function id. An unset function bitmap allows the call only at `FUNCTION_LEVEL`.

* If `sharesToRemove` equals the current request shares, the request is cancelled. Otherwise the request is decreased and remains in the queue with the original position.
* Returns `(sharesReturned, sharesRemaining)` and transfers the returned LP shares back to the owner.
* Use `requests(requestId)` or `requestsByOwner(owner)` to inspect pending requests and shares.

### Manual Requests (2 step process)

Each withdrawal request can be processed automatically (by default) or manually by calling `requestRedeem()` first then calling `redeem()`. The Pool Delegate or Operational Admin can enable manual requests for users who want to opt-out of automatic processing. The difference between automatic and manual requests is as follows:

* Automatic requests will be immediately redeemed on processing.
* Manual requests will be redeemed any time after processing (when the `redeem()` function is actually called). Manual withdrawal requests are primarily supported so that the protocol remains ERC-4626 compatible (**internal use only**). Automatic requests are the default and preferred way of handling withdrawals.

### Roles & Permissions

| Function                                                         | Allowed callers                                                              |
| ---------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `addShares`, `processExit`, `removeShares`                       | Pool Manager                                                                 |
| `processRedemptions`, `processEmptyRedemptions`, `removeRequest` | A `WITHDRAWAL_REDEEMER` instance, the Pool Delegate or the Operational Admin |
| `setManualWithdrawal`                                            | Pool Delegate or Operational Admin                                           |
| `removeSharesById`                                               | The request owner, with the `WM:removeSharesById` permission                 |
| `upgrade`                                                        | Pool Delegate through a scheduled call, or the Security Admin                |

### Events & Queue Internals

* Events: `RequestCreated`, `RequestDecreased`, `RequestRemoved`, `RequestProcessed`, `ManualWithdrawalSet`, `EmptyRedemptionsProcessed`, `ManualSharesIncreased`, `ManualSharesDecreased`.
* Queue pointers: `nextRequestId` and `lastRequestId` track processing range. `processEmptyRedemptions(n)` advances `nextRequestId` over empty entries to keep processing efficient.

### Other View Functions

* `lockedShares()`: returns the amount of `manualSharesAvailable` (only used for manual withdrawals).
* `lockedLiquidity()`: returns zero. The Pool Manager does not read it.
* `isInExitWindow()`: returns `true`, because withdrawals can be processed at any time.

### Key Assumptions

Generally a push pattern for token transfers is not recommended as it can lead to uncontrolled revert conditions. In the case of the `WithdrawalManager` this could mean issues such as the queue being blocked. This is a known issue and to address this we have added the `removeRequest()` function that can be called by the Pool Delegate, the Operational Admin or a registered redeemer to unblock the queue. Additionally:

* No tokens with callbacks will be used.
* This queue based withdrawal manager will only be used in permissioned pools, not public pools to avoid denial of service attacks.
* Extensive research will be done when onboarding new ERC20 tokens as the underlying asset.
* An LP being blacklisted by USDC/USDT is a low likelihood event due to the KYC'd nature of LPs in permissioned pools and the use of `removeRequest()` can address this issue.

To highlight the key reason to support this push pattern is to address the business need from LPs.

### Invariants

```
* Withdrawal Manager (Queue)
    * Invariant A: ∑request.shares + ∑owner.manualShares == totalShares
    * Invariant B: balanceOf(this) >= totalShares
    * Invariant C: ∀ requestId(owner) != 0 -> request.shares > 0 && request.owner == owner
    * Invariant D: nextRequestId <= lastRequestId + 1
    * Invariant E: nextRequestId != 0
    * Invariant F: requests(0) == (0, 0)
    * Invariant G: ∀ requestId[lender] ∈ [0, lastRequestId]
    * Invariant H: requestId is unique
```


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.maple.finance/technical-resources/pools/withdrawal-manager.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
