> ## Documentation Index
> Fetch the complete documentation index at: https://bluvo.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# State Machine

> Complete reference for the @bluvo/sdk-ts state machine, all 35 flow states, hook booleans, context data, and callable actions

## Overview

The `useBluvoFlow` hook from `@bluvo/react` is powered by a finite state machine that orchestrates the entire withdrawal lifecycle, from exchange selection through OAuth, wallet loading, quoting, and withdrawal execution (including 2FA). Every state transition is deterministic: given a state and an action, the next state is always predictable.

Your UI renders based on the current state. The hook exposes each state as a boolean property (e.g., `flow.isOAuthPending`, `flow.requires2FA`), so you never need to inspect raw state strings directly.

<Tip>
  If you're implementing OAuth for the first time, start with [OAuth2 Integration](/learn/oauth2-integration) for a step-by-step walkthrough. This page is a reference for when you need to look up specific states, booleans, or transitions.
</Tip>

***

## Flow Diagram

### Happy Path (OAuth)

```mermaid theme={null}
%%{init: {'flowchart': {'defaultRenderer': 'elk'}}}%%
flowchart TD
    idle["idle"]
    exc_loading["exchanges:loading"]
    exc_ready["exchanges:ready"]
    oauth_waiting["oauth:waiting"]
    oauth_processing["oauth:processing"]
    oauth_completed["oauth:completed"]
    oauth_error["oauth:error"]:::error
    oauth_fatal["oauth:fatal"]:::error
    oauth_closed["oauth:window_closed_by_user"]:::error
    wallet_loading["wallet:loading"]
    wallet_ready["wallet:ready"]
    quote_requesting["quote:requesting"]
    quote_ready["quote:ready"]
    withdraw_processing["withdraw:processing"]
    withdraw_completed["withdraw:completed"]:::success

    idle --> exc_loading
    exc_loading --> exc_ready
    exc_ready --> oauth_waiting
    idle --> oauth_waiting
    oauth_waiting --> oauth_processing
    oauth_processing --> oauth_completed
    oauth_processing --> oauth_error
    oauth_processing --> oauth_fatal
    oauth_processing --> oauth_closed
    oauth_completed --> wallet_loading
    wallet_loading --> wallet_ready
    wallet_ready --> quote_requesting
    quote_requesting --> quote_ready
    quote_ready --> withdraw_processing
    withdraw_processing --> withdraw_completed

    classDef error fill:#fca5a5,stroke:#ef4444,color:#7f1d1d
    classDef success fill:#86efac,stroke:#22c55e,color:#14532d
```

### Happy Path (QR Code Login)

```mermaid theme={null}
flowchart LR
    idle["idle"]
    qr_waiting["qrcode:waiting"]
    qr_displaying["qrcode:displaying"]
    qr_scanning["qrcode:scanning"]
    oauth_completed["oauth:completed"]
    wallet_loading["wallet:loading"]
    more["..."]
    qr_timeout["qrcode:timeout"]:::error
    qr_error["qrcode:error"]:::error
    qr_fatal["qrcode:fatal"]:::error

    idle --> qr_waiting
    qr_waiting --> qr_displaying
    qr_displaying --> qr_scanning
    qr_scanning --> oauth_completed
    oauth_completed --> wallet_loading
    wallet_loading --> more
    qr_displaying --> qr_timeout
    qr_timeout -- "refreshQRCode" --> qr_waiting
    qr_displaying --> qr_error
    qr_error -- "refreshQRCode" --> qr_waiting
    qr_displaying --> qr_fatal

    classDef error fill:#fca5a5,stroke:#ef4444,color:#7f1d1d
```

### Withdrawal Sub-Flow

