Introduction

Welcome to the Wormhole xDapp Book!

This guide aims to help you in your journey as a cross-chain developer by explaining the core concepts of Wormhole and xDapp development.

We'll start by outlining the most important aspects of Wormhole and the considerations which must be taken when developing an xDapp. After that, the second portion of the book helps you set up a development environment and get started writing actual code. By the end of this guide, you should be ready to build and deploy your first xDapp.

While this document doesn't assume you have experience with any particular technologies, a general understanding of blockchain development will help you get up to speed.

Ready to step into the wormhole?


Check out this project's github repository to find the accompanying code examples.

For additional references, see the Reference section.

An Introduction to xDapps

This chapter aims to give you a clear understanding of what xDapps are and why they're gaining traction in the blockchain development community.

To start, let's go over the current state of the decentralized finance (De-Fi) ecosystem, the emerging role of xDapps and the advantages they have over conventional Dapps.

Dapp Basics

Since the launch of Bitcoin in 2009, the cryptocurrency and decentralized computing ecosystem has rapidly evolved and expanded. The ecosystem now includes hundreds of blockchains, often also referred to as Layer 1s.

Prior to 2015, blockchain transactions were limited in their capacities, typically being used to transfer funds from one user to another. This changed with the intoduction of Ethereum and smart contracts. Smart contracts allowed developers to perform arbitrary on-chain computation as part of a blockchain transaction, opening the door for blockchain technology to become a global computing platform. These innovations laid the groundwork for the creation of Decentralized Applications or Dapps.

Now, a rich ecosystem of Dapps exists across an array of smart-contract-enabled blockchains. These Dapps provide a number of services across categories like:

  • Decentralized Exchanges (DEXs)
  • Decentralized Autonomous Organizations (DAOs)
  • Borrow-Lend Platforms
  • Decentralized Games
  • NFT Protocols
  • Metaverse Platforms
  • and more

However, Dapps are not without limitations, many of which are tied to their underlying blockchains.

Two notable limitations are that blockchains have no access to off-chain data and no mechanism to interact with other blockchains. These limitations have lead to a fractured ecosystem where each blockchain is closed off from the others by default. That means assets native to one chain are not accessible on another, and some services can't be leveraged on particular chains altogether.

Blockchain developers are now aiming to solve these interoperability problems to create a unified ecosystem. In this new cross-chain ecosystem, people can move beyond being users of individual blockchains and take advantage of Web3 on a broader scale.

In the next section, we'll discuss the history and challenges of cross-chain interoperability, as well as introduce the role Wormhole plays in the future of this space.

Cross-Chain Interoperability

Because blockchains are siloed by nature, individual cryptocurrencies being bound to their own chains has been a longtime limitation of blockchain technology. The first attempt at solving this problem was the creation of cryptocurrency exchanges like Coinbase and Binance. Today these are refered to as centralized exchanges (CEXs).

Centralized exchanges play an important role in cryptocurrency, but they are not a complete solution for cross-chain interoperability for two primary reasons: they're centralized, which is counterproductive to creating a decentralized platform, and they deal only with tokens.

As blockchains move toward being general-purpose computing platforms, interoperability will require data structures that are more complex than tokens and operations that are more sophisticated than transfers.

To solve the centralization problems with CEXs, decentralized exchanges (DEXs) were created. A DEX operates inside a smart contract runtime and can be as decentralized as the blockchain it runs on. Unfortunately, a DEX is only able to utilize the tokens on its native blockchain. In order to obtain a token which is not native to that chain, the DEX must be used in combination with a bridge.

Bridges are complex and will be discussed at length in a later section. For now, we can categorize bridges as applications which 'lock' assets on one chain in exchange for wrapped assets on another chain. The wrapped assets can then be exchanged for the original 'backing' asset.

There are some other essential things you should know about bridges before going further:

  • Bridges are capable of being decentralized in theory, but are often quite centralized in practice.
  • Bridges are currently the only way to hold a token on a chain other than its 'native' chain. If you're holding ETH on a chain other than Ethereum, it is, by definition, a wrapped token.
  • Bridges are all mutually incompatible with eachother. Using multiple bridges just makes 'double wrapped' tokens.
  • If tokens have become double wrapped after traversing multiple bridges or blockchains, there can be a complex unwrapping process to get back to the original token.

This explains how the ecosystem arrived at its current state--CEXs are a solution to siloed blockchains, DEXs are a simple response to CEXs, and DEXs have created a demand for bridges. Each solution in this timeline is an ad-hoc patch to the previous problem, and the current landscape of fractured liquidity, double wrapped tokens, isolated userbases and wallet incompatibilities is the result.

More ad-hoc solutions would only be short-term fixes for long-term problems, so it's critical to design new primatives and core infrastructure that will allow the next generation of decentralized applications to move beyond these lingering limitations.

This is why Wormhole exists. Wormhole proposes a new way of developing applications which leverages the strengths of each blockchain while mitigating the problems of the current ecosystem.

Branded Terms

In some instances, Wormhole uses general terms for decentralized, cross-chain elements as branded verbiage. In most cases, the definition of the general term does not greatly differ from Wormhole definition, though Wormhole's definitions may be more narrow than general interpretations.

xChain - Across the Wormhole ecosystem, the full range of cross-blockchain interoperability is referred to under the term "xChain." "xChain" is the concept that houses other branded terms, like the Wormhole definitions of xAssets, xData and xApps.

Rethinking the next generation of decentralized applications means dethroning the token as the fundamental atomic unit of blockchains. We'll expand on this change in the next section.

xData and xAssets

High on the wishlist of blockchain features is the ability to detach tokens from their native chains. It is a tremendous limitation that ETH only exists on Ethereum, MATIC only exists on Polygon and SOL only exists on Solana. It would be far more useful if those assets were able to move freely, independent of their native blockchains.

That thought underpins the idea of an xAsset, which could be considered a next-generation wrapped token. In a sense, xAssets exist on a layer outside of the blockchain ecosystem, and so are able to transact on a variety of blockchains. An xAsset is chain- and path-agnostic, so it retains fungibility regardless of where it travels. xAssets can also move fluidly around the blockchain ecosystem without ever becoming double-wrapped.

Now that we've established the idea of an xAsset, you might think they're an excellent atomic unit for solving interoperability challenges. However, xAssets are just one step short of the real solution. Let's take a step back: blockchains now process arbitrary data, and some of that data just happens to represent assets. The full solution then, is to create xData.

xData is akin to an xAsset in that it exists in its own layer independent of any blockchain, which makes xData accessible by all blockchains. The difference is that xData represents arbitrary data rather the token information represented by an xAsset.

Cross-chain interoperability then becomes a matter of creating, consuming and managing xData. Once blockchains have the ability to read and write data into a shared, global reservior, application design can take on innovative new dimensions.

Branded Terms

In some instances, Wormhole uses general terms for decentralized, cross-chain elements as branded verbiage. In most cases, the definition of the general term does not greatly differ from Wormhole definition, though Wormhole's definitions may be more narrow than general interpretations.

xData - Wormhole defines xData as "data that exists in a layer outside of Layer 1 blockchains, which is accessible by all chains." The Wormhole definition of xData presents it as a branded element of xChain.

xAssets - Wormhole defines xAssets as a "chain-and-path agnostic token that exists on a layer outside the blockchain ecosystem, which can be used to conduct transactions on any blockchain." The Wormhole definition of xAssets presents itself as an element of xChain.

Later in this document, we'll delve deeper into how Wormhole implements this xData layer (also referred to as the 'Core' layer of Wormhole), but for now let's talk about how xData can be used to create xDapps.

What is an xDapp?

The term xDapp is short for "Cross-Chain Decentralized Application". At first glance, this might give the impression that xDapps are simply Dapps that do cross-chain things. However, once you start building decentralized products designed to operate across a variety of blockchains and runtimes, it becomes clear that these applications are architected in a fundamentally different way than traditional Dapps.

xDapps have the capacity to perform all the operations of traditional Dapps, but they are also able to utilize xData. xData allows xDapp developers to build from a top-down, message-passing approach, rather than the bottom-up world of Dapp development. The Wormhole Core Layer implements xData, which acts as a shared repository of data across the entire Wormhole ecosystem.

Something we'll explore further in the upcoming xDapp Architecture chapter is the philosophy of Protocol-First Design. Protocol First Design is an approach to building decentralized applications where the first order of business is to lay out your application into a series of data structures, APIs and message payloads. Once you've laid out your application into a high-level protocol, the protocol acts as an agreement to which all components must adhere. From there, the smart contracts underlying the protocol can be considered an implementation detail.

If you're familiar with web2 development, you might notice that this philosophy is analogous to microservice architecture. This is no coincidence, as similar problems should expect to be solved by similar solutions, and the Wormhole Core Layer has a number of parallels to the OSI Network Model.

Thus, a more fitting depiction of xDapps might be to see them as Distributed Decentralized Applications with multiple, specialized components working in unison to deliver a smooth, unified user experience across a variety of layer 1 ecosystems.

Branded Terms

In some instances, Wormhole uses general terms for decentralized, cross-chain elements as branded verbiage. In most cases, the definition of the general term does not greatly differ from Wormhole definition, though Wormhole's definitions may be more narrow than general interpretations.

xApp - In the Wormhole xChain ecosystem, the term "xDapp" has been shortened to "xApp." These cross-chain applications are largely still decentralized, but for branding and simplicity purposes, the term "xApp" will be prioritized over "xDapp" when talking about Wormhole's xChain ecosystem.

In the next section, we'll summarize the concrete advantages which xDapps built on Wormhole have over traditional Dapps today.

Advantages of xDapps

Here are a few xDapp features that are making an impact across blockchain technologies:

  • Expanded User Base - Rather than being limited to the users of one blockchain, any user on any blockchain in the ecosystem can interact with an xDapp.

  • Unified Liquidity - Liquidity fragmentation is a major problem in the current ecosystem. Unlike traditional tokens, xAssets can be pooled and moved anywhere.

  • Decentralization - Cross-chain solutions today usually involve centralized exchanges or bridges. However, Wormhole has been designed to be decentralized from day one, and eventually totally trustless.

  • Increased Performance - xDapps are able to utilize the strengths of each blockchain. With xDapps, expensive computations can be offloaded onto high-performance platforms, final settlement can take place on a preferred chain and data can be stored wherever is cheapest.

  • Broader Market Reach - Because xAssets move freely through the ecosystem, they can be listed on a variety of exchanges and custodied on any blockchain.

  • Increased Extensibility and Composeability - xDapps can utilize anything across the ecosystem, including other xDapps, expanding upon the composability and openness of smart contracts.

  • Futureproofing - As new environments and protocols join the decentralized ecosystem, the connected nature of the Wormhole ecosystem allows existing protocols to expand and support them.

