Skip to main content

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

StateHook BooleanDescriptionTransitions Out
idleisIdleFlow not started. Initial state.exchanges:loading, oauth:waiting, qrcode:waiting

Exchanges

StateHook BooleanDescriptionTransitions Out
exchanges:loadingisExchangesLoadingFetching list of supported exchangesexchanges:ready, exchanges:error
exchanges:readyisExchangesReadyExchange list loaded. User can pick one.oauth:waiting, qrcode:waiting
exchanges:errorisExchangesErrorFailed to load exchangesTerminal, 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

StateHook BooleanDescriptionTransitions Out
oauth:waitingisOAuthWaitingPopup opened, waiting for user to authenticateoauth:processing
oauth:processingisOAuthProcessingToken exchange in progressoauth:completed, oauth:error, oauth:fatal, oauth:window_closed_by_user
oauth:completedisOAuthCompleteOAuth succeeded, auto-transitions to wallet loadingwallet:loading
oauth:errorisOAuthErrorRecoverable error (network, timeout)Retry via startWithdrawalFlow() or cancel()
oauth:fatalisOAuthFatalNon-recoverable (exchange rejected connection)cancel() only
oauth:window_closed_by_userisOAuthWindowBeenClosedByTheUserUser closed the popup manuallyRetry 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).
StateHook BooleanDescriptionTransitions Out
qrcode:waitingisQRCodeWaitingRequesting QR code URL from exchangeqrcode:displaying, qrcode:error, qrcode:fatal
qrcode:displayingisQRCodeDisplayingQR code visible, waiting for scanqrcode:scanning, oauth:completed, qrcode:timeout, qrcode:error, qrcode:fatal
qrcode:scanningisQRCodeScanningQR code scanned, completing authoauth:completed, qrcode:error, qrcode:fatal
qrcode:errorisQRCodeErrorRecoverable QR code errorqrcode:waiting (via refreshQRCode())
qrcode:timeoutisQRCodeTimeoutQR code expiredqrcode:waiting (via refreshQRCode())
qrcode:fatalisQRCodeFatalNon-recoverable QR code errorcancel() 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

StateHook BooleanDescriptionTransitions Out
wallet:loadingisWalletLoadingFetching wallet balances after connectionwallet:ready, wallet:error
wallet:readyisWalletReadyBalances loaded, ready for quotingquote:requesting
wallet:errorisWalletErrorFailed to load walletTerminal, 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

StateHook BooleanDescriptionTransitions Out
quote:requestingisQuoteLoadingRequesting withdrawal quote from exchangequote:ready, quote:error
quote:readyisQuoteReadyQuote received with fee breakdownwithdraw:processing, quote:requesting (refresh), quote:expired
quote:expiredisQuoteExpiredQuote TTL elapsedquote:requesting (via requestQuote())
quote:errorisQuoteErrorQuote request failedRetry 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

StateHook BooleanDescriptionTransitions Out
withdraw:processingisWithdrawProcessingWithdrawal executing on exchangewithdraw:completed, withdraw:error2FA, withdraw:error2FAMultiStep, withdraw:errorSMS, withdraw:errorKYC, withdraw:errorBalance, withdraw:retrying, withdraw:readyToConfirm, withdraw:blocked, withdraw:fatal
withdraw:error2FArequires2FAExchange requires single-step 2FA codewithdraw:processing (via submit2FA(code))
withdraw:error2FAMultiSteprequires2FAMultiStepExchange requires multi-step MFAwithdraw:processing (via submit2FAMultiStep() / pollFaceVerification()), withdraw:readyToConfirm
withdraw:errorSMSrequiresSMSExchange requires SMS verificationwithdraw:processing (via submit2FA(code))
withdraw:errorKYCrequiresKYCExchange requires KYC completionNon-recoverable via SDK, direct user to exchange
withdraw:errorBalancehasInsufficientBalanceInsufficient balance for withdrawalAdjust amount, re-quote
withdraw:retryingcanRetryAuto-retry in progresswithdraw:processing (automatic)
withdraw:readyToConfirmisReadyToConfirmAll 2FA steps verified, awaiting confirmationwithdraw:processing (via confirmWithdrawal())
withdraw:completedisWithdrawalCompleteWithdrawal succeededTerminal (success)
withdraw:blockedisWithdrawBlockedExchange blocked the withdrawalTerminal, non-recoverable
withdraw:fatalhasFatalErrorUnrecoverable withdrawal errorTerminal
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

