CCIP Concepts
EVM2AnyMessage, extraArgs versions, fee tokens, and chain selectors — read this before using build or getFee
The EVM2AnyMessage Tuple
Every CCIP message is, on-chain, a Solidity struct called Client.EVM2AnyMessage:
receiver address, arbitrary data payload, an array of token transfers, a fee token, and
extraArgs. message/build and router/getFee both assemble this same
tuple from the same input fields — that's why their field lists overlap almost completely (see
Message Operations and
Router Operations).
message/build's encodedMessage output is a JSON array of the tuple's raw
positional values (receiver/data/tokenAmounts/feeToken/extraArgs), not one pre-abi.encode()'d
blob. This is deliberate: the Ethereum ExecutionNode's own contract-write step ABI-encodes a tuple-typed
function argument from a nested JSON array of its component values — a single opaque blob wouldn't compose
with that delegation path. This is what makes "Chainlink builds the message → Ethereum sends it" work
end-to-end as two separate workflow steps.
extraArgs: v1 vs. genericV2
extraArgs is a 4-byte tag selector followed by ABI-encoded parameters. Two versions are
supported, both using Chainlink's own published, fixed tag values:
| Version | Tag | Body |
|---|---|---|
v1 (EVMExtraArgsV1) | 0x97a657c9 | abi.encode(uint256 gasLimit) |
genericV2 (GenericExtraArgsV2, default) | 0x181dcf10 | abi.encode(uint256 gasLimit, bool allowOutOfOrderExecution) |
Use genericV2 (the default) unless a specific destination lane requires v1.
allowOutOfOrderExecution only applies to genericV2 and is silently ignored for
v1.
Fee Tokens: Empty Means Native Gas Token
Leave feeToken empty/omitted to pay the CCIP fee in the destination chain's native gas token
— this node encodes that as the zero address (0x0000000000000000000000000000000000000000).
router/getFee's output echoes back this resolved address, not your raw input, so you can
always see exactly which token address was actually used for the fee calculation.
Addresses Are Validated Before They Reach the ABI Encoder
Every address this node encodes (receiverAddress, feeToken, each
tokenAmounts[].token) is validated as exactly 0x + 40 hex characters before being
passed to Nethereum's ABI encoder. This matters because Nethereum's own encoder does not
reliably reject a malformed address — a too-short address like 0x123 is silently accepted with
no error, which would otherwise produce a corrupted encoded message instead of a clear failure. See
Troubleshooting.
Chain Selectors vs. Native Chain IDs
Covered in full on Networks: every chain has both a native EVM chain ID and a separate CCIP chain selector, and this node carries both as strings (never numbers) throughout its config and output contract to avoid JavaScript's safe-integer precision loss.
Token Amounts Are Strings Too
A tokenAmounts[].amount is a decimal string representing a uint256
wei-denominated value — e.g. "1000000000000000000" for 1 token at 18 decimals — never a JSON
number, for the same precision reason as chain selectors.
This Node Never Sends a Message
message/build stops at producing the encoded tuple and extraArgs bytes. Actually
calling ccipSend(destinationChainSelector, message) on the source chain's Router contract — the
step that assigns the real messageId and moves funds/data — is performed by the Ethereum
ExecutionNode's own contract-write operation, using this node's output as its input. This node holds no
wallet and signs nothing.