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:
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:
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.
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:
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:
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):
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
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
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:
| Error | Meaning |
|---|---|
ChainNotAllowed | Destination chain not enabled for this token |
RateLimitReached | Token bucket rate limit exceeded — try smaller amount or wait |
UnsupportedToken | Token not supported on this lane |
InsufficientFeeTokenAmount | Not enough fee provided |
InvalidReceiver | Receiver address format invalid for destination chain |
SenderNotAllowed | Sender not on allowlist |
InvalidExtraArgsTag | Invalid extra args encoding |
MessageTooLarge | Message data exceeds max size |
TokenMaxCapacityExceeded | Transfer exceeds pool capacity |
Related
- Error Reference — Complete catalog of all SDK error codes
- Manual Execution — Handle execution failures
- Tracking Messages — Check message status