Skip to content

Latest commit

 

History

History
231 lines (166 loc) · 8.43 KB

File metadata and controls

231 lines (166 loc) · 8.43 KB

Hello Wormhole Executor Demo

Cross-chain messaging with Wormhole Executor, demonstrating both off-chain and on-chain quote methods.

This repo covers two use cases:

  • EVM ↔ EVM — both off-chain and on-chain quotes, Sepolia ↔ Base Sepolia
  • EVM ↔ Solana — both off-chain and on-chain quotes (see Cross-VM section)

License: Code provided "AS IS", without warranties. Audit before mainnet deployment.

Contracts

Contract Quote Method Base Class Constructor Param
HelloWormhole Off-chain (API) ExecutorSendReceiveQuoteOffChain executor
HelloWormholeOnChainQuote On-chain ExecutorSendReceiveQuoteOnChain executorQuoterRouter

Key Differences

Off-chain quotes require fetching a signedQuote from the Executor API before sending:

function sendGreeting(..., bytes calldata signedQuote) external payable;

On-chain quotes use a quoter contract directly—no API call needed:

function quoteGreeting(uint16 targetChain, uint128 gasLimit, address quoterAddress) external view returns (uint256);
function sendGreeting(..., address quoterAddress) external payable;

Deployed Contracts (Testnet)

Chain HelloWormhole (Off-chain) HelloWormholeOnChainQuote
Sepolia 0x8f6E15d9A4d0abCe4814c7d86D5B741A91bDCC04 0x86B9182095dca2bdFDFeC7614Af6EC5fAfa910a6
Base Sepolia 0xdF781F7473a1A7C20C1e5fC5f427Fa712dafB698 0x8Be172f2575fe38560bcF0587Ae4269Cb4CC3D18

On-chain Quoter: 0x5241C9276698439fEf2780DbaB76fEc90B633Fbd

Installation

forge install   # Solidity dependencies
npm install     # TypeScript/E2E dependencies

Building an Executor Integration

1. Choose Your Base Contract

// Off-chain quotes (requires API call for signedQuote)
import {ExecutorSendReceiveQuoteOffChain} from "wormhole-solidity-sdk/Executor/Integration.sol";

// On-chain quotes (query quoter contract directly)
import {ExecutorSendReceiveQuoteOnChain} from "wormhole-solidity-sdk/Executor/Integration.sol";

// Both methods supported
import {ExecutorSendReceiveQuoteBoth} from "wormhole-solidity-sdk/Executor/Integration.sol";

Also available: ExecutorSendQuote* (send-only) and ExecutorReceive (receive-only) variants.

2. Implement Constructor

// Off-chain: pass executor address
constructor(address coreBridge, address executor)
    ExecutorSendReceiveQuoteOffChain(coreBridge, executor) {}

// On-chain: pass executorQuoterRouter address
constructor(address coreBridge, address executorQuoterRouter)
    ExecutorSendReceiveQuoteOnChain(coreBridge, executorQuoterRouter) {}

3. Implement Required Functions

// Peer management
function _getPeer(uint16 chainId) internal view override returns (bytes32) {
    return peers[chainId];
}

// Replay protection (sequence-based for finalized VAAs)
function _replayProtect(uint16 emitterChainId, bytes32 emitterAddress, uint64 sequence, bytes calldata)
    internal override {
    SequenceReplayProtectionLib.replayProtect(emitterChainId, emitterAddress, sequence);
}

// Handle incoming messages
function _executeVaa(bytes calldata payload, uint32, uint16 peerChain, bytes32 peerAddress, uint64, uint8)
    internal override {
    string memory greeting = string(payload);
    emit GreetingReceived(greeting, peerChain, peerAddress);
}

4. Send Messages

// Off-chain quote version
sequence = _publishAndRelay(payload, consistencyLevel, totalCost, targetChain, refundAddr,
    signedQuote, gasLimit, msgVal, extraInstructions);

// On-chain quote version
sequence = _publishAndRelay(payload, consistencyLevel, totalCost, targetChain, refundAddr,
    quoterAddress, gasLimit, msgVal, extraInstructions);

