Skip to main content

cctp_rs/contracts/v2/
token_messenger_v2.rs

1// SPDX-FileCopyrightText: 2025 Semiotic AI, Inc.
2//
3// SPDX-License-Identifier: Apache-2.0
4//! `TokenMessengerV2` contract bindings and wrapper
5//!
6//! This module contains the Alloy-generated contract bindings for the CCTP v2
7//! `TokenMessenger` contract, which manages USDC burn and mint operations with
8//! Fast Transfer and hooks support.
9
10#![allow(dead_code)] // Public API methods not used internally
11
12use alloy_network::Ethereum;
13use alloy_primitives::{Address, Bytes, U256};
14use alloy_provider::Provider;
15use alloy_rpc_types::TransactionRequest;
16use alloy_sol_types::sol;
17use tracing::{debug, info};
18
19use crate::protocol::DomainId;
20use crate::spans;
21use TokenMessengerV2::TokenMessengerV2Instance;
22
23/// The CCTP v2 Token Messenger contract wrapper
24///
25/// Supports v2 features including Fast Transfer (with fees) and programmable hooks.
26#[allow(dead_code)]
27pub struct TokenMessengerV2Contract<P: Provider<Ethereum>> {
28    instance: TokenMessengerV2Instance<P>,
29}
30
31impl<P: Provider<Ethereum>> TokenMessengerV2Contract<P> {
32    /// Create a new `TokenMessengerV2Contract`
33    #[allow(dead_code)]
34    pub fn new(address: Address, provider: P) -> Self {
35        debug!(
36            contract_address = %address,
37            event = "token_messenger_v2_contract_initialized"
38        );
39        Self {
40            instance: TokenMessengerV2Instance::new(address, provider),
41        }
42    }
43
44    /// Create the transaction request for the `depositForBurn` function (v2 standard transfer)
45    ///
46    /// For standard transfers without fast transfer or hooks.
47    ///
48    /// # Arguments
49    ///
50    /// * `from_address` - Address initiating the burn
51    /// * `recipient` - Recipient address on destination chain
52    /// * `destination_domain` - CCTP domain ID for destination
53    /// * `token_address` - USDC token contract address
54    /// * `amount` - Amount to burn
55    /// * `max_fee` - Maximum fee for fast transfer (0 for standard)
56    /// * `min_finality_threshold` - 1000 (fast) or 2000 (standard)
57    /// * `destination_caller` - Authorized caller on destination (0x0 = anyone)
58    #[allow(dead_code)]
59    #[allow(clippy::too_many_arguments)]
60    fn deposit_for_burn_internal(
61        &self,
62        from_address: Address,
63        recipient: Address,
64        destination_domain: DomainId,
65        token_address: Address,
66        amount: U256,
67        max_fee: U256,
68        min_finality_threshold: u32,
69        destination_caller: Address,
70    ) -> TransactionRequest {
71        self.instance
72            .depositForBurn(
73                amount,
74                destination_domain.as_u32(),
75                recipient.into_word(),
76                token_address,
77                destination_caller.into_word(),
78                max_fee,
79                min_finality_threshold,
80            )
81            .from(from_address)
82            .into_transaction_request()
83    }
84
85    /// Create the transaction request for the `depositForBurn` function (v2 standard)
86    ///
87    /// For standard (no-hook, no-fast-fee) transfers. Pass
88    /// `FinalityThreshold::Standard.as_u32()` (2000) for this path; the
89    /// parameter is exposed rather than hardcoded so the same value can be
90    /// derived from a single caller-side source of truth.
91    #[allow(dead_code)]
92    pub fn deposit_for_burn_transaction(
93        &self,
94        from_address: Address,
95        recipient: Address,
96        destination_domain: DomainId,
97        token_address: Address,
98        amount: U256,
99        min_finality_threshold: u32,
100    ) -> TransactionRequest {
101        let span = spans::deposit_for_burn(
102            &from_address,
103            &recipient,
104            destination_domain.as_u32(),
105            &token_address,
106            &amount,
107        );
108        let _guard = span.enter();
109
110        info!(
111            from_address = %from_address,
112            recipient = %recipient,
113            destination_domain = %destination_domain,
114            token_address = %token_address,
115            amount = %amount,
116            contract_address = %self.instance.address(),
117            version = "v2",
118            finality_threshold = min_finality_threshold,
119            event = "deposit_for_burn_v2_transaction_created"
120        );
121
122        self.deposit_for_burn_internal(
123            from_address,
124            recipient,
125            destination_domain,
126            token_address,
127            amount,
128            U256::ZERO, // max_fee: 0 for standard transfers
129            min_finality_threshold,
130            Address::ZERO, // destination_caller: 0x0 = anyone
131        )
132    }
133
134    /// Create transaction for depositForBurn with Fast Transfer enabled
135    ///
136    /// # Arguments
137    ///
138    /// * `from_address` - Sender address
139    /// * `recipient` - Recipient address on destination chain
140    /// * `destination_domain` - CCTP domain ID for destination chain
141    /// * `token_address` - USDC token contract address
142    /// * `amount` - Amount to transfer
143    /// * `max_fee` - Maximum fee willing to pay for fast transfer
144    /// * `min_finality_threshold` - Pass `FinalityThreshold::Fast.as_u32()`
145    ///   (1000) for this path. Exposed alongside `max_fee` so both wire values
146    ///   can be derived from a single caller-side source of truth.
147    ///
148    /// # Fast Transfer
149    ///
150    /// When `max_fee` >= minimum fast transfer fee for the chain, the transfer
151    /// will be attested at the "confirmed" finality level (~30 seconds) instead
152    /// of "finalized" level (~15 minutes).
153    #[allow(dead_code)]
154    #[allow(clippy::too_many_arguments)]
155    pub fn deposit_for_burn_fast_transaction(
156        &self,
157        from_address: Address,
158        recipient: Address,
159        destination_domain: DomainId,
160        token_address: Address,
161        amount: U256,
162        max_fee: U256,
163        min_finality_threshold: u32,
164    ) -> TransactionRequest {
165        info!(
166            from_address = %from_address,
167            recipient = %recipient,
168            destination_domain = %destination_domain,
169            token_address = %token_address,
170            amount = %amount,
171            max_fee = %max_fee,
172            contract_address = %self.instance.address(),
173            version = "v2",
174            transfer_type = "fast",
175            finality_threshold = min_finality_threshold,
176            event = "deposit_for_burn_fast_transaction_created"
177        );
178
179        self.deposit_for_burn_internal(
180            from_address,
181            recipient,
182            destination_domain,
183            token_address,
184            amount,
185            max_fee,
186            min_finality_threshold,
187            Address::ZERO, // destination_caller: 0x0 = anyone
188        )
189    }
190
191    /// Create transaction for `depositForBurnWithHook`
192    ///
193    /// # Arguments
194    ///
195    /// * `from_address` - Sender address
196    /// * `recipient` - Recipient address on destination chain
197    /// * `destination_domain` - CCTP domain ID for destination chain
198    /// * `token_address` - USDC token contract address
199    /// * `amount` - Amount to transfer
200    /// * `max_fee` - Maximum fee willing to pay. Use `U256::ZERO` for standard
201    ///   finality; supply a non-zero cap when pairing hooks with fast finality.
202    /// * `min_finality_threshold` - 1000 (fast) or 2000 (standard). Hooks are
203    ///   supported at both thresholds at the protocol level — pass whichever
204    ///   matches the intended transfer mode.
205    /// * `hook_data` - Arbitrary bytes to pass to destination chain for
206    ///   programmable actions
207    ///
208    /// # Hooks
209    ///
210    /// Hook data is opaque to CCTP but can be used by integrators to trigger
211    /// actions on the destination chain (e.g., swap, lend, stake).
212    #[allow(clippy::too_many_arguments)]
213    pub fn deposit_for_burn_with_hooks_transaction(
214        &self,
215        from_address: Address,
216        recipient: Address,
217        destination_domain: DomainId,
218        token_address: Address,
219        amount: U256,
220        max_fee: U256,
221        min_finality_threshold: u32,
222        hook_data: Bytes,
223    ) -> TransactionRequest {
224        info!(
225            from_address = %from_address,
226            recipient = %recipient,
227            destination_domain = %destination_domain,
228            token_address = %token_address,
229            amount = %amount,
230            max_fee = %max_fee,
231            hook_data_len = hook_data.len(),
232            contract_address = %self.instance.address(),
233            version = "v2",
234            has_hooks = true,
235            finality_threshold = min_finality_threshold,
236            event = "deposit_for_burn_hooks_transaction_created"
237        );
238
239        self.instance
240            .depositForBurnWithHook(
241                amount,
242                destination_domain.as_u32(),
243                recipient.into_word(),
244                token_address,
245                Address::ZERO.into_word(), // destination_caller: 0x0 = anyone
246                max_fee,
247                min_finality_threshold,
248                hook_data,
249            )
250            .from(from_address)
251            .into_transaction_request()
252    }
253
254    /// Returns the contract address
255    pub fn address(&self) -> Address {
256        *self.instance.address()
257    }
258}
259
260sol!(
261    #[allow(clippy::too_many_arguments)]
262    #[allow(missing_docs)]
263    #[sol(rpc)]
264    TokenMessengerV2,
265    "abis/v2/token_messenger_v2.json"
266);