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);