Now that you have an understanding of what xDapps are and the advantages they offer, let's move on to the next chapter where we will delve into how Wormhole works and how it enables the creation of these next-generation protocols.

Wormhole

In the previous chapter, we established concepts like xDapps, xData and xAssets. In this chapter, we'll focus on the inner workings of the Wormhole ecosystem and how they power these ideas.

By the end of this chapter, you'll have a clear understanding of what Wormhole is, what its key components are and how each component comes together to create a powerful, new cross-chain ecosystem.

What is Wormhole?

Wormhole was introduced in 2020 by Certus One, and was initially conceived as a traditional token bridge between Ethereum and Solana. The Wormhole v1 Token Bridge was the first bridge on Solana and was responsible for bootstrapping a large amount of the liquidity in the early Solana and Serum ecosystems.

However, despite its beginnings as a token bridge, Wormhole quickly grew beyond Solana and token transfers.

Wormhole v2 launched in August 2021 as a decentralized generic interoperability protocol for multiple blockchain ecosystems with initial support for Solana, Terra, Ethereum and Binance Smart Chain.

Over the past year, Wormhole has evolved to support an ever-growing list of blockchains across an unrivaled number of smart contract runtimes.

While Wormhole is a generic interoperability protocol, it is also an ecosystem and platform for developers to grow the decentralized computing space. Wormhole consists of multiple modular swap-in components that can be leveraged independently and supports a increasing number of composible applications built by numerous teams.

In the next section, we'll go over the major components of the Wormhole ecosystem and how they fit together to enable the cross-chain functionality required to develop xDapps.

Architecture Overview

Wormhole is a complex ecosystem with several noteworthy components. Before we go into each component in depth, let's talk about the names of the major pieces and how they fit together.

Architecture Diagram

On-Chain Components

  • xDapp Contracts - Contracts developed by xDapp developers. They receive transactions from the end user and then interact with other xDapp contracts and Wormhole Ecosystem Contracts in order to provide their service.

  • Ecosystem Contracts - Contracts subject to Wormhole governance which live inside the Wormhole Ecosystem. Their job is to provide the feature suite of Wormhole to xDapp developers.

  • Core Contracts - Primary ecosystem contracts. These are the contracts which the Guardians observe and which fundamentally allow for cross-chain communication.

  • Portal xAsset Contracts - Contracts that allow normal tokens to be converted to xAssets and enable these xAssets to be bridged.

  • Relay Contracts - in development* - Contracts that allow xDapps to send messages to a specific blockchain via the decentralized Generic Relayer network.

  • Gas Oracle - in development* - Oracle for recommended fair gas prices across the ecosystem.

Off-Chain Components

  • Guardian Network - Validators that exist in their own p2p network. Guardians observe the Core Contract on each supported chain and produce VAAs (signed messages) when those contracts receive an interaction.

  • Guardian - One of 19 validators in the Guardian Network that contributes to the VAA multisig.

  • Spy - Validators on the Guardian Network which are not part of the Guardian set. A spy can observe and forward network traffic, which helps scale up VAA distribution.

  • Wormchain - in development* - A purpose-built cosmos blockchain which aids the Guardian Network and allows for formal interaction with the Guardians.

  • Specialized Relayers - Relayers that only handle VAAs for a specific protocol or xDapp. This allows them to execute custom logic off-chain, which can reduce gas costs and increase cross-chain compatibility. Currently, xDapp developers are responsible for developing and hosting specialized relayers.

  • Generic Relayers - in development* - A decentralized relayer network which delivers messages that are requested on-chain via the Wormhole Relay Contract.

  • VAAs - Verifiable Action Approvals (VAAs) are the key piece of data in the Wormhole ecosystem, containing the messages emitted by xDapps along with information such as what contract emitted the message. The VAAs are signed by the Guardians and need 13/19 signatures to be considered authentic.

**Features listed as in development are not yet available in mainnet.

Core Contracts

The Core Contracts are the mechanism by which all Wormhole messages are emitted. All xDapps either interact directly with the Core Contract or interact with another contract that does. There is one Core Contract on each blockchain in the ecosystem, and this is the contract which the Guardians are required to observe.

The Wormhole Core Contracts are one of the most pivotal pieces of the Wormhole ecosystem. They serve as a great place to start when learning about how data flows through the ecosystem.

In general, Core Contracts are simple with only a few public-facing functions, which we'll define next.


First, we have the 'sending' side of the Core Contract:

publishMessage(
    int nonce,
    byte[] payload,
    int consistencyLevel
) returns int sequenceNumber

This is the foundational mechanism by which Wormhole Messages are emitted. Let's break it down a bit:

  • payload - The content of the emitted message and an arbitrary byte array. It may be capped to a certain maximum length due to the constraints of individual blockchains.

  • consistencyLevel - The number of blocks which the Guardians should wait prior to emitting a VAA for this message. This number is usually either 1 or equal to the chain's finality period. This is a defense against transactions being orphaned.

  • nonce - If multiple messages in the same transaction have the same nonce, a batch VAA will be produced alongside the individual VAAs on chains that allow them. This reduces gas costs and simplifies composeability.

  • sequenceNumber - A unique index number for the message. When combined with the emitter contract address and emitter chain ID, the corresponding VAA can be retrieved from a guardian network node.

The implementation strategy for publishMessage differs by chain, but the general strategy consists of the Core Contract posting the emitterAddress (the contract which called publishMessage), sequenceNumber, and consistencyLevel into the blockchain logs. Once the desired consistencyLevel has elapsed, the Guardian Network will produce the requested VAAs.

Currently there are no fees to publish a message (with the exception of publishing on Solana) but this is not guaranteed to always be the case in the future.


Next, we have the 'receiving' side of the Core Contract.

parseAndVerifyVAA( byte[] VAA )

When passed a VAA, this function will either return the payload and associated metadata for the VAA or throw an exception. An exception should only ever throw if the VAA fails signature verification, indicating the VAA is invalid or inauthentic in some form.


Multicasting

Let's take a moment to point out that there is no destination address or chain in these functions.

VAAs simply attest that "this contract on this chain said this thing." Therefore, VAAs are multicast by default and will be verified as authentic on any chain they are brought to.

This multicast-by-default model is integral to the design. Having this multicast capacity makes it easy to synchronize state across the entire ecosystem, because a single blockchain can make its data available to every chain in a single action with low latency. This reduces the complexity of the n^2 problems encountered by routing data to a large number of blockchains.

Use cases where the message has an intended recipient or is only meant to be consumed a single time must be handled in logic outside the Core Contract. There are standard practices for accomplishing these features later on in the code examples, and some ecosystem contracts (namely Portal & the Relaying contract) handle this on behalf of downstream consumers.

Lastly, because the VAA creation is separate from relaying, there is no additional cost to the multicast model when a single chain is being targetted. If the data isn't needed on a certain blockchain, don't relay it there, and it won't cost anything.

In our next section, we'll dive into the technical specfications of the VAA.

VAAs (Verified Action Approvals)

VAAs are the core messaging primative in Wormhole. You can think of them as packets of xData that are emitted any time an xDapp contract interacts with the Core Contract.

The basic VAA has two components--a Header and a Body.

byte        version                  (VAA Version)
u32         guardian_set_index       (Indicates which guardian set is signing)
u8          len_signatures           (Number of signatures stored)
[][66]byte  signatures               (Collection of ecdsa signatures)

The Header is used by the Core Contract to determine the authenticity of the VAA, but can generally be ignored by other consumers.

Body

u32         timestamp                 (Timestamp of the block where the source transaction occurred)
u32         nonce                     (A grouping number)
u16         emitter_chain             (Wormhole ChainId of emitter contract)
[32]byte    emitter_address           (Emitter contract address, in Wormhole format)
u64         sequence                  (Strictly increasing sequence, tied to emitter address & chain)
u8          consistency_level         (How many blocks were waited before emitting this VAA)
[]byte      payload                   (VAA message content)

The Body is the relevant information for consumers and is handed back from parseAndVerifyVAA. Because the emitterAddress is included as part of the Body, the developer is able to tell if this VAA originated from a trusted contract.

VAAs are uniquely indexed by their emitterChain, emittedAddress and sequence. They can be obtained by querying a node in the Guardian Network with this information.

Because baseline VAAs have no destination, they are effectively multicast. They will be verified as authentic by any Core Contract on any chain in the network, and it is entirely the responsibility of relayers to deliver VAAs to the appropriate place.

Batch VAAs

Certain blockchains support version 2 VAAs, also referred to as Batch VAAs. When multiple messages with the same nonce are emitted in the same transaction, a batch VAA will be created in addition to the individual VAAs. The Batch VAA contains the body of each individual VAA, but only has a single header. This reduces the gas cost of verifying the VAA, and simplifies the process of relaying and consuming multiple VAAs.

Batch VAAs are not currently live on mainnet, but will have initial support on all EVM chains when they launch.


In the next section, we'll give an overview of how the Wormhole Guardian network creates VAAs along with a look at the key design considerations that underpin the network.

Guardian Network

The Guardian Network is designed to serve as Wormhole's oracle component, and the entire Wormhole ecosystem is founded on its technical underpinnings. It is the most critical element of the Wormhole ecosystem, and represents the single most important component to learn about if you want a deep understanding of Wormhole.

To understand not just how the Guardian Network works, but why it works the way it does, let's first take a step back and go over the key design considerations. To become the best-in-class interoperability platform, there were five critical features Wormhole needed to have:

  1. Decentralization - Control of the network needs to be distributed amongst many parties.
  2. Modularity - Disparate parts of the ecosystem such as the oracle, relayer, applications, etc, should be kept as separate and modular as possible so they can be designed, modified and upgraded independently.
  3. Chain Agnosticism - Wormhole should be able to support not only EVM, but also chains like Solana, Algorand, Cosmos, and even platforms that haven't been created yet. It also should not have any one chain as a single point of failure.
  4. Scalablity - Wormhole should be able to secure a large amount of value immediately and be able to handle the large transaction volume.
  5. Upgradeability - As the decentralized computing ecosystem evolves, Wormhole will need to be able to change the implementation of its existing modules without breaking integrators.

Next, let's go into how Wormhole achieves these one at a time.

Decentralization

Decentralization is the biggest concern. Previous interoperability solutions have largely been entirely centralized, and even newer solutions utilizing things like adversarial relayers still tend to have single points of failure or collusion thresholds as low as 1 or 2.

