# v1.2 (Beta - Developer Guide)

## Using SimpleMessageTest to Understand the XTalk Protocol

### 1. Overview

#### Purpose

This document provides a comprehensive guide for developers on how to use the `SimpleMessageTest.sol` contract. It serves as a practical, hands-on example for sending and receiving cross-chain messages using the L1X XTalk protocol.

#### What is [`SimpleMessageTest.sol`](https://l1x-sdk.gitbook.io/l1x-developer-interface/multi-chain-with-x-talk/start-building-with-x-talk/cross-chain-data-and-message-passing-xcdp/v1.2-beta-developer-guide/simplemessagetest.sol)?

`SimpleMessageTest.sol` is a minimal, developer-friendly smart contract that demonstrates the core functionality of the XTalk protocol. It is designed to be a clear and lucid starting point for building more complex cross-chain applications.

#### Target Audience & Limitations

This guide is intended for developers building applications on EVM-compatible blockchains.

* **Current Scope:** This version of the XTalk protocol and this example **support EVM-compatible networks only**.
* **Supported Test Networks:**
  * BSC Testnet
  * Ethereum Sepolia

#### Core Concepts

The heart of the XTalk protocol is the `XTalkBeacon` contract. It acts as a cross-chain "post office," where your contract drops off messages for other chains (`registerMessage`) and receives incoming messages via a standardized callback (`_xtalkMessageReceived`).

***

### 2. Contract Anatomy

The `SimpleMessageTest.sol` contract is structured to clearly separate the mandatory XTalk protocol code from the customizable user logic. This is highlighted by the in-code comments.

#### A. XTalk Infrastructure

These are the parts you **must** include and configure correctly for your contract to be XTalk-compatible.

* **`IXTalkBeacon` Interface:** A standard Solidity interface that defines how your contract will communicate with the deployed `XTalkBeacon`.
* **`xtalkBeaconAddress` State Variable & `constructor`:**

  ```solidity
  // The XTalkBeacon contract instance.
  address public immutable xtalkBeaconAddress;

  /**
   * @dev Constructor to set the address of the XTalkBeacon contract.
   */
  constructor(address _beaconAddress) {
      require(_beaconAddress != address(0), "Invalid beacon address");
      xtalkBeaconAddress = _beaconAddress;
  }
  ```

  This is a critical setup step. When you deploy your contract, you must provide the correct address of the `XTalkBeacon` for the specific blockchain you are deploying on. The contract then casts this address to the `IXTalkBeacon` interface when it needs to call a beacon function.
* **`_xtalkMessageReceived` Function:**

  ```solidity
  function _xtalkMessageReceived(bytes32 _messageId, /*...*/) public {
      require(msg.sender == address(xtalkBeacon), "Caller is not the XTalkBeacon");
      // ...
  }
  ```

  This is the mandatory callback function that the beacon executes to deliver a message to your contract.

  * **Function Signature:** The name and parameters must be exactly `_xtalkMessageReceived(bytes32,address,uint32,uint32,bytes)`.
  * **Security:** It is crucial to include the `require(msg.sender == address(xtalkBeacon), ...)` check to ensure that only the legitimate beacon can call this function.

#### B. User-Defined Logic

This is the part of the contract that you customize to build your application's specific features.

* **`Message` Struct & `receivedMessages` Mapping:**

  ```solidity
  struct Message { /* ... */ }
  mapping(bytes32 => Message) public receivedMessages;
  ```

  In this example, we use these to store the data from a received message. For your application, you might process a token swap, mint an NFT, or update some other state.
* **`MessageReceived` Event:** This event provides off-chain tracking and visibility for when your contract *receives* a message. Note that the event for *sending* a message is emitted by the `XTalkBeacon` contract itself.
* **`invokeMessage` Function:** This function demonstrates how the user-defined logic prepares data and then hands it off to the XTalk Infrastructure to be sent.

  ```solidity
  function invokeMessage(...) public {
      // 1. User-Defined: Prepare your message data
      uint32 messageType = 1;
      bytes memory messageData = abi.encode(_message);

      // 2. XTalk Infrastructure: Get nonce and register the message
      uint256 nonce = IXTalkBeacon(xtalkBeaconAddress).getSenderRegistrationNextNonce(address(this));
      IXTalkBeacon(xtalkBeaconAddress).registerMessage(...);

      // NO event is emitted here. The XTalkBeacon contract emits the
      // 'XTalkMessageBroadcasted' event, which should be used for tracking.
  }
  ```

