> For the complete documentation index, see [llms.txt](https://docs.kpk.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kpk.io/funds/infrastructure/onchain-accounting/assisted-balance-adapters/stakewise-v3-exit-queue.md).

# StakeWise V3 Exit Queue

An **assisted** adapter that reports the **in-flight assets owed to an account in StakeWise V3's exit queue**. Calling `enterExitQueue(shares, receiver)` moves vault shares into the queue and returns a `positionTicket`; the queued value is then invisible to NAV — a position is identified only by that ticket (emitted in the `ExitQueueEntered` event) and there is no per-account enumeration. The owner feeds each `(vault, positionTicket, timestamp)` tuple itself (`submit(address,uint256,uint256)`, keyed on `msg.sender`); the adapter then values it entirely from live on-chain reads.

* **Type:** [Assisted adapter](/funds/infrastructure/onchain-accounting/assisted-balance-adapters.md) — permissionless, `msg.sender`-keyed (`AssistedCoordCache`)
* **protocolSubId:** `keccak256("stakewise-v3-exit")`
* **Source:** [`StakeWiseV3ExitQueueAssistedBalanceAdapter.sol`](https://github.com/karpatkey/onchain-accounting/blob/main/src/balances/AssistedBalanceAdapters/StakeWiseV3ExitQueueAssistedBalanceAdapter.sol)
* **Assistant:** [`StakeWiseV3ExitQueueAssistant.sol`](https://github.com/karpatkey/onchain-accounting/blob/main/src/balances/AssistedBalanceAdapters/assistants/StakeWiseV3ExitQueueAssistant.sol)

{% hint style="warning" %}
**This is not the** [**StakeWise V3**](/funds/infrastructure/onchain-accounting/meta-balance-adapters/stakewise-v3.md) **page.** That meta-adapter reports active vault **staking** positions (`stakewise-v3-vaults`); this one reports **exit-queue** positions (`stakewise-v3-exit`) for shares already on their way out. A fund using StakeWise needs **both** to see its full exposure.
{% endhint %}

***

## Positions returned

Up to two legs **per exit ticket** that share that vault's positionId, split by claimability:

| Leg       | PositionKind | isDebt  | isLocked | Description                                                                          |
| --------- | ------------ | ------- | -------- | ------------------------------------------------------------------------------------ |
| Claimable | `Supplied`   | `false` | `false`  | Checkpointed (exited) staking asset past the claim delay, withdrawable now           |
| Locked    | `Supplied`   | `false` | `true`   | Still-queued shares (and exited-but-still-delayed assets) converted to staking asset |

Both legs are denominated in the staking asset, native ETH on mainnet (carried in `balanceAsset`), with `positionInstanceId = bytes32(positionTicket)`. A zero-amount leg is omitted; a ticket that values to 0/0 contributes no legs. The position is dropped entirely if that asset is not registered in NAVCalculator, if there are no live cached tickets, or if filtered out by `assetFilter`.

***

## Feed surface & assistant

The adapter exposes a small, permissionless, `msg.sender`-keyed surface (no roles, no `account` argument). Its coordinate is a **tuple**:

| Function                                                           | Purpose                                                                                                                                                                                                                                                             |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `submit(address vault, uint256 positionTicket, uint256 timestamp)` | Track one of the caller's own exit positions (idempotent). Reverts `InvalidCoordinate` if the vault is not StakeWise-registered, `CoordinateNotOwned` if the tuple resolves to no live owed amount for the caller, `CoordinateCacheFull` past `MAX_POSITIONS` (256) |
| `remove(address vault, uint256 positionTicket, uint256 timestamp)` | Stop tracking one of the caller's own exit positions (once claimed). Reverts `CoordinateNotCached` if untracked, `CoordinateStillLive` if still owed                                                                                                                |
| `positionsOf(address account)`                                     | View the candidate exit tuples cached for `account` (display/debug; not staleness-filtered)                                                                                                                                                                         |
| `clearAccount()`                                                   | Drop **all** of the caller's cached tuples (unconditional self-service GC)                                                                                                                                                                                          |

StakeWise exit positions have no `ownerOf`, so the on-chain ownership proxy is `calculateExitedAssets(account, …) > 0`: a tuple that isn't the caller's resolves to a zero owed amount and is rejected on `submit` (and dropped on read). `_removable` is *"no longer owed"* — a claimed/consumed ticket.

### The assistant (`StakeWiseV3ExitQueueAssistant`)

**Use the assistant in place of the StakeWise V3 vault.** Instead of entering the exit queue directly by calling the vault's `enterExitQueue` (which would leave the resulting exit ticket invisible to NAV), you delegatecall the assistant: it forwards the **same** `enterExitQueue` and, in the same transaction, `submit`s the returned `(vault, positionTicket, timestamp)` tuple to the adapter. Claiming works the same way through `claimExitedAssets`. It is a thin wrapper over StakeWise's own exit-queue channel — same action, plus the bookkeeping that makes the queued value visible to NAV.

| Entrypoint                                                            | Does                                                                                          | Adapter call                                           |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `enterExitQueue(vault, shares)`                                       | Enters the queue with `receiver = the Safe`, burning the Safe's shares and returning a ticket | `submit(vault, positionTicket, block.timestamp)`       |
| `claimExitedAssets(vault, positionTicket, timestamp, exitQueueIndex)` | Claims exited assets to the Safe, then tries to `remove` the ticket                           | `remove(vault, positionTicket, timestamp)` — see below |

Both are `onlyDelegateCall`. Under delegatecall `address(this)` is the Safe, so the vault sees `msg.sender = Safe` (which StakeWise's claim path requires — vault shares are non-transferable and the receiver must be the claimer) and the adapter caches against that same Safe. On claim the ticket is deregistered **best-effort** — removal never blocks the claim itself: a partial claim leaves the position still owed (adapter reverts `CoordinateStillLive` → keep it tracked), and an untracked ticket reverts `CoordinateNotCached` (nothing to untrack); the assistant **swallows** both and re-throws anything else. So the ticket is effectively dropped only once fully consumed.

<figure><img src="/files/snjR0Ibb4PqtveT8nbtR" alt="Flow to enter the StakeWise exit queue: the Manager Safe drives the Portfolio Safe to delegatecall the assistant, which enters the vault&#x27;s exit queue (receiver = Safe) and submits the (vault, ticket, timestamp) tuple to the adapter."><figcaption><p><strong>Entering the exit queue.</strong> The Safe delegatecalls the assistant, which enters the vault's exit queue with <code>receiver = the Safe</code> and <code>submit</code>s the resulting <code>(vault, positionTicket, timestamp)</code> tuple to the adapter — in one transaction.</p></figcaption></figure>

<figure><img src="/files/Tg0YDv6PDloY74iHrZGY" alt="Flow to claim from the StakeWise exit queue: the assistant claims exited assets to the Safe then best-effort removes the ticket — kept if still owed (CoordinateStillLive), dropped once fully consumed."><figcaption><p><strong>Claiming.</strong> The assistant claims exited assets to the Safe, then best-effort <code>remove</code>s the ticket: a partial claim keeps it tracked (<code>CoordinateStillLive</code>); it is dropped only once fully consumed. A re-queued remainder lands under a <em>new</em> ticket the owner must <code>submit</code> separately.</p></figcaption></figure>

{% hint style="info" %}
**Re-queued remainder lands under a new ticket.** When StakeWise re-queues a not-yet-checkpointed remainder, it does so under a **new** `positionTicket` — a distinct coordinate. The assistant only manages the ticket it was given; the caller must `submit` the new ticket separately (from the vault's re-queue event).
{% endhint %}

***

## Balance calculation

<figure><img src="/files/F3D5gmS0srMikFgsQTcz" alt="Flowchart of the StakeWise V3 Exit Queue balance adapter: cached exit tuples are valued via calculateExitedAssets into a PositionBalance entry."><figcaption><p>StakeWise V3 Exit Queue adapter — how the underlying balances reported to the NAV Calculator are derived.</p></figcaption></figure>

For each cached tuple the adapter values the position by calling the vault with **`receiver = account`**, splitting it into a claimable and a locked portion and emitting up to two legs per ticket:

```
for each cached (vault, positionTicket, timestamp):
    idx = vault.getExitQueueIndex(positionTicket)                 // -1 ⇒ not yet checkpointed
    (leftShares, _, exited) = vault.calculateExitedAssets(account, positionTicket, timestamp, idx)
    queued = leftShares > 0 ? vault.convertToAssets(leftShares) : 0   // still-queued, valued at current rate
    if block.timestamp >= timestamp + EXIT_CLAIM_DELAY:
        claimable = exited; locked = queued
    else:
        claimable = 0;      locked = exited + queued             // exited but still in claim delay ⇒ locked
    emit claimable leg (isLocked=false) if claimable > 0
    emit locked    leg (isLocked=true)  if locked    > 0
```

When `getExitQueueIndex(ticket) < 0` the position is not yet checkpointed, so it is fully pending and contributes only to the locked leg. Otherwise `calculateExitedAssets` splits it: the already-exited assets and the still-queued `leftShares` (converted at the current rate via `convertToAssets`). The exited portion is reported claimable (`isLocked=false`) **only once StakeWise's post-checkpoint claim delay has elapsed** (`block.timestamp >= timestamp + EXIT_CLAIM_DELAY`); before that it stays locked. The still-queued remainder is always locked (`isLocked=true`). Both legs carry `positionInstanceId = bytes32(positionTicket)`. Because the call passes `receiver = account`, a tuple that does not belong to `account` resolves to a zero exit request and is dropped — this is the on-chain ownership check. Each cached tuple is valued in isolation behind the base's per-coordinate self-`staticcall` (try/catch to 0), so one bad tuple never drops the rest.

***

## Identity

* **positionId:** `abi.encode(vault)` (static, **per vault**; the StakeWise V3 vault address)
* **positionKind:** `Supplied`
* **positionInstanceId:** `bytes32(positionTicket)` (the per-ticket exit coordinate; ephemeral — both legs of a ticket carry the same instance id)
* **Labels:** the adapter implements `positionLabels(positionId)`, returning `["Exit Queue", "<receiptSymbol>"]` — e.g. `["Exit Queue", "osETH"]` on mainnet — falling back to the bare `["Exit Queue"]` if `receiptSymbol` is empty (surfaced only on the verbose read path; full breadcrumb = `["StakeWise V3", "Exit Queue", "osETH"]`). The receipt symbol comes from the `receiptSymbol_` constructor arg, naming the receipt/LST token (osETH/osGNO), not the payout asset. The ticket id is **not** in the label — it rides in `positionInstanceId`.

A vault's tickets share that vault's positionId; a ticket's claimable and locked legs are distinguished for display by the `isLocked` field (`false` = exited and past the claim delay/withdrawable now, `true` = still queued or exited-but-delayed). The owed staking asset (native ETH on mainnet) rides in `balanceAsset`. The reconciliation key is `keccak256(abi.encode(chainId, protocolSubId, positionId, balanceAsset.asset, positionKind))` — it **excludes** both `isLocked` and `positionInstanceId`, so per vault all of an account's tickets (claimable and locked alike) reconcile into **one coordinate** and sum; read `isLocked` per leg for the claimable-vs-locked split.

***

## Constructor

```solidity
constructor(
    address vaultsRegistry_,
    address underlyingToken_,
    string memory receiptSymbol_,
    address navCalculator_,
    uint256 exitClaimDelay_
)
```

| Parameter          | Description                                                                                                                                                                                    |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vaultsRegistry_`  | StakeWise `VaultsRegistry` — gates which vaults the cache accepts (write-time hygiene). Must be a contract                                                                                     |
| `underlyingToken_` | Staking asset the queue pays out — native ETH (sentinel) on mainnet. All accepted vaults must settle in this asset. Must be non-zero                                                           |
| `receiptSymbol_`   | Display symbol of StakeWise's receipt/LST token for this chain (`"osETH"` mainnet, `"osGNO"` Gnosis); feeds the label token. May be empty — label then falls back to the bare `["Exit Queue"]` |
| `navCalculator_`   | NAVCalculator address, used for asset-registry metadata. Must be a contract                                                                                                                    |
| `exitClaimDelay_`  | StakeWise's post-checkpoint claim delay in seconds (24h = `86400` on V3 mainnet). Gates when an exited portion becomes claimable. Must be non-zero                                             |

There is **no** `admin_` / `assistant_` argument — the family is permissionless. The paired `StakeWiseV3ExitQueueAssistant` is deployed separately, pinned to this adapter.

{% hint style="info" %}
**Mainnet only.** Every StakeWise V3 vault on mainnet settles in ETH; do not deploy against a non-ETH `underlyingToken` on a chain with a different staking asset.
{% endhint %}

***

## Registration

Registered as a plain adapter with `addBalanceAdapters([adapter])` and removed with `removeBalanceAdapters([adapter])`. The coordinate cache is then kept current by the position owner's own transactions — typically via the assistant, delegatecalled inside the same `enterExitQueue` / `claimExitedAssets`.


---

# 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.kpk.io/funds/infrastructure/onchain-accounting/assisted-balance-adapters/stakewise-v3-exit-queue.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.