```mermaid theme={null}
%%{init: {'flowchart': {'defaultRenderer': 'elk'}}}%%
flowchart TD
    quote_ready["quote:ready"]
    processing["withdraw:processing"]
    completed["withdraw:completed"]:::success
    error2fa["withdraw:error2FA"]
    error2fa_multi["withdraw:error2FAMultiStep"]
    ready_confirm["withdraw:readyToConfirm"]
    error_sms["withdraw:errorSMS"]
    error_kyc["withdraw:errorKYC"]:::error
    error_balance["withdraw:errorBalance"]:::error
    retrying["withdraw:retrying"]
    blocked["withdraw:blocked"]:::error
    fatal["withdraw:fatal"]:::error

    quote_ready --> processing
    processing --> completed
    processing --> error2fa
    error2fa -- "submit2FA" --> processing
    processing --> error2fa_multi
    error2fa_multi -- "submit2FAMultiStep" --> processing
    error2fa_multi --> ready_confirm
    ready_confirm -- "confirmWithdrawal" --> processing
    processing --> error_sms
    error_sms -- "submitSMS" --> processing
    processing --> error_kyc
    processing --> error_balance
    processing --> retrying
    retrying -- "auto" --> processing
    processing --> blocked
    processing --> fatal

    classDef error fill:#fca5a5,stroke:#ef4444,color:#7f1d1d
    classDef success fill:#86efac,stroke:#22c55e,color:#14532d
```

***

## States by Phase

### Idle

| State | Hook Boolean | Description | Transitions Out |
| - | - | - | - |
| `idle` | `isIdle` | Flow not started. Initial state. | `exchanges:loading`, `oauth:waiting`, `qrcode:waiting` |

***

### Exchanges

| State | Hook Boolean | Description | Transitions Out |
| - | - | - | - |
| `exchanges:loading` | `isExchangesLoading` | Fetching list of supported exchanges | `exchanges:ready`, `exchanges:error` |
| `exchanges:ready` | `isExchangesReady` | Exchange list loaded. User can pick one. | `oauth:waiting`, `qrcode:waiting` |
| `exchanges:error` | `isExchangesError` | Failed to load exchanges | Terminal, call `cancel()` and retry |

<Tip>
  `listExchanges()` manages its own loading state independently of the flow machine. You can call it before starting a flow, `isExchangesReady` becomes `true` when either the machine or the standalone call has data.
</Tip>

***

### OAuth

| State | Hook Boolean | Description | Transitions Out |
| - | - | - | - |
| `oauth:waiting` | `isOAuthWaiting` | Popup opened, waiting for user to authenticate | `oauth:processing` |
| `oauth:processing` | `isOAuthProcessing` | Token exchange in progress | `oauth:completed`, `oauth:error`, `oauth:fatal`, `oauth:window_closed_by_user` |
| `oauth:completed` | `isOAuthComplete` | OAuth succeeded, auto-transitions to wallet loading | `wallet:loading` |
| `oauth:error` | `isOAuthError` | Recoverable error (network, timeout) | Retry via `startWithdrawalFlow()` or `cancel()` |
| `oauth:fatal` | `isOAuthFatal` | Non-recoverable (exchange rejected connection) | `cancel()` only |
| `oauth:window_closed_by_user` | `isOAuthWindowBeenClosedByTheUser` | User closed the popup manually | Retry via `startWithdrawalFlow()` or `cancel()` |

**Compound booleans:**

* `isOAuthPending` = `oauth:waiting` OR `oauth:processing`
* `isOAuthError` = `oauth:error` OR `oauth:fatal`
* `isWalletConnectionInvalid` = `oauth:fatal` OR `qrcode:fatal`

***

### QR Code

Used for exchanges that support QR-based login (e.g., Binance mobile app scanning).

