> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/circlefin/evm-cctp-contracts/llms.txt
> Use this file to discover all available pages before exploring further.

# What's New in V2

> Detailed breakdown of new features and changes in EVM CCTP Contracts V2

## New Features

### Fee Support

V2 introduces a comprehensive fee mechanism for cross-chain transfers.

#### Fee Parameters

**maxFee** (New in `depositForBurn`)

Specifies the maximum fee willing to pay on the destination domain:

```solidity theme={null}
// V1 - No fee parameter
function depositForBurn(
    uint256 amount,
    uint32 destinationDomain,
    bytes32 mintRecipient,
    address burnToken
) external returns (uint64 _nonce)

// V2 - Includes maxFee
function depositForBurn(
    uint256 amount,
    uint32 destinationDomain,
    bytes32 mintRecipient,
    address burnToken,
    bytes32 destinationCaller,
    uint256 maxFee,              // NEW
    uint32 minFinalityThreshold  // NEW
) external
```

**Reference**: `src/v2/TokenMessengerV2.sol:166`

#### Fee Recipient

Configurable address to receive collected fees:

```solidity theme={null}
// Set fee recipient (owner only)
tokenMessenger.setFeeRecipient(feeRecipientAddress);
```

Fees are automatically minted to the `feeRecipient` when processing unfinalized messages.

**Reference**: `src/v2/BaseTokenMessenger.sol:222`

#### Minimum Fee Control

Protocol can enforce minimum fee requirements:

```solidity theme={null}
// Set minimum fee controller (owner only)
tokenMessenger.setMinFeeController(controllerAddress);

// Set minimum fee in 1/1000 basis points (controller only)
tokenMessenger.setMinFee(5000); // 0.05%
```

The minimum fee is validated on the source domain:

```solidity theme={null}
require(
    _maxFee >= _calcMinFeeAmount(_amount),
    "Insufficient max fee"
);
```

**Reference**: `src/v2/BaseTokenMessenger.sol:232-244`

#### Fee Calculation

```solidity theme={null}
function getMinFeeAmount(uint256 amount) external view returns (uint256) {
    if (minFee == 0) return 0;
    require(amount > 1, "Amount too low");
    return _calcMinFeeAmount(amount);
}

function _calcMinFeeAmount(uint256 _amount) internal view returns (uint256) {
    uint256 _minFeeAmount = _amount.mul(minFee) / MIN_FEE_MULTIPLIER;
    return _minFeeAmount == 0 ? 1 : _minFeeAmount;
}
```

Where `MIN_FEE_MULTIPLIER = 10_000_000` for 1/1000 basis point precision.

**Reference**: `src/v2/TokenMessengerV2.sol:299-319`

### Hook Execution

V2 introduces the `hookData` parameter for custom logic execution on the destination domain.

#### depositForBurnWithHook

```solidity theme={null}
function depositForBurnWithHook(
    uint256 amount,
    uint32 destinationDomain,
    bytes32 mintRecipient,
    address burnToken,
    bytes32 destinationCaller,
    uint256 maxFee,
    uint32 minFinalityThreshold,
    bytes calldata hookData  // NEW
) external
```

**Reference**: `src/v2/TokenMessengerV2.sol:210`

#### Hook Data Requirements

* Must be non-empty (length > 0)
* Included in burn message body
* Interpreted by destination domain recipient or relayer
* Application-specific format

#### Hook Use Cases