When designing a decentralized oracle network, the first option to consider is likely a Proof-of-Stake (PoS) system--but this turns out to be a suboptimal solution. PoS is designed for blockchain consensus in smart-contract enabled environments, so it's less suitable when the network is verifying the output of many blockchains and not supporting its own smart contracts. While it looks appealing from a decentralization perspective, the network security remains unclear, and it can makes some of the other outlined goals more difficult to achieve. Let's explore other options.

The next option would be to rush straight for the finish line and use zero-knowledge proofs to secure the network. This would be the a good solution from a decentralization perspective, as it's literally trustless. However, zero-knowledge proofs are still a nascent technology and it's not really feasible to verify them on-chain, especially on chains with limited computational environments. That means a form of multisig will be needed to secure the network.

If we step back and look at the current De-Fi landscape, most of the top blockchains are secured by the same handful of validator companies. Currently, there are a limited number of companies in the world with the skills and capital to run top-notch validator companies.

If a protocol could unite a large number of those validator companies into a purpose-built consensus mechanism that's optimized for chain interoperability, that design would likely be more performant and secure than a network bootstrapped by a tokenomics model. Assuming the validators would be on board, how many could Wormhole realistically utilize?

If Wormhole were to use threshold signatures, the answer would basically be 'as many as are willing to participate.' However, threshold signatures have spotty support across the blockchain world, meaning it would be difficult and expensive to verify the signatures, ultimately limiting scalability and chain agnosticism. Thus, a t-schnorr multisig presents itself as the best option: cheap and well supported, despite the fact that its verification costs increases linearly with the number of signatures included.

All these things considered, 19 seems to be the maximum number and a good tradeoff. If 2/3 of the signatures are needed for consensus, then 13 signatures need to be verified on-chain, which remains reasonable from a gas-cost perspective.

Rather than securing the network with tokenomics, it is better to initially secure the network by involving robust companies which are heavily invested in the success of De-Fi as a whole. The 19 Guardians are not anonymous or small--they are many of the largest and most widely-known validator companies in cryptocurrency. The current list of Guardians can be viewed here

That's how we end up with the network of 19 Guardians, each with an equal stake and joined in a purpose-built Proof of Authority consensus mechanism. As threshold signatures become better supported, the Guardian set can expand, and once ZKPs are ubiquitous, the Guardian Network will become fully trustless.

With our perspective on Decentralization laid out, the remaining elements fall into place.

Modularity

The Guardian Network is robust and trustworthy by itself, so there'ss no need for components like the relayer to contribute to the security model. That makes Wormhole able to have simple components that are very good at the one thing they do. That way, Guardians only need to verify on-chain activity and produce VAAs while Relayers only need to interact with blockchains and deliver messages.

The signing scheme of the VAAs can be changed without affecting downstream users, and multiple relay mechanisms can exist independently. xAssets can be implemented purely at the application layer and xDapps can utilize whatever components suit them.

Chain Agnosticism

Today, Wormhole supports a wider range of ecosystems than any other interoperability protocol because it uses simple tech (t-schnorr signatures), an adaptable, heterogenous relayer model, and a robust validator network.

Wormhole can expand to new ecosystems as quickly as a Core Contract can be developed for the smart contract runtime. Relayers don't need to be factored into the security model--they just need to be able to upload messages to the blockchain. The Guardians are able to observe every transaction on every chain, without taking shortcuts.

Scalability

Wormhole scales well, as demonstrated by Portal's ability to handle huge TVL and transaction volume--even during tumultuous events.

The requirements for running a Guardian are relatively heavy, as they need to run a full node for every single blockchain in the ecosystem. This is another reason why a limited number of robust validator companies are beneficial for this design.

However, once all the full nodes are running, the actual computation and network overheads of the Guardian Network become lightweight. The performance of the blockchains themselves tends to be the bottleneck in Wormhole, rather than anything happening inside the Guardian Network.

Upgradability

Over time, the Guardian Set can be expanded beyond 19 with the use of threshold signatures. A variety of relaying models will emerge, each with their own strengths and weaknesses. ZKPs can be used on chains where they are well supported. The xDapp ecosystem will grow, and xDapps will become increasingly intermingled with eachother. There are very few APIs in Wormhole, and most items are implementation details from the perspective of an integrator. This creates a clear pathway towards a fully trustlessness interoperability layer which spans the entirety of decentralized computing.

In the next section, we will talk about the role and responsbilities of relayers in the Wormhole ecosystem.

Relayers

Relayers are a major part of the Wormhole Ecosystem. Where the Guardian Network is effectively the 'read' portion of interoperability, relayers are the 'write' portion.

The definition of a Relayer in the context of Wormhole is: Any process which delivers VAAs to a destination.

Unlike other interoperability protocols, Wormhole does not have a required relaying methodology.

In most designs there is a dedicated relaying mechanism which operates inside the protocol's trust boundaries. This means that the relayer either has an adversarial relationship to the oracle, or the relayer has trust assumptions and contributes to the protocol's security model. Relayers are usually a trusted party, are often also privileged, and developers are typically forced to use the relayer model built into the protocol.

In Wormhole, relayers are neither trusted nor privileged. This means relayers cannot jeopardize security, only liveness. Because Wormhole is designed to have a firm trust boundary at the level of the VAA, relayers have exactly the same capabilities as any regular, untrusted blockchain user.

From this perspective, relayers are just delivery trucks that deliver VAAs to their destination, and have no capacity to tamper with the delivery outcome. VAAs either get delivered or don't, which makes relayers analagous to the off-chain 'crank turners' of traditional Dapps.

As a result, Wormhole is able to facilitate a variety of heterogenous relaying mechanisms, and the developer is able to choose whatever best suit their needs.

Next, we'll go over a few of the most common relaying strategies.

Client-side Relaying

All simple processes on Wormhole essentially boil down to a three step process:

1. Perform an action on chain A.
2. Retrieve the resulting VAA from the Guardian Network.
3. Perform an action on chain B using the VAA.

Considering that the first step of this process is almost always initiated from a user-facing frontend like a webpage or a wallet, it is possible to also perform steps 2 and 3 in the frontend as well. This is referred to as 'client-side relaying', and it has two major benefits:

  • Low cost. Users pay exactly the transaction fee for the second transaction.
  • No backend relaying infrastructure.

That makes client-side relaying a tempting prospect, especially if you're just interested in getting an MVP running. However, client-side relaying also has two notable drawbacks:

  • Users must sign all transactions required with their own wallet.
  • Users must have funds to pay the transaction fees on every chain involved.

Overall, client-side relaying is a simple solution, but can make the user experience cumbersome. It's generally not recommended if your goal is a highly-polished user experience.

Specialized Relayers

Specialized relayers solve the UX problems of client-side relayers by adding a backend component which can handle steps 2 and 3 on behalf of the user.

In this model, relayers either listen directly to the Guardian Network via a spy (This is called Spy Relaying), or will simply provide a REST endpoint to accept a VAA which should be relayed (called REST Relaying). Once a relayer has the VAA, it simply performs any necessary off-chain calculations and submits the VAA to the required destination.

An important consideration when developing a specialized relayer is that the relayer is still considered untrusted. VAAs are public and can be submitted by anyone, so the off-chain relayer should not do any computation which is considered "trusted." However, doing things like deterministic data transforms, waiting for gas prices to drop, or various forms of 'batching' can be very useful cost-reduction strategies that do not impact security.

Specialized Relayers have the following advantages:

- They simplify user experience
- They allow off-chain calculations to be performed in the relayer, reducing gas costs
- They are generally easy to develop

However, they also have a couple notable downsides

- They add a backend relaying component which is responsible for liveness
- They can complicate fee-modeling, as relayers are responsible for paying target chain fees.

Because relayers are responsible for liveness, they become another dependency component for the xDapp. If the relayers are all down, your application has an outage. This is similar to how dependencies like the frontend, blockchain nodes, blockchains, third party APIs, etc, can also cause outages.

To mitigate this, multiple relayers can be run in order to provide redundancy. It's also possible to design specialized relaying solutions which are entirely decentralized, such that there are a network of relayers which run based off economic incentives. However, creating a robust model for decentralized relaying is generally application-specific and complex.

Due to specialized relayers being such a common solution, there is a reference implementation provided in the main Wormhole repository which stands up the infrastructure needed to provide a Spy interface, a REST interface and the ability to interact with each blockchain in the ecosystem. If you plan to develop a specialized relayer, consider starting from the reference implementation and add modifications as needed.

Generic Relayers

Note: this feature is not yet available in mainnet

Because relaying is such an integral component to xDapps, Wormhole has built a protocol which allows developers to utilize a decentralized network of untrusted relayers to deliver their messages, removing the specialized relayer as an infrastructure responsibility.

In order to utilize the generic relayer network, developers must request delivery from the Wormhole Relay Ecosystem Contract and must also implement a "receiveRelay" function in their contracts, which will be called by the relayer. Once a delivery has been requested, the VAA is guaranteed to be delivered within a certain timeframe. The specifics of this vary by blockchain and smart contract runtime.

Generic relayers have the following benefits:

- They feature simplified UX
- There are no relayer infrastructure requirements for the developer

And potential downsides:

- They require all calculations to be done on-chain
- They have less gas efficiency
- They may not be supported on all chains

In the next section, we'll discuss the Portal Ecosystem contracts that allow xAssets to be created and moved freely around the ecosystem.

Portal xAsset Bridge

Portal is a set of ecosystem contracts that provision Wormhole's xAsset layer. These contracts allow tokens to be bridged around the Wormhole Ecosystem in a path-independent fashion, and are easily composeable with other functions in the Wormhole ecosystem.

This section provides a high-level overview of how to interact with the Portal contracts. If you're looking to interact with Portal directly from a typescript client or backend, you should start with the Wormhole Typescript SDK. If you'd prefer to look at code examples, they are provided in the Portal Examples section.

Creating xAssets

xAssets always have an origin chain. This is where the token is initially minted via the standard of that chain (ERC-20, SPL, etc).

To convert this asset into an xAsset, an attestation must first be created. To create an attestation, simply call the attest function on the Portal contract of the origin chain.

function attestToken(
    address tokenAddress,
    uint32 nonce)
 returns (uint64 sequence)

The Guardian Network will then produce an attestation VAA, which can be retrieved using the sequence number returned by the attestToken function.

The attestation VAA must then be submitted to the createWrapped function of every other chain, referred to as foreign chains for this token.

function createWrapped(
    bytes memory encodedVm)
returns (address token)

Calling this function will deploy a new contract for the token on the foreign chain, creating a Wormhole-Wrapped Token. The wrapped token will use the same symbol as the origin asset, and will append (Wormhole) to the end of the name.

