cctp-rs 3.0.0

Rust SDK for CCTP
Documentation
// SPDX-FileCopyrightText: 2025 Semiotic AI, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//! Debug script to compare message extraction vs Circle API
//!
//! Run with: `cargo run --example debug_message`

use alloy_primitives::{b256, hex, B256};
use alloy_provider::{Provider, ProviderBuilder};
use dotenvy::dotenv;

const BURN_TX_HASH: B256 =
    b256!("f2ca30dd25939d665a0c2f69692777f2b3577f645e1c833ce54959ca0905ecc6");

// Circle API message (without 0x prefix)
const API_MESSAGE: &str = "000000010000000300000006eb20f0033c7fbbc8a633e215369eff6c48f0a36037134713ed435a206c044b8f0000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000000000000000000000000000000000000000000000000007d0000007d00000000100000000000000000000000075faf114eafb1bdbe2f0316df893fd58ce46aa4d0000000000000000000000007f7d081724f0240c64c9e01cde4626602f9a019200000000000000000000000000000000000000000000000000000000000f42400000000000000000000000007f7d081724f0240c64c9e01cde4626602f9a0192000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    dotenv().ok();

    println!("🔍 Debug: Comparing Message Extraction\n");

    let api_key = std::env::var("TESTNET_API_KEY")?;
    let arb_rpc = std::env::var("ARBITRUM_SEPOLIA_RPC_URL")?;
    let full_url = format!("{arb_rpc}{api_key}");

    let provider = ProviderBuilder::new().connect_http(full_url.parse()?);

    // Get transaction receipt
    println!("1️⃣  Fetching transaction receipt...");
    let receipt = provider
        .get_transaction_receipt(BURN_TX_HASH)
        .await?
        .expect("Receipt not found");

    println!("   Found {} logs\n", receipt.inner.logs().len());

    // Find MessageSent log
    let message_sent_topic = alloy_primitives::keccak256(b"MessageSent(bytes)");
    println!("2️⃣  MessageSent topic: {message_sent_topic}\n");

    for (i, log) in receipt.inner.logs().iter().enumerate() {
        println!("   Log {}: topic0 = {:?}", i, log.topics().first());

        if log
            .topics()
            .first()
            .is_some_and(|t| t.as_slice() == message_sent_topic)
        {
            println!("\n3️⃣  Found MessageSent log!");
            println!("   Raw data length: {} bytes", log.data().data.len());
            println!("   Raw data (hex): 0x{}\n", hex::encode(&log.data().data));

            // The log data is ABI-encoded: offset (32 bytes) + length (32 bytes) + message
            let raw_data = &log.data().data;

            // First 32 bytes: offset to the bytes data (should be 0x20 = 32)
            let offset = &raw_data[0..32];
            println!("   Offset (first 32 bytes): 0x{}", hex::encode(offset));

            // Next 32 bytes: length of the bytes data
            let length_bytes = &raw_data[32..64];
            let length = u64::from_be_bytes(length_bytes[24..32].try_into()?);
            println!("   Length field: {length} bytes");

            // Remaining bytes: the actual message
            let message = &raw_data[64..64 + length as usize];
            println!("   Extracted message length: {} bytes", message.len());
            println!("   Extracted message: 0x{}\n", hex::encode(message));

            // Compare with Circle API
            let api_message_bytes = hex::decode(API_MESSAGE)?;
            println!("4️⃣  Comparison with Circle API:");
            println!("   API message length: {} bytes", api_message_bytes.len());

            if message == api_message_bytes.as_slice() {
                println!("   ✅ MATCH! Messages are identical");
            } else {
                println!("   ❌ MISMATCH!");

                // Find where they differ
                let min_len = message.len().min(api_message_bytes.len());
                for i in 0..min_len {
                    if message[i] != api_message_bytes[i] {
                        println!(
                            "   First difference at byte {}: extracted=0x{:02x}, api=0x{:02x}",
                            i, message[i], api_message_bytes[i]
                        );
                        break;
                    }
                }

                if message.len() != api_message_bytes.len() {
                    println!(
                        "   Length difference: extracted={}, api={}",
                        message.len(),
                        api_message_bytes.len()
                    );
                }
            }

            // Compute hashes
            let extracted_hash = alloy_primitives::keccak256(message);
            let api_hash = alloy_primitives::keccak256(&api_message_bytes);

            println!("\n5️⃣  Hash comparison:");
            println!("   Extracted message hash: {extracted_hash}");
            println!("   API message hash:       {api_hash}");
            println!("   Expected from logs:     0x6c3f18b0822232dfa4b41429a62bfa5241d28db1edce05ea265896cba4075ed9");
        }
    }

    Ok(())
}