<Tabs>
  <Tab title="Automated Swap">
    ```solidity theme={null}
    // Encode swap parameters
    bytes memory hookData = abi.encode(
        "SWAP",
        dexAddress,
        outputToken,
        minOutputAmount
    );

    tokenMessenger.depositForBurnWithHook(
        amount,
        destinationDomain,
        recipientAddress,
        address(usdc),
        bytes32(0),
        maxFee,
        1000,
        hookData
    );
    ```
  </Tab>

  <Tab title="Conditional Transfer">
    ```solidity theme={null}
    // Encode release conditions
    bytes memory hookData = abi.encode(
        "ESCROW",
        releaseTimestamp,
        validatorAddress
    );

    tokenMessenger.depositForBurnWithHook(
        amount,
        destinationDomain,
        escrowContract,
        address(usdc),
        bytes32(uint256(uint160(validatorAddress))),
        maxFee,
        1000,
        hookData
    );
    ```
  </Tab>

  <Tab title="Multi-Recipient">
    ```solidity theme={null}
    // Encode distribution plan
    address[] memory recipients = new address[](3);
    uint256[] memory amounts = new uint256[](3);
    // ... populate arrays

    bytes memory hookData = abi.encode(
        "DISTRIBUTE",
        recipients,
        amounts
    );

    tokenMessenger.depositForBurnWithHook(
        totalAmount,
        destinationDomain,
        distributorContract,
        address(usdc),
        bytes32(0),
        maxFee,
        1000,
        hookData
    );
    ```
  </Tab>
</Tabs>

### Finality Thresholds

V2 introduces configurable finality requirements for flexible security/speed tradeoffs.

#### Finality Constants

```solidity theme={null}
// The threshold at which (and above) messages are considered finalized
uint32 constant FINALITY_THRESHOLD_FINALIZED = 2000;

// The threshold at which (and above) messages are considered confirmed
uint32 constant FINALITY_THRESHOLD_CONFIRMED = 1000;

// The minimum allowed level of finality accepted by TokenMessenger
uint32 constant TOKEN_MESSENGER_MIN_FINALITY_THRESHOLD = 500;
```

**Reference**: `src/v2/FinalityThresholds.sol`

#### Message Handlers

V2 introduces two message handler functions:

```solidity theme={null}
// For finalized messages (no fees)
function handleReceiveFinalizedMessage(
    uint32 remoteDomain,
    bytes32 sender,
    uint32 finalityThresholdExecuted,
    bytes calldata messageBody
) external returns (bool)

// For unfinalized messages (fees apply)
function handleReceiveUnfinalizedMessage(
    uint32 remoteDomain,
    bytes32 sender,
    uint32 finalityThresholdExecuted,
    bytes calldata messageBody
) external returns (bool)
```

**Reference**: `src/v2/TokenMessengerV2.sol:245-292`

#### Finality Validation

```solidity theme={null}
require(
    finalityThresholdExecuted >= TOKEN_MESSENGER_MIN_FINALITY_THRESHOLD,
    "Unsupported finality threshold"
);
```

TokenMessenger rejects messages attested below the minimum threshold (500).

**Reference**: `src/v2/TokenMessengerV2.sol:286-289`

### Denylist Functionality

V2 adds protocol-level denylist capabilities.

#### Denylistable Role

New `Denylistable` contract provides denylist functionality:

```solidity theme={null}
abstract contract Denylistable is Ownable2Step {
    address public denylister;
    mapping(address => bool) internal denylisted;

    modifier notDenylistedCallers() {
        require(!denylisted[msg.sender], "Caller is denylisted");
        _;
    }
}
```

**Reference**: `src/roles/v2/Denylistable.sol`

#### Denylist Management

```solidity theme={null}
// Set denylister (owner only)
tokenMessenger.updateDenylister(denylisterAddress);

// Add to denylist (denylister only)
tokenMessenger.addToDenylist(maliciousAddress);

// Remove from denylist (denylister only)
tokenMessenger.removeFromDenylist(rehabilitatedAddress);

// Check denylist status
bool isDenylisted = tokenMessenger.isDenylisted(address);
```

#### Protected Functions

The `notDenylistedCallers` modifier protects:

* `depositForBurn()`
* `depositForBurnWithHook()`

Denylisted addresses cannot initiate cross-chain burns.

**Reference**: `src/v2/TokenMessengerV2.sol:174`

## API Changes

### depositForBurn Signature

<CodeGroup>
  ```solidity V1 theme={null}
  function depositForBurn(
      uint256 amount,
      uint32 destinationDomain,
      bytes32 mintRecipient,
      address burnToken
  ) external returns (uint64 _nonce)
  ```

  ```solidity V2 theme={null}
  function depositForBurn(
      uint256 amount,
      uint32 destinationDomain,
      bytes32 mintRecipient,
      address burnToken,
      bytes32 destinationCaller,     // NEW
      uint256 maxFee,                // NEW
      uint32 minFinalityThreshold    // NEW
  ) external notDenylistedCallers   // NEW
  ```