These assets are all fungible with each other. This means the Wormhole-wrapped token can be exchanged for the original token or wrapped tokens from other chains.

Transferring Assets

function transferTokens(
    address token,
    uint256 amount,
    uint16 recipientChain,
    bytes32 recipient,
    uint256 arbiterFee,
    uint32 nonce) returns (uint64 sequence)

Initiating token transfers is a straightforward affair. Once the transfer is initiated, the Guardians will produce a transfer VAA when finality has been reached on the source chain. The VAA must then be relayed to the target chain.

All tokens managed by Portal are backed by the origin asset, allowing tokens to be transferred in a path-independent fashion. Regardless of what chain the tokens are passed to, a 'double-wrapped' asset will never be created for a single backing asset. Additionally, there are no liquidity limitations.

Contract-Controlled Transfers

Basic transfers are intended to transfer tokens from one wallet to another, whereas Contract Controlled Transfers (CCTs) are meant to transfer tokens from one smart contract to another. If you're writing an xDapp, CCTs will likely be a large component.

CCTs allow xDapp contracts to easily perform Portal transfers. Contract controlled transfers are quite similar to simple transfers, but have two additional features:

  • An arbitrary byte array can be appended to the transfer and can be used to easily pass additional information to the recipient contract.
  • The CCT VAA redeem can only be performed by the recipient contract, as opposed to basic transfers, which can be performed by any caller. This ensures that any additional operations which the contract wants to perform as part of the redeem transaction must be executed.

In the next section, we'll discuss Wormchain and some of the upcoming features it will enable.

Wormchain

Wormchain is a purpose-built cosmos blockchain for the Wormhole ecosystem. It has two primary functions:

  1. Provide a public and auditable mechanism for users to interact with the Guardian Network.
  2. Create a robust platform for on-chain infrastructure which would be infeasible to build elsewhere.

Wormchain is built to provide things like:

- Redundant security checks
- Governance
- Interactions with 'legacy' chains like Bitcoin

Wormchain is less relevant to xDapp developers than some other parts of the ecosystem, but it will become an increasingly important component as Wormhole matures and features are added.

In the next section, we'll get into the key concepts that underpin xDapp design.

Wormhole Development Overview

The general flow for a cross-chain message goes from an application deployed to chain A, to the Wormhole contract on chain A, to the Guardian network, then submitted to chain B.

To get started and simulate this flow locally, you'll need a local environment to test your xdapp code. To test, we need to be able to deploy some chains, deploy the Wormhole contracts to these chains, and then run at least one Wormhole validator to pick up messages.

Later, we can introduce a relayer to automatically submit messages, though that's currently supported for Mainnet Token Bridge native and stable coin transfers only. Developers currently have to use either a manual relayer method or an app-sepecific relayer (more on that in the Relayer section).

Before we setup an xdapp project, we'll need to choose a local environment to run the Wormhole Guardian Network. We can use either Wormhole Local Validator or Tilt.

  • Wormhole Local Validator: This is the simplest custom environment. It's BYOB (Bring your own Blockchain), where you can run your own local validator nodes and connect them to a single Guardian running on docker. Initial setup can take upwards of 500 seconds, but after the image is built, bringing it up and down is usually <1 minute. This environment requires installing the software for the validator nodes locally on your computer or somewhere to run them.
  • Tilt: A full-fledged Kubernetes deployment of every chain connected to Wormhole, along with a Guardian node. Usually takes 30 min to spin up fully, but comes with all chains running out of the box.

Testnet

If you want to test on the various test and devnets of existing connected chains, there's a single Guardian node watching for transactions on various test networks. You can find the contracts here and the rpc node here.

Because testnet only has a single Guardian, there's a small chance that your VAAs will not be processed. This rate is not indiciative of performance on mainnet, where there are 19 Guardians watching for transactions.

Mainnet

When you're ready to deploy to mainnet, you can find the mainnet contracts here and the mainnet rpc nodes here.

Next Steps

To get started, first clone the a local host environment (WLV or Tilt), then proceed to the first project, the evm-messenger.

Wormhole Local Validator

The Wormhole Local Validator is available here. Along with the Wormhole Local Validator, this also contains code to spin up EVM and Solana local validators, as well as deployment code to add Wormhole contracts to those new chains.

Dependencies

You will need Docker; if you're developing on your computer you should get Docker Desktop, but if you're in a headless VM, install Docker Engine. Make sure to have Docker running before you run any of the following commands.

To run EVM chains you will need Ganache.
To run Solana chains you will need Solana installed.

Run EVM Chains

npm run evm will start up two EVM chains with Wormhole Chain ID 2 (like ETH) and Wormhole Chain ID 4 (like BSC) and deploy the Wormhole Core Bridge (0xC89Ce4735882C9F0f0FE26686c53074E09B0D550), Token Bridge (0x0290FB167208Af455bB137780163b7B7a9a10C16), and NFT Bridge (0x26b4afb60d6c903165150c6f0aa14f8016be4aec) contracts to them. They'll also deploy a Test Token (TKN at 0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A), test NFT (0x5b9b42d6e4B2e4Bf8d42Eba32D46918e10899B66), and WETH Contract (0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E) as well.

They'll use the standard Wormhole test mnemonic (myth like bonus scare over problem client lizard pioneer submit female collect) and use the first key for deployment and payment (Public Key: 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1, Private Key: (0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d))

Run Solana Chain

npm run solana will start up a Solana chain and load in Core Bridge (Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o) and Token Bridge (B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE) accounts. TODO: Add emitter registrations for token bridge.

Run Wormhole

After you have the dependencies installed and the chains running, you can run Wormhole.

Simply run npm run wormhole, which will pull and run the Wormhole Guardian docker image.

FAQ & Common Problems

  • Anvil isn't working
    While we recommend Foundry's Forge tool for compiling and deploying code elsewhere in these docs, we do not at this time recommend using anvil for guardiand; this is because guardiand is spec'd against go-ethereum, and anvil is out of spec for how it reports block headers (non left padding to normalize length), which means go-ethereum reacts abnormally and can't read anvil headers.

Tilt Development Environment

For a quicker development cycle, specially when developing your own blockchain programs that interact with Wormhole or Portal contracts, consider setting up the Tilt Devnet Environment.

Tilt is a kubernetes and docker orchestration tool that will spin up all the Wormhole supported chains in containers, alongside a Guardian node that will observe and store VAAs.

This devnet environment can be set up on your computer or in a Linux VM that has at least 4 CPU cores and 16GB of RAM.

If you do decide to host the devnet in a remote VM, remember to pass in the host and webHost flags during the tilt up step and allow incoming traffic on your VM to be able to access the various RPC endpoints on the Pods.

tilt up --host=0.0.0.0 -- --webHost=0.0.0.0

While the exact commands for each environment might differ, the basic setup process for tilt is the following:

  1. Install Go
  2. Install Docker Desktop (Or Docker CE)
    a. Install Minikube if Docker CE
  3. Install Tilt
  4. Clone Wormhole Repo and Tilt Up

FAQ

Where are Fantom/Celo/Polygon/...(insert other EVM chains)

For all chains that support EVM the smart contract development environment is effectively the same. For changes in gas costs and transaction times, consider testing contract logic on devnet and then using testnet environments to get chain specific answers.

Solana is taking forever!

Unfortunately, due to Solana's architecture, it often takes 25-40min to build the Solana pod. Consider increasing CPU cores assigned to devnet for a faster build.

Solana program deploy doesn't work

Kubernetes doesn't currently allow port forwarding for UDP ports, which is what Solana uses for solana program deploy. Instead, we recommend using Solana Deployer. Not only does this deploy programs over regular RPC thus bypassing UDP port requirements, it's also much faster than solana program deploy.

Reset state for a pod

If you want to quickly iterate and don't want to bring tilt down and back up, you can reset state for a pod by clicking the 🔄 button next to the pod name in Tilt UI.

macOS Setup

Prerequisites

You'll need to have homebrew on your system if you don't already. You can grab it with:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Install Go

brew install go

Install Docker

brew install docker

After installation, go into Docker settings and switch ON kubernetes. Also configure Docker to have 4 CPUs and ~16GB of RAM.

Install Tilt

brew install tilt

Clone Wormhole Repo and Start Tilt

git clone --branch dev.v2 https://github.com/certusone/wormhole.git
cd wormhole/
tilt up

You'll be able to access the Tilt UI at localhost:10350

Linux Devnet Setup

Experimental Setup Script

There's an experimental one command setup script that should install dependencies for you on Linux and configure everything properly. This is only recommended if you're running headless Linux and unable to use Docker Desktop, as with Docker Desktop you don't need minikube and can just enable Kubernetes from Docker.

curl $URL | sh install_linux.sh
cd wormhole/
./tilt.sh

Regular Setup

1. Install Go

wget https://go.dev/dl/go1.18.1.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.18.1.linux-amd64.tar.gz

2. Install Docker

If you're using Linux with a window manager, consider getting Docker Desktop instead of the following command. It comes with Kubernetes built in and you won't need to download minikube. It's recommended to have at least 4 CPUs and 16GB RAM dedicated to Docker.

Also make sure that you set up docker as a NON ROOT USER!

https://docs.docker.com/engine/install/ubuntu/#installation-methods

3. (Docker Desktop Install)

Enable Kubernetes by going into Settings > Kubernetes

3. (Non Docker Desktop)

Install minikube

Configure minikube

minikube start --driver=docker --kubernetes-version=v1.23.3 --cpus=4 --memory=14G --disk-size=10G --namespace=wormhole

If you reboot your VM you'll need to run the minikube start command again before you bring up tilt.

4. Install Tilt

Install tilt by copy pasting this into the Terminal

curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash

5. Clone the Wormhole Repo and start Tilt

git clone --branch dev.v2 https://github.com/certusone/wormhole.git

If you're running tilt on your machine

cd wormhole/
tilt up

If you're running tilt in a VM, we need to pass in some extra flags to enable Tilt to listen to incoming traffic from external addresses.

cd wormhole
tilt up --host=0.0.0.0 -- --webHost=0.0.0.0

You can now access the Tilt UI at either your localhost:10350 or vm_external_ip:10350.

If the VM's external IP doesn't work, check firewall and port settings to make sure your VM allows incoming traffic.

Contracts and Accounts

The devnet environment deploys the Wormhole and Portal contracts to each of the chains at the same addresses every time.

It also funds specific wallets with funds.

Tilt

Guardian

  • REST Port: 7071
  • gRPC Port: 7070

ETH0

  • RPC Port: 8545

ETH1

  • RPC Port: 8546

