Exits in flight

Download Markdown

Some protocols cannot pay out at once. Huma, Hastra and Solstice can put an exit in a queue: the vault asks to withdraw, and the protocol pays days later. While the vault waits, it holds a claim, meaning a right to be paid later. This page explains what happens to that claim if the protocol gets into trouble before it pays.

The examples start from the example vault: Alice holds 4,000 USDC of Senior, June holds 1,000 USDC of Junior, and 3,000 USDC is invested in one leg (one investment position of the vault). Each share is worth 1 USDC at the start. The figures were measured in automated tests on copies of the real protocols and are rounded.

How a queued exit works

What. An adapter is the small program that connects the vault to one protocol. The second version of the adapter interface (AAI v2) adds actions that complete later. The executor, the service that moves the vault's cash into and out of legs, starts one with begin_async, the protocol completes it in its own time, and anyone then calls settle_async to bring the USDC back into the vault. The open action is stored in a PendingAction account, one per leg.

Why. Without queued exits, a leg in a protocol with a waiting period could only be left by selling on a market, at a discount. A queue lets the vault get the full value back, at the cost of waiting.

How.

sequenceDiagram
    participant E as Executor
    participant V as akashi_vault program
    participant A as Adapter (AAI v2)
    participant P as Protocol
    participant K as Anyone
    E->>V: begin_async(withdraw, amount, deadline)
    V->>A: CPI aai_begin_withdraw
    A->>P: file the exit request
    A-->>V: claim value (low, mid, high)
    V->>V: create PendingAction with the claim band
    loop until the protocol pays
        K->>V: refresh_pending
        V->>A: CPI aai_value_pending
        A-->>V: claim value now (may only go down)
    end
    P-->>A: protocol completes the exit
    K->>V: settle_async
    V->>A: CPI aai_settle_withdraw (adapter proves completion)
    A-->>V: USDC into idle
    V->>V: close PendingAction

Figure: a queued exit records a claim when it starts, may only lower that claim while it waits, and closes only when the adapter proves the protocol paid.

The takeaway is that the vault never assumes a queued exit was paid. A token transfer it sees, or a deadline passing, never clears the claim. Only the adapter's settlement call can.

What the claim is worth

The claim is valued differently on each protocol, because each one owes something different while the exit waits:

ProtocolWhat the vault holds while it waitsDoes a protocol loss reduce it?
HumaPST tokens in Huma's escrow, valued at the pool's priceYes
HastraA wYLDS redemption, redeemable 1:1 for USDC (one USDC per wYLDS)No
SolsticeUSX in escrow, valued at parNo, unless USX itself depegs when it is sold

Table: how each protocol's queued exit behaves in the tests; only Huma's claim moves with a loss in the protocol.

In the examples, the executor queues an exit of 2,000 USDC out of the 3,000 USDC leg. 2,000 USDC is the smallest exit Hastra processes in its batches, so the same amount is used for every protocol. That leaves 3,000 − 2,000 = 1,000 in the normal position, and 2,000 becomes a claim.

Example 1: a loss while the exit waits

The protocol loses half of its value while the exit is queued.

HumaHastra, Solstice
The 1,000 still in the positionFalls to 500Falls to 500
The 2,000 claimFalls to 1,000Stays at 2,000
Leg value after refresh_pending and sync_vault500 + 1,000 = 1,500500 + 2,000 = 2,500
Loss booked3,000 − 1,500 = 1,500 (Junior's 1,000, then 500 from Senior)3,000 − 2,500 = 500 (Junior)
Paid when the exit completesAbout 1,000About 2,000

Table: the same 50% protocol loss costs a Huma leg three times as much as a Hastra or Solstice leg, because Huma's claim shares the loss.

In both cases the vault enters Recovery, the state in which new deposits and investments stop while an incident is handled. The claim was never valued above what was recorded when the exit began.

Example 2: the protocol never pays

The protocol stops processing exits. The executor set a deadline when it queued the exit, and in the test 31 days pass, which is beyond that deadline:

  • settle_async still fails, because the adapter cannot prove the exit was paid.
  • The claim stays open, and refresh_pending keeps valuing it at 2,000.
  • The vault keeps running. A new investor deposits 100 USDC into Junior, and Alice withdraws 500 Senior shares for 500 USDC.

The takeaway: a stuck exit does not freeze the vault, and the vault does not write the claim off on its own. If the guardian thinks the claim will never be paid, they mark the leg (A hack the protocol has not reported).

Example 3: the protocol pays short

The protocol completes the exit with 70% of what it promised, a 30% shortfall.

  • Huma pays at a price written down by 30%: about 2,000 × 0.70 = 1,400 USDC for the 2,000 claim. The 1,000 still in Huma is also written down, to 1,000 × 0.70 = 700. The vault books both shortfalls, 600 + 300 = 900, Junior first. Junior falls from 1,000 to about 100, and Senior stays at 4,000.
  • Solstice completes the exit in USX, but USX now trades at 0.70 USDC. Selling it would lose far more than the adapter's 1% per-swap limit, so the swap's minimum-output check refuses the sale, nothing moves, and the claim stays open at its recorded value.
  • Hastra cannot pay short: its complete_redeem pays the whole request 1:1 or nothing.

The takeaway: a short payment is either booked as a loss or refused. It is never hidden.

Example 4: the exit is cancelled

Huma cancels the request on its side, or the executor takes back a Hastra request with cancel_redeem. The executor calls cancel_async. The units return to the leg, the PendingAction is closed, and the claim folds back into the normal position, so the leg is again worth about 1,000 + 2,000 = 3,000. The vault is Active and the executor can allocate to the leg again.

Advanced details

  • A pending claim must be re-valued with refresh_pending after each begin or settlement, before any share-changing action. Otherwise deposits and fundings fail with PendingClaim.
  • begin_async requires deadline to be in the future (InvalidParams otherwise). The deadline is informational for the protocol and the executor; the program never clears a claim because it has passed.
  • While a leg has an open action, a second begin_async fails with PendingClaim, and so do the synchronous allocate and deallocate on that leg. A leg with an open action cannot be retired (LegNotEmpty).
  • When settle_async reports a final settlement, every unit of the request must have been settled, otherwise it fails with PendingActionIncomplete. The remaining claim it reports may not be higher than the recorded claim (InvalidActionReport).
  • Which protocols support queued exits is set per leg by configure_v2_leg, within the capabilities the adapter declares.

On this page