Get Started with Core Contracts
Introduction
Wormhole's Core Contracts, deployed on each supported blockchain network, enable the fundamental operations of sending and receiving cross-chain messages.
While the implementation details of the Core Contracts varies by network, the core functionality remains consistent across chains. Each version of the Core Contract facilitates secure and reliable cross-chain communication, ensuring that developers can effectively publish and verify messages.
This guide will walk you through the variations and key methods of the Core Contracts, providing you with the knowledge needed to integrate them into your cross-chain contracts. To learn more about Core Contracts' features and how it works, please refer to the Core Contracts page in the Learn section.
Prerequisites
To interact with the Wormhole Core Contract, you'll need the following:
- The address of the Core Contract on the chains you're deploying your contract on
- The Wormhole chain ID of the chains you're deploying your contract on
- The consistency levels (required finality) for the chains you're deploying your contract on
How to Interact with Core Contracts
Before writing your own contracts, it's essential to understand the key functions and events of the Wormhole Core Contracts. The primary functionality revolves around:
- Sending messages - submitting messages to the Wormhole network for cross-chain communication
- Receiving and verifying messages - validating messages received from other chains via the Wormhole network
While the implementation details of the Core Contracts vary by network, the core functionality remains consistent across chains.
Sending Messages
To send a message, regardless of the environment or chain, the Core Contract is invoked with a message argument from an emitter. This emitter might be your contract or an existing application such as the Token Bridge or NFT Bridge.
The IWormhole.sol
interface provides the publishMessage
function, which can be used to publish a message directly to the Core Contract:
function publishMessage(
uint32 nonce,
bytes memory payload,
uint8 consistencyLevel
) external payable returns (uint64 sequence);
Parameters
nonce
uint32
A free integer field that can be used however you like. Note that changing the nonce
will result in a different digest.
payload
bytes memory
The content of the emitted message. Due to the constraints of individual blockchains, it may be capped to a certain maximum length.
consistencyLevel
uint8
A value that defines the required level of finality that must be reached before the Guardians will observe and attest to emitted events.
Returns
sequence
uint64
A unique number that increments for every message for a given emitter (and implicitly chain). This, combined with the emitter address and emitter chain ID, allows the VAA for this message to be queried from the Wormholescan API.
Example
IWormhole wormhole = IWormhole(wormholeAddr);
// Get the fee for publishing a message
uint256 wormholeFee = wormhole.messageFee();
// Check fee and send parameters
// Create the HelloWorldMessage struct
HelloWorldMessage memory parsedMessage = HelloWorldMessage({
payloadID: uint8(1),
message: helloWorldMessage
});
// Encode the HelloWorldMessage struct into bytes
bytes memory encodedMessage = encodeMessage(parsedMessage);
// Send the HelloWorld message by calling publishMessage on the
// wormhole core contract and paying the Wormhole protocol fee.
messageSequence = wormhole.publishMessage{value: wormholeFee}(
0, // batchID
encodedMessage,
wormholeFinality()
);
View the complete Hello World example in the Wormhole Scaffolding repository on GitHub.
The wormhole_anchor_sdk::wormhole
module and the Wormhole program account can be used to pass a message directly to the Core Contract via the wormhole::post_message
function:
pub fn post_message<'info>(
ctx: CpiContext<'_, '_, '_, 'info, PostMessage<'info>>,
batch_id: u32,
payload: Vec<u8>,
finality: Finality
) -> Result<()>
Parameters
ctx
CpiContext<'_, '_, '_, 'info, PostMessage<'info>>
Provides the necessary context for executing the function, including the accounts and program information required for the Cross-Program Invocation (CPI).
Type pub struct CpiContext<'a, 'b, 'c, 'info, T>
pub struct CpiContext<'a, 'b, 'c, 'info, T>
where
T: ToAccountMetas + ToAccountInfos<'info>,
{
pub accounts: T,
pub remaining_accounts: Vec<AccountInfo<'info>>,
pub program: AccountInfo<'info>,
pub signer_seeds: &'a [&'b [&'c [u8]]],
}
For more information, please refer to the wormhole_anchor_sdk
Rust docs.
Type PostMessage<'info>
pub struct PostMessage<'info> {
pub config: AccountInfo<'info>,
pub message: AccountInfo<'info>,
pub emitter: AccountInfo<'info>,
pub sequence: AccountInfo<'info>,
pub payer: AccountInfo<'info>,
pub fee_collector: AccountInfo<'info>,
pub clock: AccountInfo<'info>,
pub rent: AccountInfo<'info>,
pub system_program: AccountInfo<'info>,
}
For more information, please refer to the wormhole_anchor_sdk
Rust docs.
batch_id
u32
An identifier for the message batch.
payload
Vec<u8>
The data being sent in the message. This is a variable-length byte array that contains the actual content or information being transmitted. To learn about the different types of payloads, check out the VAAs page.
finality
Finality
Specifies the level of finality or confirmation required for the message.
Returns
Result<()>
The result of the function’s execution. If the function completes successfully, it returns Ok(())
, otherwise it returns Err(E)
, indicating that an error occurred along with the details about the error
Example
let fee = ctx.accounts.wormhole_bridge.fee();
// ... Check fee and send parameters
let config = &ctx.accounts.config;
let payload: Vec<u8> = HelloWorldMessage::Hello { message }.try_to_vec()?;
// Invoke `wormhole::post_message`.
wormhole::post_message(
CpiContext::new_with_signer(
ctx.accounts.wormhole_program.to_account_info(),
wormhole::PostMessage {
// ... Set fields
},
&[
// ... Set seeds
],
),
config.batch_id,
payload,
config.finality.into(),
)?;
View the complete Hello World example in the Wormhole Scaffolding repository on GitHub.
Once the message is emitted from the Core Contract, the Guardian Network will observe the message and sign the digest of an Attestation VAA. On EVM chains, the body of the VAA is hashed twice with keccak256 to produce the signed digest message. On Solana, the Solana secp256k1 program will hash the message passed. In this case, the argument for the message should be a single hash of the body, not the twice-hashed body.
VAAs are multicast by default. This means there is no default target chain for a given message. The application developer decides on the format of the message and its treatment upon receipt.
Receiving Messages
The way a message is received and handled depends on the environment.
On EVM chains, the message passed is the raw VAA encoded as binary. The IWormhole.sol
interface provides the parseAndVerifyVM
function, which can be used to parse and verify the received message.
function parseAndVerifyVM(
bytes calldata encodedVM
) external view returns (VM memory vm, bool valid, string memory reason);
Parameters
encodedVM
bytes calldata
The encoded message as a Verified Action Approval (VAA), which contains all necessary information for verification and processing.
Returns
vm
VM memory
The valid parsed VAA, which will include the original emitterAddress
, sequenceNumber
, and consistencyLevel
, among other fields outlined on the VAAs page.
Struct VM
struct VM {
uint8 version;
uint32 timestamp;
uint32 nonce;
uint16 emitterChainId;
bytes32 emitterAddress;
uint64 sequence;
uint8 consistencyLevel;
bytes payload;
uint32 guardianSetIndex;
Signature[] signatures;
bytes32 hash;
}
For more information, refer to the IWormhole.sol
interface.
valid
bool
A boolean indicating whether the VAA is valid or not.
reason
string
If the VAA is not valid, a reason will be provided
Example
function receiveMessage(bytes memory encodedMessage) public {
// Call the Wormhole core contract to parse and verify the encodedMessage
(
IWormhole.VM memory wormholeMessage,
bool valid,
string memory reason
) = wormhole().parseAndVerifyVM(encodedMessage);
// Perform safety checks here
// Decode the message payload into the HelloWorldMessage struct
HelloWorldMessage memory parsedMessage = decodeMessage(
wormholeMessage.payload
);
// Your custom application logic here
}
View the complete Hello World example in the Wormhole Scaffolding repository on GitHub.
On Solana, the VAA is first posted and verified by the Core Contract, after which it can be read by the receiving contract and action taken.
Retrieve the raw message data:
Example
pub fn receive_message(ctx: Context<ReceiveMessage>, vaa_hash: [u8; 32]) -> Result<()> {
let posted_message = &ctx.accounts.posted;
if let HelloWorldMessage::Hello { message } = posted_message.data() {
// Check message
// Your custom application logic here
Ok(())
} else {
Err(HelloWorldError::InvalidMessage.into())
}
}
View the complete Hello World example in the Wormhole Scaffolding repository on GitHub.
Validating the Emitter
When processing cross-chain messages, it's critical to ensure that the message originates from a trusted sender (emitter). This can be done by verifying the emitter address and chain ID in the parsed VAA.
Typically, contracts should provide a method to register trusted emitters and check incoming messages against this list before processing them. For example, the following check ensures that the emitter is registered and authorized:
This check can be applied after the VAA is parsed, ensuring only authorized senders can interact with the receiving contract. Trusted emitters can be registered using a method like setRegisteredSender
during contract deployment or initialization.
const tx = await receiverContract.setRegisteredSender(
sourceChain.chainId,
ethers.zeroPadValue(senderAddress as BytesLike, 32)
);
await tx.wait();
Additional Checks
In addition to environment-specific checks that should be performed, a contract should take care to check other fields in the body, including:
- Sequence - is this the expected sequence number? How should out-of-order deliveries be handled?
- Consistency level - for the chain this message came from, is the consistency level enough to guarantee the transaction won't be reverted after taking some action?
The VAA digest is separate from the VAA body but is also relevant. It can be used for replay protection by checking if the digest has already been seen. Since the payload itself is application-specific, there may be other elements to check to ensure safety.
Source Code References
For a deeper understanding of the Core Contract implementation for a specific blockchain environment and to review the actual source code, please refer to the following links:
- Algorand Core Contract source code
- Aptos Core Contract source code
- EVM Core Contract source code (
IWormhole.sol
interface) - NEAR Core Contract source code
- Solana Core Contract source code
- Sui Core Contract source code
- Terra Core Contract source code