Solana

  • RPC Port: 8899

Algorand

  • RPC Port:

Terra

  • RPC Port:

xDapp Scaffold

To help you get started with cross chain development, we've provided a template project in projects/xdapp-starter. All the sample projects will be made using this template, so check them out if you want to get a feel for how the various modules interact with each other.

The template uses npm workspaces to setup a main project with subdirectories for each chain you want to interact with. This allows you to initialize each subdirectory using whatever scaffolding tool you want for each individual chain, and orchestration code in a common directory.

Let's break down what's in the xdapp-starter project:

chains/

  • This folder contains the subdirectories for chain specific code. For example, I might use the anchor tool to anchor init solana-project within the chains/ directory.

handlers/

The handlers folder contains the js client code to deal with each chain's specific needs. They expose a common API that we can consume in starter.js for code cleanliness.

They all take in a context object that's made up of the

orchestrator.js

This file parses command line args and filters calls to chain management handlers.

xdapp.config.json

The config file contains all the information about the network rpc nodes, accounts, and other constants used to communicate with contracts deployed to the selected chains.

Overview of Sending Messages

While the specific code varies chain by chain, sending a message always requires your contract to interact with the Core Bridge contract deployed on each chain to emit a VAA.

Emitting a VAA requires three pieces of information:

  1. Nonce (u32)

    The nonce is a random number assigned to each message. This allows the receiving contract a way to make sure it doesn't double process messages.

  2. Consistency (u8)

    This is the number of blocks for Guardians to wait before they sign the message. Higher consistencies mean more security against blockchain reorgs. For example, if this is set too low, and the block you're emitting from reorgs, then it's possible that even though the message was emitted and signed by the Guardians and processed on the receiving chain, no record of it exists on the emitting chain. If you were sending tokens across, this would allow for double spend attacks.

  3. Payload (bytes[])

    This is a payload of raw bytes that you want to emit. It's up to the receiving contract to know how to parse it.

Sending Messages: EVM

To send messages from EVM, first we have to download the Core Bridge interfaces.

We need two interfaces, IWormhole.sol and Structs.sol

In your xdapp-starter, place those files in

- chains/
    - evm/
        - src/
            - Wormhole/
                - IWormhole.sol
                - Structs.sol

Let's also modify the IWormhole.sol file to update the import for Structs.sol.

// contracts/Messages.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

import "./Structs.sol";

..

Now, let's create a new contract in our src/ folder Messenger.sol. In this contract, we'll also create a uint32 nonce. You can think of this nonce like a message id--it's just a number that lets the receiving contract know if it has already processed a message.

Also, we'll set the consistency level here to 1, because we're just testing and want the Guardians to sign this VAA as soon as they see it. If we were deploying to production, we might want to match this level to the deployed chain's finality guarantees.


//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "./Wormhole/IWormhole.sol";

contract Messenger {
    //This is the Tilt Devnet address constant. 
    //Replace this address with relevant testnet or mainnet address of the chain you're deploying too.
    address private wormhole_core_bridge_address = address(0xC89Ce4735882C9F0f0FE26686c53074E09B0D550);

    IWormhole core_bridge = IWormhole(wormhole_core_bridge_address);

    uint32 nonce = 0;

    constructor(){}

    function sendMsg(bytes memory str) public returns (uint64 sequence) {
        uint8 consistency_level = 1;
        sequence = core_bridge.publishMessage(nonce, str, consistency_level);
        nonce = nonce+1;
    }
}


Registering Emitters

While not strictly required, when writing xDapps it's important to listen for messages from specific apps on other chains, otherwise attacks could create fake applications that emit messages that look like what we expect, but have fake payloads.

To do this, we register the sending contract's addresses with the receiving contracts. Because each VAA has the contract address that asked the core bridge to emit the VAA, we call the sending contracts emitters. Additionally, the emitters you're listening to do not need to be your own contracts. You might want to listen to the emits of a different xDapp, in which case you'd register its address in your code.

Then, when receiving messages, we can check the VAA being submitted to make sure it came from one of the contracts we were expecting and from the chain we were expecting.

Registering Emitters: EVM

To specify applications that our EVM application is allowed to listen to, we can create a mapping of chainId to bytes32 addresses of the relevant contracts on other chains.

The chainId used here is the wormhole chainId, and the address used here is the address in bytes.


contract Messenger {
    mapping(uint16 => bytes32) _applicationContracts;

    address owner;
    constructor(){
        owner = msg.sender;
    }
    
    /**
        Registers it's sibling applications on other chains as the only ones that can send this instance messages
    */

    function registerApplicationContracts(uint16 chainId, bytes32 applicationAddr) public {
        require(msg.sender == owner, "Only owner can register new chains!");
        _applicationContracts[chainId] = applicationAddr;
    }
}

If you have more than one address per chainId that you want to listen to, consider making the mapping into bytes32[].

Relaying Messages

Relaying Messages can be done one of three ways:

  1. Manual Relaying

    Manual Relaying is usally done on the front end. Manual relyaing requires the front end to fetch the VAA it just created and then submit on the target chain. This means the user ends up paying for the gas fee and has to go through the additional step to submit the tx on the target chain.

  2. Protocol Specific Relayers

    Protocols and Apps can run their own relayers, listening to messages as they are created by the Core Bridge and submitting them to their application on the target chain. This is the ideal user experience but requires more work from the developer.

  3. Generic Relayers

    Generic Relayers can pick up any app or protocol's messages and submit them to the target chain for a fee. This is the ideal developer and user experience, but is still being developed.

Overview of Receving Messages

Receving messages requires you (or a relayer) to submit the VAA to your application contract. This contract then calls the Core Bridge on the receving chain to check the message signatures against the stored Guardian set signatures.

If those checks pass, then the application can see if it's a message that's already been processed by checking its sequence number against a store list of processed message sequence numbers.

The final optional check is to make sure that the message came from an application from the source chain that we were expecting. Usually this is our own application, and should be registered during initialization steps (see registration).

After we have ensured all those things are correct, we can process the message according to the business logic in our application.

Receving Messages on EVM

To receive messages in EVM, first we need to implement a function receiveMsg. The name of this function is arbitrary, so any function name will work as long as your relayer knows what to call on the application contract when submitting messages.

This function will take in an encoded set of bytes as the VAA and then call the parseAndVerifyVM function on the Core Bridge to validate the message.


    function receiveEncodedMsg(bytes memory encodedMsg) public {
        (IWormhole.VM memory vm, bool valid, string memory reason) = core_bridge.parseAndVerifyVM(encodedMsg);
        
        //1. Check Wormhole Guardian Signatures
        //  If the VM is NOT valid, will return the reason it's not valid
        //  If the VM IS valid, reason will be blank
        require(valid, reason);

        //2. Check if the Emitter Chain contract is registered
        require(_applicationContracts[vm.emitterChainId] == vm.emitterAddress, "Invalid Emitter Address!");
    
        //3. Check that the message hasn't already been processed
        require(!_completedMessages[vm.hash], "Message already processed");
        _completedMessages[vm.hash] = true;

        //Process the message. In this case, we just store the string in the VAA to storage
        current_msg = string(vm.payload);
    }

Projects

The projects for this repository are located here.

EVM Messenger

The EVM messenger project is a simple contract that sends messages from one contract on an EVM chain to its sibling contract on another chain.

Before you get started with this project, make sure you have a local Wormhole Guardian Network running (either WLV or Tilt). If you're running WLV, you'll also need to spin up EVM0 and EVM1 so there are two EVM chains to send messages back and forth.

Let's break down the files you're going to find in the evm-messenger folder.

Chains

The chains/ folder contains the source code that's actually being deployed to the EVM chain. The evm/ folder found inside was generated using forge init. There are two files of note in this folder, src/Wormhole/IWormhole.sol and src/Messenger.sol.

The IWormhole file is the Wormhole Core Bridge interface, and is required if your app wants to talk to the Wormhole Core Bridge. It outlines the functions and return values you can expect from the Wormhole contract.

The second file, Messenger, is covered in our breakdown of the EVM code here.

Tests

We have a very simple test script written in bash, but it's less of a test script and more of a happy path walkthrough. It makes uses of Orchestrator.js (see below) to call the functions on our EVM contract in order.

To start, deploy the code, register the applications on each chain and then send a message.

Orchestrator

Orchestrator is a js client that takes arguments from the command line to call various functions on our contract. We'll break down everything orchestator does here.

xdapp.config.json

This maintains some constants about the chains RPC endpoints, private keys used to deploy code, etc. It also includes the Wormhole RPC endpoint.

Messenger.sol

Messenger.sol is an application contract on EVM capable of communicating with the Wormhole core bridge.

Start by hard coding the Wormhole core bridge address, and creating a interfaced link to it.

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "./Wormhole/IWormhole.sol";

contract Messenger {
    string private current_msg;
    address private wormhole_core_bridge_address = address(0xC89Ce4735882C9F0f0FE26686c53074E09B0D550);
    IWormhole core_bridge = IWormhole(wormhole_core_bridge_address);

    // Used to calculate the Sequence for each message sent from this contract    
    uint32 nonce = 0;
    // Chain ID => Application Contract mapping to ensure we only process messages from contracts we want to.
    mapping(uint16 => bytes32) _applicationContracts;
    address owner;
    // Track which messages we've already processed so we don't double process messages.
    mapping(bytes32 => bool) _completedMessages;
}

Constructor

This sets the owner of the contract to the deployer. The owner is used later to register sibling contracts on foreign chains.


constructor(){
    owner = msg.sender;
}

SendMsg

This takes in a bytes payload and calls the Wormhole Core Bridge to publish the bytes as a message.

The publishMessage() function of the core_bridge take three arguements:

  • Nonce: a number to uniquely identify this message, used to make sure that the target chain doesn't double process the same message
  • Payload: the bytes payload
  • Confirmations: the number of blocks the guardians should wait before signing this VAA. For low security applications, this number can be low, but if you're on a chain that often reorgs a high number of blocks (like Polygon) you might want to set this number high enough to ensure your transaction from the source chain doesn't get lost after the guardians sign it.
function sendMsg(bytes memory str) public returns (uint64 sequence) {
    sequence = core_bridge.publishMessage(nonce, str, 1);
    nonce = nonce+1;
}

ReceiveEncodedMsg