***

### 3. How It Works: The Cross-Chain Flow

The following diagram illustrates the end-to-end journey of a message. The L1X Validator Network and the XTalk Consensus Contract on the L1X chain are the core components that make this possible.

```mermaid
sequenceDiagram
    participant User
    participant SMT_Source as SimpleMessageTest (Source Chain)
    participant Beacon_Source as XTalkBeacon (Source Chain)
    participant L1X_Network as L1X Validator Network
    participant Consensus_Contract as XTalk Consensus (L1X)
    participant Beacon_Dest as XTalkBeacon (Destination Chain)
    participant SMT_Dest as SimpleMessageTest (Destination Chain)

    User->>+SMT_Source: invokeMessage(...)
    SMT_Source->>+Beacon_Source: registerMessage(...)
    note right of Beacon_Source: Emits 'XTalkMessageBroadcasted'<br/>(messageId is retrieved here)
    Beacon_Source-->>-SMT_Source:
    SMT_Source-->>-User:

    L1X_Network->>Beacon_Source: Observes event on Source Chain
    L1X_Network->>+Consensus_Contract: Reaches consensus on the message
    note over L1X_Network, Consensus_Contract: Validators sign the message payload

    User->>Consensus_Contract: (Optional) get_consensus_status(messageId)
    Consensus_Contract-->>User: Returns signer count, status, etc.

    L1X_Network->>+Beacon_Dest: Relays message with signatures
    Beacon_Dest->>+SMT_Dest: _xtalkMessageReceived(...)
    note right of SMT_Dest: Executes user logic
    SMT_Dest-->>-Beacon_Dest:
    Beacon_Dest-->>-L1X_Network:
```

The process unfolds in these steps:

1. **Invocation (Source Chain):** A user calls `invokeMessage` on the `SimpleMessageTest` contract.
2. **Registration (Source Chain):** The contract calls `registerMessage` on the source chain's `XTalkBeacon`, which emits the `XTalkMessageBroadcasted` event containing a unique `messageId`.
3. **Consensus (L1X Network):** The L1X network validators observe this event. They process the message, and a sufficient number of them sign it, reaching consensus. This status is recorded in the `XTalk Consensus Contract` on the L1X chain.
4. **Execution (Destination Chain):** Once consensus is reached, the validators relay the message and the collected signatures to the `XTalkBeacon` on the destination chain. The beacon verifies the signatures and calls `_xtalkMessageReceived` on the target contract, completing the cross-chain transaction.

***

### 4. Practical Guide: Deployment and Interaction

#### Supported Networks & Chain IDs

List the currently supported test networks and their corresponding Chain IDs, which you will need for the `_destinationChainId` parameter when sending a message.

* **BSC Testnet:**
  * Chain ID: `97`
* **Ethereum Sepolia:**
  * Chain ID: `11155111`

#### Deployment

1. **Compile:** Use a Solidity compiler (version `^0.8.0` or compatible) to compile `SimpleMessageTest.sol`.
2. **Deploy:** Deploy the compiled contract to a supported network (e.g., BSC Testnet or ETH Sepolia).
3. **Provide Beacon Address:** During deployment, you **must** provide the correct `XTalkBeacon` address for that network in the constructor.
   * **BSC Testnet `XTalkBeacon` Address:** `0x283217CFb842A235994397a36714eF90F7744B51`
   * **ETH Sepolia `XTalkBeacon` Address:** `0x8cA818D30c01AD6ebD2273CC419553F38fE7c92f`

#### Sending a Message

To send a message, call the `invokeMessage` function on your deployed contract.

