Redemptions
Olla offers two ways to exit stAztec back into Aztec tokens. Choosing the right one depends on whether the user can wait, whether there is buffer liquidity, and whether paying a fee is acceptable.
Which path should I use?
User wants to exit
|
Can user wait for finalization?
/ \
no yes
| |
Is buffer liquidity Use requestRedeem
sufficient right now? (async, no fee,
| slashing-adjusted)
/ \
yes no
| |
instantRedeem fall back to
(fee applies, requestRedeem
capped by
buffer)
Use availableForInstantRedemption() to size the decision. It returns the buffer balance available to instant redeemers after subtracting liquidity already earmarked for the async queue.
| Criterion | instantRedeem | requestRedeem |
|---|---|---|
| Time to receive assets | Same transaction | Async, after the next rebalance finalizes the request |
| Fee | Yes, up to 20% (configurable, 5% default) | None |
| Liquidity ceiling | Capped by availableForInstantRedemption() | Queued, no upper cap |
| User-supplied slippage floor | Yes, minAssetsOut | No. Rate is locked at request time; slashing can adjust down |
| Rate risk | Slippage bound protects against adverse movement | Payout uses min(lockedRate, rateAtFinalization), so slashing between request and finalization reduces the payout |
✅ Recommended: instant redemption
Use instantRedeem when the user wants to exit immediately and buffer liquidity covers the position.
function instantRedeem(uint256 shares, address recipient, uint256 minAssetsOut)
external
returns (uint256 assetsAfterFee);
The minAssetsOut floor is checked against the net assets after the instant redemption fee is deducted. Quote with previewInstantRedeem(shares) and apply a tolerance:
uint256 quoted = vault.previewInstantRedeem(shares);
uint256 minAssetsOut = quoted * (10_000 - toleranceBps) / 10_000;
uint256 assets = vault.instantRedeem(shares, recipient, minAssetsOut);
The permit variant works the same way:
function instantRedeemWithPermit(
uint256 shares,
address recipient,
uint256 minAssetsOut,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 assetsAfterFee);
The permit call has the same frontrun protection as depositWithPermit: if the permit fails but the allowance is already sufficient, the redemption proceeds.
An instantRedeem call reverts if:
- The vault or SafetyModule is paused.
- The withdrawal minimum is not met.
availableForInstantRedemption()cannot cover the post-fee payout.assetsAfterFee < minAssetsOut(slippage guard).
✅ Recommended: queued redemption
Use requestRedeem when the user prefers to avoid the fee and can wait for the next rebalance.
function requestRedeem(uint256 shares, address controller, address owner)
external
returns (uint256 requestId);
The flow is:
requestRedeemburnssharesfromowner, locks the current exchange rate, and enqueues a request againstcontroller.- At the next rebalance, OllaCore calls
finalizeWithdrawalson the vault. Requests are finalized in FIFO order until buffer liquidity is exhausted. - Once a request is finalized,
claimableRedeemRequest(requestId, controller)returns a non-zero value. The controller (or an operator) can then claim the assets.
Poll for finalization:
uint256 claimable = vault.claimableRedeemRequest(requestId, controller);
if (claimable > 0) {
uint256 assets = vault.claimRequestById(requestId);
}
claimRequestById is the preferred claim entry point because it takes an explicit request id. See the next section for the ERC-4626 alias.
Slippage semantics on the queued path
requestRedeem has no minAssetsOut parameter. Instead, the exchange rate is locked at the moment the request is enqueued, and the final payout is:
payout = shares * min(lockedRate, rateAtFinalization) / 1e18
In practice this means:
- If no slashing occurs between request and finalization, the payout equals
shares * lockedRate. The user is protected from adverse rate movement after they sign. - If slashing occurs, the payout is adjusted down so the loss is shared with non-exiting holders. This cannot be avoided by any participant, by design.
If your integration needs a hard floor on the payout, check claimableRedeemRequest(requestId, controller) before calling the claim function and surface the value to the user. There is no on-chain revert for "payout below target" on the queued path.
Because the rate is locked at request time, the user does not need to re-sign or re-quote between request and claim. The only variable is whether a slashing adjustment happens in between.
Permit variant and the owner argument
requestRedeemWithPermit behaves like requestRedeem but pulls shares into the vault using the permit-set allowance:
function requestRedeemWithPermit(
uint256 shares,
address controller,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 requestId);
The permit variant always treats msg.sender as the owner. Unlike requestRedeem(shares, controller, owner), you cannot use it to queue a redemption on behalf of a different owner. For operator-mediated flows, use the non-permit requestRedeem with setOperator (see Operators and Controllers).
Claiming finalized requests
There are two ways to claim a finalized request. Both end up in the same internal path.
✅ Preferred: claim by id
function claimRequestById(uint256 requestId) external returns (uint256 assets);
Takes an explicit request id. Reverts if the request does not exist, is not yet finalized, or the caller is neither the controller nor an approved operator.
❌ Compatibility alias: redeem
function redeem(uint256 shares, address receiver, address controller)
external
returns (uint256 assets);
This is not a synchronous redeem. It is an ERC-4626-shaped wrapper that looks up a finalized request for controller whose share amount matches shares and claims it. If no matching finalized request exists, the call reverts. Generic ERC-4626 tooling will call this expecting a synchronous exit and be surprised. Prefer claimRequestById when you control the integration.
Use maxRedeem(controller) to check the total claimable shares for a controller. Note that maxRedeem on Olla returns claimable shares (finalized and waiting to be claimed), not "how many shares the controller could exit right now via requestRedeem". See ERC-4626 Compatibility for the full table.
Inspecting requests
| Function | Returns |
|---|---|
pendingRedeemRequest(requestId, controller) | Shares still pending for the given request (zero once finalized) |
claimableRedeemRequest(requestId, controller) | Assets claimable for the given request (non-zero once finalized) |
activeRequestIds(owner) | All request ids currently tracked for an owner |
maxRedeem(controller) | Total claimable shares across all finalized requests for a controller |
Related reading
- Function reference: OllaVault
- Sequence diagrams: User Actions
- How finalization and slashing adjustments work: WithdrawalQueue
- ERC-4626 method behavior: ERC-4626 Compatibility