</CodeGroup>

**Breaking Change**: V2 requires additional parameters. Use `bytes32(0)` for `destinationCaller` to allow any caller.

### depositForBurnWithCaller

<Warning>
  `depositForBurnWithCaller()` is removed in V2. Use `depositForBurn()` with `destinationCaller` parameter instead.
</Warning>

```solidity theme={null}
// V1 approach
tokenMessenger.depositForBurnWithCaller(
    amount,
    destinationDomain,
    mintRecipient,
    burnToken,
    destinationCaller
);

// V2 equivalent
tokenMessenger.depositForBurn(
    amount,
    destinationDomain,
    mintRecipient,
    burnToken,
    destinationCaller,  // Directly in main function
    maxFee,
    minFinalityThreshold
);
```

### Event Signature Changes

#### DepositForBurn Event

<CodeGroup>
  ```solidity V1 theme={null}
  event DepositForBurn(
      uint64 indexed nonce,
      address indexed burnToken,
      uint256 amount,
      address indexed depositor,
      bytes32 mintRecipient,
      uint32 destinationDomain,
      bytes32 destinationTokenMessenger,
      bytes32 destinationCaller
  );
  ```

  ```solidity V2 theme={null}
  event DepositForBurn(
      address indexed burnToken,
      uint256 amount,
      address indexed depositor,
      bytes32 mintRecipient,
      uint32 destinationDomain,
      bytes32 destinationTokenMessenger,
      bytes32 destinationCaller,
      uint256 maxFee,                      // NEW
      uint32 indexed minFinalityThreshold, // NEW (indexed)
      bytes hookData                       // NEW
  );
  ```
</CodeGroup>

**Key Differences**:

* `nonce` removed from V2 event (no longer indexed)
* `maxFee` added
* `minFinalityThreshold` added (indexed)
* `hookData` added
* Index changes: V1 indexes `nonce`, V2 indexes `minFinalityThreshold`

**Reference**: `src/v2/TokenMessengerV2.sol:62-73`

#### MintAndWithdraw Event

<CodeGroup>
  ```solidity V1 theme={null}
  event MintAndWithdraw(
      address indexed mintRecipient,
      uint256 amount,
      address indexed mintToken
  );
  ```

  ```solidity V2 theme={null}
  event MintAndWithdraw(
      address indexed mintRecipient,
      uint256 amount,
      address indexed mintToken,
      uint256 feeCollected  // NEW
  );
  ```
</CodeGroup>

**Reference**: `src/v2/BaseTokenMessenger.sol:84-89`

### Message Handler Interface

<CodeGroup>
  ```solidity V1 (IMessageHandler) theme={null}
  interface IMessageHandler {
      function handleReceiveMessage(
          uint32 sourceDomain,
          bytes32 sender,
          bytes calldata messageBody
      ) external returns (bool);
  }
  ```

  ```solidity V2 (IMessageHandlerV2) theme={null}
  interface IMessageHandlerV2 {
      function handleReceiveFinalizedMessage(
          uint32 sourceDomain,
          bytes32 sender,
          uint32 finalityThresholdExecuted,
          bytes calldata messageBody
      ) external returns (bool);

      function handleReceiveUnfinalizedMessage(
          uint32 sourceDomain,
          bytes32 sender,
          uint32 finalityThresholdExecuted,
          bytes calldata messageBody
      ) external returns (bool);
  }
  ```
</CodeGroup>

**Reference**: `src/interfaces/v2/IMessageHandlerV2.sol`

## Contract Architecture Changes

### Base Contracts

V2 introduces base contracts for shared functionality:

```solidity theme={null}
// V2 structure
contract TokenMessengerV2 is IMessageHandlerV2, BaseTokenMessenger {
    // ...
}

abstract contract BaseTokenMessenger is Rescuable, Denylistable, Initializable {
    // Shared admin functions
    // Fee management
    // Remote messenger management
}
```