* **`_destinationChainId`:** The unique identifier for the destination chain. Use `97` for BSC Testnet or `11155111` for Ethereum Sepolia.
* **`_destinationAddress`:** The address of the recipient contract on the destination chain, formatted as `bytes32`.
* **`_message`:** The string message you want to send.

#### Confirming a Message on the Destination Chain

1. When you call `invokeMessage`, listen for the `XTalkMessageBroadcasted` event on the `XTalkBeacon` contract. It contains the `messageId`.
2. After a short delay for the L1X Network to relay the message, you can check for its arrival on the destination chain.
3. Call the `receivedMessages` public mapping on the destination contract with the `messageId` to retrieve the stored message data and confirm its receipt.

#### Tracking Message Status on the L1X Network

The ultimate source of truth for a message's journey is the XTalk Consensus Contract deployed on the L1X Network. You can query this contract directly using the `messageId` to see its current consensus status, including how many validators have signed it.

The address for this contract on **L1X Testnet v2** is:

* **XTalk Consensus Contract:** `ce527bdc3eeaa201a2706ab2e447fc7f0db5cb68`

The following script shows how to do this using the `@l1x/l1x-wallet-sdk`.

```javascript
// Example script to check the consensus status of a message on the L1X Network.
// This requires the `@l1x/l1x-wallet-sdk`. You can install it via npm:
// npm install @l1x/l1x-wallet-sdk
const { L1XProvider } = require("@l1x/l1x-wallet-sdk");

// The address of the XTalk Consensus Contract on the L1X Network
const CONSENSUS_CONTRACT_ADDRESS = "ce527bdc3eeaa201a2706ab2e447fc7f0db5cb68";

/**
 * Initializes and returns an L1XProvider instance for the L1X V2 Testnet.
 * @returns {L1XProvider}
 */
function getL1XProvider() {
    // For mainnet, change clusterType to "mainnet" and use the mainnet RPC endpoint.
    return new L1XProvider({
      clusterType: "testnet",
      endpoint: "https://v2-testnet-rpc.l1x.foundation"
    });
}

/**
 * Queries the L1X Consensus Contract for the status of a specific message.
 * @param {string} messageId The message ID obtained from the 'XTalkMessageBroadcasted' event.
 * @returns {Promise<object>} The consensus status of the message.
 */
async function checkL1XConsensusStatus(messageId) {
    console.log(`\nQuerying L1X for status of message: ${messageId}`);

    try {
        // Initialize the L1X provider
        const l1xProvider = getL1XProvider();

        // Make a read-only call to the smart contract
        const responseJson = await l1xProvider.core.makeReadOnlyCall({
            contract_address: CONSENSUS_CONTRACT_ADDRESS,
            function_name: "get_consensus_status",
            args: {
                message_id: messageId
            },
        });
        
        // The response is a JSON string, so we parse it
        return JSON.parse(responseJson);

    } catch (error) {
        console.error("Error fetching consensus status:", error);
        throw error;
    }
}

// --- Main execution ---
(async () => {
    // Replace with the messageId you want to track
    const messageIdToTrack = "0xffd6da09d5047e7ce19bf818e0d93c11a0bd2fc79ec80c18a523569f6a902973";
    
    const status = await checkL1XConsensusStatus(messageIdToTrack);
    
    console.log("\n--- L1X Consensus Status ---");
    console.log(JSON.stringify(status, null, 2)); // Pretty-print the status object
})();
```

#### Understanding the Consensus Status Response

The JSON object returned by `get_consensus_status` represents the complete state of a cross-chain message as it moves through the L1X consensus process. Here is a breakdown of each field:

**Validator and Confirmation Counts**

These fields provide an overview of the validator set and the progress of votes for this specific message.

