isVAAConsumed

### isVAAConsumed

Returns whether a VAA has been consumed. *(Defined in [WormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol){target=\_blank})*

```sol
function isVAAConsumed(bytes32 hash) public view returns (bool)
```

??? interface "Parameters"

    `hash` ++"bytes32"++

    The hash of the VAA.

??? interface "Returns"

    `consumed` ++"bool"++

    Whether the VAA has been consumed.


Native Token Transfers Transceivers Contracts (EVM)

isWormholeEvmChain

### isWormholeEvmChain

Returns whether a chain is EVM compatible. *(Defined in [WormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol){target=\_blank})*

```sol
function isWormholeEvmChain(uint16 chainId) public view returns (bool)
```

??? interface "Parameters"

    `chainId` ++"uint16"++

    The chain ID to query.

??? interface "Returns"

    `isEvm` ++"bool"++

    Whether the chain is EVM compatible.


isWormholeRelayingEnabled

### isWormholeRelayingEnabled

Returns whether relaying is enabled for a given chain. *(Defined in [WormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol){target=\_blank})*

```sol
function isWormholeRelayingEnabled(uint16 chainId) public view returns (bool)
```

??? interface "Parameters"

    `chainId` ++"uint16"++

    The chain ID to query.

??? interface "Returns"

    `isEnabled` ++"bool"++

    Whether relaying is enabled.


migrate

### migrate

Migrates the contract to a new implementation. Only callable during upgrades through a delegate call. *(Defined in [Implementation.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/libraries/Implementation.sol){target=\_blank})*

```sol
function migrate() external
```


parseWormholeTransceiverInstruction

### parseWormholeTransceiverInstruction

Parses the encoded instruction and returns the instruction struct. *(Defined in [WormholeTransceiver.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol){target=\_blank})*

```sol
function parseWormholeTransceiverInstruction(
    bytes memory encoded
) public pure returns (WormholeTransceiverInstruction memory instruction)
```

??? interface "Parameters"

    `encoded` ++"bytes"++

    The encoded instruction.

??? interface "Returns"

    `instruction` ++"WormholeTransceiverInstruction"++

    The parsed `WormholeTransceiverInstruction`.

    ??? child "`WormholeTransceiverInstruction` type"

        `shouldSkipRelayerSend` ++"bool"++

        Whether to skip delivery via the relayer.


quoteDeliveryPrice

### quoteDeliveryPrice

Fetches the delivery price for a given recipient chain transfer. *(Defined in [Transceiver.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/Transceiver.sol){target=\_blank})*

```sol
function quoteDeliveryPrice(
    uint16 recipientChain,
    TransceiverStructs.TransceiverInstruction memory instruction
) external view returns (uint256)
```

??? interface "Parameters"

    `recipientChain` ++"uint16"++

    The Wormhole chain ID of the target chain.

    ---

    `instruction` ++"TransceiverStructs.TransceiverInstruction"++

    An additional Instruction provided by the Transceiver to be executed on the recipient chain.

    ??? child "`TransceiverInstruction` type"

        `index` ++"uint8"++

        The index of the transceiver.

        ---

        `payload` ++"bytes"++

        The instruction payload.

??? interface "Returns"

    `deliveryPrice` ++"uint256"++

    The cost of delivering a message to the recipient chain, in this chain's native token.


owner

### owner

Returns the address of the current owner. *(Defined in [OwnableUpgradeable.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/libraries/external/OwnableUpgradeable.sol){target=\_blank})*

```sol
function owner() public view returns (address)
```

??? interface "Returns"

    `owner` ++"address"++

    The address of the current owner.


pauser

### pauser

Returns the address of the current pauser. *(Defined in [PausableUpgradeable.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/libraries/PausableUpgradeable.sol){target=\_blank})*

```sol
function pauser() public view returns (address)
```

??? interface "Returns"

    `pauser` ++"address"++

    The address of the current pauser.


receiveMessage

### receiveMessage

Receives an attested message from the verification layer. *(Defined in [WormholeTransceiver.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol){target=\_blank})*

```sol
function receiveMessage(bytes memory encodedMessage) external
```

??? interface "Parameters"

    `encodedMessage` ++"bytes"++

    The attested message.

> **Emits**: `ReceivedMessage`


receiveWormholeMessages

### receiveWormholeMessages

Receives and processes Wormhole messages via the relayer. Only callable by the relayer. *(Defined in [WormholeTransceiver.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol){target=\_blank})*

```sol
function receiveWormholeMessages(
    bytes memory payload,
    bytes[] memory additionalMessages,
    bytes32 sourceAddress,
    uint16 sourceChain,
    bytes32 deliveryHash
) external payable
```

??? interface "Parameters"

    `payload` ++"bytes"++

    The message payload.

    ---

    `additionalMessages` ++"bytes[]"++

    Additional messages array.

    ---

    `sourceAddress` ++"bytes32"++

    The source address of the message.

    ---

    `sourceChain` ++"uint16"++

    The source chain ID.

    ---

    `deliveryHash` ++"bytes32"++

    The delivery hash.

> **Emits**: `ReceivedRelayedMessage`


sendMessage

### sendMessage

Sends a message to another chain. *(Defined in [Transceiver.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/Transceiver.sol){target=\_blank})*

```sol
function sendMessage(
    uint16 recipientChain,
    TransceiverStructs.TransceiverInstruction memory instruction,
    bytes memory nttManagerMessage,
    bytes32 recipientNttManagerAddress,
    bytes32 refundAddress
) external payable
```

??? interface "Parameters"

    `recipientChain` ++"uint16"++

    The Wormhole chain ID of the recipient.

    ---

    `instruction` ++"TransceiverStructs.TransceiverInstruction"++

    An additional Instruction provided by the Transceiver to be executed on the recipient chain.

    ??? child "`TransceiverInstruction` type"

        `index` ++"uint8"++

        The index of the transceiver.

        ---

        `payload` ++"bytes"++

        The instruction payload.

    ---

    `nttManagerMessage` ++"bytes"++

    A message to be sent to the nttManager on the recipient chain.

    ---

    `recipientNttManagerAddress` ++"bytes32"++

    The Wormhole formatted address of the peer NTT Manager on the recipient chain.

    ---

    `refundAddress` ++"bytes32"++

    The Wormhole formatted address of the refund recipient.

> **Emits**: `SendTransceiverMessage`, `RelayingInfo`


setIsSpecialRelayingEnabled

### setIsSpecialRelayingEnabled

Sets whether special relaying is enabled for the given chain. *(Defined in [WormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol){target=\_blank})*

```sol
function setIsSpecialRelayingEnabled(uint16 chainId, bool isRelayingEnabled) external
```

??? interface "Parameters"

    `chainId` ++"uint16"++

    The Wormhole chain ID to set.

    ---

    `isRelayingEnabled` ++"bool"++

    A boolean indicating whether special relaying is enabled.

> **Emits**: `SetIsSpecialRelayingEnabled`


setIsWormholeEvmChain

### setIsWormholeEvmChain

Sets whether the chain is EVM compatible. *(Defined in [WormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol){target=\_blank})*

```sol
function setIsWormholeEvmChain(uint16 chainId, bool isEvm) external
```

??? interface "Parameters"

    `chainId` ++"uint16"++

    The Wormhole chain ID to set.

    ---

    `isEvm` ++"bool"++

    A boolean indicating whether the chain is an EVM chain.

> **Emits**: `SetIsWormholeEvmChain`


setIsWormholeRelayingEnabled

### setIsWormholeRelayingEnabled

Sets whether Wormhole relaying is enabled for the given chain. *(Defined in [WormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol){target=\_blank})*

```sol
function setIsWormholeRelayingEnabled(uint16 chainId, bool isRelayingEnabled) external
```

??? interface "Parameters"

    `chainId` ++"uint16"++

    The Wormhole chain ID to set.

    ---

    `isRelayingEnabled` ++"bool"++

    A boolean indicating whether relaying is enabled.

> **Emits**: `SetIsWormholeRelayingEnabled`


setWormholePeer

### setWormholePeer

Sets the Wormhole peer contract for the given chain. *(Defined in [WormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol){target=\_blank})*

```sol
function setWormholePeer(uint16 chainId, bytes32 peerContract) external payable
```

??? interface "Parameters"

    `chainId` ++"uint16"++

    The Wormhole chain ID of the peer to set.

    ---

    `peerContract` ++"bytes32"++

    The address of the peer contract on the given chain.

> **Emits**: `SetWormholePeer`


transferOwnership

### transferOwnership

Transfers ownership of the contract to a new account. Can only be called by the current owner. *(Defined in [OwnableUpgradeable.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/libraries/external/OwnableUpgradeable.sol){target=\_blank})*

```sol
function transferOwnership(address newOwner) public
```

??? interface "Parameters"

    `newOwner` ++"address"++

    The address of the new owner.

> **Emits**: `OwnershipTransferred`


transferPauserCapability

### transferPauserCapability

Transfers the ability to pause to a new account. *(Defined in [PausableOwnable.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/libraries/PausableOwnable.sol){target=\_blank})*

```sol
function transferPauserCapability(address newPauser) public
```

??? interface "Parameters"

    `newPauser` ++"address"++

    The address of the new pauser.

> **Emits**: `PauserTransferred`


transferTransceiverOwnership

### transferTransceiverOwnership

Transfers the ownership of the transceiver to a new address. *(Defined in [Transceiver.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/Transceiver.sol){target=\_blank})*

```sol
function transferTransceiverOwnership(address newOwner) external
```

??? interface "Parameters"

    `newOwner` ++"address"++

    The address of the new owner.

> **Emits**: `OwnershipTransferred`


upgrade

### upgrade

Upgrades the transceiver to a new implementation. *(Defined in [Transceiver.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/Transceiver/Transceiver.sol){target=\_blank})*

```sol
function upgrade(address newImplementation) external
```

??? interface "Parameters"

    `newImplementation` ++"address"++

    The address of the new implementation contract.


Errors

## Errors


CallerNotNttManager

### CallerNotNttManager

The caller is not the NttManager. *(Defined in [ITransceiver.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/ITransceiver.sol){target=\_blank})*

```sol
error CallerNotNttManager(address caller);
```

??? interface "Parameters"

    `caller` ++"address"++

    The address of the caller.


CallerNotRelayer

### CallerNotRelayer

The caller is not the relayer. *(Defined in [IWormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/IWormholeTransceiverState.sol){target=\_blank})*

```sol
error CallerNotRelayer(address caller);
```

??? interface "Parameters"

    `caller` ++"address"++

    The caller.


CannotRenounceTransceiverOwnership

### CannotRenounceTransceiverOwnership

Error when trying renounce transceiver ownership. *(Defined in [ITransceiver.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/ITransceiver.sol){target=\_blank})*

```sol
error CannotRenounceTransceiverOwnership(address currentOwner);
```

??? interface "Parameters"

    `currentOwner` ++"address"++

    The current owner of the transceiver.