The receive encoded message takes in a VAA as bytes. Then it calls the Core Bridge to verify the signatures match those of the gaurdians, check that it's from a contract on a foreign chain that we actually want to listen to and that the message hasn't been processed already. If all those checks pass, we can decode the payload (in this case we know it's a string) and set the current_msg for the contract to that payload.


function receiveEncodedMsg(bytes memory encodedMsg) public {
    (IWormhole.VM memory vm, bool valid, string memory reason) = core_bridge.parseAndVerifyVM(encodedMsg);
    
    //1. Check Wormhole Guardian Signatures
    //  If the VM is NOT valid, will return the reason it's not valid
    //  If the VM IS valid, reason will be blank
    require(valid, reason);

    //2. Check if the Emitter Chain contract is registered
    require(_applicationContracts[vm.emitterChainId] == vm.emitterAddress, "Invalid Emitter Address!");

    //3. Check that the message hasn't already been processed
    require(!_completedMessages[vm.hash], "Message already processed");
    _completedMessages[vm.hash] = true;

    //Do the thing
    current_msg = string(vm.payload);
}


GetCurrentMsg

A simple method that returns the current stored message.


function getCurrentMsg() public view returns (string memory){
    return current_msg;
}

RegisterApplicationContracts

It's typically a good idea to register and track the contracts from foreign chains that you're accepting VAAs from, as anyone could deploy a contract and generate a fake VAA that looks like a real VAA you'd want to accept.


/**
    Registers it's sibling applications on other chains as the only ones that can send this instance messages
    */
function registerApplicationContracts(uint16 chainId, bytes32 applicationAddr) public {
    require(msg.sender == owner, "Only owner can register new chains!");
    _applicationContracts[chainId] = applicationAddr;
}

EVM Messenger Client

Let's walk through a few of the elements of the EVM Messenger client you'll see in the project folders:

Orchestrator.js

A JS client that deploys and calls the functions of the two Messenger contracts on two chains.

Deploy

Uses forge to compile and deploy the code. Stores the deployed address to be used later.

Register Chain

Takes the deployed address from the target chain and registers it on the source chain. No Wormhole interaction is necessary for this step.

Send Msg

Calls the sendMsg() function on the source chain, which emits a VAA. Fetches the VAA from the Wormhole Guardian and stores it.

Submit VAA

Manually relays the VAA to the target chain.

Get Current Msg

Returns the chain's current message.

Messenger

Portal Token Bridge Transfers

Portal Token Bridge is one of the biggest applications built on Wormhole. It uses structured payloads to transfer tokens and NFTs from one wallet to another.

Attesting a Token

Before a token can be transferred, the token need to be attested to another chain. To attest a token, you first create a AssetMeta VAA by calling the attest() function on Token Bridge. Then you take the VAA over to the receipient chain, where you call createWrapped() which deploys a wrapped version of the Token.

This only needs to happen once per payload, and trying to attest a token a second time will simply result in the address of the already-created Wrapped Token Address.

Transfering Tokens

To transfer tokens, the payer of tokens first authorizes the Token Bridge contract to move the tokens on their behalf, then locks them up with the Token Bridge, which then emits a VAA. This VAA can then be submitted on target chain's Token Bridge's completeTransfer() to mint the wrapped version of the Token.

When transfering tokens from Chain A to B and beyond, the token is only "wrapped" once, as every time it's attested, it's always from the chain the token is natively located on. If the token being transferred is native to the chain it's being transfered to, you'll receive the original token back instread of a wrapped version.

There are typically two functions for transfer: transfer() and transferNative(). This is because native currencies of most blockchains (ETH on Ethereum, SOL on Solana, etc) don't follow the Token spec of that chain, so to transfer native currencies, we wrap them first into a tokenized version and then transfer.

For transfers, there's also an arbiterFee you can set. If this fee is set, when completeTransfer() is called, that amount of tokens are withheld from the release of tokens on the target chain and instead given to the submitter of the message (for example, a relayer). This allows third party to submit transactions on your behalf, for a fee.

Transfering with a Payload

Transfering with a Payload is much like transfering normal tokens, with two major differences.

First, as the name implies, you can attach a bytes payload to the transfer message. Secondly, the completeTransfer() function for Transfer with Payload can only be called by the receipient of that VAA. This means the flow is slightly different; instead of the user calling the completeTransfer() function on the Token Bridge, they call a function on the application they are interacting with which will check the payload, do any state changes it needs to make and then call completeTransfer() on Token Bridge to mint tokens to itself.

EVM: Attesting a Token

Attesting a token from EVM is fairly simple, and usually done via the Portal UI since it's a step that only needs to happen once per Token.

If for whatever reason you need to do it programmatically, you can also do it using the JS SDK:

The first step is to create an AttestMeta VAA. We do this by calling attestFromEth() function from the JS SDK and passing in the Token Bridge address, a Ethers signer object, and the address of the Token we want to attest:


const networkTokenAttestation = await attestFromEth(
    network.tokenBridgeAddress, // Token Bridge Address
    signer, //Private Key to sign and pay for TX + RPC Endpoint
    network.testToken //Token Address
);

Anyone can attest any token on the network.

To complete the Attestation, we grab the VAA that the attestFromEth() function generates by getting the Emitter address of the Token Bridge and the Sequence from the logs of the transaction receipt. We then fetch against a guardian REST endpoint. It could take a couple seconds (up to 30s!) for the guardian to see and sign the VAA, so it's a good idea to poll the guardian every couple seconds until the VAA is found.


const emitterAddr = getEmitterAddressEth(network.tokenBridgeAddress);
const seq = parseSequenceFromLogEth(networkTokenAttestation, network.bridgeAddress);
const vaaURL =  `${config.wormhole.restAddress}/v1/signed_vaa/${network.wormholeChainId}/${emitterAddr}/${seq}`;
console.log("Searching for: ", vaaURL);
let vaaBytes = await (await fetch(vaaURL)).json();
while(!vaaBytes.vaaBytes){
    console.log("VAA not found, retrying in 5s!");
    await new Promise((r) => setTimeout(r, 5000)); //Timeout to let Guardiand pick up log and have VAA ready
    vaaBytes = await (await fetch(vaaURL)).json();
}

Next, we submit the VAA onto the target chain to create a wrapped version of the Token by calling createWrapped(). On an EVM chain, this will deploy a Portal Wrapped Token contract who's mint authority is the Portal Token Bridge on that chain. Sometimes this transaction throws an unpredicatable gas price error, so it's a good idea to set a high gas limit.

After the wrapped token is created, you can get the new wrapped token address by calling the wrappedAsset() function of the TokenBridge.


await targetTokenBridge.createWrapped(Buffer.from(vaaBytes.vaaBytes, "base64"), {
    gasLimit: 2000000
});
await new Promise((r) => setTimeout(r, 5000)); //Time out to let block propogate
const wrappedTokenAddress = await targetTokenBridge.wrappedAsset(
    network.wormholeChainId,
    Buffer.from(
        tryNativeToHexString(network.testToken, "ethereum"),
        "hex"
    )
);
console.log("Wrapped token created at: ", wrappedTokenAddress);

EVM: Transferring a Token

WARNING: To be able to successfully transfer a token from Chain A to Chain B, make sure you attest it first. Otherwise the Token may be transferred, but you won't be able to claim it til it's attested.

One big gotcha new EVM developers usually run into when working with ERC20 tokens is that because EVM uses unsigned integers, there's no concept of decimals. Therefore, tokens usually have up to 18 zeros behind them to denote up to 18 decimal places. Wormhole normalizes this to eight zeros, with transfer amounts rounded down to the nearest 8the decimal.

To wrap the Token Bridge functions in your contract, you can use the Token Bridge interfaces provided under projects/evm-tokenbridge/chains/evm/src/Wormhole folder of the xDapp Book repository.

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol";
import "./Wormhole/ITokenBridge.sol";
import "./Wormhole/PortalWrappedToken.sol";

contract Treasury {

    address private token_bridge_address = address(0x0290FB167208Af455bB137780163b7B7a9a10C16);
    ITokenBridge token_bridge = ITokenBridge(token_bridge_address);
    address private TKN_address = address(0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A); 
    ERC20PresetMinterPauser TKN = ERC20PresetMinterPauser(TKN_address);

    uint32 nonce = 0;

    function bridgeToken(uint256 amt, uint16 receipientChainId, bytes32 recipient) public returns (uint64 sequence) {
        nonce += 1;
        return token_bridge.transferTokens(TKN_address, amt, receipientChainId, recipient, 0, nonce);
    }   

    function approveTokenBridge(uint256 amt) public returns (bool) {
        return TKN.approve(token_bridge_address, amt);
    }
}

To transfer a token, first we have to approve the Token Bridge to be able to spend that Token on our behalf (so it can transfer tokens form our contract to tself). Make sure the bridgeAmt properly takes into account decimals for the ERC20 token.

// Here we are approving and transfering 50 tokens. The ERC20 token we are transfering has 18 decimal places.
const bridgeAmt = ethers.utils.parseUnits("50", "18");

await treasury.approveTokenBridge(bridgeAmt, {
    gasLimit: 2000000,
});

Then we simply call transfer to create the transfer VAA and fetch it from the guardians when it's ready. Note that the target receipient is a Wormhole normalized hex address left-padded to 32 bytes.

const targetRecepient = Buffer.from(tryNativeToHexString(targetDeployment.deployedAddress, "ethereum"), 'hex');

const tx = await (await treasury.bridgeToken(
    bridgeAmt,
    targetNetwork.wormholeChainId,
    targetRecepient
)).wait();
const emitterAddr = getEmitterAddressEth(network.tokenBridgeAddress);
const seq = parseSequenceFromLogEth(tx, network.bridgeAddress);
const vaaURL =  `${config.wormhole.restAddress}/v1/signed_vaa/${network.wormholeChainId}/${emitterAddr}/${seq}`;
let vaaBytes = await (await fetch(vaaURL)).json();
while(!vaaBytes.vaaBytes){
    console.log("VAA not found, retrying in 5s!");
    await new Promise((r) => setTimeout(r, 5000)); //Timeout to let Guardiand pick up log and have VAA ready
    vaaBytes = await (await fetch(vaaURL)).json();
}

After we've fetched the VAA, we can call the completeTransfer() function on the target chain if it's an EVM.


const completeTransferTx = await targetTokenBridge.completeTransfer(Buffer.from(vaaBytes.vaaBytes, "base64"));

Portal JS SDK Overview

For applications that only need to interact with the Core and Token Bridge contracts off chain, there is a Wormhole JS SDK provided.

It can be installed using npm like so:

npm i @certusone/wormholesdk

EVM to Solana Token Transfer

A cornerstone of cross chain apps (xDapps) is the ability to move tokens from one chain to another. Wormhole’s APIs make that a breeze.

Let’s do a simple programmatic transfer from Eth to Solana. First, we need to figure out what address on Solana where we are sending the tokens. Unlike EVM chains where the address is just the wallet address, we need to send the tokens to our recipient address associated token account for that token. We can use a couple helper functions from the Wormhole SDK to make this possible.

import {
	Token,
	ASSOCIATED_TOKEN_PROGRAM_ID,
	TOKEN_PROGRAM_ID
} from '@solana/spl-token';
import {
	getForeignAssetSolana,
	hexToUint8Array,
	nativeToHexString,
	CHAIN_ID_ETH,
} from '@certusone/wormhole-sdk';

const SOLANA_TOKEN_BRIDGE_ADDRESS = "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb";
// determine destination address - an associated token account
const solanaMintKey = new PublicKey(
  (await getForeignAssetSolana(
    connection,
    SOLANA_TOKEN_BRIDGE_ADDRESS,
    CHAIN_ID_ETH,
    hexToUint8Array(nativeToHexString(tokenAddress, CHAIN_ID_ETH) || "")
  )) || ""
);
const recipientAddress = await Token.getAssociatedTokenAddress(
  ASSOCIATED_TOKEN_PROGRAM_ID,
  TOKEN_PROGRAM_ID,
  solanaMintKey,
  recipientWalletAddress
);

After we have the receipt token account on Solana, we can come back and submit the transfer message on Ethereum. This will output a log that contains a sequence number (A nonce for the message) and an emitter address (the ETH Token Bridge Address as bytes) . The sequence number and emitter address will be used to fetch a VAA after it’s been signed by Guardians.

import {
	trasnferFromEth,
	parseSequenceFromLogEth,
	getEmitterAddressEth,
	CHAIN_ID_SOLANA,
} from '@certusone/wormhole-sdk';

const ETH_TOKEN_BRIDGE_ADDRESS = "0x3ee18B2214AFF97000D974cf647E7C347E8fa585";

// Submit transaction - results in a Wormhole message being published
const receipt = await transferFromEth(
  ETH_TOKEN_BRIDGE_ADDRESS,
  signer,
  tokenAddress,
  amount,
  CHAIN_ID_SOLANA,
  recipientAddress
);
// Get the sequence number and emitter address required to fetch the signedVAA of our message
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS);
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);

