Skip to content

Building Protocols and Payloads

Introduction

The Wormhole TypeScript SDK provides a flexible and powerful system for integrating cross-chain communication into your applications. A key feature of the SDK is its ability to define protocols—modular units representing distinct functionalities—and their associated payloads, which encapsulate the data required for specific operations within those protocols.

This guide will help you understand how to build protocols and payloads in the SDK, covering:

  • The role of protocols and payloads in cross-chain communication
  • The mechanics of registering protocols and payloads using the SDK
  • Best practices for creating strongly typed layouts to ensure compatibility and reliability
  • Real-world examples using the TokenBridge as a reference implementation

By the end of this guide, you’ll have a solid understanding of how to define, register, and use protocols and payloads in your projects.

What is a Protocol?

In the Wormhole SDK, a protocol represents a significant feature or functionality that operates across multiple blockchains. Protocols provide the framework for handling specific types of messages, transactions, or operations consistently and standardized.

Examples of Protocols:

  • TokenBridge - enables cross-chain token transfers, including operations like transferring tokens and relaying payloads
  • NTT (Native Token Transfers) - manages native token movements across chains

Protocols are defined by:

  • A name - a string identifier (e.g., TokenBridge, Ntt)
  • A set of payloads - these represent the specific actions or messages supported by the protocol, such as Transfer or TransferWithPayload

Each protocol is registered in the Wormhole SDK, allowing developers to leverage its predefined features or extend it with custom payloads.

What is a Payload?

A payload is a structured piece of data that encapsulates the details of a specific operation within a protocol. It defines the format, fields, and types of data used in a message or transaction. Payloads ensure consistency and type safety when handling complex cross-chain operations.

Each payload is defined as:

  • A layout - describes the binary format of the payload fields
  • A literal - combines the protocol name and payload name into a unique identifier (e.g., TokenBridge:Transfer)

By registering payloads, developers can enforce type safety and enable serialization and deserialization for specific protocol operations.

Register Protocols and Payloads

Protocols and payloads work together to enable cross-chain communication with precise type safety. For instance, in the TokenBridge protocol:

  • The protocol is registered under the TokenBridge namespace
  • Payloads like Transfer or AttestMeta are linked to the protocol to handle specific operations

Understanding the connection between these components is important for customizing or extending the SDK to suit your needs.

Register Protocols

Registering a protocol establishes its connection to Wormhole's infrastructure, ensuring it interacts seamlessly with payloads and platforms while maintaining type safety and consistency.

How Protocol Registration Works

Protocol registration involves two key tasks:

  • Mapping protocols to interfaces - connect the protocol to its corresponding interface, defining its expected behavior across networks (N) and chains (C). This ensures type safety, similar to strong typing, by preventing runtime errors if protocol definitions are incorrect
  • Linking protocols to platforms - specify platform-specific implementations if needed, or use default mappings for platform-agnostic protocols

For example, here's the TokenBridge protocol registration:

declare module '../../registry.js' {
  export namespace WormholeRegistry {
    interface ProtocolToInterfaceMapping<N, C> {
      TokenBridge: TokenBridge<N, C>;
    }
    interface ProtocolToPlatformMapping {
      TokenBridge: EmptyPlatformMap<'TokenBridge'>;
    }
  }
}

This code snippet:

  • Maps the TokenBridge protocol to its interface to define how it operates
  • Links the protocol to a default platform mapping via EmptyPlatformMap

You can view the full implementation in the TokenBridge protocol file.

Platform-Specific Protocols

Some protocols require platform-specific behavior. For instance, the EVM-compatible Wormhole Registry maps native addresses for Ethereum-based chains:

declare module '@wormhole-foundation/sdk-connect' {
  export namespace WormholeRegistry {
    interface PlatformToNativeAddressMapping {
      Evm: EvmAddress;
    }
  }
}

registerNative(_platform, EvmAddress);

This ensures that EvmAddress is registered as the native address type for EVM-compatible platforms. See the EVM platform address file for details.

Register Payloads

Payload registration enables developers to define, serialize, and handle custom message types within their protocols. It establishes the connection between a protocol and its payloads, ensuring seamless integration, type enforcement, and runtime efficiency.

This process ties a protocol to its payloads using a combination of:

  • Payload literals - unique identifiers in the format <ProtocolName>:<PayloadName>. These literals map each payload to a layout
  • Payload layouts - structures that define the binary representation of payload data
  • The payload factory - a centralized runtime registry that maps payload literals to layouts for dynamic resolution and serialization

These components work together to streamline the definition and management of protocol payloads.

How Payload Registration Works

Payload registration involves:

  1. Define payload layouts - create layouts to structure your payloads. For instance, a protocol might use a TransferWithPayload layout:

    export const transferWithPayloadLayout = <
      const P extends CustomizableBytes = undefined
    >(
      customPayload?: P
    ) =>
      [
        payloadIdItem(3),
        ...transferCommonLayout,
        { name: 'from', ...universalAddressItem },
        customizableBytes({ name: 'payload' }, customPayload),
      ] as const;
    
  2. Register payloads - use registerPayloadTypes to map payload literals to their layouts:

    registerPayloadTypes('ProtocolName', protocolNamedPayloads);
    
  3. Access registered payloads - use the getPayloadLayout function to fetch the layout for a specific payload literal. This method ensures that the correct layout is retrieved dynamically and safely:

    const layout = getPayloadLayout('ProtocolName:PayloadName');
    

These steps link payload literals and their layouts, enabling seamless runtime handling.

The Payload Factory

At the core of the payload registration process is the payloadFactory, a registry that manages the mapping between payload literals and layouts:

export const payloadFactory = new Map<LayoutLiteral, Layout>();

export function registerPayloadType(
  protocol: ProtocolName,
  name: string,
  layout: Layout
) {
  const payloadLiteral = composeLiteral(protocol, name);
  if (payloadFactory.has(payloadLiteral)) {
    throw new Error(`Payload type ${payloadLiteral} already registered`);
  }
  payloadFactory.set(payloadLiteral, layout);
}

This implementation ensures dynamic, efficient handling of payloads at runtime.

Integrate Protocols with Payloads

Integrating payloads with protocols enables dynamic identification through payload literals, while serialization and deserialization ensure their binary representation is compatible across chains. For more details on these processes, refer to the Layouts page.

Payload Discriminators

Payload discriminators are mechanisms in the Wormhole SDK that dynamically identify and map incoming payloads to their respective layouts at runtime. They are relevant for protocols like TokenBridge, enabling efficient handling of diverse payload types while ensuring type safety and consistent integration.

How Discriminators Work

Discriminators evaluate serialized binary data and determine the corresponding payload layout by inspecting fixed fields or patterns within the data. Each payload layout is associated with a payload literal (e.g., TokenBridge:Transfer or TokenBridge:TransferWithPayload).

This system ensures:

  • Dynamic runtime identification - payloads are parsed based on their content, even if a single protocol handles multiple payload types
  • Strict type enforcement - discriminators leverage layout mappings to prevent invalid payloads from being processed

Below is an example of how the Wormhole SDK builds a discriminator to distinguish between payload layouts:

export function layoutDiscriminator<B extends boolean = false>(
  layouts: readonly Layout[],
  allowAmbiguous?: B
): Discriminator<B> {
  // Internal logic to determine distinguishable layouts
  const [distinguishable, discriminator] = internalBuildDiscriminator(layouts);
  if (!distinguishable && !allowAmbiguous) {
    throw new Error('Cannot uniquely distinguish the given layouts');
  }

  return (
    !allowAmbiguous
      ? (encoded: BytesType) => {
          const layout = discriminator(encoded);
          return layout.length === 0 ? null : layout[0];
        }
      : discriminator
  ) as Discriminator<B>;
}
  • layoutDiscriminator takes a list of layouts and generates a function that can identify the appropriate layout for a given serialized payload
  • The allowAmbiguous parameter determines whether layouts with overlapping characteristics are permitted

Real-World Example: Token Bridge Protocol

Integrating protocols with their respective payloads exemplifies how the Wormhole SDK leverages layouts and type-safe registration mechanisms to ensure efficient cross-chain communication. This section focuses on how protocols like TokenBridge use payloads to facilitate specific operations.

Token Bridge Protocol and Payloads

The TokenBridge protocol enables cross-chain token transfers through its payloads. Key payloads include:

  • Transfer - handles basic token transfer operations
  • TransferWithPayload - extends the Transfer payload to include custom data, enhancing functionality

Payloads are registered to the TokenBridge protocol via the PayloadLiteralToLayoutMapping interface, which links payload literals (e.g., TokenBridge:Transfer) to their layouts.

Additionally, the protocol uses reusable layouts like transferCommonLayout and extends them in more specialized layouts such as transferWithPayloadLayout:

export const transferWithPayloadLayout = <
  const P extends CustomizableBytes = undefined
>(
  customPayload?: P
) =>
  [
    payloadIdItem(3),
    ...transferCommonLayout,
    { name: 'from', ...universalAddressItem },
    customizableBytes({ name: 'payload' }, customPayload),
  ] as const;

This layout includes:

  • A payloadIdItem to identify the payload type
  • Common fields for token and recipient details
  • A customizable payload field for additional data

Use the Discriminator

To manage multiple payloads, the TokenBridge protocol utilizes a discriminator to distinguish between payload types dynamically. For example:

const tokenBridgePayloads = ['Transfer', 'TransferWithPayload'] as const;

export const getTransferDiscriminator = lazyInstantiate(() =>
  payloadDiscriminator([_protocol, tokenBridgePayloads])
);
  • The getTransferDiscriminator function dynamically evaluates payloads using predefined layouts
  • This ensures that each payload type is processed according to its unique structure and type-safe layout

Register Payloads to Protocols

Here’s how the TokenBridge protocol connects its payloads to the Wormhole SDK:

declare module '../../registry.js' {
  export namespace WormholeRegistry {
    interface PayloadLiteralToLayoutMapping
      extends RegisterPayloadTypes<
        'TokenBridge',
        typeof tokenBridgeNamedPayloads
      > {}
  }
}

registerPayloadTypes('TokenBridge', tokenBridgeNamedPayloads);

This registration links the TokenBridge payload literals to their respective layouts, enabling serialization and deserialization at runtime.

You can explore the complete TokenBridge protocol and payload definitions in the TokenBridge layout file.

Token Bridge Payloads

The following payloads are registered for the TokenBridge protocol:

  • AttestMeta - used for token metadata attestation
  • Transfer - facilitates token transfers
  • TransferWithPayload - adds a custom payload to token transfers

These payloads and their layouts are defined in the TokenBridge layout file.

Other Protocols: Native Token Transfers (NTT)

While this guide focuses on the TokenBridge protocol, other protocols, like NTT, follow a similar structure.

  • NTT manages the transfer of native tokens across chains
  • Payloads such as WormholeTransfer and WormholeTransferStandardRelayer are registered to the protocol using the same patterns for payload literals and layouts
  • The same mechanisms for type-safe registration and payload discriminators apply, ensuring reliability and extensibility

For more details, you can explore the NTT implementation in the SDK.

Got any questions?

Find out more