CannotTransferTransceiverOwnership

### CannotTransferTransceiverOwnership

Error when trying to transfer transceiver ownership. *(Defined in [ITransceiver.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/ITransceiver.sol){target=\_blank})*

```sol
error CannotTransferTransceiverOwnership(address currentOwner, address newOwner);
```

??? interface "Parameters"

    `currentOwner` ++"address"++

    The current owner of the transceiver.

    ---

    `newOwner` ++"address"++

    The new owner of the transceiver.


InvalidPauser

### InvalidPauser

The pauser is not a valid pauser account. *(Defined in [PausableUpgradeable.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/libraries/PausableUpgradeable.sol){target=\_blank})*

```sol
error InvalidPauser(address account);
```

??? interface "Parameters"

    `account` ++"address"++

    The invalid pauser account.


InvalidRelayingConfig

### InvalidRelayingConfig

Error when the relaying configuration is invalid. *(Defined in [IWormholeTransceiver.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/IWormholeTransceiver.sol){target=\_blank})*

```sol
error InvalidRelayingConfig(uint16 chainId);
```

??? interface "Parameters"

    `chainId` ++"uint16"++

    The chain ID that is invalid.


InvalidVaa

### InvalidVaa

Error if the VAA is invalid. *(Defined in [IWormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/IWormholeTransceiverState.sol){target=\_blank})*

```sol
error InvalidVaa(string reason);
```

??? interface "Parameters"

    `reason` ++"string"++

    The reason the VAA is invalid.


InvalidWormholeChainIdZero

### InvalidWormholeChainIdZero

The chain ID cannot be zero. *(Defined in [IWormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/IWormholeTransceiverState.sol){target=\_blank})*

```sol
error InvalidWormholeChainIdZero();
```


InvalidWormholePeer

### InvalidWormholePeer

Error when the peer transceiver is invalid. *(Defined in [IWormholeTransceiver.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/IWormholeTransceiver.sol){target=\_blank})*

```sol
error InvalidWormholePeer(uint16 chainId, bytes32 peerAddress);
```

??? interface "Parameters"

    `chainId` ++"uint16"++

    The chain ID of the peer.

    ---

    `peerAddress` ++"bytes32"++

    The address of the invalid peer.


InvalidWormholePeerZeroAddress

### InvalidWormholePeerZeroAddress

Error the peer contract cannot be the zero address. *(Defined in [IWormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/IWormholeTransceiverState.sol){target=\_blank})*

```sol
error InvalidWormholePeerZeroAddress();
```


NotMigrating

### NotMigrating

The contract is not currently migrating. *(Defined in [Implementation.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/libraries/Implementation.sol){target=\_blank})*

```sol
error NotMigrating();
```


OnlyDelegateCall

### OnlyDelegateCall

Function can only be called through delegate call. *(Defined in [Implementation.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/libraries/Implementation.sol){target=\_blank})*

```sol
error OnlyDelegateCall();
```


OwnableInvalidOwner

### OwnableInvalidOwner

The owner is not a valid owner account. *(Defined in [OwnableUpgradeable.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/libraries/external/OwnableUpgradeable.sol){target=\_blank})*

```sol
error OwnableInvalidOwner(address owner);
```

??? interface "Parameters"

    `owner` ++"address"++

    The invalid owner address.


OwnableUnauthorizedAccount

### OwnableUnauthorizedAccount

The caller account is not authorized to perform an operation. *(Defined in [OwnableUpgradeable.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/libraries/external/OwnableUpgradeable.sol){target=\_blank})*

```sol
error OwnableUnauthorizedAccount(address account);
```

??? interface "Parameters"

    `account` ++"address"++

    The unauthorized account.


RequireContractIsNotPaused

### RequireContractIsNotPaused

Contract is not paused, functionality is unblocked. *(Defined in [PausableUpgradeable.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/libraries/PausableUpgradeable.sol){target=\_blank})*

```sol
error RequireContractIsNotPaused();
```


RequireContractIsPaused

### RequireContractIsPaused

Contract state is paused, blocking functionality. *(Defined in [PausableUpgradeable.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/libraries/PausableUpgradeable.sol){target=\_blank})*

```sol
error RequireContractIsPaused();
```


PeerAlreadySet

### PeerAlreadySet

Error if the peer has already been set. *(Defined in [IWormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/IWormholeTransceiverState.sol){target=\_blank})*

```sol
error PeerAlreadySet(uint16 chainId, bytes32 peerAddress);
```

??? interface "Parameters"

    `chainId` ++"uint16"++

    The chain ID of the peer.

    ---

    `peerAddress` ++"bytes32"++

    The address of the peer.


UnexpectedAdditionalMessages

### UnexpectedAdditionalMessages

Additional messages are not allowed. *(Defined in [IWormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/IWormholeTransceiverState.sol){target=\_blank})*

```sol
error UnexpectedAdditionalMessages();
```


TransferAlreadyCompleted

### TransferAlreadyCompleted

The transfer has already been completed. *(Defined in [IWormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/IWormholeTransceiverState.sol){target=\_blank})*

```sol
error TransferAlreadyCompleted(bytes32 digest);
```

??? interface "Parameters"

    `digest` ++"bytes32"++

    The digest of the completed transfer message.


UnexpectedRecipientNttManagerAddress

### UnexpectedRecipientNttManagerAddress

The recipient NTT Manager address in the message does not match this transceiver’s NTT Manager. *(Defined in [IWormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/IWormholeTransceiverState.sol){target=\_blank})*

```sol
error UnexpectedRecipientNttManagerAddress(bytes32 recipientNttManagerAddress);
```

??? interface "Parameters"

    `recipientNttManagerAddress` ++"bytes32"++

    The unexpected NTT Manager address from the message.


InvalidFork

### InvalidFork

The current EVM chain ID does not match the stored chain ID, indicating a possible fork. *(Defined in [IWormholeTransceiverState.sol](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/IWormholeTransceiverState.sol){target=\_blank})*

```sol
error InvalidFork(uint256 expectedChainId, uint256 actualChainId);
```

??? interface "Parameters"

    `expectedChainId` ++"uint256"++

    The chain ID stored at deployment.

    ---

    `actualChainId` ++"uint256"++

    The chain ID returned by the current network.


Native Token Transfers Transceiver Program (Solana)

Structure Overview

## Structure Overview

The NTT Transceiver system on Solana is implemented as a standalone Anchor program that provides Wormhole-based message verification and relay capabilities. The transceiver acts as a bridge between the NTT Manager and the Wormhole protocol, handling cross-chain message transmission and verification.

```text
NTT Transceiver Program
├── Wormhole Integration
│   ├── Message Transmission
│   ├── Message Reception & Verification
│   ├── Peer Management
│   └── Broadcasting Capabilities
├── Admin Functions
└── Message Processing
```

**Key Components:**

- **NTT Transceiver Program**: Transmits, receives, and verifies NTT messages between chains, integrating with the Wormhole messaging layer.
- **Wormhole Integration**: Enables native message transmission, reception, and verification using the Wormhole protocol.
- **Administrative Functions**: Provides interfaces for setting up peer configurations and managing broadcast behavior.
- **Message Processing**: Automatically processes inbound and outbound messages and forwards valid messages to the NTT Manager.


State Accounts

## State Accounts

`TransceiverPeer` ++"account (PDA: 'transceiver_peer')"++: Per-chain peer entry for the Wormhole transceiver path; stores the peer transceiver `address` (wormhole-formatted `[u8; 32]`) and `bump`.


Instructions

## Instructions


broadcast_wormhole_id

### broadcast_wormhole_id

Broadcasts the transceiver ID via Wormhole to announce presence on the network. *(Defined in ntt-transceiver)*

```rust
pub fn broadcast_wormhole_id(ctx: Context<BroadcastId>) -> Result<()>
```

??? interface "Accounts"

    `payer` ++"mut Signer"++

    The account paying for the broadcast transaction.

    ---

    `config` ++"Account<Config>"++

    The NTT Manager configuration account.

    ---

    `mint` ++"InterfaceAccount<Mint>"++

    The mint account for the managed token.

    ---

    `wormhole_bridge` ++"mut Account<BridgeData>"++

    The Wormhole bridge data account.

    ---

    `wormhole_message` ++"mut Signer"++

    The Wormhole message account to create.

    ---

    `wormhole_emitter` ++"Account<EmitterData>"++

    The Wormhole emitter account.

    ---

    `wormhole_sequence` ++"mut Account<SequenceData>"++

    The Wormhole sequence tracking account.

    ---

    `wormhole_fee_collector` ++"mut Account<FeeCollectorData>"++

    The Wormhole fee collector account.

    ---

    `clock` ++"Sysvar<Clock>"++

    The clock sysvar.

    ---

    `rent` ++"Sysvar<Rent>"++

    The rent sysvar.

    ---

    `system_program` ++"Program<System>"++

    The system program.

    ---

    `ntt_program` ++"Program<NttProgram>"++

    The NTT Manager program.

    ---

    `wormhole_program` ++"Program<WormholeProgram>"++

    The Wormhole core bridge program.


broadcast_wormhole_peer

### broadcast_wormhole_peer

Broadcasts peer transceiver information via Wormhole. *(Defined in ntt-transceiver)*

```rust
pub fn broadcast_wormhole_peer(
    ctx: Context<BroadcastPeer>,
    args: BroadcastPeerArgs
) -> Result<()>
```

??? interface "Parameters"

    `args` ++"BroadcastPeerArgs"++

    The broadcast peer arguments.

    ??? child "`BroadcastPeerArgs` type"

        `chain_id` ++"ChainId"++

        The chain ID to broadcast peer information for.

??? interface "Accounts"

    `payer` ++"mut Signer"++

    The account paying for the broadcast transaction.

    ---

    `config` ++"Account<Config>"++

    The NTT Manager configuration account.

    ---

    `peer` ++"Account<TransceiverPeer>"++

    The peer transceiver account containing peer information.

    ---

    `wormhole_bridge` ++"mut Account<BridgeData>"++

    The Wormhole bridge data account.

    ---

    `wormhole_message` ++"mut Signer"++

    The Wormhole message account to create.

    ---

    `wormhole_emitter` ++"Account<EmitterData>"++

    The Wormhole emitter account.

    ---

    `wormhole_sequence` ++"mut Account<SequenceData>"++

    The Wormhole sequence tracking account.

    ---

    `wormhole_fee_collector` ++"mut Account<FeeCollectorData>"++

    The Wormhole fee collector account.

    ---

    `clock` ++"Sysvar<Clock>"++

    The clock sysvar.

    ---

    `rent` ++"Sysvar<Rent>"++

    The rent sysvar.

    ---

    `system_program` ++"Program<System>"++

    The system program.

    ---

    `ntt_program` ++"Program<NttProgram>"++

    The NTT Manager program.

    ---

    `wormhole_program` ++"Program<WormholeProgram>"++

    The Wormhole core bridge program.


