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.
If you’re implementing OAuth for the first time, start with 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.
Flow Diagram
Happy Path (OAuth)
Happy Path (QR Code Login)
Withdrawal Sub-Flow
States by Phase
Idle
State Hook Boolean Description Transitions Out idleisIdleFlow not started. Initial state. exchanges:loading, oauth:waiting, qrcode:waiting
Exchanges
State Hook Boolean Description Transitions Out exchanges:loadingisExchangesLoadingFetching list of supported exchanges exchanges:ready, exchanges:errorexchanges:readyisExchangesReadyExchange list loaded. User can pick one. oauth:waiting, qrcode:waitingexchanges:errorisExchangesErrorFailed to load exchanges Terminal, call cancel() and retry
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.
OAuth
State Hook Boolean Description Transitions Out oauth:waitingisOAuthWaitingPopup opened, waiting for user to authenticate oauth:processingoauth:processingisOAuthProcessingToken exchange in progress oauth:completed, oauth:error, oauth:fatal, oauth:window_closed_by_useroauth:completedisOAuthCompleteOAuth succeeded, auto-transitions to wallet loading wallet:loadingoauth:errorisOAuthErrorRecoverable error (network, timeout) Retry via startWithdrawalFlow() or cancel() oauth:fatalisOAuthFatalNon-recoverable (exchange rejected connection) cancel() onlyoauth:window_closed_by_userisOAuthWindowBeenClosedByTheUserUser 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:waitingisQRCodeWaitingRequesting QR code URL from exchange qrcode:displaying, qrcode:error, qrcode:fatalqrcode:displayingisQRCodeDisplayingQR code visible, waiting for scan qrcode:scanning, oauth:completed, qrcode:timeout, qrcode:error, qrcode:fatalqrcode:scanningisQRCodeScanningQR code scanned, completing auth oauth:completed, qrcode:error, qrcode:fatalqrcode:errorisQRCodeErrorRecoverable QR code error qrcode:waiting (via refreshQRCode())qrcode:timeoutisQRCodeTimeoutQR code expired qrcode:waiting (via refreshQRCode())qrcode:fatalisQRCodeFatalNon-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
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.
Wallet
State Hook Boolean Description Transitions Out wallet:loadingisWalletLoadingFetching wallet balances after connection wallet:ready, wallet:errorwallet:readyisWalletReadyBalances loaded, ready for quoting quote:requestingwallet:errorisWalletErrorFailed 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:requestingisQuoteLoadingRequesting withdrawal quote from exchange quote:ready, quote:errorquote:readyisQuoteReadyQuote received with fee breakdown withdraw:processing, quote:requesting (refresh), quote:expiredquote:expiredisQuoteExpiredQuote TTL elapsed quote:requesting (via requestQuote())quote:errorisQuoteErrorQuote 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
Quotes have a TTL (check quote.expiresAt). If autoRefreshQuotation is enabled in options, the hook automatically requests a fresh quote before expiry.
Withdrawal
State Hook Boolean Description Transitions Out withdraw:processingisWithdrawProcessingWithdrawal executing on exchange withdraw:completed, withdraw:error2FA, withdraw:error2FAMultiStep, withdraw:errorSMS, withdraw:errorKYC, withdraw:errorBalance, withdraw:retrying, withdraw:readyToConfirm, withdraw:blocked, withdraw:fatalwithdraw:error2FArequires2FAExchange requires single-step 2FA code withdraw:processing (via submit2FA(code))withdraw:error2FAMultiSteprequires2FAMultiStepExchange requires multi-step MFA withdraw:processing (via submit2FAMultiStep() / pollFaceVerification()), withdraw:readyToConfirmwithdraw:errorSMSrequiresSMSExchange requires SMS verification withdraw:processing (via submit2FA(code))withdraw:errorKYCrequiresKYCExchange requires KYC completion Non-recoverable via SDK, direct user to exchange withdraw:errorBalancehasInsufficientBalanceInsufficient balance for withdrawal Adjust amount, re-quote withdraw:retryingcanRetryAuto-retry in progress withdraw:processing (automatic)withdraw:readyToConfirmisReadyToConfirmAll 2FA steps verified, awaiting confirmation withdraw:processing (via confirmWithdrawal())withdraw:completedisWithdrawalCompleteWithdrawal succeeded Terminal (success) withdraw:blockedisWithdrawBlockedExchange blocked the withdrawal Terminal, non-recoverable withdraw:fatalhasFatalErrorUnrecoverable 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
withdraw:error2FA and withdraw:error2FAMultiStep are mutually exclusive and exchange-specific . See Error Handling & 2FA for the full breakdown.
Flow Control
State Hook Boolean Description Transitions Out flow:cancelledisFlowCancelledFlow 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 isIdleidleIdle isFlowCancelledflow:cancelledFlow Control
Exchanges
Boolean State Check isExchangesLoadingexchanges:loading OR standalone loadingisExchangesReadyexchanges:ready OR standalone data availableisExchangesErrorexchanges:error
OAuth
Boolean State Check isOAuthPendingoauth:waiting OR oauth:processingisOAuthWaitingoauth:waitingisOAuthProcessingoauth:processingisOAuthCompleteoauth:completedisOAuthErroroauth:error OR oauth:fatalisOAuthFataloauth:fatalisOAuthWindowBeenClosedByTheUseroauth:window_closed_by_userisWalletConnectionInvalidoauth:fatal OR qrcode:fatal
QR Code
Boolean State Check isQRCodePendingAny qrcode:* except error, fatal, timeout isQRCodeWaitingqrcode:waitingisQRCodeDisplayingqrcode:displayingisQRCodeScanningqrcode:scanningisQRCodeErrorqrcode:error OR qrcode:fatalisQRCodeFatalqrcode:fatalisQRCodeTimeoutqrcode:timeoutisQRCodeFlowContext: isQRCodeFlow === true
Wallet
Boolean State Check isWalletLoadingwallet:loadingisWalletReadywallet:readyisWalletErrorwallet:errorhasWalletNotFoundErrorwallet:error + message contains “not found”hasInvalidCredentialsErrorwallet:error + message contains “invalid” + “credential”
Quote
Boolean State Check isQuoteLoadingquote:requestingisQuoteReadyquote:readyisQuoteExpiredquote:expiredisQuoteErrorquote:errorhasAmountError(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 isWithdrawingAny active withdraw:* (not completed, fatal, or error states) isWithdrawProcessingwithdraw:processingisWithdrawalCompletewithdraw:completedisWithdrawBlockedwithdraw:blockedhasFatalErrorwithdraw:fatalrequires2FAwithdraw:error2FArequires2FAMultiStepwithdraw:error2FAMultiStepisReadyToConfirmwithdraw:readyToConfirmrequiresSMSwithdraw:errorSMSrequiresKYCwithdraw:errorKYChasInsufficientBalancewithdraw:errorBalancecanRetrywithdraw:retryingrequiresValid2FAMethodwithdraw:fatal + errorDetails.valid2FAMethods presentrequiresEmailVerificationwithdraw:fatal + message contains “email”
Multi-Step 2FA
Boolean State Check hasGoogleStepmultiStep2FA.steps contains type GOOGLEhasEmailStepmultiStep2FA.steps contains type EMAILhasFaceStepmultiStep2FA.steps contains type FACEhasSmsStepmultiStep2FA.steps contains type SMSisGoogleStepVerifiedmfa.verified.GOOGLE === true OR step status successisEmailStepVerifiedmfa.verified.EMAIL === true OR step status successisFaceStepVerifiedmfa.verified.FACE === true OR step status successisSmsStepVerifiedmfa.verified.SMS === true OR step status successallMultiStep2FAStepsVerifiedAll 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 exchangesArray<{ id, name, logoUrl, status }>Available exchanges (also top-level flow.exchanges) walletBalancesArray<{ 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) retryAttemptsnumberCurrent retry count (also flow.retryAttempts) maxRetryAttemptsnumberMax allowed retries, default 3 (also flow.maxRetryAttempts) invalid2FAAttemptsnumberConsecutive invalid 2FA submissions (also flow.invalid2FAAttempts) errorDetails{ valid2FAMethods?: string[] }Additional error context qrCodeUrlstring?QR code URL to display (also flow.qrCodeUrl) qrCodeExpiresAtnumber?QR code expiry timestamp in ms (also flow.qrCodeExpiresAt) multiStep2FAobject?Multi-step 2FA context, see Error Handling & 2FA
Multi-Step 2FA Context
When requires2FAMultiStep is true, the multiStep2FA object is populated:
Field Type Description bizNostringBusiness number for the 2FA session (also flow.multiStep2FABizNo) stepsArray<{ 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) faceQrCodeUrlstring?QR code for FACE verification (also flow.faceQrCodeUrl) faceQrCodeExpiresAtnumber?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') => PromiseAny (standalone) Fetches exchange list, sets exchanges startWithdrawalFlow(options: WithdrawalFlowOptions) => Promiseidle, exchanges:readyStarts OAuth/QR flow for the given exchange resumeWithdrawalFlow(options: ResumeWithdrawalFlowOptions) => PromiseAny Resumes a flow with a known wallet ID silentResumeWithdrawalFlow(options: SilentResumeWithdrawalFlowOptions) => PromiseAny Resumes silently (no error throw if wallet not found) requestQuote(options: QuoteRequestOptions) => Promisewallet:ready, quote:ready, quote:expiredRequests a withdrawal quote executeWithdrawal(quoteId: string) => Promisequote:readyStarts withdrawal execution submit2FA(code: string) => Promisewithdraw:error2FASubmits single-step 2FA code submit2FAMultiStep(stepType: 'GOOGLE' | 'EMAIL' | 'SMS', code: string) => Promisewithdraw:error2FAMultiStepSubmits a code for one multi-step 2FA step pollFaceVerification() => Promisewithdraw:error2FAMultiStepPolls server for FACE biometric verification status confirmWithdrawal() => Promisewithdraw:readyToConfirmConfirms withdrawal after all 2FA steps verified retryWithdrawal() => Promisewithdraw:retryingTriggers manual retry refreshQRCode() => Promiseqrcode:error, qrcode:timeoutRequests a fresh QR code cancel() => voidAny Cancels the flow, transitions to flow:cancelled
Next Steps
Error Handling & 2FA Error recovery patterns, exchange-specific 2FA behavior, and multi-step MFA deep dive
OAuth2 Integration Step-by-step implementation guide with React and Next.js
Code Samples Full working examples for Next.js, React, and more
Wallet ID How to generate and manage wallet IDs for your users