| State | Hook Boolean | Description | Transitions Out |
| - | - | - | - |
| `qrcode:waiting` | `isQRCodeWaiting` | Requesting QR code URL from exchange | `qrcode:displaying`, `qrcode:error`, `qrcode:fatal` |
| `qrcode:displaying` | `isQRCodeDisplaying` | QR code visible, waiting for scan | `qrcode:scanning`, `oauth:completed`, `qrcode:timeout`, `qrcode:error`, `qrcode:fatal` |
| `qrcode:scanning` | `isQRCodeScanning` | QR code scanned, completing auth | `oauth:completed`, `qrcode:error`, `qrcode:fatal` |
| `qrcode:error` | `isQRCodeError` | Recoverable QR code error | `qrcode:waiting` (via `refreshQRCode()`) |
| `qrcode:timeout` | `isQRCodeTimeout` | QR code expired | `qrcode:waiting` (via `refreshQRCode()`) |
| `qrcode:fatal` | `isQRCodeFatal` | Non-recoverable QR code error | `cancel()` only |

**Compound booleans:**

* `isQRCodePending` = any `qrcode:*` state except error, fatal, timeout
* `isQRCodeError` = `qrcode:error` OR `qrcode:fatal`

**Context data available:**

* `qrCodeUrl`, the URL to render as a QR code
* `qrCodeExpiresAt`, Unix timestamp (ms) when the QR code expires
* `qrCodeStatus`, current status string from the exchange
* `isQRCodeFlow`, `true` when using QR code auth instead of OAuth popup

<Tip>
  After a successful QR code scan, the flow transitions to `oauth:completed` and reuses the same wallet-loading path as OAuth. From `wallet:loading` onward, the flow is identical regardless of authentication method.
</Tip>

***

### Wallet

| State | Hook Boolean | Description | Transitions Out |
| - | - | - | - |
| `wallet:loading` | `isWalletLoading` | Fetching wallet balances after connection | `wallet:ready`, `wallet:error` |
| `wallet:ready` | `isWalletReady` | Balances loaded, ready for quoting | `quote:requesting` |
| `wallet:error` | `isWalletError` | Failed to load wallet | Terminal, call `cancel()` and retry |

**Error detection helpers:**

* `hasWalletNotFoundError`, wallet ID no longer valid (deleted or expired)
* `hasInvalidCredentialsError`, exchange tokens revoked; user must re-authenticate

***

### Quote

| State | Hook Boolean | Description | Transitions Out |
| - | - | - | - |
| `quote:requesting` | `isQuoteLoading` | Requesting withdrawal quote from exchange | `quote:ready`, `quote:error` |
| `quote:ready` | `isQuoteReady` | Quote received with fee breakdown | `withdraw:processing`, `quote:requesting` (refresh), `quote:expired` |
| `quote:expired` | `isQuoteExpired` | Quote TTL elapsed | `quote:requesting` (via `requestQuote()`) |
| `quote:error` | `isQuoteError` | Quote request failed | Retry via `requestQuote()` |

**Error detection helpers:**

* `hasAmountError`, amount below minimum or above maximum
* `hasAddressError`, invalid destination address format
* `hasNetworkError`, unsupported or invalid network

<Tip>
  Quotes have a TTL (check `quote.expiresAt`). If `autoRefreshQuotation` is enabled in options, the hook automatically requests a fresh quote before expiry.
</Tip>

***

### Withdrawal

| State | Hook Boolean | Description | Transitions Out |
| - | - | - | - |
| `withdraw:processing` | `isWithdrawProcessing` | Withdrawal executing on exchange | `withdraw:completed`, `withdraw:error2FA`, `withdraw:error2FAMultiStep`, `withdraw:errorSMS`, `withdraw:errorKYC`, `withdraw:errorBalance`, `withdraw:retrying`, `withdraw:readyToConfirm`, `withdraw:blocked`, `withdraw:fatal` |
| `withdraw:error2FA` | `requires2FA` | Exchange requires single-step 2FA code | `withdraw:processing` (via `submit2FA(code)`) |
| `withdraw:error2FAMultiStep` | `requires2FAMultiStep` | Exchange requires multi-step MFA | `withdraw:processing` (via `submit2FAMultiStep()` / `pollFaceVerification()`), `withdraw:readyToConfirm` |
| `withdraw:errorSMS` | `requiresSMS` | Exchange requires SMS verification | `withdraw:processing` (via `submit2FA(code)`) |
| `withdraw:errorKYC` | `requiresKYC` | Exchange requires KYC completion | Non-recoverable via SDK, direct user to exchange |
| `withdraw:errorBalance` | `hasInsufficientBalance` | Insufficient balance for withdrawal | Adjust amount, re-quote |
| `withdraw:retrying` | `canRetry` | Auto-retry in progress | `withdraw:processing` (automatic) |
| `withdraw:readyToConfirm` | `isReadyToConfirm` | All 2FA steps verified, awaiting confirmation | `withdraw:processing` (via `confirmWithdrawal()`) |
| `withdraw:completed` | `isWithdrawalComplete` | Withdrawal succeeded | Terminal (success) |
| `withdraw:blocked` | `isWithdrawBlocked` | Exchange blocked the withdrawal | Terminal, non-recoverable |
| `withdraw:fatal` | `hasFatalError` | Unrecoverable withdrawal error | Terminal |

