Get Started with Messaging#
Wormhole's core functionality allows you to send any data packet from one supported chain to another. This guide demonstrates how to publish your first simple, arbitrary data message from an EVM environment source chain using the Wormhole TypeScript SDK's core messaging capabilities.
Prerequisites#
Before you begin, ensure you have the following:
- Node.js and npm installed
- TypeScript installed
- Ethers.js installed (this example uses version 6)
- A small amount of testnet tokens for gas fees. This example uses Sepolia ETH but can be adapted for any supported network
- A private key for signing blockchain transactions
Configure Your Messaging Environment#
-
Create a directory and initialize a Node.js project:
-
Install TypeScript, tsx, Node.js type definitions, and Ethers.js:
-
Create a
tsconfig.json
file if you don't have one. You can generate a basic one using the following command:Make sure your
tsconfig.json
includes the following settings: -
Install the TypeScript SDK:
-
Create a new file named
main.ts
:
Construct and Publish Your Message#
-
Open
main.ts
and update the code there as follows:main.tsimport { wormhole, signSendWait, toNative, encoding, type Chain, type Network, type NativeAddress, type WormholeMessageId, type UnsignedTransaction, type TransactionId, type WormholeCore, type Signer as WormholeSdkSigner, type ChainContext, } from '@wormhole-foundation/sdk'; // Platform-specific modules import EvmPlatformLoader from '@wormhole-foundation/sdk/evm'; import { getEvmSigner } from '@wormhole-foundation/sdk-evm'; import { ethers, Wallet, JsonRpcProvider, Signer as EthersSigner, } from 'ethers'; /** * The required value (SEPOLIA_PRIVATE_KEY) must * be loaded securely beforehand, for example via a keystore, secrets * manager, or environment variables (not recommended). */ const SEPOLIA_PRIVATE_KEY = SEPOLIA_PRIVATE_KEY!; // Provide a private endpoint RPC URL for Sepolia, defaults to a public node // if not set const RPC_URL = process.env.SEPOLIA_RPC_URL || 'https://ethereum-sepolia-rpc.publicnode.com'; async function main() { // Initialize Wormhole SDK const network = 'Testnet'; const wh = await wormhole(network, [EvmPlatformLoader]); console.log('Wormhole SDK Initialized.'); // Get the EVM signer and provider let ethersJsSigner: EthersSigner; let ethersJsProvider: JsonRpcProvider; try { if (!SEPOLIA_PRIVATE_KEY) { console.error('Please set the SEPOLIA_PRIVATE_KEY environment variable.'); process.exit(1); } ethersJsProvider = new JsonRpcProvider(RPC_URL); const wallet = new Wallet(SEPOLIA_PRIVATE_KEY); ethersJsSigner = wallet.connect(ethersJsProvider); console.log( `Ethers.js Signer obtained for address: ${await ethersJsSigner.getAddress()}`, ); } catch (error) { console.error('Failed to get Ethers.js signer and provider:', error); process.exit(1); } // Define the source chain context const sourceChainName: Chain = 'Sepolia'; const sourceChainContext = wh.getChain(sourceChainName) as ChainContext< 'Testnet', 'Sepolia', 'Evm' >; console.log(`Source chain context obtained for: ${sourceChainContext.chain}`); // Get the Wormhole SDK signer, which is a wrapper around the Ethers.js // signer using the Wormhole SDK's signing and transaction handling // capabilities let sdkSigner: WormholeSdkSigner<Network, Chain>; try { sdkSigner = await getEvmSigner(ethersJsProvider, ethersJsSigner); console.log( `Wormhole SDK Signer obtained for address: ${sdkSigner.address()}`, ); } catch (error) { console.error('Failed to get Wormhole SDK Signer:', error); process.exit(1); } // Construct your message payload const messageText = `HelloWormholeSDK-${Date.now()}`; const payload: Uint8Array = encoding.bytes.encode(messageText); console.log(`Message to send: "${messageText}"`); // Define message parameters const messageNonce = Math.floor(Math.random() * 1_000_000_000); const consistencyLevel = 1; try { // Get the core protocol client const coreProtocolClient: WormholeCore<Network> = await sourceChainContext.getWormholeCore(); // Generate the unsigned transactions const whSignerAddress: NativeAddress<Chain> = toNative( sdkSigner.chain(), sdkSigner.address(), ); console.log( `Preparing to publish message from ${whSignerAddress.toString()} on ${ sourceChainContext.chain }...`, ); const unsignedTxs: AsyncGenerator<UnsignedTransaction<Network, Chain>> = coreProtocolClient.publishMessage( whSignerAddress, payload, messageNonce, consistencyLevel, ); // Sign and send the transactions console.log( 'Signing and sending the message publication transaction(s)...', ); const txIds: TransactionId[] = await signSendWait( sourceChainContext, unsignedTxs, sdkSigner, ); if (!txIds || txIds.length === 0) { throw new Error('No transaction IDs were returned from signSendWait.'); } const primaryTxIdObject = txIds[txIds.length - 1]; const primaryTxid = primaryTxIdObject.txid; console.log(`Primary transaction ID for parsing: ${primaryTxid}`); console.log( `View on Sepolia Etherscan: https://sepolia.etherscan.io/tx/${primaryTxid}`, ); console.log( '\nWaiting a few seconds for transaction to propagate before parsing...', ); await new Promise((resolve) => setTimeout(resolve, 8000)); // Retrieve VAA identifiers console.log( `Attempting to parse VAA identifiers from transaction: ${primaryTxid}...`, ); const messageIds: WormholeMessageId[] = await sourceChainContext.parseTransaction(primaryTxid); if (messageIds && messageIds.length > 0) { const wormholeMessageId = messageIds[0]; console.log('--- VAA Identifiers (WormholeMessageId) ---'); console.log(' Emitter Chain:', wormholeMessageId.chain); console.log(' Emitter Address:', wormholeMessageId.emitter.toString()); console.log(' Sequence:', wormholeMessageId.sequence.toString()); console.log('-----------------------------------------'); } else { console.error( `Could not parse Wormhole message IDs from transaction ${primaryTxid}.`, ); } } catch (error) { console.error( 'Error during message publishing or VAA identifier retrieval:', error, ); if (error instanceof Error && error.stack) { console.error('Stack Trace:', error.stack); } } } main().catch((e) => { console.error('Critical error in main function (outer catch):', e); if (e instanceof Error && e.stack) { console.error('Stack Trace:', e.stack); } process.exit(1); });
This script initializes the SDK, defines values for the source chain, creates an EVM signer, constructs the message, uses the core protocol to generate, sign, and send the transaction, and returns the VAA identifiers upon successful publication of the message.
-
Run the script using the following command:
You will see terminal output similar to the following:
npx tsx main.ts Wormhole SDK Initialized. Ethers.js Signer obtained for address: 0xCD8Bcd9A793a7381b3C66C763c3f463f70De4e12 Source chain context obtained for: Sepolia Wormhole SDK Signer obtained for address: 0xCD8Bcd9A793a7381b3C66C763c3f463f70De4e12 Message to send: "HelloWormholeSDK-1748362375390" Preparing to publish message from 0xCD8Bcd9A793a7381b3C66C763c3f463f70De4e12 on Sepolia... Signing and sending the message publication transaction(s)... Primary Transaction ID for parsing: 0xeb34f35f91c72e4e5198509071d24fd25d8a979aa93e2f168de075e3568e1508 View on Sepolia Etherscan: https://sepolia.etherscan.io/tx/0xeb34f35f91c72e4e5198509071d24fd25d8a979aa93e2f168de075e3568e1508 Waiting a few seconds for transaction to propagate before parsing... Attempting to parse VAA identifiers from transaction: 0xeb34f35f91c72e4e5198509071d24fd25d8a979aa93e2f168de075e3568e1508... --- VAA Identifiers (WormholeMessageId) --- Emitter Chain: Sepolia Emitter Address: 0x000000000000000000000000cd8bcd9a793a7381b3c66c763c3f463f70de4e12 Sequence: 1 ----------------------------------------- -
Make a note of the transaction ID and VAA identifier values. You can use the transaction ID to view the transaction on Wormholescan. The emitter chain, emitter address, and sequence values are used to retrieve and decode signed messages
Congratulations! You've published your first multichain message using Wormhole's TypeScript SDK and core protocol functionality. Consider the following options to build upon what you've accomplished.
Next Steps#
- Get Started with Token Bridge: Follow this guide to start working with multichain token transfers using Wormhole Token Bridge's lock and mint mechanism to send tokens across chains.