* **`total_listener_validators`**: The total number of registered "Listener" validators at the time the message was processed. Listeners are responsible for observing and confirming the initial event on the source chain.
* **`total_signer_validators`**: The total number of registered "Signer" validators. Signers are responsible for cryptographically signing the message payload that will be executed on the destination chain.
* **`total_relayer_validators`**: The total number of registered "Relayer" validators. Relayers are responsible for broadcasting the transaction to the destination chain and confirming its final state.
* **`agreed_listener_validators`**: The current count of Listener validators who have successfully voted and agreed on the validity of the source chain event.
* **`agreed_signer_validators`**: The current count of Signer validators who have successfully voted on and signed the destination payload.
* **`agreed_relayer_confirmations`**: The current count of Relayer validators who have confirmed that the transaction was successfully executed on the destination chain.
* **`disagreed_relayer_confirmations`**: The current count of Relayer validators who have reported that the transaction failed on the destination chain.

**Lifecycle and Timestamps**

These fields track the message's current stage and when key milestones were reached.

* **`consensus_stage`**: The current stage of the message. Possible values are:
  * `'Pending'`: The message has not yet reached consensus in any phase.
  * `'ListenerFinalized'`: The Listeners have reached consensus on the source chain event.
  * `'SignerFinalized'`: The Signers have reached consensus on the destination payload.
  * `'RelayerBroadcasted'`: The Relayers have successfully broadcasted the transaction to the destination chain.
  * `'RelayerFinalized'`: The Relayers have confirmed the final status (success or failure) of the destination transaction.
* **`listener_finalized_timestamp`**: A Unix timestamp (in milliseconds) of when the Listener consensus was reached.
* **`signer_finalized_timestamp`**: A Unix timestamp (in milliseconds) of when the Signer consensus was reached.
* **`relayer_finalized_timestamp`**: A Unix timestamp (in milliseconds) of when the Relayer consensus was reached.
  * **Note on `relayer_finalized_timestamp`**: This field is reserved for future protocol enhancements where relayers explicitly confirm the final on-chain status (e.g., after a certain number of block confirmations). In the current implementation, the most reliable indicator that a message has been successfully passed to the destination chain is the presence of a non-empty `destination_tx_hash`.

**Message Payload and Routing**

These fields contain the core data of the cross-chain message and the necessary information to route it correctly.

* **`signer_payload`**: The raw byte payload that the Signer validators have agreed upon. This is the exact data passed to the `XTalkBeacon` on the destination chain.
* **`signer_signatures`**: A list of the raw, recoverable signatures from the Signer validators. These are submitted to the destination `XTalkBeacon` to prove the payload's authenticity.

**Transaction and Address Details**

These fields provide the specific identifiers and addresses involved in the cross-chain transaction.

* **`source_network_id`**: The chain ID of the network where the message originated.
* **`source_tx_hash`**: The transaction hash of the initial call on the source chain.
* **`flow_contract_address`**: The address of an intermediary "flow" contract on the L1X network that may have processed the event data.
* **`source_contract_address`**: The address that emitted the event on the source chain (i.e., the `XTalkBeacon`).
* **`destination_tx_hash`**: The transaction hash of the execution call on the destination chain, as reported by the first relayer.
* **`destination_network_id`**: The chain ID of the target network.
* **`destination_contract_address`**: The address of the target contract on the destination chain.
* **`destination_relayer_address`**: The address of the relayer validator that broadcast the transaction.

***

### 5. Next Steps

This `SimpleMessageTest` contract and its usage guide are designed to be your launchpad. You can use this contract as a boilerplate for your own projects. By replacing the user-defined logic, you can build powerful cross-chain applications such as:

* Cross-chain token swaps
* Multi-chain NFT marketplaces
* Decentralized governance across multiple networks

***

### 6. Example: Standalone Ethers.js Script

This example shows how to interact with the contract in a standalone Node.js script using the `ethers` library (v6), without relying on a framework like Hardhat.

#### Important Note: Listening for the Correct Event

When you call `invokeMessage`, your contract does **not** emit an event. The event is emitted by the `XTalkBeacon` contract that `invokeMessage` calls. You must listen to the `XTalkBeacon` contract for the `XTalkMessageBroadcasted` event to get the `messageId` and confirm the message was sent.

#### Important Note: Address to bytes32 Conversion

