Build your first XCDP - Solana to EVM Contract and XCDP - EVM to Solana Contract
Step by Step guide with detailed process. In this guide we will use Solana devnet, Sepolia and L1X TestNet to deploy X-Talk Flow Contract.
Building Blocks for XCDP
Smart Contracts on Solana that will be the endpoints for sending and receiving messages. This is runtime agnostic.
X-Talk XCDP Solana-EVM Flow Contract Deployment and Initialisation to facilitate programmability while sending and receiving messages.
X-Talk XCDP EVM-Solana Flow Contract Deployment and Initialisation to facilitate programmability while sending and receiving messages.
Smart Contracts on EVM-compatible chain (Sepolia in this example) that will be the endpoints for sending and receiving messages. This is runtime agnostic.
XCDP Process
This page is divided into 4 parts, for the ease of understanding, as listed below
Basic Steps: Common for both Solana-EVM and EVM-Solana
Solana to EVM Integration
EVM to Solana Integration
X-Talk Gateway Contract Address
I. Basic Steps
This section is categorised into Pre-Requisites, Set up Solana Project and Set up Sepolia Project.
Step 1: Pre-Requisites
Step 2: Set up Solana Project
Open a new terminal to create Solana project
Step 1: Initialize a New Project
Create a New Directory
mkdirsolana-project-namecdsolana-project-name
Initialize a New Anchor Project
anchorinitflow_contract
Step 2: Write your Solana smart contract
Paste your contract code at /programs/flow_contract/src/lib.rs
Ensure that you update at programs/flow_contract/Cargo.toml
[package]name ="flow-contract"version ="0.1.0"description ="Created with Anchor"edition ="2021"[lib]crate-type = ["cdylib","lib"]name ="flow_contract"[features]no-entrypoint = []no-idl = []no-log-ix-name = []cpi = ["no-entrypoint"]default = [][dependencies]anchor-lang ="0.29.0"serde = { version ="1", features = ["derive"] }serde_json ="1"
Step 4: Install Dependencies
Goto solana-project-name/flow_contract and then run below commands
npminstall
Step 5: Compile the Smart Contract
anchorbuild
Step 6: Display the list of Key Pairs
anchorkeyslist
Output is YOUR_SOLANA_PROGRAM_ID. Save it as it is used later at various instances.
flow:8LHf4FmTPrXkPg9Jtgoexj64GTE8SbaSXNJuNeQBDSUG
Step 7: Declare PROGRAM_ID
Goto program/flow_contract/src/lib.rs and update declare_id! with YOUR_SOLANA_PROGRAM_ID.
Below is the snapshot for your reference
declare_id!("YOUR_SOLANA_PROGRAM_ID");
Step 8: Configure Scripts
Create scripts folder and add below scripts to it.
Script to Initiate Contract
Ensure that the connection is set to devnet
Load the wallet keypair path (for the file id.json) as YOUR_KEYPAIR_PATH
Update programId with YOUR_SOLANA_PROGRAM_ID
Update idlString with YOUR_IDL_PATH (the path to /target/idl/flow_contract.json)
//initiate_flow.tsimport*as anchor from"@coral-xyz/anchor";import { Program, AnchorProvider, Wallet, web3, Idl } from"@coral-xyz/anchor";import { Connection, PublicKey, Keypair } from"@solana/web3.js";import fs from"fs";// Set to devnetconstconnection=newConnection("https://api.devnet.solana.com","confirmed");// Load the wallet keypair from the file id.jsonconstwalletKeypair=Keypair.fromSecretKey(newUint8Array(JSON.parse(fs.readFileSync("YOUR_KEYPAIR_PATH","utf8"))));constwallet=newWallet(walletKeypair);constprovider=newAnchorProvider(connection, wallet,AnchorProvider.defaultOptions());anchor.setProvider(provider);// Ensure the correct program ID and IDL are usedconstprogramId=newPublicKey("YOUR_SOLANA_PROGRAM_ID");constidlString=fs.readFileSync("YOUR_IDL_PATH","utf8");constidl=JSON.parse(idlString) asIdl;console.log("Program ID:",programId.toString());console.log("IDL:", idl);console.log("Provider:", provider);// Correctly instantiate the programconstprogram=newProgram(idl, programId, provider asanchor.Provider);asyncfunctionairdropSol(connection:Connection, publicKey:PublicKey) {constairdropSignature=awaitconnection.requestAirdrop(publicKey,web3.LAMPORTS_PER_SOL);awaitconnection.confirmTransaction(airdropSignature);}asyncfunctioncallProgramFunction() {constdataAccount=Keypair.generate();console.log("data_account",dataAccount.publicKey.toBase58());// Check if the wallet has sufficient funds, if not, airdrop SOL to the walletconstbalance=awaitconnection.getBalance(provider.wallet.publicKey);if (balance <web3.LAMPORTS_PER_SOL) {awaitairdropSol(connection,provider.wallet.publicKey); }try {consttxHash=awaitprogram.methods.initialize().accounts({ calledAccount:dataAccount.publicKey, payer:provider.wallet.publicKey, systemProgram:web3.SystemProgram.programId }).signers([dataAccount]).rpc();console.log("Transaction hash:", txHash);// Optional: Wait for confirmationawaitconnection.confirmTransaction(txHash); } catch (err) {console.error("Error calling program function:", err); }}// Example usage:callProgramFunction().catch(err =>console.error("Error calling program function:", err));
Write deployment scripts or tests as needed, using the setup you've created. Example Provided below.
// deploy.jsconsthre=require("hardhat");asyncfunctionmain() {const [deployer] =awaithre.ethers.getSigners();console.log("Deploying contracts with the account:",deployer.address );// getBalance is a method on the provider, not the signerconsole.log("Account balance:", (awaitdeployer.provider.getBalance(deployer.address)).toString());constXCDP=awaithre.ethers.getContractFactory("XCDPCore");constXCDPContract=awaitXCDP.deploy([deployer.address]);awaitXCDPContract.waitForDeployment();console.log("XCDP contract deployed to:",awaitXCDPContract.getAddress()); }main().then(() =>process.exit(0)).catch((error) => {console.error(error);process.exit(1); });
Flow Contracts are at the core of cross-chain application development. They enable logic programmability, reducing integration and maintenance overhead and enable scalability.
Create a new L1X project that provides the necessary files to get started
cargol1xcreatexcdp-sol-evm
Use the provided code below to implement the cross-chain xcdp Solana to EVM logic.
Create your L1X XCDP Solana-EVM Flow Contact at src/lib.rs
The Fundamental Building Blocks of X-Talk require the below building blocks in X-Talk Contract.
Data Structures: Define messages and events used within the contract.
Event Handling: Parse, log, and handle blockchain events.
State Management: Load and save the contract's state to maintain event records.
Message Parsing: Convert blockchain event logs into usable data formats.
Event Emission and Logging: Emit structured logs for cross-chain messaging.
Security: Ensure that only valid and expected data is processed.
Ensure you have all the necessary packages, libraries and dependencies in your Cargo.toml file as below.
[package]name ="l1x-sol-evm-xcdp"version ="0.1.0"edition ="2021"[lib]crate-type = ["cdylib"][dependencies]borsh = { version ="0.9", features = ["const-generics"] }l1x-sdk ="0.3.1"serde = { version ="1", features = ["derive"] }serde_json ="1"getrandom = { version ="0.2.10", features = ["js"] }log ="0.4.20"bincode ="1.3.3"base64 ="*"
To deploy the X-Talk flow contract, you can use this command on your project level. After successfully building, you will find the "project_name.o" file in target/l1x/release folder. This is the object file which will be deployed.
cargol1xbuild
To deploy your contract, use an existing L1X Account on TestNet.
Create Wallet
Create your own wallet to generate a new keypair.
l1x-cli-betawalletcreate
Import Wallet
Import you existing wallet using the l1x-cli-beta tool, by providing a private key. If you don't have one, Create Wallet.
l1x-cli-betawalletimport<PRIVATEKEY>
Default Wallet
After Importing your wallet set it to be the default interaction account.
l1x-cli-betawalletdefault<Wallet_Address>
-- Check Endpoints in Interface Essentials for TestNet Faucet
SOURCE_REGISTRY_INSTANCE_ADDRESS => This is provided
source_contract_address => YOUR_SOLANA_PROGRAM_ID
YOUR_FLOW_CONTRACT_ADDRESS => The initiated X-TalK Solana - EVM Flow Contract Address
Note that event_filters is blank for Solana
Step 3: Implementing Script to Emit Event from Solana to EVM
Step 1: Add Script to Emit Events
In your solana project, inside scripts folder add emit_event.ts script.
Update walletKeypair with YOUR_KEYPAIR_PATH
emitEvent() arguments are in the sequence: message, destinationNetwork and destinationAddress.
message is set to "Hello world!". You can send the message you want.
destinationNetwork is set to "sepolia". (Update your EVM-compatible destination network here.)
Update destinationAddress with YOUR_EVM_CONTRACT_ADDRESS
// emit_event.tsimport*as anchor from"@coral-xyz/anchor";import { Program } from"@coral-xyz/anchor";import { FlowContract } from"../target/types/flow_contract";import { Keypair } from"@solana/web3.js";import fs from"fs";//set this "https://api.devnet.solana.com" in case of using devnet instead of localnetconstconnection=newanchor.web3.Connection("https://api.devnet.solana.com","confirmed");// Load the wallet keypair from the file - path to id.jsonconstwalletKeypair=Keypair.fromSecretKey(newUint8Array(JSON.parse(fs.readFileSync("YOUR_KEYPAIR_PATH","utf8"))));constwallet=newanchor.Wallet(walletKeypair);constprovider=newanchor.AnchorProvider(connection, wallet,anchor.AnchorProvider.defaultOptions());anchor.setProvider(provider);constprogram=anchor.workspace.FlowContract asProgram<FlowContract>;asyncfunctioncallProgramFunction() {constpayer=provider.wallet;// Update YOUR_AVAX_CONTRACT_ADDRESS. Hellow world! is the message you want to send to EVM chain. Feel free to update message.
try {consttxHash=awaitprogram.methods.emitEvent("Hello World","sepolia","EVM_CONTRACT_ADDRESS").accounts({ payer:payer.publicKey }).rpc();console.log("Transaction hash:", txHash);// Increase timeout period for confirmationconstconfirmed=awaitconnection.confirmTransaction(txHash,'confirmed');console.log("Confirmation:", confirmed); } catch (err) {console.error("Error calling program function:", err); }}// Example usage:callProgramFunction().catch(err =>console.error("Error calling program function:", err));
Step 2: Update script path in Anchor.toml
Add the path for emit_event.ts script in Anchor.toml
To verify that event is received at YOUR_EVM_CONTRACT_ADDRESS, goto EVM blockchain explorer, check for the transaction with X-Talk Gateway for Sepolia mentioned in the table.
III. EVM to Solana Integration
This section is dedicated to send a message from any EVM-compatible chain (Sepolia in this example) to Solana.
The Fundamental Building Blocks of X-Talk require the below building blocks in X-Talk Contract.
Data Structures: Define messages and events used within the contract.
Event Handling: Parse, log, and handle blockchain events.
State Management: Load and save the contract's state to maintain event records.
Message Parsing: Convert blockchain event logs into usable data formats.
Event Emission and Logging: Emit structured logs for cross-chain messaging.
Security: Ensure that only valid and expected data is processed.
Ensure you have all the necessary packages, libraries and dependencies in your Cargo.toml file as below.
// Cargo.toml[package]name ="evm-solana"version ="0.1.0"edition ="2021"[lib]crate-type = ["cdylib"][dependencies]borsh = { version ="0.9", features = ["const-generics"] }l1x-sdk ="0.3.1"serde = { version ="1", features = ["derive"] }serde_json ="1"getrandom = { version ="0.2.10", features = ["js"] }log ="0.4.20"
To deploy the X-Talk flow contract, you can use this command on your project level. After successfully building, you will find the "project_name.o" file in target/l1x/release folder. This is the object file which will be deployed.
cargol1xbuild
To deploy your contract, use an existing L1X Account with L1X for Mainnet and L1X TestNet for TestNet.
Create Wallet
Create your own wallet to generate a new keypair.
l1x-cli-betawalletcreate
Import Wallet
Import you existing wallet using the l1x-cli-beta tool, by providing a private key. If you don't have one, Create Wallet.
l1x-cli-betawalletimport<PRIVATEKEY>
Default Wallet
After Importing your wallet set it to be the default interaction account.
l1x-cli-betawalletdefault<Wallet_Address>
-- Check Endpoints in Interface Essentials for TestNet Faucet
SOURCE_REGISTRY_INSTANCE_ADDRESS => This is provided
source_contract_address => YOUR_EVM_CONTRACT_ADDRESS
TOPIC_OF_YOUR_EVENT => Topic of the event
YOUR_FLOW_CONTRACT_ADDRESS => The initiated X-TalK EVM-Solana Flow Contract Address
Step 3: Implementing Script to Emit Event from EVM to Solana
Step 1: Add Script to Emit Events
In your EVM project, inside scripts folder add emit_event.js script.
Update XCDPContractAddress with YOUR_EVM_CONTRACT_ADDRESS
_l1xSend() arguments are in the sequence: message, destinationNetwork and destinationAddress.
message is set to "Hello from L1X!". You can send the message you want.
destinationNetwork is set to "Solana".
Update destinationAddress with Solana Program ID concatenated with Solana Data Account i.e. YOUR_SOLANA_PROGRAM_ID_YOUR_SOLANA_DATA_ACCOUNT
// emit_event.js
const hre = require("hardhat");
const { ethers } = hre;
async function main() {
const [deployer, user] = await ethers.getSigners();
console.log("Deployer address:", deployer.address);
const XCDP = await ethers.getContractFactory("XCDPCore");
const XCDPContractAddress = "YOUR_EVM_CONTRACT_ADDRESS";
const XCDPContract = XCDP.attach(XCDPContractAddress);
let tx = await XCDPContract.connect(deployer)._l1xSend(
"Hello from L1X!",
"Solana",
"YOUR_SOLANA_PROGRAM_ID_YOUR_SOLANA_DATA_ACCOUNT"
);
let receipt = await tx.wait(); // Wait for the transaction to be mined
// Get the logs from the receipt
let logs = receipt.logs;
// Assuming there is only one log emitted
if (logs.length > 0) {
console.log("Event topic:", logs[0].topics[0]);
} else {
console.log("No logs emitted.");
}
console.log("send message tx hash:", tx.hash);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
```
Step 4: Send Message from Sepolia to Solana
npx hardhat run --network sepolia ./scripts/emit_event.js
To verify that event is received at YOUR_SOLANA_PROGRAM_ID, go to Solana explorer, check for the transaction with X-Talk Gateway for Solana mentioned in the table.
IV. X-Talk Gateway Contract Address
Below table contains a list of contract address that serve as an authenticated entry to interact with X-Talk from respective client chain network.