Skip to main content
Version: 1.1.1

Error Handling

All SDK errors extend CCIPError, which provides structured information for programmatic error handling. See the Error Reference for the complete catalog of error codes.

CCIPError Structure

Every CCIPError includes:

TypeScript
interface CCIPError {
code: CCIPErrorCode // Machine-readable error code (e.g. 'TRANSACTION_NOT_FOUND')
message: string // Human-readable description
context: Record<string, unknown> // Structured context (IDs, addresses, hashes)
isTransient: boolean // True if retry may succeed
retryAfterMs?: number // Suggested retry delay in milliseconds
recovery?: string // Recovery suggestion
}

Catching Errors

Use CCIPError.isCCIPError() to check if an error is from the SDK, then switch on error.code:

TypeScript
import { CCIPError, CCIPErrorCode } from '@chainlink/ccip-sdk'

try {
const requests = await chain.getMessagesInTx(txHash)
} catch (error) {
if (!CCIPError.isCCIPError(error)) throw error

switch (error.code) {
case CCIPErrorCode.TRANSACTION_NOT_FOUND:
console.log('Transaction not found — verify the hash')
break
case CCIPErrorCode.MESSAGE_NOT_FOUND_IN_TX:
console.log('No CCIP messages in this transaction')
break
default:
console.log(`${error.code}: ${error.message}`)
if (error.recovery) console.log('Recovery:', error.recovery)
}
}

To wrap an unknown error as a CCIPError, use CCIPError.from(error).

Transient vs Permanent Errors

Transient errors (error.isTransient === true) may succeed on retry — for example, a message not yet indexed by the API, or a commit not yet landed on-chain. Permanent errors will not succeed on retry.

TypeScript
import { CCIPError, getRetryDelay } from '@chainlink/ccip-sdk'

try {
const request = await chain.getMessageById(messageId)
} catch (error) {
if (CCIPError.isCCIPError(error) && error.isTransient) {
const delay = getRetryDelay(error) // Uses retryAfterMs hint or defaults by error code
if (delay !== null) {
console.log(`Transient error — retrying in ${delay}ms`)
await new Promise((resolve) => setTimeout(resolve, delay))
// Retry...
}
} else {
throw error // Permanent error — do not retry
}
}

getRetryDelay returns a delay based on the error code (e.g., 60s for COMMIT_NOT_FOUND, 30s for attestation errors, 5s for HTTP errors) or null if the error is not transient.

You can also check a specific error code directly:

TypeScript
import { isTransientError } from '@chainlink/ccip-sdk'

isTransientError('COMMIT_NOT_FOUND') // true
isTransientError('MERKLE_ROOT_MISMATCH') // false

withRetry Utility

The SDK exports a withRetry function with exponential backoff. It only retries on transient errors — permanent errors are thrown immediately:

TypeScript
import { withRetry } from '@chainlink/ccip-sdk'

const request = await withRetry(
() => chain.getMessageById(messageId),
{
maxRetries: 5,
initialDelayMs: 2000,
backoffMultiplier: 2,
maxDelayMs: 60000,
respectRetryAfterHint: true, // Uses error.retryAfterMs when available
logger: console, // Optional — logs retry attempts
}
)

The SDK also exports DEFAULT_API_RETRY_CONFIG with sensible defaults (3 retries, 1s initial delay, 2x backoff, 30s max).

Error Serialization

formatErrorForLogging safely serializes a CCIPError for structured logging (handles non-enumerable properties):

TypeScript
import { CCIPError, formatErrorForLogging } from '@chainlink/ccip-sdk'

try {
await chain.sendMessage({ router, destChainSelector, message, wallet })
} catch (error) {
if (CCIPError.isCCIPError(error)) {
console.error(JSON.stringify(formatErrorForLogging(error)))
// => { name, code, message, isTransient, context, recovery, stack, cause }
}
}

Contract Error Parsing

SDK errors are thrown by the SDK itself. On-chain contract reverts are different — they come from the EVM or Solana runtime. Use the chain-specific parse method to decode them:

EVM

TypeScript
import { EVMChain } from '@chainlink/ccip-sdk'

try {
await publicClient.call({ to, data, value })
} catch (error) {
const parsed = EVMChain.parse(error)
if (parsed) {
console.log('Parsed error:', parsed)
// => { revert: 'ChainNotAllowed(uint64 destChainSelector)', ... }
}
}

Solana

TypeScript
import { SolanaChain } from '@chainlink/ccip-sdk'

try {
await sendTransaction(transaction, connection)
} catch (error) {
const parsed = SolanaChain.parse(error)
if (parsed) {
console.log('Program:', parsed.program)
console.log('Error:', parsed.error)
}
}

Common On-Chain Revert Errors

These are contract-level errors (not SDK errors). Use EVMChain.parse to decode them from reverts:

ErrorMeaning
ChainNotAllowedDestination chain not enabled for this token
RateLimitReachedToken bucket rate limit exceeded — try smaller amount or wait
UnsupportedTokenToken not supported on this lane
InsufficientFeeTokenAmountNot enough fee provided
InvalidReceiverReceiver address format invalid for destination chain
SenderNotAllowedSender not on allowlist
InvalidExtraArgsTagInvalid extra args encoding
MessageTooLargeMessage data exceeds max size
TokenMaxCapacityExceededTransfer exceeds pool capacity