Skip to content

Withdraw (Async Redeem)

Overview

King's Vault V2 uses the ERC-7540 asynchronous redeem pattern. Direct withdraw() by asset amount is disabled (always reverts with OnlyRedeemSupported()). All exit flows use the three-phase lifecycle:

  1. Request — user locks shares via requestRedeem().
  2. Execute — Keeper/Admin processes the request off-chain (cross-chain retrieval + CCIP).
  3. Claim — user calls redeem() to receive USDC.

withdraw() is Disabled

withdraw(uint256, address, address) always reverts. Frontend must never expose a withdraw-by-assets flow.


State Lifecycle

stateDiagram-v2
    [*] --> Pending : requestRedeem()
    Pending --> Executed : Keeper executes request<br/>(sets executionPrice, isExecuted=true)
    Executed --> Claimed : redeem() by user
    Claimed --> [*]

    note right of Pending
        shares are locked
        (deducted from maxRedeem)
        executionPrice = 0
        isExecuted = false
    end note

    note right of Executed
        shares remain locked
        executionPrice > 0
        isExecuted = true
        user can now claim
    end note

Phase 1: Request Redeem

sequenceDiagram
    actor User as Investor
    participant UI as Frontend
    participant Vault as KingsVault

    User->>UI: Enter shares to redeem
    UI->>Vault: maxRedeem(owner)
    Vault-->>UI: available shares (excluding existing requests)

    alt shares <= maxRedeem
        UI->>Vault: requestRedeem(shares, owner)
        Note over Vault: Create RedeemRequest:<br/>shares, exceptedPrice=markedPrice,<br/>executionPrice=0, isExecuted=false
        Vault-->>UI: requestId
        UI-->>User: "Request submitted, requestId: N"
    else shares > maxRedeem
        UI-->>User: Error: exceeds redeemable shares
    end
Step Actor Action Detail
1 User Check available shares. maxRedeem(owner) = balanceOf(owner) - sum(all request shares)
2 User Submit redeem request. requestRedeem(shares, owner) — records exceptedPrice at current marked price.
3 Vault Store request. Creates RedeemRequest struct with executionPrice = 0, isExecuted = false.
4 Vault Emit event. WithdrawQueued(owner, shares)

Phase 2: Execution (Off-Chain)

The Keeper/Admin backend handles execution. This phase is not triggered by on-chain user transactions.

Step Actor Action
1 Keeper Detects pending redeem requests.
2 Keeper Triggers HyperEVM withdrawal (Action 2: HLP → Core Spot).
3 Keeper Bridges Core Spot → HyperEVM USDC (Action 6).
4 Keeper Sends USDC back to Ethereum via CCIP.
5 Keeper Marks request as executed on-chain (isExecuted = true, sets executionPrice).

Estimated latency: 30-60 minutes (cross-chain settlement + CCIP finality).


Phase 3: Claim

sequenceDiagram
    actor User as Investor
    participant UI as Frontend
    participant Vault as KingsVault
    participant Token as USDC (ERC-20)

    User->>UI: Click "Claim"
    UI->>Vault: claimableRedeemRequest(owner)
    Vault-->>UI: claimableShares
    UI->>Vault: previewClaimableRedeem(claimableShares, owner)
    Vault-->>UI: estimatedAssets
    UI-->>User: Preview: "You will receive ~X USDC"

    User->>UI: Confirm claim
    UI->>Vault: redeem(shares, receiver, owner)
    Note over Vault: FIFO: iterate executed requests,<br/>assets += claimed * executionPrice
    Vault->>Token: transfer(receiver, assets)
    Vault->>Vault: _burn(owner, shares)
    Vault-->>UI: assets returned
    UI-->>User: "Claimed X USDC successfully"
Step Actor Action Detail
1 User Check claimable. claimableRedeemRequest(owner) — total shares in executed requests.
2 User Preview assets. previewClaimableRedeem(shares, owner) — FIFO calculation of expected USDC.
3 User Execute claim. redeem(shares, receiver, owner) — transfers USDC and burns shares.
4 Vault Source liquidity. If idle cash is insufficient, triggers _replenish(shortage) from strategy pools.

Key View Functions

Function Returns Description
maxRedeem(owner) uint256 Shares available for new requests = balanceOf - sum(all request shares)
pendingRedeemRequest(owner) uint256 Total shares in pending (not yet executed) requests
claimableRedeemRequest(owner) uint256 Total shares in executed (ready-to-claim) requests
previewClaimableRedeem(shares, owner) uint256 Estimated USDC output for given claimable shares (FIFO)

FIFO Claim Order

previewClaimableRedeem and redeem consume executed requests in First-In-First-Out order. Each request may have a different executionPrice depending on the epoch in which it was executed. Partial claims across multiple requests are supported.


Events

Event Parameters
WithdrawQueued owner (indexed), shares
Withdraw sender (indexed), receiver (indexed), owner (indexed), assets, shares

Errors

Error Trigger
OnlyRedeemSupported() Caller invoked withdraw() (disabled).
InsufficientQueuedShares(requested, available) Redeem amount exceeds claimable shares.
NoSharesToRedeem() Emergency redeem called with zero balance.

Security Considerations

  1. No Share Escrow: Shares are not locked/escrowed upon requestRedeem. They remain in the owner's balance. maxRedeem prevents double-commitment by subtracting request shares, but shares could theoretically be transferred to another address. Users should not transfer shares while requests are pending.
  2. Execution Price Divergence: The exceptedPrice (recorded at request time) may differ from the executionPrice (set at execution time). Users bear the risk of PPS change between request and execution.
  3. Irrevocable Requests: Once a CCIP cross-chain process is triggered, the withdrawal cannot be cancelled.
  4. Emergency Redeem: emergencyRedeem() is available only when isShutdown == true and allows ROLE_INVESTOR holders to exit at a pro-rata share of remaining assets.