Hooks and Errors
Where each hook goes, and how to handle the errors the SDK throws.
Pass hooks in the
Every SDK error extends
Constants are on
TypeScript
A
options argument of each swap method. execute and simulateExecute take no hooks.
Hooks
| Option | Methods | Receives | Details |
|---|---|---|---|
hooks.onIntent | swapWithExactIn, swapWithExactOut | IntentHookData: quote, execution, allow, deny, refresh | Review quotes |
hooks.onIntent | swapAndExecute | SwapAndExecuteHookData: intent, allow, deny, refresh | Swap and execute |
beforeExecute | swapAndExecute | BeforeExecuteContext; returns { data?, value?, gas? } | Swap and execute |
onEvent | All three swap methods | IntentEvent: quote, step, or status | Swap events |
- Without
hooks.onIntent, the SDK accepts the quote automatically. - With it, the operation waits until you call
allow()ordeny(). Returning from the callback does not accept the quote. deny()rejects the operation withUSER_INTENT_HOOK_DENIED. A hook that throws rejects the operation with that error.- Errors thrown inside
onEventare caught and do not interrupt the operation.
Errors
NexusError. Branch on the subclass or code, not on message.
| Field | Type | Description |
|---|---|---|
category | ErrorCategory | Matches the subclass, e.g. 'backend' |
code | ErrorCode | Stable string such as 'backend/no_routable_source'; compare with ERROR_CODES.* |
message | string | Displayable text, including the underlying wallet or RPC message |
context | ErrorContext | operation, service, stepId, stepType, and chainId, where known |
details | Record<string, unknown> | undefined | Failure data such as txHash, requestHash, approvals, or httpStatus |
| Subclass | context.service | Thrown when |
|---|---|---|
ValidationError | none | Input is invalid, the wallet is not connected, or balances cannot cover the request |
UserActionError | wallet, hook | The user rejects a wallet prompt or your hook calls deny() |
ExecutionError | wallet, rpc | A transaction fails to send, confirm, or execute |
BackendError | middleware | A quote, submission, status, balance, or catalog request fails |
ExternalServiceError | coinbase | getCoinbaseRates fails |
InternalError | none | An SDK invariant breaks; report it with code and details |
Codes to handle
ERROR_CODES. Treat any UserActionError as a cancellation, not a failure.
| Constant | Recovery |
|---|---|
WALLET_NOT_CONNECTED | Call setEVMProvider, then start a new operation |
SDK_NOT_INITIALIZED | Call client.initialize() first |
ENVIRONMENT_NOT_SUPPORTED | Use mainnet or canary; testnet supports standalone execution only |
INVALID_INPUT | Fix the parameter named in message |
INSUFFICIENT_BALANCE | Destination or native funds cannot cover execution; add funds or change sources |
BACKEND_INSUFFICIENT_BALANCE | Reduce the amount or choose another source |
BACKEND_INSUFFICIENT_APPROVAL_GAS | Add native gas on the source chain or choose another source |
BACKEND_VALUE_ABOVE_CEILING | Reduce the value; getIntentQuoteFailure(error)?.details.maxValueUsd is the limit in USD |
BACKEND_INPUT_BELOW_DEPOSIT_FEE | Increase the source amount |
BACKEND_NO_ROUTE_TO_DESTINATION | Choose a destination served by the allowed providers |
BACKEND_NO_ROUTABLE_SOURCE | Read sourceVerdicts and change sources or amounts |
BACKEND_QUOTE_UNAVAILABLE, BACKEND_INTENT_REFUSED, BACKEND_QUOTE_PRICE_OUTLIER | Change sources or the amount, then request a new quote |
BACKEND_SAME_CHAIN_GAS_DROP_UNSUPPORTED, BACKEND_GAS_DROP_TOO_SMALL | Remove or increase toNativeAmountRaw |
BACKEND_REQUEST_EXPIRED, BACKEND_INVALID_INTENT_SIGNATURE, BACKEND_INVALID_PERMIT_SIGNATURE | Start a new operation to get a fresh quote and signatures |
BACKEND_ERROR with details.reasonBucket === 'expired' | The quote or intent expired; start a new operation |
BACKEND_PROVIDER_UNAVAILABLE, BACKEND_RATE_LIMITED, BACKEND_UPSTREAM_ERROR | Retry later |
BACKEND_UNAUTHORIZED | Check the clientId passed to createNexusClient |
BACKEND_FULFILMENT_WAIT_TIMEOUT | Not filled within fillTimeoutMinutes; check status for details.requestHash |
BACKEND_UPSTREAM_TIMEOUT, BACKEND_NETWORK_ERROR | After commitment, check status before retrying |
EXEC_TX_ONCHAIN_REVERTED | Inspect details.txHash; fix calldata, value, or gas |
EXEC_TX_CONFIRM_FAILED, EXEC_APPROVAL_TX_CONFIRM_FAILED | The transaction in details.txHash may still confirm; check it before resending |
getIntentQuoteFailure
getIntentQuoteFailure(error: unknown): IntentQuoteFailure | null returns routing diagnostics when the quote service rejects a request. It returns null for other errors.
| Field | Type | Description |
|---|---|---|
subcode | IntentQuoteFailureSubcode | Reason, e.g. 'NO_ROUTABLE_SOURCE', 'VALUE_ABOVE_CEILING' |
retryable | boolean | true only for PROVIDER_UNAVAILABLE; otherwise change the request first |
sourceVerdicts | IntentSourceVerdict[] | Per source: chainId, tokenAddress, tokenSymbol, state (selected, unused, or unroutable), optional reason and detail |
providerReasons | string[] | Provider-specific reasons |
details | Record<string, unknown> | Endpoint data, e.g. maxValueUsd |
errorId, code | string | undefined | Service identifiers for support |
Example
import {
ERROR_CODES,
NexusError,
UserActionError,
getIntentQuoteFailure,
} from '@avail-project/nexus-core';
try {
const result = await client.swapWithExactOut({
toChainId: 8453,
toTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
toAmountRaw: amountRaw,
});
showSuccess(result.intentExplorerUrl);
} catch (error) {
if (error instanceof UserActionError) return; // User cancelled.
if (!(error instanceof NexusError)) throw error;
switch (error.code) {
case ERROR_CODES.BACKEND_VALUE_ABOVE_CEILING: {
const maxUsd = getIntentQuoteFailure(error)?.details.maxValueUsd;
showError(`Reduce the amount. Limit: $${String(maxUsd ?? 'unknown')}`);
break;
}
case ERROR_CODES.BACKEND_INSUFFICIENT_BALANCE:
showError('Reduce the amount or choose another source.');
break;
case ERROR_CODES.BACKEND_FULFILMENT_WAIT_TIMEOUT:
showPending(String(error.details?.requestHash)); // Do not retry yet.
break;
default:
showError(error.message);
}
reportError({ code: error.code, context: error.context, details: error.details });
}After a timeout
step event with committed: true means the user signed the intent or broadcast a native source deposit. After that point, a rejected promise does not mean the intent failed; it can still fulfill.
Do not start a new swap after a timeout or connection failure that follows commitment. Retrying can spend the funds twice.
- Find the intent ID in
details.requestHashor in the laststatusevent'sintentId. - Check its status with
listIntentsor the intent explorer. - Check
details.approvals, if present. Each entry hastxHashand astateofconfirmed,reverted, orunconfirmed. An unconfirmed approval may still confirm. - Start a new operation with a fresh quote only after the intent shows
expired.
swapAndExecute wallet mode, an error after fulfillment leaves the funds in the destination wallet. Retry the call with execute instead of swapping again.How is this guide?