**Reference**: `src/v2/BaseTokenMessenger.sol`

### Initializable Pattern

V2 uses OpenZeppelin's initializer pattern instead of direct constructor initialization:

```solidity theme={null}
// V1 - Direct setup in constructor
constructor(address _messageTransmitter, uint32 _messageBodyVersion) {
    localMessageTransmitter = IMessageTransmitter(_messageTransmitter);
    messageBodyVersion = _messageBodyVersion;
}

// V2 - Proxy initialization
constructor(address _messageTransmitter, uint32 _messageBodyVersion) 
    BaseTokenMessenger(_messageTransmitter, _messageBodyVersion) 
{
    _disableInitializers();
}

function initialize(
    TokenMessengerV2Roles calldata roles,
    uint256 minFee_,
    uint32[] calldata remoteDomains_,
    bytes32[] calldata remoteTokenMessengers_
) external initializer
```

**Reference**: `src/v2/TokenMessengerV2.sol:84-142`

### CREATE2 Deployment

V2 uses CREATE2 for deterministic addresses:

```solidity theme={null}
contract Create2Factory is Ownable2Step {
    function create2Deploy(
        bytes32 salt,
        bytes memory bytecode
    ) external onlyOwner returns (address) {
        address addr;
        assembly {
            addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
        }
        require(addr != address(0), "Create2: Failed on deploy");
        return addr;
    }
}
```

**Reference**: `src/v2/Create2Factory.sol`

## TokenMinter Changes

### Dual-Recipient Mint

V2 TokenMinter supports minting to two recipients (for fee collection):

```solidity theme={null}
// V1 - Single recipient
function mint(
    uint32 sourceDomain,
    bytes32 burnToken,
    address to,
    uint256 amount
) external returns (address mintToken)

// V2 - Dual recipient (NEW)
function mint(
    uint32 sourceDomain,
    bytes32 burnToken,
    address recipientOne,
    address recipientTwo,
    uint256 amountOne,
    uint256 amountTwo
) external returns (address mintToken)
```

**Reference**: `src/v2/TokenMinterV2.sol:54`

## Message Format Changes

V2 uses a new burn message format:

<Tabs>
  <Tab title="V1 BurnMessage">
    ```solidity theme={null}
    // V1 Burn Message Layout
    // [0:4]    - version
    // [4:36]   - burnToken
    // [36:68]  - mintRecipient  
    // [68:100] - amount
    // [100:132] - messageSender
    ```
  </Tab>

  <Tab title="V2 BurnMessage">
    ```solidity theme={null}
    // V2 Burn Message Layout
    // [0:4]      - version
    // [4:36]     - burnToken
    // [36:68]    - mintRecipient
    // [68:100]   - amount
    // [100:132]  - messageSender
    // [132:164]  - maxFee         // NEW
    // [164:196]  - expirationBlock // NEW
    // [196:228]  - feeExecuted     // NEW
    // [228:260]  - hookDataLength  // NEW
    // [260:...]  - hookData        // NEW (variable length)
    ```
  </Tab>
</Tabs>

**Reference**: `src/messages/v2/BurnMessageV2.sol`

## State Variables

### New State Variables

```solidity theme={null}
// Fee management
address public feeRecipient;
address public minFeeController;
uint256 public minFee;
uint256 public constant MIN_FEE_MULTIPLIER = 10_000_000;
```

**Reference**: `src/v2/BaseTokenMessenger.sol:104-114`

## Next Steps

<CardGroup cols={2}>
  <Card title="Migration Guide" icon="arrow-right" href="/v2/migration-guide">
    Learn how to migrate from V1 to V2
  </Card>

  <Card title="Deployment" icon="rocket" href="/v2/deployment">
    Deploy V2 contracts to your network
  </Card>

  <Card title="TokenMessengerV2 API" icon="code" href="/api/v2/token-messenger-v2">
    Complete API reference
  </Card>

  <Card title="Integration Guide" icon="plug" href="/guides/integration">
    Integrate V2 into your application
  </Card>
</CardGroup>
