Flow of a Transfer#
This page outlines the full lifecycle of a Native Token Transfers (NTT) message, covering how transfers are initiated, sent, verified, and completed across supported chains. It highlights the distinct roles of the NTT Manager and Transceivers.
NTT Managers oversee transfers, handle rate-limiting and attestations, and manage multiple transceivers per token. They ensure that tokens are locked or burned on the source chain before being minted or unlocked on the destination chain.
Transceivers route transfers between source and destination managers, ensuring accurate message delivery and token transfers. They operate independently of Wormhole’s core and can support various verification backends.
Transfer Flow#
Cross-chain token transfers using NTT follow these steps:
-
Initiation on the Source Chain
The transfer begins when a user calls the NTT Manager contract on the source chain:- Burning mode: The token is burned from the user's account.
- Locking mode: If the token is native to the source chain, the token is locked in the NTT Manager contract.
-
Outbound Rate Limiting Check
The NTT Manager checks if the transfer amount exceeds the current outbound capacity:- Within capacity: Transfer proceeds immediately.
- Exceeds capacity with queueing: Transfer is queued for later completion after the rate limit window expires.
- Exceeds capacity without queueing: Transfer fails.
-
Message Creation and Distribution
The NTT Manager creates an NTT message containing transfer details and forwards it to all enabled transceivers. Each transceiver packages this into its own message format. -
Cross-Chain Message Transmission
Each transceiver sends the message through its verification network:- Wormhole Transceiver: Uses Wormhole's Guardian network for message attestation and optional automatic relaying.
- Custom Transceivers: Can use any verification backend (validators, multi-sig, etc.).
-
Message Reception and Attestation
On the destination chain, transceivers receive and verify their respective messages:- Each transceiver validates the message according to its verification method.
- Transceivers forward verified messages to the destination NTT Manager.
- The NTT Manager collects attestations from transceivers.
-
Threshold Verification
The destination NTT Manager waits until enough transceivers have attested to the transfer (based on the configured threshold):- Threshold met: Transfer proceeds to execution.
- Threshold not met: Transfer waits for more attestations.
-
Inbound Rate Limiting Check
The NTT Manager checks if the incoming transfer exceeds inbound capacity:- Within capacity: Transfer completes immediately.
- Exceeds capacity: Transfer is queued for later completion.
-
Transfer Completion on Destination Chain
After rate limiting checks pass, the NTT Manager completes the transfer:- Burning mode: New tokens are minted to the recipient.
- Locking mode: If tokens are native to the destination chain, they are released from the contract to the recipient.
Consider the following example: Alice wants to send 100 ALICE tokens from Ethereum to Solana using NTT in burn mode. The ALICE is burned on Ethereum's NTT Manager, transceivers attest to the transfer, and an equivalent amount of ALICE is minted on Solana. The diagram below illustrates this transfer flow.
sequenceDiagram
participant Alice as Alice
participant NttManagerEth as NTT Manager Ethereum<br>(Source Chain)
participant TransceiverEth as Transceivers Ethereum<br>(e.g., Wormhole)
participant GuardianNetwork as Guardians
participant TransceiverSol as Transceivers Solana<br>(e.g., Wormhole)
participant NttManagerSol as NTT Manager Solana<br>(Destination Chain)
Alice->>NttManagerEth: Initiate ALICE transfer<br>(burn 100 ALICE)
NttManagerEth->>NttManagerEth: Check outbound capacity
NttManagerEth->>TransceiverEth: Forward NTT message<br>to transceivers
TransceiverEth->>GuardianNetwork: Send message via<br>verification network
GuardianNetwork->>TransceiverSol: Deliver verified<br>message
TransceiverSol->>NttManagerSol: Attest to transfer
NttManagerSol->>NttManagerSol: Check threshold &<br> inbound capacity
NttManagerSol-->>Alice: Mint 100 ALICE on Solana (complete transfer)
Now, consider Alice wants to send her ALICE back from Solana to Ethereum. The ALICE is burned on Solana's NTT Manager, and the equivalent amount is minted on Ethereum. The diagram below illustrates this reverse transfer flow.
sequenceDiagram
participant Alice as Alice
participant NttManagerSol as NTT Manager Solana<br>(Source Chain)
participant TransceiverSol as Transceivers Solana<br>(e.g., Wormhole)
participant GuardianNetwork as Guardians
participant TransceiverEth as Transceivers Ethereum<br>(e.g., Wormhole)
participant NttManagerEth as NTT Manager Ethereum<br>(Destination Chain)
Alice->>NttManagerSol: Initiate transfer<br>(burn 100 ALICE)
NttManagerSol->>NttManagerSol: Check outbound capacity
NttManagerSol->>TransceiverSol: Forward NTT message<br>to transceivers
TransceiverSol->>GuardianNetwork: Send message via<br>verification network
GuardianNetwork->>TransceiverEth: Deliver verified<br>message
TransceiverEth->>NttManagerEth: Attest to transfer
NttManagerEth->>NttManagerEth: Check threshold &<br> inbound capacity
NttManagerEth-->>Alice: Mint 100 ALICE on Ethereum (complete transfer)
EVM Transfer Flow Details#
Transfer#
The transfer
function is called with details of the transfer, and the TransferSent
event is emitted.
Rate Limiting#
If a transfer is rate limited on the source chain and the shouldQueue
flag is enabled, it is added to an outbound queue. The transfer can be released after the configured _rateLimitDuration
has expired via the completeOutboundQueuedTransfer
method. The OutboundTransferQueued
and OutboundTransferRateLimited
events are emitted.
If the client attempts to release the transfer from the queue before the rateLimitDuration
expires, the contract reverts with an OutboundQueuedTransferStillQueued
error.
Similarly, rate limited transfers on the destination chain are added to an inbound queue. These transfers can be released from the queue via the completeInboundQueuedTransfer
method, and the InboundTransferQueued
event is emitted.
If the client attempts to release the transfer from the queue before the rateLimitDuration
expires, the contract reverts with an InboundQueuedTransferStillQueued
error.
To deactivate the rate limiter, set _rateLimitDuration
to 0 and enable the _skipRateLimiting
field in the NttManager
constructor. Configuring this incorrectly will throw an error. If the rate limiter is deactivated, the inbound and outbound rate limits can be set to 0.
Sending the Message#
Once the NttManager
forwards the message to the transceiver, the message is transmitted via the sendMessage
method. The transceiver enforces the method signature, but transceivers are free to determine their implementation for transmitting messages (e.g., a message routed through the Wormhole transceiver can be sent via Wormhole relaying, a custom relayer or manually published via the core bridge).
Once the message has been transmitted, the contract emits the SendTransceiverMessage
event.
Receiving the Message#
Once a message has been emitted by a transceiver on the source chain, an off-chain process (for example, a relayer) will forward the message to the corresponding transceiver on the recipient chain. The relayer interacts with the transceiver via an entry point to receive messages. For example, the relayer will call the receiveWormholeMessage
method on the WormholeTransceiver
contract to execute the message. The ReceiveRelayedMessage
event is emitted during this process.
This method should also forward the message to the NttManager
on the destination chain. Note that the transceiver interface doesn't declare a signature for this method because receiving messages is specific to each transceiver, and a one-size-fits-all solution would be overly restrictive.
The NttManager
contract allows an M of N threshold for transceiver attestations to determine whether a message can be safely executed. For example, if the threshold requirement is 1, the message will be executed after a single transceiver delivers a valid attestation. If the threshold requirement is 2, the message will only be executed after two transceivers deliver valid attestations. When a transceiver attests to a message, the contract emits the MessageAttestedTo
event.
NTT implements replay protection, so if a given transceiver attempts to deliver a message attestation twice, the contract reverts with the TransceiverAlreadyAttestedToMessage
error. NTT also implements replay protection against re-executing messages. This check also serves as reentrancy protection.
If a message has already been executed, the contract ends execution early and emits the MessageAlreadyExecuted
event instead of reverting via an error. This mitigates the possibility of race conditions from transceivers attempting to deliver the same message when the threshold is less than the total number of available transceivers (i.e., threshold < totalTransceivers) and notifies the client (off-chain process) so they don't attempt redundant message delivery.
Minting or Unlocking#
Once a transfer has been successfully verified, the tokens can be minted (if the mode is "burning") or unlocked (if the mode is "locking") to the recipient on the destination chain. Note that the source token decimals are bounded between 0
and TRIMMED_DECIMALS
as enforced in the wire format. The transfer amount is untrimmed (scaled-up) if the destination chain token decimals exceed TRIMMED_DECIMALS
. Once the appropriate number of tokens have been minted or unlocked to the recipient, the TransferRedeemed
event is emitted.
Solana Transfer Flow Details#
Transfer#
A client calls the transfer_lock
or transfer_burn
instruction based on whether the program is in LOCKING
or BURNING
mode. The program mode is set during initialization. When transferring, the client must specify the amount of the transfer, the recipient chain, the recipient address on the recipient chain, and the boolean flag should_queue
to specify whether the transfer should be queued if it hits the outbound rate limit. If should_queue
is set to false, the transfer reverts instead of queuing if the rate limit is hit.
Note
Using the wrong transfer instruction, i.e., transfer_lock
for a program that is in BURNING
mode, will result in an InvalidMode
error.
Depending on the mode and instruction, the following will be produced in the program logs:
Outbound transfers are always added to an Outbox via the insert_into_outbox
method. This method checks the transfer against the configured outbound rate limit amount to determine whether the transfer should be rate limited. An OutboxItem
is a Solana Account that holds details of the outbound transfer. The transfer can be released from the Outbox immediately if no rate limit is hit. The transfer can be released from the Outbox immediately unless a rate limit is hit, in which case it will only be released after the delay duration associated with the rate limit has expired.
Rate Limiting#
During the transfer process, the program checks rate limits via the consume_or_delay
function. The Solana rate-limiting logic is equivalent to the EVM rate-limiting logic.
If the transfer amount fits within the current capacity:
- Reduce the current capacity.
- Refill the inbound capacity for the destination chain.
- Add the transfer to the Outbox with
release_timestamp
set to the current timestamp so it can be released immediately.
If the transfer amount doesn't fit within the current capacity:
- If
shouldQueue = true
, add the transfer to the Outbox withrelease_timestamp
set to the current timestamp plus the configuredRATE_LIMIT_DURATION
. - If
shouldQueue = false
, revert with aTransferExceedsRateLimit
error.
Sending the Message#
The caller then needs to request each transceiver to send messages via the release_outbound
instruction. To execute this instruction, the caller needs to pass the account of the Outbox item to be released. The instruction will then verify that the transceiver is one of the specified senders for the message. Transceivers then send the messages based on the verification backend they are using.
For example, the Wormhole transceiver sends messages by calling post_message
on the Wormhole program, allowing Guardians to observe and verify the message.
Note
When revert_on_delay
is true, the transaction will revert if the release timestamp hasn't been reached. When revert_on_delay
is false, the transaction succeeds, but the outbound release isn't performed.
The following will be produced in the program logs:
Receiving the Message#
Similar to EVM, transceivers vary in how they receive messages since message relaying and verification methods may differ between implementations.
The Wormhole transceiver receives a verified Wormhole message on Solana via the receive_message
entry point instruction. Callers can use the receive_wormhole_message
Anchor library function to execute this instruction. The instruction verifies the Wormhole Verified Action Approval (VAA) and stores it in a VerifiedTransceiverMessage
account.
The following will be produced in the program logs:
redeem
checks the inbound rate limit and places the message in an Inbox. Logic works similarly to the outbound rate limit mentioned previously.
The following will be produced in the program logs:
Mint or Unlock#
The inbound transfer is released, and the tokens are unlocked or minted to the recipient through either release_inbound_mint
if the mode is BURNING
, or release_inbound_unlock
if the mode is LOCKING
. Similar to transfer, using the wrong transfer instruction (such as release_inbound_mint
for a program that is in locking mode) will result in an InvalidMode
error.
Note
When revert_on_delay
is true, the transaction will revert if the release timestamp hasn't been reached. When revert_on_delay
is false, the transaction succeeds, but the minting/unlocking isn't performed.
Depending on the mode and instruction, the following will be produced in the program logs:
Rate Limiting#
A transfer can be rate limited on both the source and destination chains.
Outbound Rate Limiting (Source Chain)#
- Limits the amount that can be sent from a chain within a time window.
- Queue enabled: Transfers exceeding capacity are queued for later completion.
- Queue disabled: Transfers exceeding capacity fail immediately.
Inbound Rate Limiting (Destination Chain)#
- Limits the amount that can be received on a chain within a time window.
- Transfers exceeding capacity are automatically queued for later completion.
Cancel-Flows#
- Outbound transfers refill inbound capacity on the source chain.
- Inbound transfers refill outbound capacity on the destination chain.
- Prevents capacity exhaustion from frequent bidirectional transfers.
Rate Limit Type | Exceeds Capacity | Queue Setting | Result |
---|---|---|---|
Outbound | Yes | Enabled | Transfer queued on source chain |
Outbound | Yes | Disabled | Transfer fails |
Inbound | Yes | N/A | Transfer queued on destination chain |
Queued Transfer Management#
When transfers are rate limited, NTT provides management functions.
Outbound Queued Transfers#
- Complete: After the rate limit window expires, the user can complete the queued transfer.
- Cancel: The user can cancel their queued transfer and receive tokens back.
Inbound Queued Transfers#
- Complete: After the rate limit window expires, anyone can complete the queued transfer.
- Automatic: Some implementations may auto-complete queued transfers.