Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Martinh/protocols payloads #142

Merged
merged 55 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
40fe4e4
updated pages with ew page and directory
martin0995 Oct 15, 2024
8f5251a
created index for wh sdk
martin0995 Oct 15, 2024
76f57fa
first draft sdk layouts page
martin0995 Oct 15, 2024
5922c3b
reorg page structure
martin0995 Oct 23, 2024
442587d
grammarly check
martin0995 Oct 23, 2024
078bc7d
removed duplicated info
martin0995 Oct 23, 2024
ddf10cf
updated code snippets format
martin0995 Oct 23, 2024
e3e118a
added code snippets in separate folder
martin0995 Oct 23, 2024
9f602ce
Update build/toolkit/wormhole-sdk/sdk-layout.md
martin0995 Oct 24, 2024
07c852d
Update build/toolkit/wormhole-sdk/sdk-layout.md
martin0995 Oct 24, 2024
15aeaa8
Update build/toolkit/wormhole-sdk/sdk-layout.md
martin0995 Oct 24, 2024
46fc43f
Update build/toolkit/wormhole-sdk/sdk-layout.md
martin0995 Oct 24, 2024
4e75542
updated code based on feedback
martin0995 Oct 24, 2024
1c32dad
reorg pages and titles
martin0995 Oct 24, 2024
ff4783a
updated page structure/reorg
martin0995 Oct 24, 2024
1472bb2
updated page based on feedback
martin0995 Oct 31, 2024
346d834
Merge branch 'main' into martinh/sdk-layout
martin0995 Oct 31, 2024
f62dba9
moved layout snippets to different folder
martin0995 Nov 13, 2024
074146e
Update build/applications/wormhole-sdk/index.md
martin0995 Nov 13, 2024
a0a2166
Update build/applications/wormhole-sdk/sdk-layout.md
martin0995 Nov 13, 2024
6eb17b1
Update build/applications/wormhole-sdk/sdk-layout.md
martin0995 Nov 13, 2024
24c36dc
Update build/toolkit/wormhole-sdk/index.md
martin0995 Nov 13, 2024
f7f6478
updated subtitle
martin0995 Nov 13, 2024
867ccb5
updated data layouts based on feedback
martin0995 Nov 19, 2024
b45c4ff
updated vaa layout specifics
martin0995 Nov 20, 2024
bbe50fc
updated words based on feedback
martin0995 Nov 20, 2024
d293403
updated omitted fields section
martin0995 Nov 20, 2024
e555330
removed subsection Mismatched Types in Layouts
martin0995 Nov 20, 2024
227225e
updated strong types and nested layouts section
martin0995 Nov 20, 2024
da6a7a6
updated best practices
martin0995 Nov 20, 2024
aa5b8bc
added resources section
martin0995 Nov 20, 2024
6c9242a
grammarly check
martin0995 Nov 20, 2024
fc15c05
updated snippets
martin0995 Nov 20, 2024
5093377
Merge branch 'main' into martinh/sdk-layout
martin0995 Nov 20, 2024
384f4bb
create basic structure for protocols and payloads registration
martin0995 Nov 22, 2024
fb5d9d0
create code snippets for payloads and protocols registration page
martin0995 Nov 22, 2024
3fa62ec
grammarly check
martin0995 Nov 22, 2024
28b2e6c
removed repetead information
martin0995 Nov 22, 2024
c60c772
update titles and subtitles
martin0995 Nov 22, 2024
1f903aa
format update
martin0995 Nov 22, 2024
f5c7400
Update build/applications/wormhole-sdk/sdk-layout.md
martin0995 Nov 22, 2024
0be0d16
Update build/applications/wormhole-sdk/sdk-layout.md
martin0995 Nov 22, 2024
e59db9f
Update build/applications/wormhole-sdk/sdk-layout.md
martin0995 Nov 22, 2024
40c7dd5
Update build/applications/wormhole-sdk/sdk-layout.md
martin0995 Nov 22, 2024
e1aa180
Update build/applications/wormhole-sdk/sdk-layout.md
martin0995 Nov 22, 2024
4758bee
Merge branch 'martinh/sdk-layout' into martinh/protocols-payloads
martin0995 Nov 22, 2024
cebe19e
update page with new standalone layout package
martin0995 Nov 22, 2024
f9f7dff
updated section based on feedback
martin0995 Nov 27, 2024
5e2eada
removed redundant section
martin0995 Nov 27, 2024
8417ef8
Merge branch 'main' into martinh/sdk-layout
martin0995 Nov 27, 2024
1709b13
Merge branch 'martinh/sdk-layout' into martinh/protocols-payloads
martin0995 Nov 27, 2024
a62cc71
Merge branch 'main' into martinh/protocols-payloads
martin0995 Nov 27, 2024
112d4c6
updated names based on feedback
martin0995 Nov 27, 2024
42bc5a6
Merge branch 'martinh/protocols-payloads' of https://github.com/wormh…
martin0995 Nov 27, 2024
bfdf69d
Merge branch 'main' into martinh/protocols-payloads
martin0995 Nov 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
declare module '../../registry.js' {
export namespace WormholeRegistry {
interface ProtocolToInterfaceMapping<N, C> {
TokenBridge: TokenBridge<N, C>;
}
interface ProtocolToPlatformMapping {
TokenBridge: EmptyPlatformMap<'TokenBridge'>;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
declare module '@wormhole-foundation/sdk-connect' {
export namespace WormholeRegistry {
interface PlatformToNativeAddressMapping {
Evm: EvmAddress;
}
}
}

registerNative(_platform, EvmAddress);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const transferWithPayloadLayout = <
const P extends CustomizableBytes = undefined
>(
customPayload?: P
) =>
[
payloadIdItem(3),
...transferCommonLayout,
{ name: 'from', ...universalAddressItem },
customizableBytes({ name: 'payload' }, customPayload),
] as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const transferWithPayloadLayout = <
const P extends CustomizableBytes = undefined
>(
customPayload?: P
) =>
[
payloadIdItem(3),
...transferCommonLayout,
{ name: 'from', ...universalAddressItem },
customizableBytes({ name: 'payload' }, customPayload),
] as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const tokenBridgePayloads = ['Transfer', 'TransferWithPayload'] as const;

export const getTransferDiscriminator = lazyInstantiate(() =>
payloadDiscriminator([_protocol, tokenBridgePayloads])
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
declare module '../../registry.js' {
export namespace WormholeRegistry {
interface PayloadLiteralToLayoutMapping
extends RegisterPayloadTypes<
'TokenBridge',
typeof tokenBridgeNamedPayloads
> {}
}
}

registerPayloadTypes('TokenBridge', tokenBridgeNamedPayloads);
2 changes: 1 addition & 1 deletion build/applications/wormhole-sdk/.pages
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ nav:
- index.md
- 'TypeScript SDK' : 'wormhole-sdk.md'
- 'Data Layouts': 'sdk-layout.md'

- 'Building Protocols and Payloads': 'protocols-payloads.md'
232 changes: 232 additions & 0 deletions build/applications/wormhole-sdk/protocols-payloads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
---
title: Building Protocols and Payloads
description: Learn how to build, register, and integrate protocols and payloads in the Wormhole TypeScript SDK with type-safe layouts
---

# Building Protocols and Payloads

## Introduction

The [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank} 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:

```typescript
--8<-- "code/build/applications/wormhole-sdk/protocols-payloads/pl-1.ts"
```

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](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L14-L70){target=\_blank}.

#### Platform-Specific Protocols

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

```typescript
--8<-- "code/build/applications/wormhole-sdk/protocols-payloads/pl-2.ts"
```

This ensures that `EvmAddress` is registered as the native address type for EVM-compatible platforms. See the [EVM platform address file](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/platforms/evm/src/address.ts#L98-L106){target=\_blank} for details.

### Register Payloads

[Payload registration](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/dbbbc7c365db602dd3b534f6d615ac80c3d2aaf1/core/definitions/src/vaa/registration.ts){target=\_balnk} 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:

```typescript
--8<-- "code/build/applications/wormhole-sdk/protocols-payloads/pl-3.ts"
```

2. **Register payloads** - use `registerPayloadTypes` to map payload literals to their layouts:

```typescript
registerPayloadTypes("ProtocolName", protocolNamedPayloads)
```

3. **Access registered payloads** - use the [`getPayloadLayout`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/9105de290c91babbf8ad031bd89cc75ee38739c8/core/definitions/src/vaa/functions.ts#L19-L23){target=\_blank} function to fetch the layout for a specific payload literal. This method ensures that the correct layout is retrieved dynamically and safely:

```typescript
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:

```typescript
--8<-- "code/build/applications/wormhole-sdk/protocols-payloads/pl-4.ts"
```

- The `payloadFactory` ensures each payload literal maps to its layout uniquely
- The [`registerPayloadType`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/dbbbc7c365db602dd3b534f6d615ac80c3d2aaf1/core/definitions/src/vaa/registration.ts#L46-L52){target=\_blank} function adds individual payloads, while [`registerPayloadTypes`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/dbbbc7c365db602dd3b534f6d615ac80c3d2aaf1/core/definitions/src/vaa/registration.ts#L62-L64){target=\_blank} supports bulk registration

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](/docs/build/applications/wormhole-sdk/sdk-layout/){target=\_blank}.

### 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:

```typescript
--8<-- "code/build/applications/wormhole-sdk/protocols-payloads/pl-5.ts"
```

- [`layoutDiscriminator`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/9105de290c91babbf8ad031bd89cc75ee38739c8/core/base/src/utils/layout.ts#L16){target=\_blank} 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: TokenBridge 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`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/76b20317b0f68e823d4e6c4a2e41bb2a7705c64f/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts#L29C7-L47){target=\_blank} and extends them in more specialized layouts such as [`transferWithPayloadLayout`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/76b20317b0f68e823d4e6c4a2e41bb2a7705c64f/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts#L49-L57){target=\_blank}:

```typescript
--8<-- "code/build/applications/wormhole-sdk/protocols-payloads/pl-6.ts"
```

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:

```typescript
--8<-- "code/build/applications/wormhole-sdk/protocols-payloads/pl-7.ts"
```

- The [`getTransferDiscriminator`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/dbbbc7c365db602dd3b534f6d615ac80c3d2aaf1/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L67-L70){target=\_blank} 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:

```typescript
--8<-- "code/build/applications/wormhole-sdk/protocols-payloads/pl-8.ts"
```

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](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts){target=\_blank}.

#### 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](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts){target=\_blank}.

### 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](https://github.com/wormhole-foundation/example-native-token-transfers/blob/00f83aa215338b1b8fd66f522bd0f45be3e98a5a/sdk/definitions/src/ntt.ts){target=\_blank}.