receive_wormhole_message

### receive_wormhole_message

Receives and processes an inbound message from Wormhole. *(Defined in ntt-transceiver)*

```rust
pub fn receive_wormhole_message(ctx: Context<ReceiveMessage>) -> Result<()>
```

??? interface "Accounts"

    `payer` ++"mut Signer"++

    The account paying for message processing.

    ---

    `config` ++"mut Account<Config>"++

    The NTT Manager configuration account.

    ---

    `peer` ++"Account<TransceiverPeer>"++

    The peer transceiver account for verification.

    ---

    `vaa` ++"Account<PostedVaa<TransceiverMessage>>"++

    The verified VAA (Verifiable Action Approval) containing the message.

    ---

    `transceiver_message` ++"mut UncheckedAccount"++

    The transceiver message account to create.

    ---

    `ntt_program` ++"Program<NttProgram>"++

    The NTT Manager program.

    ---

    `system_program` ++"Program<System>"++

    The system program.


release_wormhole_outbound

### release_wormhole_outbound

Releases an outbound message via Wormhole. *(Defined in ntt-transceiver)*

```rust
pub fn release_wormhole_outbound(
    ctx: Context<ReleaseOutbound>,
    args: ReleaseOutboundArgs
) -> Result<()>
```

??? interface "Parameters"

    `args` ++"ReleaseOutboundArgs"++

    The release outbound arguments.

    ??? child "`ReleaseOutboundArgs` type"

        `recipient_chain` ++"ChainId"++

        The chain ID of the recipient chain.

??? interface "Accounts"

    `payer` ++"mut Signer"++

    The account paying for the release transaction.

    ---

    `config` ++"Account<Config>"++

    The NTT Manager configuration account.

    ---

    `outbox_item` ++"mut Account<OutboxItem>"++

    The outbox item to be released.

    ---

    `registered_transceiver` ++"Account<RegisteredTransceiver>"++

    The registered transceiver account.

    ---

    `transceiver_message` ++"mut UncheckedAccount"++

    The transceiver message account to create.

    ---

    `wormhole_bridge` ++"mut Account<BridgeData>"++

    The Wormhole bridge data account.

    ---

    `wormhole_message` ++"mut Signer"++

    The Wormhole message account to create.

    ---

    `wormhole_emitter` ++"Account<EmitterData>"++

    The Wormhole emitter account.

    ---

    `wormhole_sequence` ++"mut Account<SequenceData>"++

    The Wormhole sequence tracking account.

    ---

    `wormhole_fee_collector` ++"mut Account<FeeCollectorData>"++

    The Wormhole fee collector account.

    ---

    `clock` ++"Sysvar<Clock>"++

    The clock sysvar.

    ---

    `rent` ++"Sysvar<Rent>"++

    The rent sysvar.

    ---

    `system_program` ++"Program<System>"++

    The system program.

    ---

    `ntt_program` ++"Program<NttProgram>"++

    The NTT Manager program.

    ---

    `wormhole_program` ++"Program<WormholeProgram>"++

    The Wormhole core bridge program.


set_wormhole_peer

### set_wormhole_peer

Sets or updates a peer transceiver on another chain. *(Defined in ntt-transceiver)*

```rust
pub fn set_wormhole_peer(
    ctx: Context<SetTransceiverPeer>,
    args: SetTransceiverPeerArgs
) -> Result<()>
```

??? interface "Parameters"

    `args` ++"SetTransceiverPeerArgs"++

    The transceiver peer arguments.

    ??? child "`SetTransceiverPeerArgs` type"

        `chain_id` ++"ChainId"++

        The chain ID of the peer.

        ---

        `address` ++"[u8; 32]"++

        The address of the peer transceiver.

??? interface "Accounts"

    `payer` ++"mut Signer"++

    The account paying for peer configuration.

    ---

    `owner` ++"Signer"++

    The owner of the NTT Manager (must authorize peer changes).

    ---

    `config` ++"Account<Config>"++

    The NTT Manager configuration account.

    ---

    `peer` ++"mut Account<TransceiverPeer>"++

    The peer account to create or update.

    ---

    `system_program` ++"Program<System>"++

    The system program.

    ---

    `ntt_program` ++"Program<NttProgram>"++

    The NTT Manager program.


transceiver_type

### transceiver_type

Returns the type identifier for this transceiver. *(Defined in ntt-transceiver)*

```rust
pub fn transceiver_type(_ctx: Context<TransceiverType>) -> Result<String>
```

??? interface "Returns"

    `transceiver_type` ++"String"++

    The transceiver type identifier ("wormhole").

??? interface "Accounts"

    No accounts required (empty context).


Data Structures

## Data Structures


TransceiverPeer

### TransceiverPeer

Stores information about a peer transceiver on another chain. *(Defined in peer.rs)*

```rust
pub struct TransceiverPeer {
    pub bump: u8,
    pub address: [u8; 32],
}
```

??? interface "Fields"

    bump ++"u8"++

    The canonical bump for the peer account.

    ---

    `address` ++"[u8; 32]"++

    The wormhole-formatted address of the peer transceiver.


TransceiverMessage

### TransceiverMessage

The message format used for cross-chain communication. *(Defined in messages.rs)*

```rust
pub struct TransceiverMessage<P> {
    pub source_ntt_manager: [u8; 32],
    pub recipient_ntt_manager: [u8; 32],
    pub ntt_manager_payload: P,
    pub transceiver_payload: Vec<u8>,
}
```

??? interface "Fields"

    `source_ntt_manager` ++"[u8; 32]"++

    The address of the source NTT Manager.

    ---

    `recipient_ntt_manager` ++"[u8; 32]"++

    The address of the recipient NTT Manager.

    ---

    `ntt_manager_payload` ++"P"++

    The payload specific to the NTT Manager.

    ---

    `transceiver_payload` ++"Vec<u8>"++

    Additional payload specific to the transceiver.


ChainId

### ChainId

A Wormhole chain identifier. *(Defined in ntt-messages)*

```rust
pub struct ChainId {
    pub id: u16,
}
```

??? interface "Fields"

    `id` ++"u16"++

    The numeric chain ID.


BroadcastPeerArgs

### BroadcastPeerArgs

Arguments for broadcasting peer information. *(Defined in broadcast_peer.rs)*

```rust
pub struct BroadcastPeerArgs {
    pub chain_id: ChainId,
}
```

??? interface "Fields"

    `chain_id` ++"ChainId"++

    The chain ID to broadcast peer information for.


ReleaseOutboundArgs

### ReleaseOutboundArgs

Arguments for releasing outbound messages. *(Defined in release_outbound.rs)*

```rust
pub struct ReleaseOutboundArgs {
    pub recipient_chain: ChainId,
}
```

??? interface "Fields"

    `recipient_chain` ++"ChainId"++

    The chain ID of the recipient chain.


SetTransceiverPeerArgs

### SetTransceiverPeerArgs

Arguments for setting transceiver peers. *(Defined in admin.rs)*

```rust
pub struct SetTransceiverPeerArgs {
    pub chain_id: ChainId,
    pub address: [u8; 32],
}
```

??? interface "Fields"

    `chain_id` ++"ChainId"++

    The chain ID of the peer.

    ---

    `address` ++"[u8; 32]"++

    The address of the peer transceiver.


Constants

## Constants


TRANSCEIVER_TYPE

### TRANSCEIVER_TYPE

The type identifier for this transceiver implementation. *(Defined in lib.rs)*

```rust
pub const TRANSCEIVER_TYPE: &str = "wormhole";
```


Token Transfers Overview

## Token Transfers Overview

Wormhole Token Transfers let you move assets seamlessly across chains. Developers can choose between [Native Token Transfers (NTT)](/docs/products/token-transfers/native-token-transfers/overview/){target=\_blank}, which enable direct movement of native tokens, or [Wrapped Token Transfers (WTT)](/docs/products/token-transfers/wrapped-token-transfers/overview/){target=\_blank}, which use a lock-and-mint model for broad compatibility. Both approaches are secured by the Wormhole [Guardians](/docs/protocol/infrastructure/guardians/){target=\_blank} and integrate with the same cross-chain messaging layer.


How Token Transfers Work

## How Token Transfers Work

Both NTT and WTT rely on Guardian-signed messages ([VAAs](/docs/protocol/infrastructure/vaas/){target=\_blank}) to transfer tokens across chains securely. The difference lies in how tokens are represented on the destination chain.

At a high level, the flow looks like this:

1. A user sends tokens to the Wormhole contract on the source chain.
2. The contract emits a message, which the Guardians sign as a VAA.
3. The VAA is submitted to the destination chain.
4. Depending on the transfer type:
    - **NTT**: Tokens are minted or released from escrow.
    - **WTT**: Wrapped tokens are minted to the recipient’s wallet.

```mermaid
flowchart LR
    A[User] --> B[Source chain<br/>Wormhole contract]
    B --> C[Guardians<br/>sign VAA]
    C --> D[Destination chain<br/>Wormhole contract]
    D -->|NTT| E[Mint or release<br/>native tokens]
    D -->|WTT| F[Mint wrapped<br/>tokens]
    E --> G[Recipient]
    F --> G[Recipient]
```


Choosing Between NTT and WTT

## Choosing Between NTT and WTT

Wormhole provides two distinct mechanisms for transferring assets cross-chain: [Native Token Transfers (NTT)](/docs/products/token-transfers/native-token-transfers/overview/){target=\_blank} and [Wrapped Token Transfers (WTT)](/docs/products/token-transfers/wrapped-token-transfers/overview/){target=\_blank}. Both options offer distinct integration paths and feature sets tailored to your requirements, as outlined below.

Choosing between the two models comes down to trade-offs. NTT offers an adaptable, upgradable, and customizable framework that enables teams to retain ownership and define policies across chains. WTT provides the most straightforward and permissionless path, but wrapped token contracts are managed by Wormhole Governance, with no ownership transfer or contract upgradeability possible.