The `_destinationAddress` parameter in the `invokeMessage` function expects a `bytes32` value. However, standard Ethereum addresses are 20-byte values. You must pad the 20-byte address with leading zeros to convert it to a 32-byte value. With `ethers.js`, you can do this easily:

```javascript
const destinationAddress = "0x..."; // Your 20-byte destination address
const destinationAddressBytes32 = ethers.zeroPadValue(destinationAddress, 32);
```

#### Sample Interaction Script

This script shows a complete flow: setting up a connection, sending a message, and retrieving the `messageId` by listening to the **XTalkBeacon contract's** events.

```javascript
// A standalone script to interact with the SimpleMessageTest contract using ethers.js v6
const { ethers } = require("ethers");

// ======== 1. Configuration ========

// Provider setup: Replace with your RPC URL (e.g., from Infura, Alchemy)
const provider = new ethers.JsonRpcProvider("https://sepolia.infura.io/v3/YOUR_PROJECT_ID");

// Signer setup: Replace with the private key of the account you want to send the transaction from
const privateKey = "0x..."; // WARNING: Do not hardcode private keys in production. Use environment variables.
const wallet = new ethers.Wallet(privateKey, provider);

// Your contract details
const simpleMessageTestAddress = "0x..."; // Address of your deployed SimpleMessageTest contract
const simpleMessageTestAbi = [
    // We only need the function signature for the function we are calling
    "function invokeMessage(uint32 _destinationChainId, bytes32 _destinationAddress, string memory _message)"
];

// XTalkBeacon contract details
const beaconAddress = "0x8cA818D30c01AD6ebD2273CC419553F38fE7c92f"; // The XTalkBeacon address for Sepolia
const beaconAbi = [
    // The ABI for the event we want to listen for
    "event XTalkMessageBroadcasted(bytes32 indexed messageId, uint32 sourceChainId, uint32 destinationChainId, uint32 messageType, address indexed sourceInvokerAddress, bytes32 destinationAddress, uint256 timestamp, bytes messageData)"
];

// Create contract instances
const simpleMessageContract = new ethers.Contract(simpleMessageTestAddress, simpleMessageTestAbi, wallet);
const beaconContract = new ethers.Contract(beaconAddress, beaconAbi, provider);

// ======== 2. Interaction Parameters ========
const destinationChainId = 97; // e.g., BSC Testnet
const destinationAddress = "0x..."; // Address of the recipient contract on the destination chain
const message = "Hello from a standalone ethers.js script!";


async function main() {
    console.log(`Sending transaction from: ${wallet.address}`);
    
    // 3. Convert the 20-byte destination address to bytes32
    const destinationAddressBytes32 = ethers.zeroPadValue(destinationAddress, 32);

    // 4. Invoke the message by calling the contract function
    console.log(`Invoking message on contract at ${simpleMessageTestAddress}...`);
    const tx = await simpleMessageContract.invokeMessage(
        destinationChainId,
        destinationAddressBytes32,
        message
    );
    
    console.log("Transaction sent! Hash:", tx.hash);
    const receipt = await tx.wait();
    console.log(`Transaction confirmed in block: ${receipt.blockNumber}`);

    // 5. Find and parse the 'XTalkMessageBroadcasted' event from the transaction receipt
    //    We must look for this event from the BEACON's address in the logs.
    const eventTopic = beaconContract.interface.getEvent("XTalkMessageBroadcasted").topicHash;
    const log = receipt.logs.find(l => l.address === beaconAddress && l.topics[0] === eventTopic);
    
    if (log) {
        const parsedLog = beaconContract.interface.parseLog(log);
        const { messageId } = parsedLog.args;
        console.log(`\n✅ Message Broadcasted via Beacon!`);
        console.log(`  - Message ID: ${messageId}`);
        console.log(`  - Use this ID to track the message status on the destination chain.`);
    } else {
        console.log("Could not find 'XTalkMessageBroadcasted' event in the transaction receipt.");
    }
}

main().catch((error) => {
    console.error("Script failed:", error);
    process.exit(1);
});
```