Once the Guardians have signed the token message, we can fetch it to use in the redeem step. If you’re a developer, you might run this as an automatic process through an application specific relayer (more on that in a later thread!)

import {
	getSignedVAA,
} from '@certusone/wormhole-sdk';

// Fetch the signedVAA from the Wormhole Network (this may require retries while you wait for confirmation)
const { signedVAA } = await getSignedVAA(
  WORMHOLE_RPC_HOST,
  CHAIN_ID_ETH,
  emitterAddress,
  sequence
);

Then we can post the VAA to Solana to mint the tokens. Because of the compute limit on Solana, we split the signature verification and token claim into steps. First we'll verify all the signatures and create a claim account for the Token.

const SOL_BRIDGE_ADDRESS = "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth";
// On Solana, we have to post the signedVAA ourselves
await postVaaSolana(
  connection, // Solana Mainnet Connection
  wallet,      //Solana Wallet Signer
  SOL_BRIDGE_ADDRESS,
  payerAddress,
  signedVAA
);

Finally we can claim the tokens

// Finally, redeem on Solana
const transaction = await redeemOnSolana(
  connection,
  SOL_BRIDGE_ADDRESS,
  SOL_TOKEN_BRIDGE_ADDRESS,
  payerAddress,
  signedVAA,
  isSolanaNative,
  mintAddress
);
const signed = await wallet.signTransaction(transaction);
const txid = await connection.sendRawTransaction(signed.serialize());
await connection.confirmTransaction(txid);

Polygon to Oasis with Relayers

In this example we’ll fetch the fee schedule and attach a relayer fee onto our transaction. This is a non trivial example as we’ll also use Polygon as a source chain, which has some quirks when it comes to gas estimation. In the future, this whole process is being simplified, so check back in the future for hopefully much simpler version of this example.

For this example, we’ll need a couple of packages:

npm i --save @certusone/wormhole-sdk ethers node-fetch

Then let's get started writing some code:

import { BigNumber, ethers } from "ethers";
import fetch from "node-fetch";
import {
    getEmitterAddressEth,
    hexToUint8Array,
    nativeToHexString,
    parseSequenceFromLogEth,
    CHAIN_ID_POLYGON,
    CHAIN_ID_OASIS,
    transferFromEthNative,
    getIsTransferCompletedEth,
		setDefaultWasm
} from "@certusone/wormhole-sdk";

Setup the Polygon and Oasis Wallets

First, let us set up the two wallets we’ll be sending and receiving from. While we are instantiating both wallets with their private keys, we only need the Public key of the receiving wallet for this example.

const EmeraldWallet = new ethers.Wallet(
    privatekey_emerald,
    new ethers.providers.JsonRpcProvider("https://emerald.oasis.dev")
);
const PolygonWallet = new ethers.Wallet(
    privatekey_polygon,
    new ethers.providers.JsonRpcProvider("https://polygon-rpc.com/")
);

Fetch the fee schedule

Next, we’ll fetch the fee schedule for the Portal Token Bridge relayer. This fee schedule will give us the minimum fee for each recipient chain that the relayer will accept. As long as we attach at least that fee in the relayer fee, we can be fairly confident that the relayer will pick up the transaction and relay it to the recipient chain. The fee will cover the gas cost for the relayer along with a little extra to make it worth their time to run the relayer service.

We will also define the transfer amount in this step. The fee schedule will either return a flat fee in USD for the recipient chain, or a percentage fee (usually only for Ethereum). Either way, we’ll need to calculate the fee in in BigNumber format (no decimals).

For example, 1 MATIC on Polygon is 1e18 wei, or 1000000000000000000 wei. Because EVM has a hard time with floating point math, we have to do all our transactions in this small unit, to avoid decimal numbers.

const transferAmount = BigNumber.from("1000000000000000000"); // We are sending 1 MATIC over the wall to Oasis
const relayerFeeSchedule = await (await fetch(
	"https://raw.githubusercontent.com/certusone/wormhole-relayer-list/main/relayer.json"
)).json();

The fee schedule has the following interface:

export interface RelayerFeeSchedule {
    supportedTokens: ChainAddress[];
    relayers: Relayer[];
    feeSchedule: FeeSchedule;
}

interface ChainAddress {
    chainId: number;
    address: string;
    coingeckoId: string;
}

interface Relayer {
    name: string;
    url: string;
}

interface FeeSchedule {
    [chainId: string]: {
        type: "flat" | "percent";
        feeUsd?: number;
        feePercent?: number;
        gasEstimate?: number;
    };
}

After we’ve fetched the fee schedule, we need to find the fee in Wei that needs to be paid to the Relayer. At the time of writing, Oasis has a flat fee of $0.50, so to calculate how much MATIC we need to pay for the $0.50 fee, we need to fetch the MATIC price. Let’s use the free CoinGecko api:

let feeWei: number;
if (relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].type == "flat") {
    const feeUsd = relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].feeUsd
    const MATIC_PRICE = (
        await (
            await fetch(
                "https://api.coingecko.com/api/v3/simple/token_price/polygon-pos?contract_addresses=0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270&vs_currencies=usd"
            )
        ).json()
    )["0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270"]["usd"];

    feeWei = (feeUsd / MATIC_PRICE) * 1e18;
} else if (relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].type == "percent") {
    let feeWei = (relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].feePercent /100) * transferAmount.toNumber();
}

Add override for gas estimation for Polygon

Because the source chain is Polygon, we need to do this additional step to overestimate the gas. This is because ethers library has some problems with fee estimation after EIP-1559.

let overrides;
let feeData = await PolygonWallet.provider.getFeeData();
overrides = {
    maxFeePerGas: feeData.maxFeePerGas?.mul(50) || undefined,
    maxPriorityFeePerGas:
        feeData.maxPriorityFeePerGas?.mul(50) || undefined,
};

Emit Portal Message

Now we have all the pieces we need to emit a Portal Bridge message with a relay fee attached. We do this using the transferFromEthNative() method. EthNative is used because we’re transferring the native token of the Polygon network rather than an ERC20 token.

const POLYGON_TOKEN_BRIDGE = "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE";

const receipt = await transferFromEthNative(
    POLYGON_TOKEN_BRIDGE,
    PolygonWallet,
    transferAmount,
    CHAIN_ID_OASIS,
    hexToUint8Array(
        nativeToHexString(
            await EmeraldWallet.getAddress(),
            CHAIN_ID_OASIS
        ) || ""
    ),
    BigNumber.from(feeWei.toString()),
    overrides
);
console.log("Receipt: ", receipt);

const POLYGON_CORE_BRIDGE_ADDRESS =
    "0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7";
const sequence = parseSequenceFromLogEth(
    receipt,
    POLYGON_CORE_BRIDGE_ADDRESS
);
const emitterAddress = getEmitterAddressEth(POLYGON_TOKEN_BRIDGE);
console.log("Sequence: ", sequence);
console.log("EmitterAddress: ", emitterAddress);

Let’s walk through each of the arguments of this function and what they mean.

POLYGON_TOKEN_BRIDGE is the address of the Portal Token Bridge on the Polygon network. You can find it, amongst other addresses on the Deployment Info page.

PolygonWallet is a signer you get from the Ethers library that holds a private key that can sign transactions,

transferAmount is a BigNumber that contains the amount to transfer in the smallest unit of the network.

CHAIN_ID_OASIS is a constant that identifies the target chain

hexToUint8Array() translates the target publickey into a wormhole public key.

BigNumber.from(feeWei.toString()) identifies the fee in smallest unit of the network for the relayer.

overrides are used if we need to override the gas cost, which we need to do for Polygon.

Check VAA was signed

Wait 15 min for finality on Polygon and check to see if was submitted. If successful you’ll be able to fetch a base64 encoded vaaBytes. We need this in the next step where we check if the transaction was successfully relayed.

await new Promise((r) => setTimeout(r, 900000)); //15m in seconds
const WORMHOLE_RPC = "https://wormhole-v2-mainnet-api.certus.one";
let vaaBytes = undefined;
while (!vaaBytes) {
    try {
        vaaBytes = (
            await (
                await fetch(
                    `${WORMHOLE_RPC}/v1/signed_vaa/${CHAIN_ID_POLYGON}/${emitterAddress}/${sequence}`
                )
            ).json()
        ).vaaBytes;
    } catch (e) {
        await new Promise((r) => setTimeout(r, 5000));
    }
}
console.log("VAA Bytes: ", vaaBytes);

Check if the transfer was completed