**Compound booleans:**

* `isWithdrawing` = any `withdraw:*` state that is active (not completed, fatal, or error states)
* `requiresValid2FAMethod` = `withdraw:fatal` AND `errorDetails.valid2FAMethods` is present

<Warning>
  `withdraw:error2FA` and `withdraw:error2FAMultiStep` are **mutually exclusive** and **exchange-specific**. See [Error Handling & 2FA](/learn/sdk/error-handling) for the full breakdown.
</Warning>

***

### Flow Control

| State | Hook Boolean | Description | Transitions Out |
| - | - | - | - |
| `flow:cancelled` | `isFlowCancelled` | Flow was cancelled by the user | Terminal, start a new flow |

`cancel()` can be called from **any** state. It disposes the withdrawal machine and resets to `flow:cancelled`.

***

## Hook Booleans Quick Reference

### General

| Boolean | State Check | Phase |
| - | - | - |
| `isIdle` | `idle` | Idle |
| `isFlowCancelled` | `flow:cancelled` | Flow Control |

### Exchanges

| Boolean | State Check |
| - | - |
| `isExchangesLoading` | `exchanges:loading` OR standalone loading |
| `isExchangesReady` | `exchanges:ready` OR standalone data available |
| `isExchangesError` | `exchanges:error` |

### OAuth

| Boolean | State Check |
| - | - |
| `isOAuthPending` | `oauth:waiting` OR `oauth:processing` |
| `isOAuthWaiting` | `oauth:waiting` |
| `isOAuthProcessing` | `oauth:processing` |
| `isOAuthComplete` | `oauth:completed` |
| `isOAuthError` | `oauth:error` OR `oauth:fatal` |
| `isOAuthFatal` | `oauth:fatal` |
| `isOAuthWindowBeenClosedByTheUser` | `oauth:window_closed_by_user` |
| `isWalletConnectionInvalid` | `oauth:fatal` OR `qrcode:fatal` |

### QR Code

| Boolean | State Check |
| - | - |
| `isQRCodePending` | Any `qrcode:*` except error, fatal, timeout |
| `isQRCodeWaiting` | `qrcode:waiting` |
| `isQRCodeDisplaying` | `qrcode:displaying` |
| `isQRCodeScanning` | `qrcode:scanning` |
| `isQRCodeError` | `qrcode:error` OR `qrcode:fatal` |
| `isQRCodeFatal` | `qrcode:fatal` |
| `isQRCodeTimeout` | `qrcode:timeout` |
| `isQRCodeFlow` | Context: `isQRCodeFlow === true` |

### Wallet

| Boolean | State Check |
| - | - |
| `isWalletLoading` | `wallet:loading` |
| `isWalletReady` | `wallet:ready` |
| `isWalletError` | `wallet:error` |
| `hasWalletNotFoundError` | `wallet:error` + message contains "not found" |
| `hasInvalidCredentialsError` | `wallet:error` + message contains "invalid" + "credential" |

### Quote