Testing

forge test -vvv                                    # All tests
forge test --match-contract HelloWormholeOnChainQuoteTest -vvv  # On-chain quote tests only

Deployment

# Set environment
export PRIVATE_KEY=0x...
export ETHERSCAN_API_KEY=...
export SEPOLIA_RPC_URL=https://...
export BASE_SEPOLIA_RPC_URL=https://...

# Deploy (auto-detects chain and uses correct addresses)
forge script script/HelloWormholeOnChainQuote.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast --verify
forge script script/HelloWormholeOnChainQuote.s.sol --rpc-url $BASE_SEPOLIA_RPC_URL --broadcast --verify

# Set peers (after updating .env with deployed addresses)
forge script script/SetupPeersOnChainQuote.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast
forge script script/SetupPeersOnChainQuote.s.sol --rpc-url $BASE_SEPOLIA_RPC_URL --broadcast

E2E Testing

cd e2e
cp .env.example .env  # Fill in private keys and contract addresses
npx tsx testOnChainQuote.ts  # On-chain quote E2E
npx tsx test.ts              # Off-chain quote E2E

Cross-VM: EVM ↔ Solana

Both HelloWormhole (off-chain quotes) and HelloWormholeOnChainQuote (on-chain quotes) support Solana destinations.

See the Solana demo repo for the Solana-side implementation.

Peer Registration

For EVM ↔ Solana, peer registration on the EVM side requires two separate addresses because the Executor uses peers[chainId] as a routing address (must be an executable program), while incoming VAAs carry the emitter PDA as their source:

// 1. Program ID → executor routing (must be an executable account on Solana)
hello.setPeer(CHAIN_ID_SOLANA, solanaProgramIdBytes32);

// 2. Emitter PDA → VAA verification (PDA(["emitter"], programId))
hello.setVaaEmitter(CHAIN_ID_SOLANA, solanaEmitterPdaBytes32);

Run script/SetupSolanaPeer.s.sol to register both in one step. For EVM↔EVM, only setPeer() is needed.

Derive both Solana addresses from your program ID:

const programId = new PublicKey("7eiTqf1b1dNwpzn27qEr4eGSWnuon2fJTbnTuWcFifZG");
const [emitterPda] = PublicKey.findProgramAddressSync([Buffer.from("emitter")], programId);

const programIdBytes32  = '0x' + Buffer.from(programId.toBytes()).toString('hex');
const emitterPdaBytes32 = '0x' + Buffer.from(emitterPda.toBytes()).toString('hex');

Sending to Solana

Use the overloaded sendGreeting with msgValue in lamports to cover rent and fees on Solana:

// msgValue covers rent + fees on Solana (~0.015 SOL = 15_000_000 lamports)
sequence = sendGreeting(
    greeting,
    1,              // Wormhole chain ID for Solana
    500000,         // compute units (not gas)
    15_000_000,     // msgValue in lamports
    totalCost,
    signedQuote
);

Message size limit: The Solana receiver enforces a 512-byte max on greeting messages. Messages longer than 512 bytes will revert on Sepolia with PayloadTooLargeForSolana.

Deployment

# Deploy HelloWormhole (cross-VM variant)
forge script script/HelloWormhole.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast --verify

# Register Solana as a peer (both program ID and emitter PDA)
forge script script/SetupSolanaPeer.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast

E2E Testing

cd e2e
cp .env.example .env  # Fill in HELLO_WORMHOLE_SEPOLIA_CROSSVM and Solana peer addresses
npx tsx sendToSolana.ts      # EVM → Solana
# For Solana → EVM, run sendToSepolia.ts from the Solana demo repo

Key Concepts

Concept Description
Chain IDs https://wormhole.com/docs/products/reference/chain-ids/
Universal Addresses https://wormhole.com/docs/products/reference/wormhole-formatted-addresses/
Consistency Levels https://wormhole.com/docs/products/reference/consistency-levels/

Resources