In the final step we use the getIsTransferCompletedEth() method to check if the transfer was completed on the Oasis Emerald chain. If it’s not, we wait 5 seconds and check again.

setDefaultWasm("node"); //only needed if running in node.js
const EMERALD_TOKEN_BRIDGE = "0x5848C791e09901b40A9Ef749f2a6735b418d7564";
let transferCompleted = await getIsTransferCompletedEth(
    EMERALD_TOKEN_BRIDGE,
    EmeraldWallet.provider,
    vaaBytes
);
while (!transferCompleted) {
    await new Promise((r) => setTimeout(r, 5000));
    transferCompleted = await getIsTransferCompletedEth(
        EMERALD_TOKEN_BRIDGE,
        EmeraldWallet.provider,
        vaaBytes
    );
}

console.log("VAA Relayed!");

And that's it! You've successfully programmatically relayed a transaction!

Tools

There are various tools in the Wormhole ecosystem that can help you in developing xDapps. Here are a few of the most notable:

Testnet

Wormhole has deployed Core Bridge, Token Bridge and NFT Bridge contracts on various testnets of the chains connected by Wormhole. You can see the deployed addresses here. There's only a single Guardian that oversees the testnets, so you might get a higher rate of missed VAAs than you would on mainnet.

Wormhole Explorer

Wormhole Explorer is a tool that will help you parse VAAs after they've been picked up the Guardian network. It's available here.

Testnet Bridge UI

If you'd like to try out Portal Bridge on Testnet, there's a UI you can use to attest and transfer tokens for testnet, hosted here.

Tilt

Tilt is a Kubernetes based tool that runs a copy of every chain along side a guardian node to create a simulated testing environment. To set it up and test against it, start here.

Wormhole SDK

The SDK is a set of Javascript tools to help you do Token Bridge transfers, plus fetch and submit VAAs from one chain to another. You can install it via NPM here.

Repository

The Wormhole core repository can be found here.

Design Documents

Wormhole's component design specifications can be found here. These outline the reasoning behind design decisions with added technical depth.

Contracts

Here you can find the addresses for the deployed contracts on all the chains that Wormhole supports, including Testnet. The SDK makes these addresses available in the CONTRACTS constant.

Mainnet

Core Bridge

Chain NameWormhole Chain IDNetwork IDAddress
Solana1mainnet-betaworm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth
Ethereum210x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B
Terra Classic3columbus-5terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5
Binance Smart Chain4560x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B
Polygon51370x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7
Avalanche (C-Chain)6431140x54a8e5f9c4CbA08F9943965859F6c34eAF03E26c
Oasis (Emerald)742620xfE8cD454b4A1CA468B57D79c0cc77Ef5B6f64585
Aurora913131615540xa321448d90d4e5b0A732867c18eA198e75CAC48E
Fantom102500x126783A6Cb203a3E35344528B26ca3a0489a1485
Karura116860xa321448d90d4e5b0A732867c18eA198e75CAC48E
Acala127870xa321448d90d4e5b0A732867c18eA198e75CAC48E
Klaytn1382170x0C21603c4f3a6387e241c0091A7EA39E43E90bb7
Celo14422200xa321448d90d4e5b0A732867c18eA198e75CAC48E
Terra18phoenix-1terra12mrnzvhx3rpej6843uge2yyfppfyd3u9c3uq223q8sl48huz9juqffcnh

Token Bridge

Chain NameWormhole Chain IDNetwork IDAddress
Solana1mainnet-betawormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb
Ethereum210x3ee18B2214AFF97000D974cf647E7C347E8fa585
Terra3columbus-5terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf
Binance Smart Chain4560xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7
Polygon51370x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE
Avalanche (C-Chain)6431140x0e082F06FF657D94310cB8cE8B0D9a04541d8052
Oasis (Emerald)742620xfE8cD454b4A1CA468B57D79c0cc77Ef5B6f64585
Aurora913131615540x51b5123a7b0F9b2bA265f9c4C8de7D78D52f510F
Fantom102500x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2
Karura116860xae9d7fe007b3327AA64A32824Aaac52C42a6E624
Acala127870xae9d7fe007b3327AA64A32824Aaac52C42a6E624
Klaytn1382170x5b08ac39EAED75c0439FC750d9FE7E1F9dD0193F
Celo14422200x796Dff6D74F3E27060B71255Fe517BFb23C93eed
Terra18phoenix-1terra153366q50k7t8nn7gec00hg66crnhkdggpgdtaxltaq6xrutkkz3s992fw9

NFT Bridge

Chain NameWormhole Chain IDNetwork IDAddress
Solana1mainnet-betaWnFt12ZrnzZrFZkt2xsNsaNWoQribnuQ5B5FrDbwDhD
Ethereum210x6FFd7EdE62328b3Af38FCD61461Bbfc52F5651fE
Binance Smart Chain4560x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE
Polygon51370x90BBd86a6Fe93D3bc3ed6335935447E75fAb7fCf
Avalanche (C-Chain)6431140xf7B6737Ca9c4e08aE573F75A97B73D7a813f5De5
Oasis (Emerald)742620x04952D522Ff217f40B5Ef3cbF659EcA7b952a6c1
Aurora913131615540x6dcC0484472523ed9Cdc017F711Bcbf909789284
Fantom102500xA9c7119aBDa80d4a4E0C06C8F4d8cF5893234535
Karura116860xb91e3638F82A1fACb28690b37e3aAE45d2c33808
Acala127870xb91e3638F82A1fACb28690b37e3aAE45d2c33808
Klaytn1382170x3c3c561757BAa0b78c5C025CdEAa4ee24C1dFfEf
Celo14422200xA6A377d75ca5c9052c9a77ED1e865Cc25Bd97bf3

Testnet

Core Bridge

Chain NameWormhole Chain IDNetwork IDAddress
Solana1devnet3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5
Ethereum (Goerli)250x706abc4E45D419950511e474C7B9Ed348A4a716c
Ethereum (Ropsten)1000130x210c5F5e2AF958B4defFe715Dc621b7a3BA888c5
Terra3bombay-12terra1pd65m0q9tl3v8znnz5f5ltsfegyzah7g42cx5v
Binance Smart Chain4970x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D
Polygon (Mumbai)5800010x0CBE91CF822c73C2315FB05100C2F714765d5c20
Avalanche (Fuji)6431130x7bbcE28e64B3F8b84d876Ab298393c38ad7aac4C
Oasis (Emerald Testnet)7422610xc1C338397ffA53a2Eb12A7038b4eeb34791F8aCb
Algorand (Testnet)886525623
Aurora913131615550xBd07292de7b505a4E803CEe286184f7Acf908F5e
Fantom1040020x1BB3B4119b7BA9dfad76B0545fb3F531383c3bB7
Karura116860xE4eacc10990ba3308DdCC72d985f2a27D20c7d03
Acala127870x4377B49d559c0a9466477195C6AdC3D433e265c0
Klaytn1310010x1830CC6eE66c84D2F177B94D544967c774E624cA
Celo14447870x88505117CA88e7dd2eC6EA1E13f0948db2D50D56
Terra18pisco-1terra19nv3xr5lrmmr7egvrk2kqgw4kcn43xrtd5g0mpgwwvhetusk4k7s66jyv0
Injective19testnetinj1xx3aupmgv3ce537c0yce8zzd3sz567syuyedpg

Token Bridge

Chain NameWormhole Chain IDNetwork IDAddress
Solana1devnetDZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe
Ethereum (Goerli)250xF890982f9310df57d00f659cf4fd87e65adEd8d7
Ethereum (Ropsten)1000130xF174F9A837536C449321df1Ca093Bb96948D5386
Terra3bombay-12terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a
Binance Smart Chain4970x9dcF9D205C9De35334D646BeE44b2D2859712A09
Polygon (Mumbai)5800010x377D55a7928c046E18eEbb61977e714d2a76472a
Avalanche (Fuji)6431130x61E44E506Ca5659E6c0bba9b678586fA2d729756
Oasis (Emerald Testnet)7422610x88d8004A9BdbfD9D28090A02010C19897a29605c
Algorand (Testnet)886525641
Aurora913131615550xD05eD3ad637b890D68a854d607eEAF11aF456fba
Fantom1040020x599CEa2204B4FaECd584Ab1F2b6aCA137a0afbE8
Karura116860xd11De1f930eA1F7Dd0290Fe3a2e35b9C91AEFb37
Acala127870xebA00cbe08992EdD08ed7793E07ad6063c807004
Klaytn1310010xC7A13BE098720840dEa132D860fDfa030884b09A
Celo14447870x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153
Injective19testnetinj1q0e70vhrv063eah90mu97sazhywmeegp7myvnh

NFT Bridge

Chain NameWormhole Chain IDNetwork IDAddress
Solana1devnet2rHhojZ7hpu1zA91nvZmT8TqWWvMcKmmNBCr2mKTtMq4
Ethereum (Goerli)250xD8E4C2DbDd2e2bd8F1336EA691dBFF6952B1a6eB
Ethereum (Ropsten)1000130x2b048Da40f69c8dc386a56705915f8E966fe1eba
Binance Smart Chain4970xcD16E5613EF35599dc82B24Cb45B5A93D779f1EE
Polygon (Mumbai)5800010x51a02d0dcb5e52F5b92bdAA38FA013C91c7309A9
Avalanche (Fuji)6431130xD601BAf2EEE3C028344471684F6b27E789D9075D
Oasis (Emerald Testnet)7422610xC5c25B41AB0b797571620F5204Afa116A44c0ebA
Aurora913131615550x8F399607E9BA2405D87F5f3e1B78D950b44b2e24
Fantom1040020x63eD9318628D26BdCB15df58B53BB27231D1B227
Karura116860x0A693c2D594292B6Eb89Cb50EFe4B0b63Dd2760D
Acala127870x96f1335e0AcAB3cfd9899B30b2374e25a2148a6E
Klaytn1310010x94c994fC51c13101062958b567e743f1a04432dE
Celo14447870xaCD8190F647a31E56A656748bC30F69259f245Db

RPC Nodes

These RPC nodes are maintained by the Guardians to help fetch VAAs and query the Wormhole network.

Mainnet Guardian RPC

https://wormhole-v2-mainnet-api.certus.one
https://wormhole.inotel.ro
https://wormhole-v2-mainnet-api.mcf.rocks
https://wormhole-v2-mainnet-api.chainlayer.network
https://wormhole-v2-mainnet-api.staking.fund
https://wormhole-v2-mainnet.01node.com

Testnet Guardian RPC

https://wormhole-v2-testnet-api.certus.one

Testnet Guardian Public Key

0x13947Bd48b18E53fdAeEe77F3473391aC727C638