| Boolean | State Check |
| - | - |
| `isQuoteLoading` | `quote:requesting` |
| `isQuoteReady` | `quote:ready` |
| `isQuoteExpired` | `quote:expired` |
| `isQuoteError` | `quote:error` |
| `hasAmountError` | (`quote:error` OR `withdraw:fatal`) + message contains amount/minimum/maximum |
| `hasAddressError` | (`quote:error` OR `withdraw:fatal`) + message contains address/invalid destination |
| `hasNetworkError` | (`quote:error` OR `withdraw:fatal`) + message contains network |

### Withdrawal

| Boolean | State Check |
| - | - |
| `isWithdrawing` | Any active `withdraw:*` (not completed, fatal, or error states) |
| `isWithdrawProcessing` | `withdraw:processing` |
| `isWithdrawalComplete` | `withdraw:completed` |
| `isWithdrawBlocked` | `withdraw:blocked` |
| `hasFatalError` | `withdraw:fatal` |
| `requires2FA` | `withdraw:error2FA` |
| `requires2FAMultiStep` | `withdraw:error2FAMultiStep` |
| `isReadyToConfirm` | `withdraw:readyToConfirm` |
| `requiresSMS` | `withdraw:errorSMS` |
| `requiresKYC` | `withdraw:errorKYC` |
| `hasInsufficientBalance` | `withdraw:errorBalance` |
| `canRetry` | `withdraw:retrying` |
| `requiresValid2FAMethod` | `withdraw:fatal` + `errorDetails.valid2FAMethods` present |
| `requiresEmailVerification` | `withdraw:fatal` + message contains "email" |

### Multi-Step 2FA

| Boolean | State Check |
| - | - |
| `hasGoogleStep` | `multiStep2FA.steps` contains type `GOOGLE` |
| `hasEmailStep` | `multiStep2FA.steps` contains type `EMAIL` |
| `hasFaceStep` | `multiStep2FA.steps` contains type `FACE` |
| `hasSmsStep` | `multiStep2FA.steps` contains type `SMS` |
| `isGoogleStepVerified` | `mfa.verified.GOOGLE === true` OR step status `success` |
| `isEmailStepVerified` | `mfa.verified.EMAIL === true` OR step status `success` |
| `isFaceStepVerified` | `mfa.verified.FACE === true` OR step status `success` |
| `isSmsStepVerified` | `mfa.verified.SMS === true` OR step status `success` |
| `allMultiStep2FAStepsVerified` | All required steps have `mfa.verified[type] === true` |

***

## Context Data Reference

The `flow.context` object (and shortcut properties on the hook return) carry data through the flow:

| Field | Type | Description |
| - | - | - |
| `exchanges` | `Array<{ id, name, logoUrl, status }>` | Available exchanges (also top-level `flow.exchanges`) |
| `walletBalances` | `Array<{ asset, balance, balanceInFiat?, networks? }>` | Wallet balances with network details (also `flow.walletBalances`) |
| `quote` | `{ id, asset, amount, estimatedFee, estimatedTotal, amountWithFeeInFiat, amountNoFeeInFiat, estimatedFeeInFiat, additionalInfo, expiresAt }` | Current quote (also `flow.quote`) |
| `withdrawal` | `{ id, status, transactionId? }` | Withdrawal result (also `flow.withdrawal`) |
| `retryAttempts` | `number` | Current retry count (also `flow.retryAttempts`) |
| `maxRetryAttempts` | `number` | Max allowed retries, default 3 (also `flow.maxRetryAttempts`) |
| `invalid2FAAttempts` | `number` | Consecutive invalid 2FA submissions (also `flow.invalid2FAAttempts`) |
| `errorDetails` | `{ valid2FAMethods?: string[] }` | Additional error context |
| `qrCodeUrl` | `string?` | QR code URL to display (also `flow.qrCodeUrl`) |
| `qrCodeExpiresAt` | `number?` | QR code expiry timestamp in ms (also `flow.qrCodeExpiresAt`) |
| `multiStep2FA` | `object?` | Multi-step 2FA context, see [Error Handling & 2FA](/learn/sdk/error-handling) |