| Feature                | Native Token Transfers                                                                                | Wrapped Token Transfers                                                                                |
|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Best for**           | DeFi governance, native assets with multichain liquidity, stablecoins, institutional use cases, and projects that want full control of their cross-chain token                       | Consumer apps, games, wrapped-token use cases, and projects that want a fast, managed bridging solution                                                                                |
| **Mechanism**          | Burn-and-mint or hub-and-spoke                                                                                | Lock-and-mint                                                                                |
| **Security**           | Configurable rate limiting, pausing, access control, threshold attestations. Integrated Global Accountant                                                                            | Preconfigured rate limiting and integrated Global Accountant                                                                                |
| **Contract Ownership** | User retains ownership and upgrade authority on each chain                                                                                | Managed via Wormhole Governance; wrapped token contracts are controlled by WTT (ownership is not transferable, and integrators cannot upgrade wrapped contracts)                              |
| **Token Contracts**    | Native contracts owned by your protocol governance, maintain the same token across chains                                                                                | Wrapped asset contract owned by the Wormhole WTT contract, creates a new wrapped version on the destination chain                                                                             |
| **Integration**        | Customizable, flexible framework for advanced deployments                                                                                | Straightforward, permissionless deployment                                                                                |
| **User Experience**    | Seamless, users interact with the same token everywhere                                                                                | Wrapped assets may need [explorer metadata updates](/docs/products/token-transfers/wrapped-token-transfers/faqs/#how-do-i-update-the-metadata-of-a-wrapped-token){target=\_blank} for clarity |
| **Examples**           | [NTT Connect](https://github.com/wormhole-foundation/demo-ntt-connect){target=\_blank}, [NTT TypeScript SDK](https://github.com/wormhole-foundation/demo-ntt-ts-sdk){target=\_blank} | [Portal Bridge UI](https://portalbridge.com/){target=\_blank}                                                                                |

!!! note "Terminology"
    In the SDK and smart contracts, Wrapped Token Transfers (WTT) are referred to as Token Bridge. In documentation, we use WTT for clarity. Both terms describe the same protocol.

In the following video, Wormhole Foundation DevRel Pauline Barnades walks you through the key differences between Wormhole’s Native Token Transfers (NTT) and Wrapped Token Transfers (WTT) and how to select the best option for your use case:


Next Steps

## Next Steps

If you are looking for more guided practice, take a look at:

<div class="grid cards" markdown>

-   :octicons-tools-16:{ .lg .middle } **Get Started with NTT**

    ---

    Learn how to deploy and register contracts to transfer native tokens across chains.

    [:custom-arrow: Get Started](/docs/products/token-transfers/native-token-transfers/get-started/)

-   :octicons-book-16:{ .lg .middle } **Get Started with WTT**

    ---

    Perform token transfers using WTT, including manual and automatic transfers.

    [:custom-arrow: Get Started](/docs/products/token-transfers/wrapped-token-transfers/get-started/)

</div>


Wrapped Token Transfers (WTT) Payload Structure

Transfer

## Transfer

The `Transfer` payload (ID = `1`) is the core mechanism for moving tokens across chains. It is emitted when a user locks or burns tokens on the source chain. On the destination chain, it instructs the bridge to either mint a wrapped token or release native tokens from custody.

```text
PayloadID uint8 = 1
Amount uint256
TokenAddress bytes32
TokenChain uint16
To bytes32
ToChain uint16
Fee uint256
```

??? interface "Parameters"

    `PayloadID` ++"uint8"++

    Value must be `1`, indicating a `Transfer` operation.

    ---

    `Amount` ++"uint256"++

    Amount being transferred, truncated to 8 decimals for consistency across all chains.

    ---

    `TokenAddress` ++"bytes32"++

    Address of the token. Left-zero-padded if shorter than 32 bytes.

    ---

    `TokenChain` ++"uint16"++

    Chain ID of the token.

    ---

    `To` ++"bytes32"++

    Address of the recipient. Left-zero-padded if shorter than 32 bytes.

    ---

    `ToChain` ++"uint16"++

    Chain ID of the recipient.

    ---

    `Fee` ++"uint256"++

    Amount of tokens that the user is willing to pay as relayer fee. Must be less than Amount. Optional and can be claimed by relayers who submit the VAA on the target chain.

To keep `Transfer` messages small, they don't carry all the token's metadata. However, this means that before a token can be transferred to a new chain for the first time, the metadata needs to be bridged, and the wrapped asset needs to be created. Metadata, in this case, includes the number of decimals, which is a core requirement for instantiating a token.


AssetMeta

## AssetMeta

Before a token can be transferred to a new chain for the first time, its metadata must be attested using the `AssetMeta` payload (ID = `2`). This ensures proper decimal precision and display.

```text
PayloadID uint8 = 2
TokenAddress [32]uint8
TokenChain uint16
Decimals uint8
Symbol [32]uint8
Name [32]uint8
```

??? interface "Parameters"

    `PayloadID` ++"uint8"++

    Value must be `2`, indicating an `AssetMeta` operation.

    ---

    `TokenAddress` ++"[32]uint8"++

    Address of the token. Left-zero-padded if shorter than 32 bytes.

    ---

    `TokenChain` ++"uint16"++

    Chain ID of the token.

    ---

    `Decimals` ++"uint8"++

    Number of decimals the token uses on its native chain (not truncated to 8 decimal places).

    ---

    `Symbol` ++"[32]uint8"++

    Symbol of the token, UTF-8 encoded and padded to 32 bytes.

    ---

    `Name` ++"[32]uint8"++

    Name of the token, UTF-8 encoded and padded to 32 bytes.


TransferWithPayload

## TransferWithPayload

The `TransferWithPayload` payload (ID = `3`) extends the standard token transfer by allowing developers to include arbitrary data. This enables interactions with destination chain smart contracts, such as triggering swaps or staking.

```text
PayloadID uint8 = 3
Amount uint256
TokenAddress bytes32
TokenChain uint16
To bytes32
ToChain uint16
FromAddress bytes32
Payload bytes
```

??? interface "Parameters"

    `PayloadID` ++"uint8"++

    Value must be `3`, indicating a `TransferWithPayload` operation.

    ---

    `Amount` ++"uint256"++

    Amount being transferred, truncated to 8 decimals.

    ---

    `TokenAddress` ++"bytes32"++

    Address of the token. Left-zero-padded if shorter than 32 bytes.

    ---

    `TokenChain` ++"uint16"++

    Chain ID of the token.

    ---

    `To` ++"bytes32"++

    Address of the recipient. Must be a contract capable of parsing and handling the payload. Left-zero-padded if shorter than 32 bytes.

    ---

    `ToChain` ++"uint16"++

    Chain ID of the recipient.

    ---

    `FromAddress` ++"bytes32"++

    Address of the sender on the source chain.

    ---

    `Payload` ++"bytes"++

    Arbitrary data passed to the recipient contract. Can be used for DeFi operations, authentication, or app-specific logic.

Unlike `Transfer`, the `TransferWithPayload` message must be redeemed by the recipient contract since only that contract can handle the custom payload properly.


RegisterChain

## RegisterChain

The `RegisterChain` governance payload (Action ID = `1`) registers a WTT emitter address for a foreign chain. This ensures the bridge only accepts messages from known peers.

```text
Module [32]byte
Action uint8 = 1
ChainId uint16

EmitterChainID uint16
EmitterAddress [32]uint8
```

??? interface "Parameters"

    `Module` ++"[32]byte"++

    Module identifier. Left-padded with `TokenBridge` for WTT.

    ---

    `Action` ++"uint8"++

    Value must be `1`, indicating a `RegisterChain` operation.

    ---

    `ChainID` ++"uint16"++

    The chain where this governance action should be applied. `0` is a valid value for all chains.

    ---

    `EmitterChainID` ++"uint16"++

    Chain ID of the registered emitter.

    ---

    `EmitterAddress` ++"[32]uint8"++

    Address of the registered emitter, left-zero-padded if shorter than 32 bytes.

This payload can only be emitted by the Wormhole governance contract, ensuring that each chain accepts messages only from one verified bridge emitter per remote chain.


UpgradeContract

## UpgradeContract

The `UpgradeContract` governance payload (Action ID = `2`) facilitates upgrades to the WTT contract on a specific chain.

```text
Module [32]byte
Action uint8 = 2
ChainId uint16

NewContract [32]uint8
```

??? interface "Parameters"

    `Module` ++"[32]byte"++

    Module identifier, left-padded with `TokenBridge` for WTT.

    ---

    `Action` ++"uint8"++

    Value must be `2`, indicating an `UpgradeContract` operation.

    ---

    `ChainID` ++"uint16"++

    The target chain where the governance action should be applied.

    ---

    `NewContract` ++"[32]uint8"++

    Address of the new WTT contract, left-zero-padded to 32 bytes.

This message allows the Wormhole governance system to deploy new versions of the bridge while retaining control over interoperability and security.


Summary of Payload Structure

## Summary of Payload Structure

| Payload Type          | ID            | Purpose                                                                 | Who Emits It          |
|-----------------------|---------------|-------------------------------------------------------------------------|-----------------------|
| `Transfer`            | PayloadID `1` | Moves tokens between chains by minting or releasing on the destination. | WTT contract |
| `AssetMeta`           | PayloadID `2` | Attests token metadata (decimals, symbol, name) before first transfer.  | WTT contract |
| `TransferWithPayload` | PayloadID `3` | Transfers tokens along with a custom payload for contract execution.    | WTT contract |
| `RegisterChain`       | Action `1`    | Registers a verified WTT emitter for a foreign chain.          | Wormhole governance   |
| `UpgradeContract`     | Action `2`    | Upgrades the WTT contract on a specific chain.                 | Wormhole governance   |


Transfer Flow

## Transfer Flow

Cross-chain token transfers using WTT follow these steps:

1. **Initiation on the Source Chain**

    The transfer begins when a user calls the WTT contract on the source chain:

    - **Wrapped tokens**: The token is burned.
    - **Original tokens**: If the token is native to the source chain, the token is locked in the contract.

2. **Transfer Message Publication**

    The WTT contract invokes the Wormhole [Core Contract](/docs/protocol/infrastructure/core-contracts/){target=\_blank}, which emits an on-chain message event describing the transfer.

3. **Message Observation and Signing**

    [Guardians](/docs/protocol/infrastructure/guardians/){target=\_blank}—a decentralized network of validators—monitor the source chain for these message events. A supermajority (13 out of 19) signs the event to generate a [Verified Action Approval (VAA)](/docs/protocol/infrastructure/vaas/){target=\_blank}—a cryptographically signed attestation of the transfer.

    The VAA is then published to the Wormhole network.

4. **VAA Submission to the Destination Chain**

    The VAA must be submitted to the WTT contract on the destination chain to complete the transfer. The WTT contract then verifies the VAA by calling the Core Contract behind the scenes. This step can be handled in two ways:

    - **Automatic**: A relayer service detects the VAA and submits it to the WTT contract.
    - **Manual**: The user or dApp retrieves the VAA and submits it directly to the WTT contract.

5. **Finalization of the Transfer on the Destination Chain**

    After the VAA is verified on the destination chain, the WTT contract completes the transfer:

    - **Wrapped tokens**: A wrapped representation of the original token is minted.
    - **Original tokens**: If the token is native to the destination chain, the token is released to the recipient.

Consider this example: Alice wants to send 5 ETH from Ethereum to Solana. The ETH is locked on Ethereum’s WTT, and an equivalent amount of wrapped ETH is minted on Solana. The diagram below illustrates this transfer flow.

```mermaid
sequenceDiagram
    participant Alice as Alice
    participant WTTEth as WTT Ethereum<br>(Source Chain)
    participant CoreEth as Core Contract Ethereum<br>(Source Chain)
    participant Guardians
    participant WTTSol as WTT Solana<br>(Destination Chain)
    participant CoreSol as Core Contract Solana<br>(Destination Chain)

    Alice->>WTTEth: Initiate ETH transfer<br>(lock ETH)
    WTTEth->>CoreEth: Publish transfer message
    CoreEth-->>Guardians: Emit message event
    Guardians->>Guardians: Sign and publish VAA

    alt Automatic VAA submission
        Guardians->>WTTSol: Relayer submits VAA
    else Manual VAA submission
        Alice->>Guardians: Retrieve VAA
        Alice->>WTTSol: Submit VAA
    end

    WTTSol->>CoreSol: Verify VAA
    CoreSol-->>WTTSol: VAA verified
    WTTSol-->>Alice: Mint wrapped ETH on Solana (complete transfer)
```

Maybe Alice wants to transfer her wrapped ETH on Solana back to native ETH on Ethereum. The wrapped ETH is burned on Solana’s WTT, and the equivalent 5 ETH are released on Ethereum. The diagram below illustrates this transfer flow.

```mermaid
sequenceDiagram
    participant User as Alice
    participant WTTSrc as WTT Solana<br>(Source Chain)
    participant CoreSrc as Core Contract Solana<br>(Source Chain)
    participant Guardians
    participant WTTDst as WTT Ethereum<br>(Destination Chain)
    participant CoreDst as Core Contract Ethereum<br>(Destination Chain)

    User->>WTTSrc: Initiate transfer <br> (burn wrapped ETH)
    WTTSrc->>CoreSrc: Publish message
    CoreSrc-->>Guardians: Emit message event
    Guardians->>Guardians: Sign and publish VAA

    alt Automatic VAA submission
        Guardians->>WTTDst: Relayer submits VAA
    else Manual VAA submission
        User->>Guardians: Retrieve VAA
        User->>WTTDst: User submits VAA directly
    end

    WTTDst->>CoreDst: Verify VAA
    CoreDst-->>WTTDst: VAA verified
    WTTDst-->>User: Release native ETH on Ethereum (Complete transfer)
```


Automatic vs. Manual Transfers

## Automatic vs. Manual Transfers

WTT supports two modes of transfer, depending on whether the VAA submission step is handled automatically or manually:

- **Automatic**: A relayer service listens for new VAAs and automatically submits them to the destination chain.
- **Manual**: The user (or dApp) must retrieve the VAA and manually submit it to the destination chain.

Here's a quick breakdown of the key differences:

| Feature                   | Automatic Transfer          | Manual Transfer                     |
|---------------------------|-----------------------------|-------------------------------------|
| Who submits the VAA?      | Relayer                     | User or dApp                        |
| User Experience           | Seamless, one-step          | Requires manual intervention        |
| Best for                  | End-users, simple UIs       | Custom dApps, advanced control      |
| Dependency                | Requires relayer support    | None                                |


Completing Manual Transfers

### Completing Manual Transfers

The user who initiated the transfer must complete it within 24 hours for manual transfers. Guardian Sets are guaranteed to be valid for at least that long. If a user waits longer, the Guardian Set may have changed between initiation and redemption, causing the VAA to be rejected.

If this occurs, follow the [Replace Outdated Signatures in VAAs](){target=\_blank} tutorial to update the VAA with signatures from the current Guardian Set.


WTT Relayer (TBR)

## WTT Relayer (TBR)

When completing an automatic transfer using WTT, either through [Connect](/docs/products/connect/overview/){target=\_blank} or programmatically via the [Wormhole TypeScript SDK](/docs/tools/typescript-sdk/get-started/){target=\_blank}, the WTT Relayer (TBR) manages the interaction with the underlying WTT contracts on [supported chains where the TBR is available](/docs/products/connect/reference/support-matrix/){target=\_blank}.


Flow of an Automatic Transfer via TBR

### Flow of an Automatic Transfer via TBR

The flow of an automatic transfer using the TBR looks like this:

1. **Initiation on the Source Chain**

    The transfer begins when a user initiates a transfer on the source chain, which results in the TBR contract being called.

2. **Prepare and Forward the Transfer**

    The TBR verifies the token, encodes transfer details (relayer fee, native gas request, recipient), and forwards the transfer to WTT.

3. **Core Messaging Layer Processes the Transfer**

    WTT emits a message to the Core Contract. Guardians observe the message and produce a signed VAA attesting to the transfer.

4. **Off-Chain Relayer Observes the VAA**

    An off-chain relayer verifies the destination chain and token registration and then prepares to complete the transfer.

5. **Relayer Computes Native Drop-Off and Submits the VAA**

    The relayer queries the destination TBR for the native gas amount, includes it in the transaction, and submits the signed VAA.

6. **TBR Validates and Completes the Transfer**

    The destination TBR validates the VAA by invoking the WTT contract, confirms it's from a registered TBR, verifies the token and native gas request, and then takes custody of the tokens.

7. **Asset Distribution on the Destination Chain**

    The TBR sends the remaining tokens and native gas to the user, pays the off-chain relayer fee, and refunds any excess native tokens.

The following diagram illustrates the key steps in the source chain during a transfer:

```mermaid
sequenceDiagram
    participant User
    participant SourceTBR as Source Chain TBR
    participant SourceWTT as Source Chain WTT
    participant Messaging as Core Messaging Layer

    User->>SourceTBR: Initiate transfer (token, <br>recipient, fees, native gas)
    SourceTBR->>SourceWTT: Forward transfer (burn or lock tokens)
    SourceWTT->>Messaging: Publish transfer message
```

Once the core messaging layer processes the transfer, the destination chain handles completion as shown below:

```mermaid
sequenceDiagram
    participant Messaging as Core Messaging Layer
    participant Relayer as Off-chain Relayer
    participant DestTBR as Destination Chain TBR
    participant DestWTT as Destination Chain <br> WTT
    participant DestUser as User <br> (Destination Chain)

    Messaging->>Relayer: Emit signed VAA for transfer
    Relayer->>Relayer: Verifies destination chain and token registration
    Relayer->>DestTBR: Query native gas amount
    Relayer->>DestTBR: Submit signed VAA
    DestTBR->>DestWTT: Validate VAA
    DestTBR->>DestTBR: Take custody of tokens
    DestTBR->>DestUser: Send tokens (after fees & native gas)
    DestTBR->>Relayer: Pay relayer fee & refund excess
```


Next Steps

## Next Steps

Now that you’ve seen how a transfer works, try both types yourself to experience the full process.

<div class="grid cards" markdown>

-   :octicons-tools-16:{ .lg .middle } **Get Started with WTT**

    ---

    Perform token transfers using WTT, including manual and automatic transfers.

    [:custom-arrow: Get Started](/docs/products/token-transfers/wrapped-token-transfers/get-started/)

</div>


## Can ownership of wrapped tokens be transferred from the WTT?

No. Ownership of wrapped token contracts cannot be transferred, because [WTT](/docs/products/token-transfers/wrapped-token-transfers/overview/){target=\_blank} deploys and retains control of these contracts and tokens.

 - **On EVM chains**: When you attest a token, WTT deploys a new ERC-20 contract as a beacon proxy. The upgrade authority for these contracts is the WTT contract itself.
 - **On Solana**: The WTT deploys a new SPL token, where the upgrade authority is a Program Derived Address (PDA) controlled by the WTT contract.

The logic behind deploying these token contracts involves submitting an attestation VAA, which allows WTT to verify and deploy the wrapped token contract on the destination chain.

Relevant contracts:

 - [Ethereum ERC-20](https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/bridge/token/Token.sol){target=\_blank}
 - [Solana SPL](https://github.com/wormhole-foundation/wormhole/blob/main/solana/modules/token_bridge/program/src/api/create_wrapped.rs#L128-L145){target=\_blank}
 - [Attestation VAA and Token Contract Deployment Logic](https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/bridge/Bridge.sol#L385-L431){target=\_blank}


## How do I update the metadata of a wrapped token?

Wrapped tokens are deployed and controlled by the WTT program under Guardian authority. You cannot update their metadata directly. Instead, you must coordinate with the respective block explorer teams to request and apply metadata changes.


## How do I calculate the current gas costs for Ethereum Mainnet VAA verification?

You can refer to the [core-bridge repository](https://github.com/nonergodic/core-bridge){target=\_blank} for guidance on how to calculate the current gas costs associated with verifying VAAs on Ethereum Mainnet. This repository provides up-to-date references and examples to help you gauge costs accurately.


## How can I update my wrapped token image on Solscan?

Updating the metadata (such as the token image, name, or symbol) of a wrapped token on [Solscan](https://solscan.io/){target=\_blank} requires [contacting the Solscan team](https://solscan.io/contactus){target=\_blank} directly. Wormhole cannot make these updates for you because the wrapped token contracts are owned and controlled by the WTT program, not individual developers or projects.

To request an update, contact Solscan via [support@solscan.io](mailto:support@solscan.io) or their [contact form](https://solscan.io/contactus){target=\_blank}.


Get Started with Wrapped Token Transfers (WTT)

Introduction

## Introduction

Wormhole's [Wrapped Token Transfers (WTT)](/docs/products/token-transfers/wrapped-token-transfers/overview/){target=\_blank} enables seamless multichain token transfers by locking tokens on a source chain and minting equivalent wrapped tokens on a destination chain. This mechanism preserves token properties such as name, symbol, and decimal precision across chains.

In this guide, you will use the [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank} to perform two types of transfers.

 - **Manual transfer**: Where you control each step.
 - **Automatic transfer**: Where a relayer finalizes the transfer for you.

These examples will help you understand how WTT works across EVM and non-EVM chains.

!!! note "Terminology"
    The SDK and smart contracts use the name Token Bridge. In documentation, this product is referred to as Wrapped Token Transfers (WTT). Both terms describe the same protocol.


Prerequisites

## Prerequisites

Before you begin, make sure you have the following:

 - [Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm){target=\_blank}.
 - Wallets funded with tokens on two [supported chains](/docs/products/reference/supported-networks/#wtt){target=\_blank}.

This guide uses a Solana wallet with [devnet SOL](https://faucet.solana.com/){target=\_blank} and an EVM wallet with [Sepolia ETH](https://www.alchemy.com/faucets/ethereum-sepolia){target=\_blank} for the manual transfer example, and [Avalanche Fuji](https://core.app/tools/testnet-faucet/?subnet=c&token=c){target=\_blank} and [Base Sepolia](https://docs.base.org/docs/tools/network-faucets/){target=\_blank} wallets funded with testnet tokens for the automatic transfer. You can adapt the examples to match your preferred chains.


## Configure Your Token Transfer Environment

1. Create a new directory and initialize a Node.js project:

    ```bash
    mkdir wh-wtt
    cd wh-wtt
    npm init -y
    ```

2. Install the required dependencies. This example uses the SDK version `4.14.1`:

    ```bash
    npm install @wormhole-foundation/sdk@4.14.1
    npm install -D tsx typescript
    ```

3. Create a `transfer.ts` file to handle the multichain transfer logic, and a `helper.ts` file to manage wallet signers and token utilities:

    ```bash
    touch transfer.ts helper.ts
    ```

4. Set up secure access to your wallets. This guide assumes you are loading your `SOL_PRIVATE_KEY` and `EVM_PRIVATE_KEY` from a secure keystore of your choice, such as a secrets manager or a CLI-based tool like [`cast wallet`](https://getfoundry.sh/cast/reference/wallet/#cast-wallet){target=\_blank}.

    !!! warning
        If you use a `.env` file during development, add it to your `.gitignore` to exclude it from version control. Never commit private keys or mnemonics to your repository.


Perform a Token Transfer

## Perform a Token Transfer

This section shows how to run manual and automatic token transfers using a shared project structure. You will define helper utilities once and reuse them across both flows.

In the manual transfer, you initiate a transfer on Solana, wait for Guardian signatures, and redeem the tokens on Sepolia, giving you complete control over each step. In the automatic transfer, the relayer handles attestation and redemption, simplifying the process between EVM chains.

1. Open `helper.ts` and define utility functions to load private keys, instantiate signers for Solana and EVM chains, and retrieve token decimals as needed:

    ```ts title="helper.ts"
    import {
      ChainAddress,
      ChainContext,
      Network,
      Signer,
      Wormhole,
      Chain,
      isTokenId,
      TokenId,
    } from '@wormhole-foundation/sdk';
    import solana from '@wormhole-foundation/sdk/solana';
    import sui from '@wormhole-foundation/sdk/sui';
    import evm from '@wormhole-foundation/sdk/evm';

    /**
     * Returns a signer for the given chain using locally scoped credentials.
     * The required values (EVM_PRIVATE_KEY, SOL_PRIVATE_KEY, SUI_MNEMONIC) must
     * be loaded securely beforehand, for example via a keystore, secrets
     * manager, or environment variables (not recommended).
     */
    export async function getSigner<N extends Network, C extends Chain>(
      chain: ChainContext<N, C>
    ): Promise<{
      chain: ChainContext<N, C>;
      signer: Signer<N, C>;
      address: ChainAddress<C>;
    }> {
      let signer: Signer;
      const platform = chain.platform.utils()._platform;

      switch (platform) {
        case 'Evm':
          signer = await (
            await evm()
          ).getSigner(await chain.getRpc(), EVM_PRIVATE_KEY!);
          break;
        case 'Solana':
          signer = await (
            await solana()
          ).getSigner(await chain.getRpc(), SOL_PRIVATE_KEY!);
          break;
        case 'Sui':
          signer = await (
            await sui()
          ).getSigner(await chain.getRpc(), SUI_MNEMONIC!);
          break;
        default:
          throw new Error(`Unsupported platform: ${platform}`);
      }

      return {
        chain,
        signer: signer as Signer<N, C>,
        address: Wormhole.chainAddress(chain.chain, signer.address()),
      };
    }

    /**
     * Get the number of decimals for the token on the source chain.
     * This helps convert a user-friendly amount (e.g., '1') into raw units.
     */
    export async function getTokenDecimals<N extends Network>(
      wh: Wormhole<N>,
      token: TokenId,
      chain: ChainContext<N, any>
    ): Promise<number> {
      return isTokenId(token)
        ? Number(await wh.getDecimals(token.chain, token.address))
        : chain.config.nativeTokenDecimals;
    }
    ```

2. In `transfer.ts`, choose your transfer mode by selecting the [route](/docs/products/connect/concepts/routes/#wtt-routes){target=\_blank} you pass to the `tokenTransfer()` object:
    - `TokenBridge` for manual transfers.
    - `AutomaticTokenBridge` for automatic transfers.

    === "Manual Transfer"

        ```ts title="transfer.ts"
        import { wormhole, amount, Wormhole } from '@wormhole-foundation/sdk';
        import solana from '@wormhole-foundation/sdk/solana';
        import sui from '@wormhole-foundation/sdk/sui';
        import evm from '@wormhole-foundation/sdk/evm';
        import { getSigner, getTokenDecimals } from './helper';

        (async function () {
          // Initialize Wormhole SDK for Solana and Sepolia on Testnet
          const wh = await wormhole('Testnet', [solana, sui, evm]);

          // Define the source and destination chains
          const sendChain = wh.getChain('Solana');
          const rcvChain = wh.getChain('Sepolia');

          // Load signers and addresses from helpers
          const source = await getSigner(sendChain);
          const destination = await getSigner(rcvChain);

          // Define the token and amount to transfer
          const tokenId = Wormhole.tokenId('Solana', 'native');
          const amt = '0.1';

          // Convert to raw units based on token decimals
          const decimals = await getTokenDecimals(wh, tokenId, sendChain);
          const transferAmount = amount.units(amount.parse(amt, decimals));

          // Construct the transfer object
          const xfer = await wh.tokenTransfer(
            tokenId,
            transferAmount,
            source.address,
            destination.address,
            'TokenBridge',
            undefined
          );

          // Initiate the transfer from Solana
          console.log('Starting Transfer');
          const srcTxids = await xfer.initiateTransfer(source.signer);
          console.log(`Started Transfer: `, srcTxids);

          // Wait for the signed attestation from the Guardian network
          console.log('Fetching Attestation');
          const timeout = 5 * 60 * 1000; // 5 minutes
          await xfer.fetchAttestation(timeout);

          // Redeem the tokens on Sepolia
          console.log('Completing Transfer');
          const destTxids = await xfer.completeTransfer(destination.signer);
          console.log(`Completed Transfer: `, destTxids);

          process.exit(0);
        })();
        ```

    === "Automatic Transfer"

        ```ts title="transfer.ts"
        import { wormhole, amount, Wormhole } from '@wormhole-foundation/sdk';
        import solana from '@wormhole-foundation/sdk/solana';
        import sui from '@wormhole-foundation/sdk/sui';
        import evm from '@wormhole-foundation/sdk/evm';
        import { getSigner, getTokenDecimals } from './helper';

        (async function () {
          // Initialize Wormhole SDK for Avalanche and Base Sepolia on Testnet
          const wh = await wormhole('Testnet', [solana, sui, evm]);

          // Define the source and destination chains
          const sendChain = wh.getChain('Avalanche');
          const rcvChain = wh.getChain('BaseSepolia');

          // Load signers and addresses from helpers
          const source = await getSigner(sendChain);
          const destination = await getSigner(rcvChain);

          // Define the token and amount to transfer
          const tokenId = Wormhole.tokenId('Avalanche', 'native');
          const amt = '0.2';

          // Convert to raw units based on token decimals
          const decimals = await getTokenDecimals(wh, tokenId, sendChain);
          const transferAmount = amount.units(amount.parse(amt, decimals));

          // Set to false to require manual approval steps
          const nativeGas = amount.units(amount.parse('0.0', 6));

          // Construct the transfer object
          const xfer = await wh.tokenTransfer(
            tokenId,
            transferAmount,
            source.address,
            destination.address,
            'AutomaticTokenBridge',
            nativeGas
          );

          // Initiate the transfer from Avalanche Fuji
          console.log('Starting Transfer');
          const srcTxids = await xfer.initiateTransfer(source.signer);
          console.log(`Started Transfer: `, srcTxids);

          process.exit(0);
        })();
        ```

3. Execute the script to initiate and complete the transfer:

    ```bash
    npx tsx transfer.ts
    ```

    If successful, the expected output should be similar to this:

    	npx tsx transfer.ts
    	Starting Transfer
    	Started Transfer:  ['36UwBBh6HH6wt3VBbNNawMd1ijCk28YgFePrBWfE3vGQFHtbMjY5626nqHubmyQWGNh2ZrN1vHKRrSQDNC3gkZgB']

        Getting Attestation
    	Retrying Wormholescan:GetVaaBytes, attempt 0/900
        Retrying Wormholescan:GetVaaBytes, attempt 1/900
        Retrying Wormholescan:GetVaaBytes, attempt 2/900

        Completing Transfer
        Completed Transfer:  [ '53Nt4mp2KRTk2HFyvUcmP9b6cRXjVAN3wCksoBey9WmT' ]

To verify the transaction and view its details, copy the transaction hash from the output and paste it into [Wormholescan](https://wormholescan.io/#/?network=Testnet){target=\_blank}.


Next Steps

## Next Steps

Now that you've completed a manual multichain token transfer, explore these guides to continue building.

<div class="grid cards" markdown>

-   :octicons-tools-16:{ .lg .middle } **Complete Token Transfer Workflow**

    ---

    Build a reusable application that supports multiple chain combinations and transfer modes (manual and automatic).

    [:custom-arrow: Get Started](/docs/products/token-transfers/wrapped-token-transfers/tutorials/transfer-workflow/)

-   :octicons-tools-16:{ .lg .middle } **Create Multichain Tokens**

    ---

    Learn how to issue tokens that work across chains.

    [:custom-arrow: Get Started](/docs/products/token-transfers/wrapped-token-transfers/tutorials/multichain-token/)

-   :octicons-tools-16:{ .lg .middle } **Wormhole Dev Arena**

    ---

    A structured learning hub with hands-on tutorials across the Wormhole ecosystem.

    [:custom-arrow: Explore the Dev Arena](https://arena.wormhole.com/){target=\_blank}

</div>


Prerequisites

## Prerequisites

Before you begin, ensure you have the following:

- [Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm){target=\_blank} installed on your machine.
- [TypeScript](https://www.typescriptlang.org/download/){target=\_blank} installed globally.
- The contract address for the token you wish to register.
- A wallet setup with the following:
    - Private keys for your source and destination chains.
    - A small amount of gas tokens on your source and destination chains.


Set Up Your Development Environment

## Set Up Your Development Environment

Follow these steps to initialize your project, install dependencies, and prepare your developer environment for token attestation.

1. Create a new directory and initialize a Node.js project using the following commands:

    ```bash
    mkdir attest-token
    cd attest-token
    npm init -y
    ```

2. Install dependencies, including the [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank}. This example uses the SDK version `4.14.1`:

    ```bash
    npm install @wormhole-foundation/sdk@4.14.1 -D tsx typescript
    ```

3. Set up secure access to your wallets. This guide assumes you are loading your private key values from a secure keystore of your choice, such as a secrets manager or a CLI-based tool like [`cast wallet`](https://getfoundry.sh/cast/reference/wallet/#cast-wallet){target=\_blank}.

    !!! warning
        If you use a `.env` file during development, add it to your `.gitignore` to exclude it from version control. Never commit private keys or mnemonics to your repository.

4. Create a new file named `helper.ts` to hold signer functions:

    ```bash
    touch helper.ts
    ```

5. Open `helper.ts` and add the following code:

    ```typescript title="helper.ts"
    import {
      Chain,
      ChainAddress,
      ChainContext,
      Wormhole,
      Network,
      Signer,
    } from '@wormhole-foundation/sdk';
    import type { SignAndSendSigner } from '@wormhole-foundation/sdk';
    import evm from '@wormhole-foundation/sdk/evm';
    import solana from '@wormhole-foundation/sdk/solana';
    import sui from '@wormhole-foundation/sdk/sui';

    /**
     * Returns a signer for the given chain using locally scoped credentials.
     * The required values (EVM_PRIVATE_KEY, SOL_PRIVATE_KEY, SUI_MNEMONIC) must
     * be loaded securely beforehand, for example via a keystore, secrets
     * manager, or environment variables (not recommended).
     */
    export async function getSigner<N extends Network, C extends Chain>(
      chain: ChainContext<N, C>
    ): Promise<{
      chain: ChainContext<N, C>;
      signer: SignAndSendSigner<N, C>;
      address: ChainAddress<C>;
    }> {
      let signer: Signer<any, any>;
      const platform = chain.platform.utils()._platform;

      // Customize the signer by adding or removing platforms as needed. Be sure
      // to import the necessary packages for the platforms you want to support
      switch (platform) {
        case 'Evm':
          signer = await (
            await evm()
          ).getSigner(await chain.getRpc(), EVM_PRIVATE_KEY!);
          break;
        case 'Solana':
          signer = await (
            await solana()
          ).getSigner(await chain.getRpc(), SOL_PRIVATE_KEY!);
          break;
        case 'Sui':
          signer = await (
            await sui()
          ).getSigner(await chain.getRpc(), SUI_MNEMONIC!);
          break;
        default:
          throw new Error(`Unsupported platform: ${platform}`);
      }

      const typedSigner = signer as SignAndSendSigner<N, C>;

      return {
        chain,
        signer: typedSigner,
        address: Wormhole.chainAddress(chain.chain, signer.address()),
      };
    }
    ```

    You can view the list of [supported platform constants](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/4.14.1/core/base/src/constants/platforms.ts#L6){target=_blank} in the Wormhole SDK GitHub repo.


Check for a Wrapped Version of a Token

## Check for a Wrapped Version of a Token

If you are working with a newly created token that you know has never been transferred to the destination chain, you can continue to the [Create Attestation on the Source Chain](#create-attestation-on-the-source-chain) section.

Since attestation is a one-time process, it is good practice when working with existing tokens to incorporate a check for wrapped versions into your WTT flow. Follow these steps to check for a wrapped version of a token:

1. Create a new file called `attest.ts` to hold the wrapped version check and attestation logic:

    ```bash
    touch attest.ts
    ```

2. Open `attest.ts` and add the following code:

    ```typescript title="attest.ts"
    import {
      wormhole,
      Wormhole,
      TokenId,
      TokenAddress,
    } from '@wormhole-foundation/sdk';
    import { signSendWait, toNative } from '@wormhole-foundation/sdk-connect';
    import evm from '@wormhole-foundation/sdk/evm';
    import solana from '@wormhole-foundation/sdk/solana';
    import { getSigner } from './helper';

    async function attestToken() {
      // Initialize wormhole instance, define the network, platforms, and chains
      const wh = await wormhole('Testnet', [evm, solana]);
      const sourceChain = wh.getChain('Moonbeam');
      const destinationChain = wh.getChain('Solana');

      // Define the token to check for a wrapped version
      const tokenId: TokenId = Wormhole.tokenId(
        sourceChain.chain,
        'INSERT_TOKEN_CONTRACT_ADDRESS'
      );
      // Check if the token is registered with the destination chain WTT (Token Bridge) contract
      // Registered = returns the wrapped token ID
      // Not registered = runs the attestation flow to register the token
      let wrappedToken: TokenId;
      try {
        wrappedToken = await wh.getWrappedAsset(destinationChain.chain, tokenId);
        console.log(
          '✅ Token already registered on destination:',
          wrappedToken.address
        );
      } catch (e) {
        // Attestation on the source chain flow code
        console.log(
          '⚠ Token is NOT registered on destination. Running attestation flow...'
        );
      }
    }

    attestToken().catch((e) => {
      console.error('❌ Error in attestToken', e);
      process.exit(1);
    });
    ```

    After initializing a Wormhole instance and defining the source and destination chains, this code does the following:

    - **Defines the token to check**: Use the contract address on the source chain for this value.
    - **Calls [`getWrappedAsset`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/4.14.1/connect/src/wormhole.ts#L277){target=\_blank}**: Part of the `Wormhole` class, the method does the following:
        - Accepts a [`TokenId`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/4.14.1/platforms/aptos/protocols/tokenBridge/src/types.ts#L12){target=\_blank} representing a token on the source chain.
        - Checks for a corresponding wrapped version of the destination chain's WTT contract.
        - Returns the `TokenId` for the wrapped token on the destination chain if a wrapped version exists.

3. Run the script using the following command:

    ```bash
    npx tsx attest.ts
    ```

4. If the token has a wrapped version registered with the destination chain WTT contract, you will see terminal output similar to the following:

      npx tsx attest.ts
      ✅ Token already registered on destination: SolanaAddress {
        type: 'Native',
        address: PublicKey [PublicKey(2qjSAGrpT2eTb673KuGAR5s6AJfQ1X5Sg177Qzuqt7yB)] {
        _bn: BN: 1b578bb9b7a04a1aab3b5b64b550d8fc4f73ab343c9cf8532d2976b77ec4a8ca
        }
        }

    You can safely use WTT to transfer this token to the destination chain.

    If a wrapped version isn't found on the destination chain, your terminal output will be similar to the following, and you must attest the token before transfer:

    	npx tsx attest.ts
    	⚠ Token is NOT registered on destination. Running attestation flow...


Create Attestation on the Source Chain

## Create Attestation on the Source Chain

To create the attestation transaction on the source chain, open `attest.ts` and replace the `// Attestation flow code` comment with the following code:

```typescript title="attest.ts"
    // Retrieve the WTT (Token Bridge) contract text for the source chain
    const tb = await sourceChain.getTokenBridge();
    // Get the signer for the source chain
    const sourceSigner = await getSigner(sourceChain);
    // Define the token to attest and a payer address
    const token: TokenAddress<typeof sourceChain.chain> = toNative(
      sourceChain.chain,
      tokenId.address.toString()
    );
    const payer = toNative(sourceChain.chain, sourceSigner.signer.address());
    // Create a new attestation and sign and send the transaction
    for await (const tx of tb.createAttestation(token, payer)) {
      const txids = await signSendWait(
        sourceChain,
        tb.createAttestation(token),
        sourceSigner.signer
      );
      // Attestation on the destination chain flow code
      console.log('✅ Attestation transaction sent:', txids);

```

This code does the following:

- **Gets the source chain WTT context**: This is where the transaction is sent to create the attestation.
- Defines the token to attest and the payer.
- **Calls `createAttestation`**: Defined in the `TokenBridge` interface, the [`createAttestation`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/4.14.1/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L237){target=\_blank} method does the following:
    - Accepts a `TokenAddress` representing the token on its native chain.
    - Accepts an optional `payer` address to cover the transaction fees for the attestation transaction.
    - Prepares an attestation for the token, including metadata such as address, symbol, and decimals.
    - Returns an `AsyncGenerator` that yields unsigned transactions, which are then signed and sent to initiate the attestation process on the source chain.


Submit Attestation on Destination Chain

## Submit Attestation on Destination Chain

The attestation flow finishes with the following:

- Using the transaction ID returned from the `createAttestation` transaction on the source chain to retrieve the associated signed `TokenBridge:AttestMeta` VAA.
- Submitting the signed VAA to the destination chain to provide Guardian-backed verification of the attestation transaction on the source chain.
- The destination chain uses the attested metadata to create the wrapped version of the token and register it with its WTT contract.

Follow these steps to complete your attestation flow logic:

1. Add the following code to `attest.ts`:

    ```typescript title="attest.ts"
          // Parse the transaction to get Wormhole message ID
          const messages = await sourceChain.parseTransaction(txids[0].txid);
          console.log('✅ Attestation messages:', messages);
          // Set a timeout for fetching the VAA, this can take several minutes
          // depending on the source chain network and finality
          const timeout = 25 * 60 * 1000;
          // Fetch the VAA for the attestation message
          const vaa = await wh.getVaa(
            messages[0]!,
            'TokenBridge:AttestMeta',
            timeout
          );
          if (!vaa) throw new Error('❌ VAA not found before timeout.');
          // Get the WTT (Token Bridge) contract text for the destination chain
          // and submit the attestation VAA
          const destTb = await destinationChain.getTokenBridge();
          // Get the signer for the destination chain
          const destinationSigner = await getSigner(destinationChain);
          const payer = toNative(
            destinationChain.chain,
            destinationSigner.signer.address()
          );
          const destTxids = await signSendWait(
            destinationChain,
            destTb.submitAttestation(vaa, payer),
            destinationSigner.signer
          );
          console.log('✅ Attestation submitted on destination:', destTxids);
        }
        // Poll for the wrapped token to appear on the destination chain
        const maxAttempts = 50; // ~5 minutes with 6s interval
        const interval = 6000;
        let attempt = 0;
        let registered = false;

        while (attempt < maxAttempts && !registered) {
          attempt++;
          try {
            const wrapped = await wh.getWrappedAsset(
              destinationChain.chain,
              tokenId
            );
            console.log(
              `✅ Wrapped token is now available on ${destinationChain.chain}:`,
              wrapped.address
            );
            registered = true;
          } catch {
            console.log(
              `⏳ Waiting for wrapped token to register on ${destinationChain.chain}...`
            );
            await new Promise((res) => setTimeout(res, interval));
          }
        }
        if (!registered) {
          throw new Error(
            `❌ Token attestation did not complete in time on ${destinationChain.chain}`
          );
        }
        console.log(
          `🚀 Token attestation complete! Token registered with ${destinationChain.chain}.`
        );
    ```

2. Run the script using the following command:

    ```bash
    npx tsx attest.ts
    ```

3. You will see terminal output similar to the following:

      npx tsx attest.ts
      ⚠ Token is NOT registered on destination. Running attestation
        flow...
      ✅ Attestation transaction sent: [ { chain: 'Moonbeam', txid:
        '0xbaf7429e1099cac6f39ef7e3c30e38776cfb5b6be837dcd8793374c8ee491799' }
        ]
      ✅ Attestation messages: [ { chain: 'Moonbeam', emitter: UniversalAddress {
        address: [Uint8Array] }, sequence: 1507n } ]
      Retrying Wormholescan:GetVaaBytes, attempt 0/750
      Retrying Wormholescan:GetVaaBytes, attempt 1/750
      .....
      Retrying Wormholescan:GetVaaBytes, attempt 10/750
      📨 Submitting attestation VAA to Solana...
      ✅ Attestation submitted on destination: [ { chain: 'Solana', txid:
        '3R4oF5P85jK3wKgkRs5jmE8BBLoM4wo2hWSgXXL6kA8efbj2Vj9vfuFSb53xALqYZuv3FnXDwJNuJfiKKDwpDH1r'
        } ]
      ✅ Wrapped token is now available on Solana: SolanaAddress { type:
        'Native', address: PublicKey
        [PublicKey(2qjSAGrpT2eTb673KuGAR5s6AJfQ1X5Sg177Qzuqt7yB)] { _bn: BN:
        1b578bb9b7a04a1aab3b5b64b550d8fc4f73ab343c9cf8532d2976b77ec4a8ca } }
      🚀 Token attestation complete!

    ??? example "View complete script"
        ```typescript title="attest.ts"
        import {
          wormhole,
          Wormhole,
          TokenId,
          TokenAddress,
        } from '@wormhole-foundation/sdk';
        import { signSendWait, toNative } from '@wormhole-foundation/sdk-connect';
        import evm from '@wormhole-foundation/sdk/evm';
        import solana from '@wormhole-foundation/sdk/solana';
        import { getSigner } from './helper';

        async function attestToken() {
          // Initialize wormhole instance, define the network, platforms, and chains
          const wh = await wormhole('Testnet', [evm, solana]);
          const sourceChain = wh.getChain('Moonbeam');
          const destinationChain = wh.getChain('Solana');

          // Define the token to check for a wrapped version
          const tokenId: TokenId = Wormhole.tokenId(
            sourceChain.chain,
            'INSERT_TOKEN_CONTRACT_ADDRESS'
          );
          // Check if the token is registered with the destination chain WTT (Token Bridge) contract
          // Registered = returns the wrapped token ID
          // Not registered = runs the attestation flow to register the token
          let wrappedToken: TokenId;
          try {
            wrappedToken = await wh.getWrappedAsset(destinationChain.chain, tokenId);
            console.log(
              '✅ Token already registered on destination:',
              wrappedToken.address
            );
          } catch (e) {
            // Attestation on the source chain flow code
            console.log(
              '⚠ Token is NOT registered on destination. Running attestation flow...'
            );

            // Retrieve the WTT (Token Bridge) contract text for the source chain
            const tb = await sourceChain.getTokenBridge();
            // Get the signer for the source chain
            const sourceSigner = await getSigner(sourceChain);
            // Define the token to attest and a payer address
            const token: TokenAddress<typeof sourceChain.chain> = toNative(
              sourceChain.chain,
              tokenId.address.toString()
            );
            const payer = toNative(sourceChain.chain, sourceSigner.signer.address());
            // Create a new attestation and sign and send the transaction
            for await (const tx of tb.createAttestation(token, payer)) {
              const txids = await signSendWait(
                sourceChain,
                tb.createAttestation(token),
                sourceSigner.signer
              );
              // Attestation on the destination chain flow code
              console.log('✅ Attestation transaction sent:', txids);

              // Parse the transaction to get Wormhole message ID
              const messages = await sourceChain.parseTransaction(txids[0].txid);
              console.log('✅ Attestation messages:', messages);
              // Set a timeout for fetching the VAA, this can take several minutes
              // depending on the source chain network and finality
              const timeout = 25 * 60 * 1000;
              // Fetch the VAA for the attestation message
              const vaa = await wh.getVaa(
                messages[0]!,
                'TokenBridge:AttestMeta',
                timeout
              );
              if (!vaa) throw new Error('❌ VAA not found before timeout.');
              // Get the WTT (Token Bridge) contract text for the destination chain
              // and submit the attestation VAA
              const destTb = await destinationChain.getTokenBridge();
              // Get the signer for the destination chain
              const destinationSigner = await getSigner(destinationChain);
              const payer = toNative(
                destinationChain.chain,
                destinationSigner.signer.address()
              );
              const destTxids = await signSendWait(
                destinationChain,
                destTb.submitAttestation(vaa, payer),
                destinationSigner.signer
              );
              console.log('✅ Attestation submitted on destination:', destTxids);
            }
            // Poll for the wrapped token to appear on the destination chain
            const maxAttempts = 50; // ~5 minutes with 6s interval
            const interval = 6000;
            let attempt = 0;
            let registered = false;

            while (attempt < maxAttempts && !registered) {
              attempt++;
              try {
                const wrapped = await wh.getWrappedAsset(
                  destinationChain.chain,
                  tokenId
                );
                console.log(
                  `✅ Wrapped token is now available on ${destinationChain.chain}:`,
                  wrapped.address
                );
                registered = true;
              } catch {
                console.log(
                  `⏳ Waiting for wrapped token to register on ${destinationChain.chain}...`
                );
                await new Promise((res) => setTimeout(res, interval));
              }
            }
            if (!registered) {
              throw new Error(
                `❌ Token attestation did not complete in time on ${destinationChain.chain}`
              );
            }
            console.log(
              `🚀 Token attestation complete! Token registered with ${destinationChain.chain}.`
            );
          }
        }

        attestToken().catch((e) => {
          console.error('❌ Error in attestToken', e);
          process.exit(1);
        });
        ```

Congratulations! You've successfully created and submitted an attestation to register a token for transfer via WTT.


Next Steps

## Next Steps

<div class="grid cards" markdown>

-   :octicons-tools-16:{ .lg .middle } **Transfer Wrapped Assets**

    ---

    Follow this guide to incorporate token attestation and registration into an end-to-end WTT flow.

    [:custom-arrow: Get Started](/docs/products/token-transfers/wrapped-token-transfers/guides/attest-tokens/)

</div>


Prerequisites

## Prerequisites

Before you begin, ensure you have the following installed:

- [Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm){target=_blank}
- [TypeScript](https://www.typescriptlang.org/download/){target=_blank} (installed globally)


Set Up Your Developer Environment

## Set Up Your Developer Environment

Follow these steps to initialize your project, install dependencies, and prepare your developer environment:

1. Create a new directory and initialize a Node.js project using the following commands:

    ```bash
    mkdir fetch-vaa
    cd fetch-vaa
    npm init -y
    ```

2. Install dependencies, including the [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank}. This example uses the SDK version `4.14.1`:

    ```bash
   npm install @wormhole-foundation/sdk@4.14.1 -D tsx typescript
   ```


Fetch VAA via TypeScript SDK

## Fetch VAA via TypeScript SDK

Follow these steps to search for and retrieve a VAA using the TypeScript SDK:

1. Create a new file called `fetch-vaa.ts` using the following command:

    ```bash
    touch fetch-vaa.ts
    ```

2. Open your `fetch-vaa.ts` file and add the following code:

    ```typescript title="fetch-vaa.ts"
    import { wormhole } from '@wormhole-foundation/sdk';
    import evm from '@wormhole-foundation/sdk/evm';
    import { serialize } from '@wormhole-foundation/sdk-definitions';
    import { toChainId } from '@wormhole-foundation/sdk-base';

    async function main() {
      // Initialize the Wormhole SDK with the network and platform
      // to match the source chain for the transaction ID
      const wh = await wormhole('Testnet', [evm]);
      // Source chain transaction ID for the VAA you want to fetch
      const txid =
        'INSERT_TRANSACTION_ID';
      // Call getVaa to fetch the VAA associated with the transaction ID
      // and decode returned data into a human-readable format
      const vaa = await wh.getVaa(txid, 'Uint8Array', 60000);
      if (!vaa) {
        console.error('❌ VAA not found');
        process.exit(1);
      }
      const { emitterChain, emitterAddress, sequence } = vaa;
      const chainId = toChainId(emitterChain);
      const emitterHex = emitterAddress.toString();

      const vaaBytes = serialize(vaa);
      const vaaHex = Buffer.from(vaaBytes).toString('hex');

      console.log('✅ VAA Info');
      console.log(`Chain: ${chainId}`);
      console.log(`Emitter: ${emitterHex}`);
      console.log(`Sequence: ${sequence}`);
      console.log('---');
      console.log(`VAA Bytes (hex):\n${vaaHex}`);
      // Return the VAA object for further processing if needed
      return vaa;
    }

    main().catch(console.error);
    ```

    This code does the following:

    - Initializes a Wormhole instance with the same `network` and `platform` as the source chain transfer transaction.
    - Accepts the transaction ID from the source chain transfer transaction.
    - Prints the associated `chain`, `emitter`, `sequence`, and VAA bytes to the terminal.
    - Returns the `vaa` object for any further processing.

3. Run the script with the following command:

    ```bash
    npx tsx fetch-vaa.ts
    ```

4. You will see terminal output similar to the following:

    	npx tsx fetch-vaa.ts
    	✅ VAA Info
    	Chain: 16
    	Emitter: 0x000000000000000000000000bc976d4b9d57e57c3ca52e1fd136c45ff7955a96
        Sequence: 1512
    	---
        VAA Bytes (hex):
        010000000001004d34d189b894acf4c16b9f456f908ca8b60aa9b2fa77cfa6ebc18f864818c21a7e18b6c4f72415f441be4d2b666c5b897d354cec0e950b935b15806d002d39670168557fb6000000000010000000000000000000000000bc976d4b9d57e57c3ca52e1fd136c45ff7955a9600000000000005e8010100000000000000000000000000000000000000000000000000000000009896800000000000000000000000009b2ff7b2b5a459853224a3317b786d8e85026660001084b1e2f8a26ddff1a55eed46add73a9b556256f2afda1072f6cfdab1dcb2d53000010000000000000000000000000000000000000000000000000000000000000000


Fetch VAA via Wormholescan

## Fetch VAA via Wormholescan

You can also use [Wormholescan's](https://wormholescan.io/){target=\_blank} UI to manually search for a VAA using the source transaction ID, VAA ID, or a wallet address. This type of quick search is helpful during debugging or testing of your integration. Follow these steps to fetch a VAA using Wormholescan:

1. On [Wormholescan](https://wormholescan.io/){target=\_blank}, use the dropdown menu in the top right corner to select either **Mainnet** or **Testnet**.

2. Enter your transaction ID in the search bar and select "return" or "enter" to submit your search request. Alternatively, you can enter the wallet address of the transaction signer and return any transactions under that account.

    ![](/docs/images/products/wrapped-token-transfers/guides/fetch-vaa/fetch-vaa-1.webp)

3. Inspect the returned search results. Note that the source transaction ID, current status, transaction details, and the VAA ID are included.

    ![](/docs/images/products/wrapped-token-transfers/guides/fetch-vaa/fetch-vaa-2.webp)

Congratulations! You've now fetched a signed VAA using both the TypeScript SDK and Wormholescan UI. These skills are valuable when developing manual transfer or messaging processes, as well as debugging and testing an integration build.


Prerequisites

## Prerequisites

Before you begin, ensure you have the following:

- [Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm){target=\_blank} installed on your machine.
- [TypeScript](https://www.typescriptlang.org/download/){target=\_blank} installed globally.
- The Wormhole TypeScript SDK version 3.0 or above.
- The contract address for the ERC-20 token you wish to transfer.
- A wallet setup with the following:
    - Private keys for your source and destination chains.
    - A small amount of gas tokens on your source and destination chains.
    - A balance on your source chain of the ERC-20 token you want to transfer.