Appearance
Errors
How Exchange Pay failures surface in React and what to do at each step.
How errors surface
- Hook
errorstate —useExchangePaySupportedCurrencies().error,useExchangePayOrder().error, amount validation viauseExchangePayAmount().error. - Thrown / rejected promises —
createOrder/closeOrder/getOrder/refetchalso reject. - Events —
useOnExchangePayOrderExpiredis not an error, but you must handle it in UI.
Amount validation errors
useExchangePayAmount().error is an AmountValidationError ({ code, message, metadata }), not a string. Switch on code for i18n:
tsx
import type { AmountValidationError } from '@swapped/connect-sdk'
import { useExchangePayAmount } from '@swapped/connect-sdk/react'
function AmountError() {
const { error } = useExchangePayAmount()
if (!error) return null
const text = translateAmountError(error)
return <p>{text}</p>
}
function translateAmountError(error: AmountValidationError): string {
switch (error.code) {
case 'below_min':
return `Min amount ${error.metadata.formattedMinAmount}`
case 'above_spendable':
return `Max amount ${error.metadata.formattedMaxAmount}`
case 'above_balance':
return `Balance is ${error.metadata.formattedBalanceAmount}`
default:
return error.message
}
}Typed SDK failures are ConnectSdkError with a stable code:
tsx
import { ConnectSdkError, ConnectSdkErrorCode } from '@swapped/connect-sdk'
import { useExchangePayOrder } from '@swapped/connect-sdk/react'
function CreateWithErrors() {
const { createOrder, error, isCreating } = useExchangePayOrder()
async function onSubmit() {
try {
await createOrder()
} catch (err) {
if (
err instanceof ConnectSdkError &&
err.code === ConnectSdkErrorCode.SESSION_NOT_ACTIVE
) {
// Call restartSession() via useSwappedConnectClient()
}
}
}
return (
<div>
{error && <p>{error.message}</p>}
<button type="button" disabled={isCreating} onClick={() => void onSubmit()}>
Pay
</button>
</div>
)
}When things fail in the flow
| Step | What goes wrong | How you see it | What to do in UI |
|---|---|---|---|
| Mount hooks | Outside ExchangePayContextProvider | REACT_EXCHANGE_PAY_PROVIDER_REQUIRED | Wrap with the provider |
| Load currencies | API / network failure | currencies error / API_ERROR | Retry refetch |
| Create order | Incomplete selection | EXCHANGE_PAY_SELECTION_INCOMPLETE | Select provider, currency, amount |
| Create order | Session already completed | SESSION_NOT_ACTIVE | restartSession() first |
| Create order | Session not loaded | SESSION_REQUIRED | loadSession() |
| Create order | API rejection | API_ERROR | Fix amount / retry |
| Checkout leave | Close API failed | CLOSE_ACTIVE_ORDER_FAILED | Retry close or create again |
| Status / close | No order | NO_ACTIVE_ORDER | Back to form |
| Waiting | Timed out | useOnExchangePayOrderExpired / isExpired | Toast + navigate back; create a new order |
| After success | Create again without restart | SESSION_NOT_ACTIVE | restartSession() |
Block amounts below minAmountFiat with useExchangePayAmount before createOrder.
Error code reference
| Code | When it happens | What to do |
|---|---|---|
REACT_EXCHANGE_PAY_PROVIDER_REQUIRED | Hook used outside ExchangePayContextProvider | Wrap with ExchangePayContextProvider |
EXCHANGE_PAY_SELECTION_INCOMPLETE | createOrder() without provider / currency / amount | Complete selection first |
SESSION_REQUIRED | No loaded session | Call loadSession() |
SESSION_NOT_ACTIVE | Session cannot accept a new payment | restartSession() before createOrder |
NO_ACTIVE_ORDER | Status/close without an active order | createOrder first |
CLOSE_ACTIVE_ORDER_FAILED | Provider close failed | Retry close, or create a new order |
API_ERROR | Currencies / create / status / close failure | Retry |
CLIENT_DESTROYED | Client already destroyed | Create a new client |
canClose === false (Bybit, OKX): skip cancel UI — not an error.