### Multi-Step 2FA Context

When `requires2FAMultiStep` is `true`, the `multiStep2FA` object is populated:

| Field | Type | Description |
| - | - | - |
| `bizNo` | `string` | Business number for the 2FA session (also `flow.multiStep2FABizNo`) |
| `steps` | `Array<{ type, status, required, metadata? }>` | Verification steps (also `flow.multiStep2FASteps`) |
| `relation` | `'AND' \| 'OR'` | Whether all steps are required or any one suffices (also `flow.multiStep2FARelation`) |
| `collectedCodes` | `{ twofa?, emailCode?, smsCode? }` | Codes submitted so far (also `flow.collectedMultiStep2FACodes`) |
| `faceQrCodeUrl` | `string?` | QR code for FACE verification (also `flow.faceQrCodeUrl`) |
| `faceQrCodeExpiresAt` | `number?` | FACE QR code expiry timestamp (also `flow.faceQrCodeExpiresAt`) |
| `mfa.verified` | `{ GOOGLE?, EMAIL?, FACE?, SMS? }` | Primary source of truth for step verification status (also `flow.mfaVerified`) |

***

## Actions Reference

All actions are async callbacks returned by the `useBluvoFlow` hook.

| Action | Signature | From States | Effect |
| - | - | - | - |
| `listExchanges` | `(status?: 'live' \| 'offline' \| 'maintenance' \| 'coming_soon') => Promise` | Any (standalone) | Fetches exchange list, sets `exchanges` |
| `startWithdrawalFlow` | `(options: WithdrawalFlowOptions) => Promise` | `idle`, `exchanges:ready` | Starts OAuth/QR flow for the given exchange |
| `resumeWithdrawalFlow` | `(options: ResumeWithdrawalFlowOptions) => Promise` | Any | Resumes a flow with a known wallet ID |
| `silentResumeWithdrawalFlow` | `(options: SilentResumeWithdrawalFlowOptions) => Promise` | Any | Resumes silently (no error throw if wallet not found) |
| `requestQuote` | `(options: QuoteRequestOptions) => Promise` | `wallet:ready`, `quote:ready`, `quote:expired` | Requests a withdrawal quote |
| `executeWithdrawal` | `(quoteId: string) => Promise` | `quote:ready` | Starts withdrawal execution |
| `submit2FA` | `(code: string) => Promise` | `withdraw:error2FA` | Submits single-step 2FA code |
| `submit2FAMultiStep` | `(stepType: 'GOOGLE' \| 'EMAIL' \| 'SMS', code: string) => Promise` | `withdraw:error2FAMultiStep` | Submits a code for one multi-step 2FA step |
| `pollFaceVerification` | `() => Promise` | `withdraw:error2FAMultiStep` | Polls server for FACE biometric verification status |
| `confirmWithdrawal` | `() => Promise` | `withdraw:readyToConfirm` | Confirms withdrawal after all 2FA steps verified |
| `retryWithdrawal` | `() => Promise` | `withdraw:retrying` | Triggers manual retry |
| `refreshQRCode` | `() => Promise` | `qrcode:error`, `qrcode:timeout` | Requests a fresh QR code |
| `cancel` | `() => void` | Any | Cancels the flow, transitions to `flow:cancelled` |

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Error Handling & 2FA" icon="triangle-exclamation" href="/learn/sdk/error-handling">
    Error recovery patterns, exchange-specific 2FA behavior, and multi-step MFA deep dive
  </Card>

  <Card title="OAuth2 Integration" icon="right-left" href="/learn/oauth2-integration">
    Step-by-step implementation guide with React and Next.js
  </Card>

  <Card title="Code Samples" icon="code" href="https://github.com/bluvoinc/awesome/">
    Full working examples for Next.js, React, and more
  </Card>

  <Card title="Wallet ID" icon="wallet" href="/learn/wallet-id">
    How to generate and manage wallet IDs for your users
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.