StateHook BooleanDescriptionTransitions Out
flow:cancelledisFlowCancelledFlow was cancelled by the userTerminal, 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

BooleanState CheckPhase
isIdleidleIdle
isFlowCancelledflow:cancelledFlow Control

Exchanges

BooleanState Check
isExchangesLoadingexchanges:loading OR standalone loading
isExchangesReadyexchanges:ready OR standalone data available
isExchangesErrorexchanges:error

OAuth

BooleanState Check
isOAuthPendingoauth:waiting OR oauth:processing
isOAuthWaitingoauth:waiting
isOAuthProcessingoauth:processing
isOAuthCompleteoauth:completed
isOAuthErroroauth:error OR oauth:fatal
isOAuthFataloauth:fatal
isOAuthWindowBeenClosedByTheUseroauth:window_closed_by_user
isWalletConnectionInvalidoauth:fatal OR qrcode:fatal

QR Code

BooleanState Check
isQRCodePendingAny qrcode:* except error, fatal, timeout
isQRCodeWaitingqrcode:waiting
isQRCodeDisplayingqrcode:displaying
isQRCodeScanningqrcode:scanning
isQRCodeErrorqrcode:error OR qrcode:fatal
isQRCodeFatalqrcode:fatal
isQRCodeTimeoutqrcode:timeout
isQRCodeFlowContext: isQRCodeFlow === true

Wallet

BooleanState Check
isWalletLoadingwallet:loading
isWalletReadywallet:ready
isWalletErrorwallet:error
hasWalletNotFoundErrorwallet:error + message contains “not found”
hasInvalidCredentialsErrorwallet:error + message contains “invalid” + “credential”

Quote

BooleanState Check
isQuoteLoadingquote:requesting
isQuoteReadyquote:ready
isQuoteExpiredquote:expired
isQuoteErrorquote: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

BooleanState Check
isWithdrawingAny active withdraw:* (not completed, fatal, or error states)
isWithdrawProcessingwithdraw:processing
isWithdrawalCompletewithdraw:completed
isWithdrawBlockedwithdraw:blocked
hasFatalErrorwithdraw:fatal
requires2FAwithdraw:error2FA
requires2FAMultiStepwithdraw:error2FAMultiStep
isReadyToConfirmwithdraw:readyToConfirm
requiresSMSwithdraw:errorSMS
requiresKYCwithdraw:errorKYC
hasInsufficientBalancewithdraw:errorBalance
canRetrywithdraw:retrying
requiresValid2FAMethodwithdraw:fatal + errorDetails.valid2FAMethods present
requiresEmailVerificationwithdraw:fatal + message contains “email”

Multi-Step 2FA

BooleanState Check
hasGoogleStepmultiStep2FA.steps contains type GOOGLE
hasEmailStepmultiStep2FA.steps contains type EMAIL
hasFaceStepmultiStep2FA.steps contains type FACE
hasSmsStepmultiStep2FA.steps contains type SMS
isGoogleStepVerifiedmfa.verified.GOOGLE === true OR step status success
isEmailStepVerifiedmfa.verified.EMAIL === true OR step status success
isFaceStepVerifiedmfa.verified.FACE === true OR step status success
isSmsStepVerifiedmfa.verified.SMS === true OR step status success
allMultiStep2FAStepsVerifiedAll 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:
FieldTypeDescription
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:
FieldTypeDescription
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.
ActionSignatureFrom StatesEffect
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) => PromiseAnyResumes a flow with a known wallet ID
silentResumeWithdrawalFlow(options: SilentResumeWithdrawalFlowOptions) => PromiseAnyResumes 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() => voidAnyCancels 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