cctp_rs/contracts/
message_transmitter.rs

1// SPDX-FileCopyrightText: 2025 Semiotic AI, Inc.
2//
3// SPDX-License-Identifier: Apache-2.0
4//! MessageTransmitter contract bindings and wrapper
5//!
6//! This module contains the Alloy-generated contract bindings for the CCTP v1
7//! MessageTransmitter contract, which handles cross-chain message verification
8//! and processing.
9
10use alloy_network::Ethereum;
11use alloy_primitives::{Address, Bytes};
12use alloy_provider::Provider;
13use alloy_rpc_types::TransactionRequest;
14use alloy_sol_types::sol;
15use tracing::{debug, info};
16
17use MessageTransmitter::MessageTransmitterInstance;
18
19/// The CCTP v1 Message Transmitter contract wrapper
20///
21/// Handles message verification and reception for cross-chain transfers.
22pub struct MessageTransmitterContract<P: Provider<Ethereum>> {
23    instance: MessageTransmitterInstance<P>,
24}
25
26impl<P: Provider<Ethereum>> MessageTransmitterContract<P> {
27    /// Create a new MessageTransmitterContract
28    pub fn new(address: Address, provider: P) -> Self {
29        debug!(
30            contract_address = %address,
31            event = "message_transmitter_contract_initialized"
32        );
33        Self {
34            instance: MessageTransmitterInstance::<P>::new(address, provider),
35        }
36    }
37
38    /// Create transaction request for receiving a cross-chain message with attestation
39    ///
40    /// # Arguments
41    ///
42    /// * `message` - The message bytes from the source chain
43    /// * `attestation` - Circle's attestation signature for the message
44    /// * `from_address` - Address that will submit the transaction
45    pub fn receive_message_transaction(
46        &self,
47        message: Bytes,
48        attestation: Bytes,
49        from_address: Address,
50    ) -> TransactionRequest {
51        info!(
52            message_len = message.len(),
53            attestation_len = attestation.len(),
54            from_address = %from_address,
55            contract_address = %self.instance.address(),
56            version = "v1",
57            event = "receive_message_transaction_created"
58        );
59
60        self.instance
61            .receiveMessage(message, attestation)
62            .from(from_address)
63            .into_transaction_request()
64    }
65
66    /// Check if a message nonce has been used (anti-replay protection)
67    ///
68    /// Queries the `usedNonces` mapping to determine if a nonce has already
69    /// been processed.
70    ///
71    /// # Arguments
72    ///
73    /// * `nonce_hash` - The hash of the nonce to check (keccak256 of source domain + nonce)
74    pub async fn is_nonce_used(&self, nonce_hash: [u8; 32]) -> Result<bool, alloy_contract::Error> {
75        let nonce_status = self.instance.usedNonces(nonce_hash.into()).call().await?;
76
77        debug!(
78            nonce_hash = ?nonce_hash,
79            nonce_status = %nonce_status,
80            is_used = nonce_status == 1,
81            event = "is_nonce_used_checked"
82        );
83
84        // 1 = used, 0 = not used
85        Ok(nonce_status == 1)
86    }
87
88    /// Returns the contract address
89    pub fn address(&self) -> Address {
90        *self.instance.address()
91    }
92}
93
94sol!(
95    #[allow(clippy::too_many_arguments)]
96    #[allow(missing_docs)]
97    #[sol(rpc)]
98    MessageTransmitter,
99    "abis/v1_message_transmitter.json"
100);