# Wormhole llms-full.txt Wormhole. A cross-chain messaging protocol used to move data and assets between blockchains. ## Generated automatically. Do not edit directly. Documentation: https://wormhole.com/docs/ ## List of doc pages: Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/core-messaging/core-contracts.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/core-messaging/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/core-messaging/wormhole-relayers.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/infrastructure/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/infrastructure/relayers/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/infrastructure/relayers/run-relayer.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/infrastructure/spy/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/infrastructure/spy/run-spy.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/multigov/deploy-to-evm.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/multigov/deploy-to-solana.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/multigov/faq.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/multigov/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/multigov/upgrade-evm.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/multigov/upgrade-solana.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/queries/faqs.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/queries/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/queries/overview.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/queries/use-queries.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/reference/chain-ids.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/reference/consistency-levels.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/reference/contract-addresses.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/reference/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/reference/wormhole-formatted-addresses.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/start-building/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/start-building/products.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/start-building/supported-networks.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/start-building/testnet-faucets.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/start-building/use-cases.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/toolkit/cli.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/toolkit/dev-env.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/toolkit/faqs.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/toolkit/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/toolkit/solidity-sdk.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/toolkit/typescript-sdk/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/toolkit/typescript-sdk/protocols-payloads.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/toolkit/typescript-sdk/sdk-layout.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/toolkit/typescript-sdk/vaas-protocols.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/toolkit/typescript-sdk/wormhole-sdk.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/cctp.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/connect/configuration-v0.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/connect/configuration/configure-data.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/connect/configuration/configure-theme.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/connect/configuration/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/connect/faqs.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/connect/features.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/connect/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/connect/overview.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/connect/routes.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/connect/upgrade.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/cli-commands.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/configuration/access-control.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/configuration/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/configuration/rate-limiting.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/deployment-process/deploy-to-evm.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/deployment-process/deploy-to-solana.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/deployment-process/evm-launchpad.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/deployment-process/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/deployment-process/installation.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/deployment-process/post-deployment.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/deployment-process/troubleshooting.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/faqs.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/native-token-transfers/managers-transceivers.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/settlement/faqs.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/settlement/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/settlement/liquidity-layer.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/settlement/solver.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/build/transfers/token-bridge.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/glossary.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/governance/architecture.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/governance/faq.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/governance/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/governance/overview.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/infrastructure/architecture.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/infrastructure/core-contracts.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/infrastructure/guardians.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/infrastructure/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/infrastructure/relayer.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/infrastructure/spy.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/infrastructure/vaas.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/introduction.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/security.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/transfers/cctp.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/transfers/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/transfers/native-token-transfers/architecture.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/transfers/native-token-transfers/deployment.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/transfers/native-token-transfers/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/transfers/native-token-transfers/overview.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/transfers/native-token-transfers/security.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/transfers/settlement/architecture.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/transfers/settlement/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/transfers/settlement/overview.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/learn/transfers/token-bridge.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/connect/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/connect/react-dapp.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/multichain-assets/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/multichain-assets/multichain-token.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/multigov/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/multigov/treasury-proposal.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/settlement/.index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/settlement/.settlement-routes.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/solidity-sdk/cross-chain-contracts.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/solidity-sdk/cross-chain-token-contracts.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/solidity-sdk/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/typescript-sdk/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/typescript-sdk/tokens-via-token-bridge.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/typescript-sdk/usdc-via-cctp.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/wormholescan/index.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/tutorials/wormholescan/replace-signatures.md ## Full content for each doc page Doc-Content: https://wormhole.com/docs/build/core-messaging/core-contracts/ --- BEGIN CONTENT --- --- title: Get Started with Core Contracts description: This guide walks through the key methods of the Core Contracts, providing you with the knowledge needed to integrate them into your cross-chain contracts categories: Basics --- # Get Started with Core Contracts ## Introduction Wormhole's Core Contracts, deployed on each supported blockchain network, enable the fundamental operations of sending and receiving cross-chain messages. While the implementation details of the Core Contracts varies by network, the core functionality remains consistent across chains. Each version of the Core Contract facilitates secure and reliable cross-chain communication, ensuring that developers can effectively publish and verify messages. This guide will walk you through the variations and key methods of the Core Contracts, providing you with the knowledge needed to integrate them into your cross-chain contracts. To learn more about Core Contracts' features and how it works, please refer to the [Core Contracts](/docs/learn/infrastructure/core-contracts/){target=\_blank} page in the Learn section. ## Prerequisites To interact with the Wormhole Core Contract, you'll need the following: - The [address of the Core Contract](/docs/build/reference/contract-addresses/#core-contracts){target=\_blank} on the chains you're deploying your contract on - The [Wormhole chain ID](/docs/build/reference/chain-ids/){target=\_blank} of the chains you're deploying your contract on - The [Wormhole Finality](/docs/build/reference/consistency-levels/){target=\_blank} (consistency) levels (required finality) for the chains you're deploying your contract on ## How to Interact with Core Contracts Before writing your own contracts, it's essential to understand the key functions and events of the Wormhole Core Contracts. The primary functionality revolves around: - **Sending messages** - submitting messages to the Wormhole network for cross-chain communication - **Receiving and verifying messages** - validating messages received from other chains via the Wormhole network While the implementation details of the Core Contracts vary by network, the core functionality remains consistent across chains. ### Sending Messages To send a message, regardless of the environment or chain, the Core Contract is invoked with a message argument from an [emitter](/docs/learn/glossary/#emitter){target=\_blank}. This emitter might be your contract or an existing application such as the [Token Bridge](/docs/learn/transfers/token-bridge/#token-bridge){target=\_blank}. === "EVM" The `IWormhole.sol` interface provides the `publishMessage` function, which can be used to publish a message directly to the Core Contract: ```solidity function publishMessage( uint32 nonce, bytes memory payload, uint8 consistencyLevel ) external payable returns (uint64 sequence); ``` ??? interface "Parameters" `nonce` ++"uint32"++ A free integer field that can be used however you like. Note that changing the `nonce` will result in a different digest. --- `payload` ++"bytes memory"++ The content of the emitted message. Due to the constraints of individual blockchains, it may be capped to a certain maximum length. --- `consistencyLevel` ++"uint8"++ A value that defines the required level of finality that must be reached before the Guardians will observe and attest to emitted events. ??? interface "Returns" `sequence` ++"uint64"++ A unique number that increments for every message for a given emitter (and implicitly chain). This, combined with the emitter address and emitter chain ID, allows the VAA for this message to be queried from the [Wormholescan API](https://docs.wormholescan.io/){target=\_blank}. ??? interface "Example" ```solidity IWormhole wormhole = IWormhole(wormholeAddr); // Get the fee for publishing a message uint256 wormholeFee = wormhole.messageFee(); // Check fee and send parameters // Create the HelloWorldMessage struct HelloWorldMessage memory parsedMessage = HelloWorldMessage({ payloadID: uint8(1), message: helloWorldMessage }); // Encode the HelloWorldMessage struct into bytes bytes memory encodedMessage = encodeMessage(parsedMessage); // Send the HelloWorld message by calling publishMessage on the // wormhole core contract and paying the Wormhole protocol fee. messageSequence = wormhole.publishMessage{value: wormholeFee}( 0, // batchID encodedMessage, wormholeFinality() ); ``` View the complete Hello World example in the [Wormhole Scaffolding](https://github.com/wormhole-foundation/wormhole-scaffolding/tree/main/evm/src/01_hello_world){target=\_blank} repository on GitHub. === "Solana" The `wormhole_anchor_sdk::wormhole` module and the Wormhole program account can be used to pass a message directly to the Core Contract via the `wormhole::post_message` function: ```rs pub fn post_message<'info>( ctx: CpiContext<'_, '_, '_, 'info, PostMessage<'info>>, batch_id: u32, payload: Vec, finality: Finality ) -> Result<()> ``` ??? interface "Parameters" `ctx` ++"CpiContext<'_, '_, '_, 'info, PostMessage<'info>>"++ Provides the necessary context for executing the function, including the accounts and program information required for the Cross-Program Invocation (CPI). ??? child "Type `pub struct CpiContext<'a, 'b, 'c, 'info, T>`" ```rs pub struct CpiContext<'a, 'b, 'c, 'info, T> where T: ToAccountMetas + ToAccountInfos<'info>, { pub accounts: T, pub remaining_accounts: Vec>, pub program: AccountInfo<'info>, pub signer_seeds: &'a [&'b [&'c [u8]]], } ``` For more information, please refer to the [`wormhole_anchor_sdk` Rust docs](https://docs.rs/anchor-lang/0.29.0/anchor_lang/context/struct.CpiContext.html){target=\_blank}. ??? child "Type `PostMessage<'info>`" ```rs pub struct PostMessage<'info> { pub config: AccountInfo<'info>, pub message: AccountInfo<'info>, pub emitter: AccountInfo<'info>, pub sequence: AccountInfo<'info>, pub payer: AccountInfo<'info>, pub fee_collector: AccountInfo<'info>, pub clock: AccountInfo<'info>, pub rent: AccountInfo<'info>, pub system_program: AccountInfo<'info>, } ``` For more information, please refer to the [`wormhole_anchor_sdk` Rust docs](https://docs.rs/wormhole-anchor-sdk/latest/wormhole_anchor_sdk/wormhole/instructions/struct.PostMessage.html){target=\_blank}. --- `batch_id` ++"u32"++ An identifier for the message batch. --- `payload` ++"Vec"++ The data being sent in the message. This is a variable-length byte array that contains the actual content or information being transmitted. To learn about the different types of payloads, check out the [VAAs](/docs/learn/infrastructure/vaas#payload-types){target=\_blank} page. --- `finality` ++"Finality"++ Specifies the level of finality or confirmation required for the message. ??? child "Type `Finality`" ```rs pub enum Finality { Confirmed, Finalized, } ``` ??? interface "Returns" ++"Result<()>"++ The result of the function’s execution. If the function completes successfully, it returns `Ok(())`, otherwise it returns `Err(E)`, indicating that an error occurred along with the details about the error ??? interface "Example" ```rust let fee = ctx.accounts.wormhole_bridge.fee(); // ... Check fee and send parameters let config = &ctx.accounts.config let payload: Vec = HelloWorldMessage::Hello { message }.try_to_vec()?; // Invoke `wormhole::post_message`. wormhole::post_message( CpiContext::new_with_signer( ctx.accounts.wormhole_program.to_account_info(), wormhole::PostMessage { // ... Set fields }, &[ // ... Set seeds ], ), config.batch_id, payload, config.finality.into(), )?; ``` View the complete Hello World example in the [Wormhole Scaffolding](https://github.com/wormhole-foundation/wormhole-scaffolding/tree/main/solana/programs/01_hello_world){target=\_blank} repository on GitHub. Once the message is emitted from the Core Contract, the [Guardian Network](/docs/learn/infrastructure/guardians/){target=\_blank} will observe the message and sign the digest of an Attestation [VAA](/docs/learn/infrastructure/vaas/){target=\_blank}. On EVM chains, the body of the VAA is hashed twice with keccak256 to produce the signed digest message. On Solana, the [Solana secp256k1 program](https://docs.solana.com/developing/runtime-facilities/programs#secp256k1-program){target=\_blank} will hash the message passed. In this case, the argument for the message should be a single hash of the body, not the twice-hashed body. VAAs are [multicast](/docs/learn/infrastructure/core-contracts/#multicast){target=\_blank} by default. This means there is no default target chain for a given message. The application developer decides on the format of the message and its treatment upon receipt. ### Receiving Messages The way a message is received and handled depends on the environment. === "EVM" On EVM chains, the message passed is the raw VAA encoded as binary. The `IWormhole.sol` interface provides the `parseAndVerifyVM` function, which can be used to parse and verify the received message. ```solidity function parseAndVerifyVM( bytes calldata encodedVM ) external view returns (VM memory vm, bool valid, string memory reason); ``` ??? interface "Parameters" `encodedVM` ++"bytes calldata"++ The encoded message as a Verified Action Approval (VAA), which contains all necessary information for verification and processing. ??? interface "Returns" `vm` ++"VM memory"++ The valid parsed VAA, which will include the original `emitterAddress`, `sequenceNumber`, and `consistencyLevel`, among other fields outlined on the [VAAs](/docs/learn/infrastructure/vaas/) page. ??? child "Struct `VM`" ```solidity struct VM { uint8 version; uint32 timestamp; uint32 nonce; uint16 emitterChainId; bytes32 emitterAddress; uint64 sequence; uint8 consistencyLevel; bytes payload; uint32 guardianSetIndex; Signature[] signatures; bytes32 hash; } ``` For more information, refer to the [`IWormhole.sol` interface](https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/interfaces/IWormhole.sol){target=\_blank}. --- `valid` ++"bool"++ A boolean indicating whether the VAA is valid or not. --- `reason` ++"string"++ If the VAA is not valid, a reason will be provided ??? interface "Example" ```solidity function receiveMessage(bytes memory encodedMessage) public { // Call the Wormhole core contract to parse and verify the encodedMessage ( IWormhole.VM memory wormholeMessage, bool valid, string memory reason ) = wormhole().parseAndVerifyVM(encodedMessage); // Perform safety checks here // Decode the message payload into the HelloWorldMessage struct HelloWorldMessage memory parsedMessage = decodeMessage( wormholeMessage.payload ); // Your custom application logic here } ``` View the complete Hello World example in the [Wormhole Scaffolding](https://github.com/wormhole-foundation/wormhole-scaffolding/tree/main/evm/src/01_hello_world){target=\_blank} repository on GitHub. === "Solana" On Solana, the VAA is first posted and verified by the Core Contract, after which it can be read by the receiving contract and action taken. Retrieve the raw message data: ```rs let posted_message = &ctx.accounts.posted; posted_message.data() ``` ??? interface "Example" ```rust pub fn receive_message(ctx: Context, vaa_hash: [u8; 32]) -> Result<()> { let posted_message = &ctx.accounts.posted if let HelloWorldMessage::Hello { message } = posted_message.data() { // Check message // Your custom application logic here Ok(()) } else { Err(HelloWorldError::InvalidMessage.into()) } } ``` View the complete Hello World example in the [Wormhole Scaffolding](https://github.com/wormhole-foundation/wormhole-scaffolding/tree/main/solana/programs/01_hello_world){target=\_blank} repository on GitHub. #### Validating the Emitter When processing cross-chain messages, it's critical to ensure that the message originates from a trusted sender (emitter). This can be done by verifying the emitter address and chain ID in the parsed VAA. Typically, contracts should provide a method to register trusted emitters and check incoming messages against this list before processing them. For example, the following check ensures that the emitter is registered and authorized: ```solidity require(isRegisteredSender(emitterChainId, emitterAddress), "Invalid emitter"); ``` This check can be applied after the VAA is parsed, ensuring only authorized senders can interact with the receiving contract. Trusted emitters can be registered using a method like `setRegisteredSender` during contract deployment or initialization. ```typescript const tx = await receiverContract.setRegisteredSender( sourceChain.chainId, ethers.zeroPadValue(senderAddress as BytesLike, 32) ); await tx.wait(); ``` #### Additional Checks In addition to environment-specific checks that should be performed, a contract should take care to check other [fields in the body](/docs/learn/infrastructure/vaas/){target=\_blank}, including: - **Sequence** - is this the expected sequence number? How should out-of-order deliveries be handled? - **Consistency level** - for the chain this message came from, is the [Wormhole Finality](/docs/build/reference/consistency-levels/){target=\_blank} level enough to guarantee the transaction won't be reverted after taking some action? The VAA digest is separate from the VAA body but is also relevant. It can be used for replay protection by checking if the digest has already been seen. Since the payload itself is application-specific, there may be other elements to check to ensure safety. ## Source Code References For a deeper understanding of the Core Contract implementation for a specific blockchain environment and to review the actual source code, please refer to the following links: - [Algorand Core Contract source code](https://github.com/wormhole-foundation/wormhole/blob/main/algorand/wormhole_core.py){target=\_blank} - [Aptos Core Contract source code](https://github.com/wormhole-foundation/wormhole/tree/main/aptos/wormhole){target=\_blank} - [EVM Core Contract source code](https://github.com/wormhole-foundation/wormhole/tree/main/ethereum/contracts){target=\_blank} ([`IWormhole.sol` interface](https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/interfaces/IWormhole.sol){target=\_blank}) - [NEAR Core Contract source code](https://github.com/wormhole-foundation/wormhole/tree/main/near/contracts/wormhole){target=\_blank} - [Solana Core Contract source code](https://github.com/wormhole-foundation/wormhole/tree/main/solana/bridge/program){target=\_blank} - [Sui Core Contract source code](https://github.com/wormhole-foundation/wormhole/tree/main/sui/wormhole){target=\_blank} - [Terra Core Contract source code](https://github.com/wormhole-foundation/wormhole/tree/main/terra/contracts/wormhole){target=\_blank} --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/core-messaging/ --- BEGIN CONTENT --- --- title: Core Messaging Layer Contracts description: Learn to use Wormhole’s foundational messaging contracts to build multichain apps with direct control over publishing, verifying, relaying, and more. --- # Core Messaging Wormhole-deployed relayers and Core Contracts are essential for sending and receiving multichain messages. Learning to work directly with these building blocks offers a deeper understanding of Wormhole and increased control and customization options. Follow the links below for how-to guides about using Core Contracts and Wormhole-deployed relayers to send, receive, validate, and track multichain messages. ## Simplified Message Flow Wormhole-deployed relayer support is limited to EVM environments. For a complete list of supported EVM environment blockchains, see the [Supported Networks](/docs/build/start-building/supported-networks/){target=\_blank} page. [timeline(wormhole-docs/.snippets/text/build/core-messaging/core-messaging-timeline.json)] ## Next Steps
- :octicons-tools-16:{ .lg .middle} **Get Started with Core Contracts** --- Follow this series of how-to guides about interacting with Core Contracts to send, receive, and validate messages. [:custom-arrow: Get started](/docs/build/core-messaging/core-contracts/#prerequisites) - :octicons-tools-16:{ .lg .middle } **Get Started with Relayers** --- Follow this series of how-to guides about using Wormhole-deployed relayers to send, receive, and track the progress of messages. [:custom-arrow: Get started](/docs/build/core-messaging/wormhole-relayers/#get-started-with-the-wormhole-relayer)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/core-messaging/wormhole-relayers/ --- BEGIN CONTENT --- --- title: Wormhole-Deployed Relayers description: Learn about the Wormhole-deployed relayer configuration for seamless cross-chain messaging between contracts on different EVM blockchains without off-chain deployments. categories: Relayers, Basics --- # Wormhole Relayer ## Introduction The Wormhole-deployed relayers provide a mechanism for contracts on one blockchain to send messages to contracts on another without requiring off-chain infrastructure. Through the Wormhole relayer module, developers can use an untrusted delivery provider to transport VAAs, saving the need to build and maintain custom relaying solutions. The option to [run a custom relayer](/docs/infrastructure/relayers/run-relayer/) is available for more complex needs. This section covers the components and interfaces involved in using the Wormhole relayer module, such as message sending and receiving, delivery guarantees, and considerations for building reliable and efficient cross-chain applications. Additionally, you'll find details on how to handle specific implementation scenarios and track message delivery progress using the Wormhole CLI tool. ## Get Started with the Wormhole Relayer Before getting started, it's important to note that the Wormhole-deployed relayer configuration is currently **limited to EVM environments**. The complete list of EVM environment blockchains is on the [Supported Networks](/docs/build/start-building/supported-networks/) page. To interact with the Wormhole relayer, you'll need to create contracts on the source and target chains to handle the sending and receiving of messages. No off-chain logic needs to be implemented to take advantage of Wormhole-powered relaying.
![Wormhole Relayer](/docs/images/build/core-messaging/wormhole-relayers/relayer-1.webp)
The components outlined in blue must be implemented.
### Wormhole Relayer Interfaces There are three relevant interfaces to discuss when utilizing the Wormhole relayer module: - [**`IWormholeRelayer`**](https://github.com/wormhole-foundation/wormhole/blob/main/relayer/ethereum/contracts/interfaces/relayer/IWormholeRelayer.sol){target=\_blank} - the primary interface by which you send and receive messages. It allows you to request the sending of messages and VAAs - [**`IWormholeReceiver`**](https://github.com/wormhole-foundation/wormhole/blob/main/relayer/ethereum/contracts/interfaces/relayer/IWormholeReceiver.sol){target=\_blank} - this is the interface you are responsible for implementing. It allows the selected delivery provider to deliver messages/VAAs to your contract - [**`IDeliveryProvider`**](https://github.com/wormhole-foundation/wormhole/blob/main/relayer/ethereum/contracts/interfaces/relayer/IDeliveryProvider.sol){target=\_blank} - this interface represents the delivery pricing information for a given relayer network. Each delivery provider implements this on every blockchain they support delivering from ## Interact with the Wormhole Relayer To start interacting with the Wormhole relayer in your contracts, you'll need to import the `IWormholeRelayer` interface and set up a reference using the contract address to the Wormhole-deployed relayer on the supported network of your choice. To easily integrate with the Wormhole relayer interface, you can use the [Wormhole Solidity SDK](https://github.com/wormhole-foundation/wormhole-solidity-sdk){target=\_blank}. To retrieve the contract address of the Wormhole relayer, refer to the Wormhole relayer section on the [Contract Addresses](/docs/build/reference/contract-addresses/#wormhole-relayer) reference page. Your initial set up should resemble the following: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; import "wormhole-solidity-sdk/interfaces/IWormholeRelayer.sol"; contract Example { IWormholeRelayer public wormholeRelayer; constructor(address _wormholeRelayer) { wormholeRelayer = IWormholeRelayer(_wormholeRelayer); } } ``` The code provided sets up the basic structure for your contract to interact with the Wormhole relayer using the address supplied to the constructor. By leveraging methods from the `IWormholeRelayer` interface, you can implement message sending and receiving functionalities. The following sections will detail the specific methods you need to use for these tasks. ### Send a Message To send a message to a contract on another EVM chain, you can call the `sendPayloadToEvm` method provided by the `IWormholeRelayer` interface. ```solidity function sendPayloadToEvm( // Chain ID in Wormhole format uint16 targetChain, // Contract Address on target chain we're sending a message to address targetAddress, // The payload, encoded as bytes bytes memory payload, // How much value to attach to the delivery transaction uint256 receiverValue, // The gas limit to set on the delivery transaction uint256 gasLimit ) external payable returns ( // Unique, incrementing ID, used to identify a message uint64 sequence ); ``` !!! tip To reduce transaction confirmation time, you can lower the consistency level using the [`sendToEvm`](https://github.com/wormhole-foundation/wormhole/blob/v{{repositories.wormhole.version}}/sdk/js/src/relayer/relayer/send.ts#L33){target=\_blank} method. The `sendPayloadToEvm` method is marked `payable` to receive fee payment for the transaction. The value to attach to the invocation is determined by calling the `quoteEVMDeliveryPrice`, which provides an estimate of the cost of gas on the target chain. ```solidity function quoteEVMDeliveryPrice( // Chain ID in Wormhole format uint16 targetChain, // How much value to attach to delivery transaction uint256 receiverValue, // The gas limit to attach to the delivery transaction uint256 gasLimit ) external view returns ( // How much value to attach to the send call uint256 nativePriceQuote, uint256 targetChainRefundPerGasUnused ); ``` This method should be called before sending a message, and the value returned for `nativePriceQuote` should be attached to the call to send the payload to cover the transaction's cost on the target chain. In total, sending a message across EVM chains can be as simple as getting a fee quote and sending the message as follows: ```solidity // Get a quote for the cost of gas for delivery (cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( targetChain, valueToSend, GAS_LIMIT ); // Send the message wormholeRelayer.sendPayloadToEvm{value: cost}( targetChain, targetAddress, abi.encode(payload), valueToSend, GAS_LIMIT ); ``` ### Receive a Message To receive a message using a Wormhole relayer, the target contract must implement the [`IWormholeReceiver`](https://github.com/wormhole-foundation/wormhole-relayer-solidity-sdk/blob/main/src/interfaces/IWormholeReceiver.sol){target=\_blank} interface, as shown in the [previous section](#interact-with-the-wormhole-relayer). ```solidity function receiveWormholeMessages( bytes memory payload, // Message passed by source contract bytes[] memory additionalVaas, // Any additional VAAs that are needed (Note: these are unverified) bytes32 sourceAddress, // The address of the source contract uint16 sourceChain, // The Wormhole chain ID bytes32 deliveryHash // A hash of contents, useful for replay protection ) external payable; ``` The logic inside the function body may be whatever business logic is required to take action on the specific payload. ## Delivery Guarantees The Wormhole relayer protocol is intended to create a service interface whereby mutually distrustful integrators and delivery providers can work together to provide a seamless dApp experience. You don't trust the delivery providers with your data, and the delivery providers don't trust your smart contract. The primary agreement between integrators and delivery providers is that when a delivery is requested, the provider will attempt to deliver the VAA within the provider's stated delivery timeframe. This creates a marketplace whereby providers can set different price levels and service guarantees. Delivery providers effectively accept the slippage risk premium of delivering your VAAs in exchange for a set fee rate. Thus, the providers agree to deliver your messages even if they do so at a loss. Delivery providers should set their prices such that they turn a profit on average but not necessarily on every single transfer. Thus, some providers may choose to set higher rates for tighter guarantees or lower rates for less stringent guarantees. ## Delivery Statuses All deliveries result in one of the following four outcomes before the delivery provider's delivery timeframe. When they occur, these outcomes are emitted as EVM events from the Wormhole relayer contract. The four possible outcomes are: - (0) Delivery Success - (1) Receiver Failure - (2) Forward Request Success - (3) Forward Request Failure A receiver failure is a scenario in which the selected provider attempted the delivery but it could not be completely successfully. The three possible causes for a delivery failure are: - The target contract does not implement the `IWormholeReceiver` interface - The target contract threw an exception or reverted during the execution of `receiveWormholeMessages` - The target contract exceeded the specified `gasLimit` while executing `receiveWormholeMessages` All three of these scenarios can be avoided with correct design by the integrator, and thus, it is up to the integrator to resolve them. Any other scenario that causes a delivery to not be performed should be considered an outage by some component of the system, including potentially the blockchains themselves. `Forward Request Success` and `Forward Failure` represent when the delivery succeeded and the user requested a forward during the delivery. If the user has enough funds left over as a refund to complete the forward, the forward will be executed, and the status will be `Forward Request Success`. Otherwise, it will be `Forward Request Failure`. ## Other Considerations Some implementation details should be considered during development to ensure safety and a pleasant UX. Ensure that your engineering efforts have appropriately considered each of the following areas: - Receiving a message from a relayer - Checking for expected emitter - Calling `parseAndVerify` on any additional VAAs - Replay protection - Message ordering (no guarantees on order of messages delivered) - Forwarding and call chaining - Refunding overpayment of `gasLimit` - Refunding overpayment of value sent ## Track the Progress of Messages with the Wormhole CLI While no off-chain programs are required, a developer may want to track the progress of messages in flight. To track the progress of messages in flight, use the [Wormhole CLI](/docs/build/toolkit/cli/) tool's `status` subcommand. As an example, you can use the following commands to track the status of a transfer by providing the environment, origin network, and transaction hash to the `worm status` command: === "Mainnet" ```bash worm status mainnet ethereum INSERT_TRANSACTION_HASH ``` === "Testnet" ```bash worm status testnet ethereum INSERT_TRANSACTION_HASH ``` See the [Wormhole CLI tool docs](/docs/build/toolkit/cli/) for installation and usage. ## Step-by-Step Tutorial For detailed, step-by-step guidance on creating cross-chain contracts that interact with the Wormhole relayer, refer to the [Create Cross-Chain Contracts](/docs/tutorials/solidity-sdk/cross-chain-contracts/) tutorial. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/ --- BEGIN CONTENT --- --- title: Build with Wormhole description: Learn how to start building multichain solutions on Wormhole, with tips to get started, an overview of the toolkit, and an introduction to the protocols. template: root-index-page.html --- # Build ## Quick Start: Step-by-Step Tutorials If you learn best by building, start here to build with the Wormhole TypeScript SDK: - [**Transfer Tokens via the Token Bridge**](/docs/tutorials/typescript-sdk/tokens-via-token-bridge/){target=\_blank} - use the [Wormhole SDK's](/docs/build/toolkit/typescript-sdk/){target=\_blank} Token Bridge method to move wrapped assets across networks - [**Transfer USDC via CCTP and Wormhole SDK**](/docs/tutorials/typescript-sdk/usdc-via-cctp/){target=\_blank} - combine the [Wormhole SDK](/docs/build/toolkit/typescript-sdk/){target=\_blank} and Circle's Cross-Chain Transfer Protocol (CCTP) to bridge native USDC across networks Alternatively, start here to work with Wormhole contracts directly: - [**Create Multichain Messaging Contracts**](/docs/tutorials/solidity-sdk/cross-chain-contracts/) - create mulitchain contracts using Wormhole's Solidity SDK. You will deploy smart contracts and send messages across chains - [**Create Multichain Token Transfer Contracts**](/docs/tutorials/solidity-sdk/cross-chain-token-contracts/) - create multichain token transfers using Wormhole's Solidity SDK. You will build and deploy smart contracts to send tokens from one blockchain to another ## Builder Essentials Access essential information and tools quickly:
- :octicons-tools-16:{ .lg .middle } **Supported Networks** --- Supported blockchains by environment - main or testnet availability and quick links to the website, documentation, and block explorer for each network. [:custom-arrow: Supported Networks](/docs/build/start-building/supported-networks/) - :octicons-tools-16:{ .lg .middle } **Testnet Faucets** --- Links to testnet token faucets for supported networks. [:custom-arrow: Get Testnet Tokens](/docs/build/start-building/testnet-faucets/) - :octicons-tools-16:{ .lg .middle } **Wormhole TypeScript SDK** --- Your guide to Wormhole SDK installation and usage, concepts overview, and code samples. [:custom-arrow: Wormhole TypeScript SDK](/docs/build/toolkit/typescript-sdk/wormhole-sdk/) - :octicons-tools-16:{ .lg .middle } **Reference** --- Wormhole chain ID, contract address, address formatting and conversion, and consistency information. [:custom-arrow: Reference](/docs/build/reference/)
## Integrate Transfer Products
- :octicons-tools-16:{ .lg .middle } **Multichain Transfers** --- - **Connect UI widget** - easy-to-use UI for multichain asset transfers via Wormhole in a web application - **Native Token Transfers (NTT)** - native asset transfer, without the need for wrapped assets - **Token Bridge** - transfer wrapped assets with optional message payloads - **Settlement** - intent-based solution enabling fast and efficient asset transfers across Ethereum, Solana, Sui, and more [:custom-arrow: Build Multichain Transfers](/docs/build/transfers/)
## Access Real-Time Data
- :octicons-tools-16:{ .lg .middle } **Real-Time Data** --- - **Wormhole Queries** - on-demand access to Guardian-attested on-chain data via a simple REST endpoint to initiate an off-chain request via a proxy - **Supported query types** - includes query equivalents for `eth_call` (with options for timestamp and finality), `sol_account`, and `sol_pda` - **Use familiar endpoints** - make calls against the RPC provider endpoints you already know and use [:custom-arrow: Build with Queries](/docs/build/queries/)
## Multichain Governance
- :octicons-tools-16:{ .lg .middle } **MultiGov** --- - **Wormhole's MultiGov** - a multichain governance system using Wormhole for seamless voting and proposal execution across multiple blockchain networks - **Hub-and-spoke model** - spoke chain contracts handle local voting and proposals with results sent to the hub for vote aggregation and tallying, proposal management, and coordinating governance across connected chains - **Wormhole security** - moving vote weight checkpoints, timelocks, and Wormhole verification keep governance activity secure [:custom-arrow: Build with MultiGov](/docs/build/multigov/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/infrastructure/ --- BEGIN CONTENT --- --- title: Run Infrastructure Services description: Follow the guides in this section to learn how to run off-chain infrastructure services, such as running a spy or a customized relayer. # template: root-index-page.html --- # Infrastructure ## Get Started Follow the guides in this section to learn how to run off-chain infrastructure services, such as running a spy or a customized relayer.
- :octicons-terminal-16:{ .lg .middle } **Relayers** --- Learn how to develop your own custom off-chain relaying service, giving you greater control and flexibility than using Wormhole-deployed relayers. [:custom-arrow: Run a relayer](/docs/infrastructure/relayers/run-relayer/)
- :octicons-terminal-16:{ .lg .middle } **Spy** --- Learn how to run a Spy locally to listen for and forward messages (Verifiable Action Approvals, or VAAs) published on the Wormhole network. [:custom-arrow: Run a Spy](/docs/infrastructure/spy/run-spy/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/infrastructure/relayers/ --- BEGIN CONTENT --- --- title: Relayers description: Learn how to develop your own custom off-chain relaying service, giving you greater control and flexibility than using Wormhole-deployed relayers. categories: Relayers --- # Relayers ## Get Started
- :octicons-terminal-16:{ .lg .middle } **Run a Custom Relayer** --- This section guides you through developing your own custom off-chain relaying service, giving you greater control and flexibility than using Wormhole-deployed relayers.
Benefits of running your own relayer: - You can add logic to customize the delivery of messages - You can perform off-chain computations resulting in optimized gas costs
Requirements for running your own relayer: - You are responsible for developing and hosting your relayer - You are responsible for paying target chain fees - You are responsible for the liveness of your relayer
[:custom-arrow: Get started now](/docs/infrastructure/relayers/run-relayer/)
## Additional Resources
- :octicons-question-16:{ .lg .middle } **What is a Relayer?** --- Learn about what a relayer is, what role it plays in the delivery of cross-chain messages, and the different types of relayers in the Wormhole ecosystem. [:custom-arrow: Learn more about relayers](/docs/learn/infrastructure/relayer/) - :octicons-gear-16:{ .lg .middle } **Simplify the Development Process** --- Use the Wormhole Relayer Engine package as a foundational toolkit to develop your own customized off-chain relaying service, enabling tailored message handling. [:custom-arrow: Check out the Relayer Engine source code](https://github.com/wormhole-foundation/relayer-engine){target=\_blank}
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/infrastructure/relayers/run-relayer/ --- BEGIN CONTENT --- --- title: Run a Relayer description: Learn how to build and configure your own off-chain custom relaying solution to relay Wormhole messages for your applications using the Relayer Engine. categories: Relayers --- # Run a Custom Relayer ## Introduction Relayers play a crucial role in cross-chain communication, ensuring that messages are transferred seamlessly between different blockchains. While Wormhole relayers provide a reliable way to handle these transfers, they might not always meet every application's unique requirements. Custom relayers address these limitations by offering tailored solutions that cater to the distinct needs of your application. Developing a custom relayer gives you complete control over message processing, delivery mechanisms, and integration with existing systems. This customization allows for optimized performance and the ability to implement specific features that Wormhole-deployed relayers might not support. A custom relayer might be as simple as an in-browser process that polls the API for the availability of a VAA after submitting a transaction and delivers it to the target chain. It might also be implemented with a Spy coupled with some daemon listening for VAAs from a relevant chain ID and emitter, then taking action when one is observed. This guide teaches you how to set up and configure a custom relayer for efficient message handling. You'll start by understanding how to uniquely identify a VAA using its emitter address, sequence ID, and chain ID. Then, you'll explore the Relayer Engine, a package that provides a framework for building custom relayers, and learn how to fetch and handle VAAs using the Wormhole SDK. ## Get Started with a Custom Relayer To start building a custom relayer, it's essential to grasp the components you'll be managing as part of your relaying service. Your relayer must be capable of retrieving and delivering VAAs.
![Custom relayer](/docs/images/build/infrastructure/relayers/run-relayer/relayer-1.webp)
The off-chain components outlined in blue must be implemented.
### How to Uniquely Identify a VAA Regardless of the environment, to get the VAA you intend to relay, you need: - The `emitter` address - The `sequence` ID of the message you're interested in - The `chainId` for the chain that emitted the message With these three components, you're able to uniquely identify a VAA and process it. ## Use the Relayer Engine The [`relayer-engine`](https://github.com/wormhole-foundation/relayer-engine){target=\_blank} is a package that provides the structure and a starting point for a custom relayer. With the Relayer Engine, a developer can write specific logic for filtering to receive only the messages they care about. Once a Wormhole message is received, the developer may apply additional logic to parse custom payloads or submit the Verifiable Action Approvals (VAA) to one or many destination chains. To use the Relayer Engine, a developer may specify how to relay Wormhole messages for their app using an idiomatic Express/Koa middleware-inspired API, then let the library handle all the details. ### Install the Relayer Engine First, install the `relayer-engine` package with your favorite package manager: ```bash npm i @wormhole-foundation/relayer-engine ``` ### Get Started with the Relayer Engine In the following example, you'll: 1. Set up a `StandardRelayerApp`, passing configuration options for our relayer 2. Add a filter to capture only those messages our app cares about, with a callback to do _something_ with the VAA once received 3. Start the relayer app ```typescript import { Environment, StandardRelayerApp, StandardRelayerContext, } from '@wormhole-foundation/relayer-engine'; import { CHAIN_ID_SOLANA } from '@certusone/wormhole-sdk'; (async function main() { // Initialize relayer engine app and pass relevant config options const app = new StandardRelayerApp( Environment.TESTNET, // Other app specific config options can be set here for things // like retries, logger, or redis connection settings { name: 'ExampleRelayer', } ); // Add a filter with a callback that will be invoked // on finding a VAA that matches the filter app.chain(CHAIN_ID_SOLANA).address( // Emitter address on Solana 'DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe', // Callback function to invoke on new message async (ctx, next) => { const vaa = ctx.vaa; const hash = ctx.sourceTxHash; console.log( `Got a VAA with sequence: ${vaa.sequence} from with txhash: ${hash}` ); } ); // Add and configure any other middleware here // Start app. Blocks until unrecoverable error or process is stopped await app.listen(); })(); ``` The first meaningful line instantiates the `StandardRelayerApp`, a subclass of the `RelayerApp` with standard defaults. ```typescript export class StandardRelayerApp< ContextT extends StandardRelayerContext = StandardRelayerContext, > extends RelayerApp { // ... constructor(env: Environment, opts: StandardRelayerAppOpts) { ``` The only field you pass in the `StandardRelayerAppOpts` is the name to help identify log messages and reserve a namespace in Redis. ??? code "`StandardRelayerAppOpts`" Other options can be passed to the `StandardRelayerApp` constructor to configure the app further. ```typescript wormholeRpcs?: string[]; // List of URLs from which to query missed VAAs concurrency?: number; // How many concurrent requests to make for workflows spyEndpoint?: string; // The hostname and port of our Spy logger?: Logger; // A custom Logger privateKeys?: Partial<{ [k in ChainId]: any[]; }>; // A set of keys that can be used to sign and send transactions tokensByChain?: TokensByChain; // The token list we care about workflows?: { retries: number; }; // How many times to retry a given workflow providers?: ProvidersOpts; // Configuration for the default providers fetchSourceTxhash?: boolean; // whether or not to get the original transaction ID/hash // Redis config redisClusterEndpoints?: ClusterNode[]; redisCluster?: ClusterOptions; redis?: RedisOptions; ``` The next meaningful line in the example adds a filter middleware component. This middleware will cause the relayer app to request a subscription from the Spy for any VAAs that match the criteria and invoke the callback with the VAA. If you'd like your program to subscribe to `multiple` chains and addresses, you can call the same method several times or use the `multiple` helper. ```typescript app.multiple( { [CHAIN_ID_SOLANA]: 'DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe', [CHAIN_ID_ETH]: ['0xabc1230000000...', '0xdef456000...'], }, myCallback ); ``` The last line in the simple example runs `await app.listen()`, which starts the relayer engine. Once started, the Relayer Engine issues subscription requests to the Spy and begins any other workflows (e.g., tracking missed VAAs). This will run until the process is killed or encounters an unrecoverable error. To gracefully shut down the relayer, call `app.stop()`. The source code for this example is available in the [`relayer-engine` repository](https://github.com/wormhole-foundation/relayer-engine/blob/main/examples/simple/src/app.ts){target=\_blank}. ## Start Background Processes !!! note These processes _must_ be running for the relayer app below to work. Next, you must start a Spy to listen for available VAAs published on the Guardian network. You also need a persistence layer. This example uses Redis. More details about the Spy are available in the [Spy Documentation](/docs/learn/infrastructure/spy){target=\_blank}. ### Wormhole Network Spy For our relayer app to receive messages, a local Spy must be running that watches the Guardian network. Our relayer app will receive updates from this Spy. === "Mainnet Spy" ```bash docker run --pull=always --platform=linux/amd64 \ -p 7073:7073 \ --entrypoint /guardiand ghcr.io/wormhole-foundation/guardiand:latest \ spy \ --nodeKey /node.key \ --spyRPC "[::]:7073" \ --env mainnet ``` === "Testnet Spy" ```bash docker run --pull=always --platform=linux/amd64 \ -p 7073:7073 \ --entrypoint /guardiand ghcr.io/wormhole-foundation/guardiand:latest \ spy \ --nodeKey /node.key \ --spyRPC "[::]:7073" \ --env testnet ``` ### Redis Persistence !!! note While you're using [Redis](https://redis.io/docs/latest/develop/get-started/){target=\_blank} here, the persistence layer can be swapped out for some other database by implementing the appropriate [interface](https://github.com/wormhole-foundation/relayer-engine/blob/main/relayer/storage/redis-storage.ts){target=\_blank}. A Redis instance must also be available to persist job data for fetching VAAs from the Spy. ```bash docker run --rm -p 6379:6379 --name redis-docker -d redis ``` ## Use the Wormhole SDK !!! note The example below uses the legacy [`@certusone/wormhole-sdk`](https://www.npmjs.com/package/@certusone/wormhole-sdk){target=\_blank}, which is still supported and used in the Relayer Engine but is no longer actively maintained. For most use cases, it is recommend to use the latest [`@wormhole-foundation/sdk`](https://www.npmjs.com/package/@wormhole-foundation/sdk){target=\_blank}. You can also use the Wormhole SDK to poll the Guardian RPC until a signed VAA is ready using the SDK's `getSignedVAAWithRetry` function. ```ts import { getSignedVAAWithRetry, parseVAA, CHAIN_ID_SOLANA, CHAIN_ID_ETH, } from '@certusone/wormhole-sdk'; const RPC_HOSTS = [ /* ...*/ ]; async function getVAA( emitter: string, sequence: string, chainId: number ): Promise { // Wait for the VAA to be ready and fetch it from the guardian network const { vaaBytes } = await getSignedVAAWithRetry( RPC_HOSTS, chainId, emitter, sequence ); return vaaBytes; } const vaaBytes = await getVAA('INSERT_EMITTER_ADDRESS', 1, CHAIN_ID_ETH); ``` Once you have the VAA, the delivery method is chain-dependent. === "EVM" On EVM chains, the bytes for the VAA can be passed directly as an argument to an ABI method. ```ts // Set up eth wallet const ethProvider = new ethers.providers.StaticJsonRpcProvider( 'INSERT_RPC_URL' ); const ethWallet = new ethers.Wallet('INSERT_PRIVATE_KEY', ethProvider); // Create client to interact with our target app const ethHelloWorld = HelloWorld__factory.connect( 'INSERT_CONTRACT_ADDRESS', ethWallet ); // Invoke the receiveMessage on the ETH contract and wait for confirmation const receipt = await ethHelloWorld .receiveMessage(vaaBytes) .then((tx: ethers.ContractTransaction) => tx.wait()) .catch((msg: any) => { console.error(msg); return null; }); ``` === "Solana" On Solana, the VAA is first posted to the core bridge, and then a custom transaction is prepared to process and validate the VAA. ```ts import { CONTRACTS } from '@certusone/wormhole-sdk'; export const WORMHOLE_CONTRACTS = CONTRACTS[NETWORK]; export const CORE_BRIDGE_PID = new PublicKey(WORMHOLE_CONTRACTS.solana.core); // First, post the VAA to the core bridge await postVaaSolana( connection, wallet.signTransaction, CORE_BRIDGE_PID, wallet.key(), vaaBytes ); const program = createHelloWorldProgramInterface(connection, programId); const parsed = isBytes(wormholeMessage) ? parseVaa(wormholeMessage) : wormholeMessage; const ix = program.methods .receiveMessage([...parsed.hash]) .accounts({ payer: new PublicKey(payer), config: deriveConfigKey(programId), wormholeProgram: new PublicKey(wormholeProgramId), posted: derivePostedVaaKey(wormholeProgramId, parsed.hash), foreignEmitter: deriveForeignEmitterKey(programId, parsed.emitterChain), received: deriveReceivedKey( programId, parsed.emitterChain, parsed.sequence ), }) .instruction(); const transaction = new Transaction().add(ix); const { blockhash } = await connection.getLatestBlockhash(commitment); transaction.recentBlockhash = blockhash; transaction.feePayer = new PublicKey(payerAddress); const signed = await wallet.signTxn(transaction); const txid = await connection.sendRawTransaction(signed); await connection.confirmTransaction(txid); ``` --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/infrastructure/spy/ --- BEGIN CONTENT --- --- title: Spy description: Discover everything you need to about the Wormhole Spy, a daemon that watches the Guardian Network and subscribe to signed messages. --- # Spy ## Get Started
- :octicons-terminal-16:{ .lg .middle } **Run a Spy** --- The content in this section shows you how you can run your own infrastructure and spin up a Spy daemon locally to subscribe to a stream of messages, also known as Verifiable Action Approvals (VAAs). [:custom-arrow: Get started now](/docs/infrastructure/spy/run-spy/)
## Additional Resources
- :octicons-question-16:{ .lg .middle } **What is a Spy?** --- Learn about what a Spy is and what role it plays in the delivery of cross-chain messages. [:custom-arrow: Learn more about Spies](/docs/learn/infrastructure/spy/) - :octicons-code-16:{ .lg .middle } **Interact with a Spy** --- Use the Wormhole Spy SDK to subscribe to the stream of signed messages. [:custom-arrow: Use the Wormhole Spy SDK](https://github.com/wormhole-foundation/wormhole/blob/main/spydk/js/README.md){target=\_blank} - :octicons-bookmark-16:{ .lg .middle } **Alternative Implementations** --- Check out Beacon, an alternative highly available version of the Wormhole Spy. [:custom-arrow: Use Pyth Beacon](https://github.com/pyth-network/beacon){target=\_blank}
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/infrastructure/spy/run-spy/ --- BEGIN CONTENT --- --- title: Run a Spy description: Learn how to run a Spy locally to listen for and forward messages (Verifiable Action Approvals, or VAAs) published on the Wormhole network. --- # Run a Spy ## Introduction The Spy is a lightweight component in the Wormhole infrastructure designed to listen for and forward messages (Verifiable Action Approvals (VAAs)) published on the Wormhole network. Running a Spy locally allows developers to subscribe to a filtered stream of these messages, facilitating the development of custom relayers or other integrations with Wormhole. For a more comprehensive understanding of the Spy and its role within the Wormhole ecosystem, refer to the [Spy Documentation](/docs/learn/infrastructure/spy/){target=\_blank}. ## How to Start a Spy To start a Spy locally, run the following Docker command: === "Mainnet" ```sh docker run --pull=always --platform=linux/amd64 \ -p 7073:7073 \ --entrypoint /guardiand ghcr.io/wormhole-foundation/guardiand:latest \ spy \ --nodeKey /node.key \ --spyRPC "[::]:7073" \ --env mainnet ``` === "Testnet" ```sh docker run --pull=always --platform=linux/amd64 \ -p 7073:7073 \ --entrypoint /guardiand ghcr.io/wormhole-foundation/guardiand:latest \ spy \ --nodeKey /node.key \ --spyRPC "[::]:7073" \ --env testnet ``` If you want to run the Spy built from source, change `ghcr.io/wormhole-foundation/guardiand:latest` to `guardian` after building the `guardian` image. Optionally, add the following flags to skip any VAAs with invalid signatures: === "Mainnet" ```sh --ethRPC https://eth.drpc.org --ethContract 0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B ``` === "Testnet" ```sh --ethRPC https://sepolia.drpc.org/ --ethContract 0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78 ``` Optionally, add the following flags to prevent unbounded log growth: ```sh --log-opt max-size=10m \ --log-opt max-file=3 ``` ## Subscribe to Filtered VAAs Once running, a [gRPC](https://grpc.io/){target=\_blank} client (i.e., your program) can subscribe to a filtered stream of messages (VAAs). Use this [proto-spec file](https://github.com/wormhole-foundation/wormhole/blob/main/proto/spy/v1/spy.proto){target=\_blank} to generate a client for the gRPC service. !!! note If using JavaScript/TypeScript, the [Spydk](https://www.npmjs.com/package/@certusone/wormhole-spydk){target=\_blank} makes setting up a client easier. ## Data Persistence The Spy does not have a built-in persistence layer, so it is typically paired with something like Redis or an SQL database to record relevant messages. The persistence layer needs to implement the appropriate interface. For example, you can check out the [Redis interface](https://github.com/wormhole-foundation/relayer-engine/blob/main/relayer/storage/redis-storage.ts){target=\_blank} used by the Relayer Engine, a package that implements a client and persistence layer for messages received from a Spy subscription. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/multigov/deploy-to-evm/ --- BEGIN CONTENT --- --- title: Deploy MultiGov on EVM Chains description: Set up and deploy MultiGov to EVM locally with step-by-step instructions for configuring, compiling, and deploying smart contracts across chains. categories: MultiGov --- # Deploy MultiGov on EVM Chains This guide provodes instructions to set up and deploy the MultiGov governance system locally. Before diving into the technical deployment, ensure that MultiGov is the right fit for your project’s governance needs by following the steps for the [integration process](/docs/build/multigov/){target=\_blank}. Once your project is approved through the intake process and you’ve collaborated with the Tally team to tailor MultiGov to your requirements, use this guide to configure, compile, and deploy the necessary smart contracts across your desired blockchain networks. This deployment will enable decentralized governance across your hub and spoke chains. ## Prerequisites To interact with MultiGov, you'll need the following: - Install [Foundry](https://book.getfoundry.sh/getting-started/installation){target=\_blank} - Install [Git](https://git-scm.com/downloads){target=\_blank} - Clone the repository: ```bash git clone https://github.com/wormhole-foundation/multigov cd evm # for evm testing/deploying ``` ## Development Setup For developers looking to set up a local MultiGov environment: 1. Install dependencies: ```bash forge install ``` 2. Set up environment variables: ```bash cp .env.example .env ``` Edit `.env` with your specific [configuration](#configuration){target=\_blank} 3. Compile contracts: ```bash forge build ``` 4. Deploy contracts (example for Sepolia testnet): For hub chains: ```bash forge script script/DeployHubContractsSepolia.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast ``` For spoke chains (e.g., Optimism Sepolia): ```bash forge script script/DeploySpokeContractsOptimismSepolia.s.sol --rpc-url $OPTIMISM_SEPOLIA_RPC_URL --broadcast ``` ## Configuration When deploying MultiGov, several key parameters need to be set. Here are the most important configuration points: ### Hub Governor Key Parameters - `initialVotingDelay` ++"uint256"++ - the delay measured in seconds before voting on a proposal begins. For example, `86400` is one day - `initialProposalThreshold` ++"uint256"++ - the number of tokens needed to create a proposal - `initialQuorum` ++"uint256"++ - the minimum number of votes needed for a proposal to be successful - `initialVoteWeightWindow` ++"uint256"++ - a window where the minimum checkpointed voting weight is taken for a given address. The window ends at the vote start for a proposal and begins at the vote start minus the vote weight window. The voting window is measured in seconds, e.g., `86400` is one day !!! note This helps mitigate cross-chain double voting. ### Hub Proposal Extender Key Parameters - `extensionDuration` ++"uint256"++ - the amount of time, in seconds, for which target proposals will be extended. For example, `10800` is three hours - `minimumExtensionDuration` ++"uint256"++ - lower time limit, in seconds, for extension duration. For example, `3600` is one hour ### Spoke Vote Aggregator Key Parameters - `initialVoteWindow` ++"uint256"++ - the moving window in seconds for vote weight checkpoints. These checkpoints are taken whenever an address that is delegating sends or receives tokens. For example, `86400` is one day !!! note This is crucial for mitigating cross-chain double voting ### Hub Evm Spoke Vote Aggregator Key Parameters - `maxQueryTimestampOffset` ++"uint256"++ - the max timestamp difference, in seconds, between the requested target time in the query and the current block time on the hub. For example, `1800` is 30 minutes ### Updateable Governance Parameters The following key parameters can be updated through governance proposals: - `votingDelay` - delay before voting starts (in seconds) - `votingPeriod` - duration of the voting period (in seconds) - `proposalThreshold` - threshold for creating proposals (in tokens) - `quorum` - number of votes required for quorum - `extensionDuration` - the amount of time for which target proposals will be extended (in seconds) - `voteWeightWindow` - window for vote weight checkpoints (in seconds) - `maxQueryTimestampOffset` - max timestamp difference allowed between a query's target time and the hub's block time These parameters can be queried using their respective getter functions on the applicable contract. To update these parameters, a governance proposal must be created, voted on, and executed through the standard MultiGov process. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/multigov/deploy-to-solana/ --- BEGIN CONTENT --- --- title: MultiGov Deployment to Solana description: Learn how to deploy the MultiGov Staking Program on Solana, including setup, funding, deployment, and configuration steps. categories: MultiGov --- # Deploy MultiGov on Solana This guide provides instructions on how to set up and deploy the **MultiGov Staking Program** on Solana. Before proceeding with the deployment, ensure that MultiGov aligns with your project's governance needs by reviewing the system [architecture](/docs/learn/governance/architecture/){target=\_blank}. Once your project setup is complete, follow this guide to configure, compile, and deploy the necessary Solana programs and supporting accounts. This deployment enables decentralized governance participation on Solana as a spoke chain within the MultiGov system. ## Prerequisites To deploy MultiGov on Solana, ensure you have the following installed: - Install [Git](https://git-scm.com/downloads){target=\_blank} - Install [Node.js](https://nodejs.org/){target=\_blank} **`v20.10.0`** - Install [Solana CLI](https://docs.anza.xyz/cli/install/){target=\_blank} **`v1.18.20`** - Install [Anchor](https://www.anchor-lang.com/docs/installation){target=\_blank} **`v0.30.1`** - Install [Rust](https://www.rust-lang.org/tools/install){target=\_blank} **`v1.80.1`** - Install [Docker](https://www.docker.com/get-started/){target=\_blank} - Clone the repository: ```bash git clone https://github.com/wormhole-foundation/multigov.git cd multigov/solana/ ``` ## Build the Project To create a verifiable build of the MultiGov Staking Program, run the following command: ```bash ./scripts/build_verifiable_staking_program.sh ``` Once the build is complete, the compiled artifacts will be available in the `target` folder. ## Set Up the Deployer Account For a successful deployment, you need a funded deployer account on Solana. This account will store the program and execute deployment transactions. In this section, you will create a new keypair, check the account balance, and ensure it has enough SOL tokens to cover deployment costs. If needed, you can fund the account using different methods before deploying. ### Generate a New Keypair To create a new keypair and save it to a file, run the following command: ```bash solana-keygen new --outfile ./app/keypairs/deployer.json ``` ### Check the Deployer Account Address To retrieve the public address of the newly created keypair, run the following command: ```bash solana address -k ./app/keypairs/deployer.json ``` ### Check the Deployer Account Balance To verify the current balance of the deployer account, run the following command: ```bash solana balance -k ./app/keypairs/deployer.json ``` !!! warning When deploying the MultiGov Staking Program, the deployer account must have enough SOL to cover deployment costs and transaction fees. - 7.60219224 SOL for deployment costs - 0.00542 SOL for transaction fees ### Fund the Deployer Account If the account does not have enough SOL, use one of the following methods to add funds. - **Transfer SOL from another account** - if you already have SOL in another account, transfer it using a wallet (Phantom, Solflare, etc.) or in the terminal ```bash solana transfer --from /path/to/funder.json ``` - **Request an airdrop (devnet only)** - if deploying to devnet, you can request free SOL ```bash solana airdrop 2 -k ./app/keypairs/deployer.json ``` - **Use a Solana faucet (devnet only)** - you can use online faucets to receive 10 free SOL - [Solana Faucet](https://faucet.solana.com/){target=\_blank} ## Deploy the MultiGov Staking Program With the deployer account set up and funded, you can deploy the MultiGov Staking Program to the Solana blockchain. This step involves deploying the program, verifying the deployment, and ensuring the necessary storage and metadata are correctly configured. Once the IDL is initialized, the program will be ready for further setup and interaction. ### Deploy the Program Deploy the MultiGov Staking Program using Anchor: ```bash anchor deploy --provider.cluster https://api.devnet.solana.com --provider.wallet ./app/keypairs/deployer.json ``` ### Verify the Deployment After deployment, check if the program is successfully deployed by running the following command: ```bash solana program show INSERT_PROGRAM_ID ``` ### Extend Program Storage If the deployed program requires additional storage space for updates or functionality, extend the program storage using the following command: ```bash solana program extend INSERT_PROGRAM_ID 800000 ``` ### Initialize the IDL To associate an IDL file with the deployed program, run the following command: ```bash anchor idl init --provider.cluster https://api.devnet.solana.com --filepath ./target/idl/staking.json INSERT_PROGRAM_ID ``` ## Configure the Staking Program The final step after deploying the MultiGov Staking Program is configuring it for proper operation. This includes running a series of deployment scripts to initialize key components and set important governance parameters. These steps ensure that staking, governance, and cross-chain communication function as expected. ### Run Deployment Scripts After deploying the program and initializing the IDL, execute the following scripts **in order** to set up the staking environment and necessary accounts. 1. Initialize the MultiGov Staking Program with default settings: ```bash npx ts-node app/deploy/01_init_staking.ts ``` 2. Create an Account Lookup Table (ALT) to optimize transaction processing: ```bash npx ts-node app/deploy/02_create_account_lookup_table.ts ``` 3. Set up airlock accounts: ```bash npx ts-node app/deploy/03_create_airlock.ts ``` 4. Deploy a metadata collector: ```bash npx ts-node app/deploy/04_create_spoke_metadata_collector.ts ``` 5. Configure vote weight window lengths: ```bash npx ts-node app/deploy/05_initializeVoteWeightWindowLengths.ts ``` 6. Deploy the message executor for handling governance messages: ```bash npx ts-node app/deploy/06_create_message_executor.ts ``` ### Set MultiGov Staking Program Key Parameters When deploying MultiGov on Solana, several key parameters need to be set. Here are the most important configuration points: - `maxCheckpointsAccountLimit` ++"u64"++ - the maximum number of checkpoints an account can have. For example, `654998` is used in production, while `15` might be used for testing - `hubChainId` `u16` - the chain ID of the hub network where proposals are primarily managed. For example, `10002` for Sepolia testnet - `hubProposalMetadata` ++"[u8; 20]"++ - an array of bytes representing the address of the Hub Proposal Metadata contract on Ethereum. This is used to identify proposals from the hub - `voteWeightWindowLength` ++"u64"++ - specifies the length of the checkpoint window in seconds in which the minimum voting weight is taken. The window ends at the vote start for a proposal and begins at the vote start minus the vote weight window. The vote weight window helps solve problems such as manipulating votes in a chain - `votingTokenMint` ++"Pubkey"++ - the mint address of the token used for voting - `governanceAuthority` ++"Pubkey"++ - the account's public key with the authority to govern the staking system. The `governanceAuthority` should not be the default Pubkey, as this would indicate an uninitialized or incorrectly configured setup - `vestingAdmin` ++"Pubkey"++ - the account's public key for managing vesting operations. The `vestingAdmin` should not be the default Pubkey, as this would indicate an uninitialized or incorrectly configured setup - `hubDispatcher` ++"Pubkey"++ - the Solana public key derived from an Ethereum address on the hub chain that dispatches messages to the spoke chains. This is crucial for ensuring that only authorized messages from the hub are executed on the spoke --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/multigov/faq/ --- BEGIN CONTENT --- --- title: MultiGov Technical FAQs description: Find answers to common questions about MultiGov, covering cross-chain governance, technical setup, security, proposal creation, and more. categories: MultiGov --- # FAQs ## Technical Questions ### How does MultiGov ensure security in cross-chain communication? MultiGov leverages Wormhole's robust cross-chain communication protocol. It implements several security measures: - Message origin verification to prevent unauthorized governance actions - Timely and consistent data checks to ensure vote aggregation is based on recent and synchronized chain states - Authorized participant validation to maintain the integrity of the governance process - Replay attack prevention by tracking executed messages ### Can MultiGov integrate with any blockchain? MultiGov can potentially integrate with any blockchain supported by Wormhole. However, specific implementations may vary depending on the chain's compatibility with the Ethereum Virtual Machine (EVM) and its smart contract capabilities. [See the full list of supported networks](/docs/build/start-building/supported-networks/). The current implementation of MultiGov supports an EVM hub and both the EVM and SVM for spokes. ### How are votes aggregated across different chains? Votes are collected on each spoke chain using each chain's `SpokeVoteAggregator`. These votes are then transmitted to the HubVotePool on the hub chain for aggregation and tabulation. The `HubEvmSpokeVoteDecoder` standardizes votes from different EVM chains to ensure consistent processing. ### Can governance upgrade from a single chain to MultiGov? Yes! MultiGov can support progressively upgrading from a single-chain governance to MultiGov. Moving to MultiGov requires upgrading the token to NTT and adding Flexible Voting to the original Governor. ## Usage Questions ### How can I create a proposal in MultiGov? Proposals are created on the hub chain using the `HubEvmSpokeAggregateProposer` contract or by calling `propose` on the `HubGovernor`. You need to prepare the proposal details, including targets, values, and calldatas. The proposer's voting weight is aggregated across chains using Wormhole queries to determine eligibility. ### How do I vote on a proposal if I hold tokens on a spoke chain? You can vote on proposals via the `SpokeVoteAggregator` contract on the respective spoke chain where you hold your tokens. The votes are then automatically forwarded to the hub chain for aggregation. ### How are approved proposals executed across multiple chains? When a proposal is approved and the timelock period elapses, it's first executed on the hub chain. A proposal can include a cross-chain message by including a call to `dispatch` on the `HubMessageDispatcher`, which sends a message to the relevant spoke chains. On each spoke chain, the `SpokeMessageExecutor` receives, verifies, and automatically executes the instructions using the `SpokeAirlock` as the `msg.sender`. ## Implementation Questions ### What are the requirements for using MultiGov? To use MultiGov, your DAO must meet the following requirements: - **ERC20Votes token** - your DAO's token must implement the `ERC20Votes` standard and support `CLOCK_MODE` timestamps for compatibility with cross-chain governance - **Flexible voting support** - your DAO's Governor must support Flexible Voting to function as the Hub Governor. If your existing Governor does not support Flexible Voting, you can upgrade it to enable this feature ### What do I need to set up MultiGov for my project? Get started by filling out the form below: https://www.tally.xyz/get-started Tally will reach out to help get your DAO set up with MultiGov. To set up testing MultiGov for your DAO, you'll need: - [Foundry](https://book.getfoundry.sh/getting-started/installation){target=\_blank} and [Git](https://git-scm.com/downloads){target=\_blank} installed - Test ETH on the testnets you plan to use (e.g., Sepolia for hub, Optimism Sepolia for spoke) - Modify and deploy the hub and spoke contracts using the provided scripts - Set up the necessary environment variables and configurations ### Can MultiGov be used with non-EVM chains? The current implementation is designed for EVM-compatible chains. However, Solana (non-EVM) voting is currently in development and expected to go live after the EVM contracts. ### How can I customize voting parameters in MultiGov? Voting parameters such as voting delay, voting period, proposal threshold, and quorum (and others) can be customized in the deployment scripts (`DeployHubContractsSepolia.s.sol` and `DeploySpokeContractsOptimismSepolia.s.sol` as examples for their respective chains). Make sure to adjust these parameters according to your DAO's specific needs before deployment. Remember to thoroughly test your MultiGov implementation on testnets before deploying to Mainnet, and have your contracts audited for additional security. ### How does MultiGov handle potential network issues or temporary chain unavailability? MultiGov includes several mechanisms to handle network issues or temporary chain unavailability: 1. **Asynchronous vote aggregation** - votes are aggregated periodically, allowing the system to continue functioning even if one chain is temporarily unavailable 2. **Proposal extension** - the `HubGovernorProposalExtender` allows trusted actors to extend voting periods if needed, which can help mitigate issues caused by temporary network problems 3. **Wormhole retry mechanism** - Wormhole's infrastructure includes retry mechanisms for failed message deliveries, helping ensure cross-chain messages eventually get through 4. **Decentralized relayer network** - Wormhole's decentralized network of relayers helps maintain system availability even if some relayers are offline However, prolonged outages on the hub chain or critical spoke chains could potentially disrupt governance activities. Projects should have contingency plans for such scenarios. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/multigov/ --- BEGIN CONTENT --- --- title: Getting Started with MultiGov description: Learn how to get started with MultiGov, from evaluating cross-chain governance needs to deploying with help from the Tally team. categories: MultiGov --- # MultiGov ## Begin the MultiGov Integration Process Take the following steps to get started with a MultiGov integration: 1. Evaluate if [MultiGov](/docs/learn/governance/) meets your cross-chain governance needs 2. Fill out the intake form on the [Tally website](https://www.tally.xyz/get-started){target=\_blank} 3. The Tally team will review your application and contact you to discuss implementation details 4. Work with the Tally team to customize and deploy MultiGov for your specific use case ## Start Building
- :octicons-rocket-16:{ .lg .middle } **Deploy to EVM** --- Set up and deploy MultiGov on EVM chains with step-by-step instructions for configuring, compiling, and deploying smart contracts across chains. [:custom-arrow: Discover how to deploy MultiGov](/docs/build/multigov/deploy-to-evm/) - :octicons-rocket-16:{ .lg .middle } **Deploy to Solana** --- Set up and deploy the MultiGov Staking Program on Solana with step-by-step instructions for configuring, funding, deploying, and initializing the program. [:custom-arrow: Discover how to deploy MultiGov on Solana](/docs/build/multigov/deploy-to-solana/) - :octicons-file-code-16:{ .lg .middle } **Upgrade MultiGov on EVM Chains** --- Learn the process and key considerations for upgrading MultiGov contracts, ensuring system integrity and careful planning across cross-chain components. [:custom-arrow: Discover how to upgrade MultiGov on EVM Chains](/docs/build/multigov/upgrade-evm/) - :octicons-file-code-16:{ .lg .middle } **Upgrade MultiGov on Solana** --- Learn how to upgrade the MultiGov Staking Program on Solana, including updating the program binary, IDL, and more. [:custom-arrow: Discover how to upgrade MultiGov on Solana](/docs/build/multigov/upgrade-solana/) - :octicons-question-16:{ .lg .middle } **Technical FAQs** --- Find answers to common technical questions about MultiGov, covering technical setup, security, proposal creation, and more. [:custom-arrow: Find the answer to your technical questions](/docs/build/multigov/faq/)
## Additional Resources
- :octicons-book-16:{ .lg .middle } **What is MultiGov?** --- Need to familiarize yourself with MultiGov? Discover everything you need to know about MultiGov, Wormhole's cross-chain governance solution. [:custom-arrow: Learn the basics](/docs/learn/governance/) - :octicons-checklist-16:{ .lg .middle } **Tutorials** --- Access step-by-step tutorials for executing cross-chain governance actions, including treasury management proposals with MultiGov. [:custom-arrow: Learn by building](/docs/tutorials/multigov/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/multigov/upgrade-evm/ --- BEGIN CONTENT --- --- title: Upgrading MultiGov on EVM description: Learn the process and key considerations for upgrading MultiGov on EVM, ensuring system integrity and careful planning across cross-chain components. categories: MultiGov --- # Upgrade MultiGov Contracts on EVM Chains MultiGov is designed to be flexible but stable. Due to the system's complexity and cross-chain nature, upgrades should be rare and carefully considered. When upgrades are necessary, they must be meticulously planned and executed to ensure system integrity and continuity. ## Key Considerations for Upgrades - **`HubGovernor`**: - Not upgradeable. A new deployment requires redeploying several components of the MultiGov system. Refer to the [Process for Major System Upgrade](#process-for-major-system-upgrade) section for more details - **`HubVotePool`**: - Can be replaced by setting a new `HubVotePool` on the `HubGovernor` - Requires re-registering all spokes on the new `HubVotePool` - Must register the query type and implementation for vote decoding by calling `registerQueryType` on the new `HubVotePool` - A new proposal would have to authorize the governor to use the newly created hub vote pool and will also handle registering the appropriate query decoders and registering the appropriate spoke `SpokeVoteAggregators` - **`SpokeMessageExecutor`**: - Upgradeable via [UUPS](https://www.rareskills.io/post/uups-proxy){target=\_blank} proxy pattern - Stores critical parameters in `SpokeMessageExecutorStorage` - **`HubEvmSpokeAggregateProposer`**: - Needs redeployment if `HubGovernor` changes - Requires re-registering all spokes after redeployment - **`HubProposalMetadata`**: - Needs redeployment if `HubGovernor` changes, as it references `HubGovernor` as a parameter - **`SpokeMetadataCollector`**: - Requires redeployment if the hub chain ID changes or if `HubProposalMetadata` changes ## Process for Major System Upgrade 1. **New `HubGovernor` deployment**: - Deploy the new `HubGovernor` contract 1. **Component redeployment**: - Redeploy `HubEvmSpokeAggregateProposer` with the new `HubGovernor` address - Redeploy `HubProposalMetadata` referencing the new `HubGovernor` - If hub chain ID changes, redeploy `SpokeMetadataCollector` on all spoke chains 1. **`HubVotePool` update**: - Set the new `HubVotePool` on the new `HubGovernor` - Register all spokes on the new `HubVotePool` - Register the query type and implementation for vote decoding (`HubEvmSpokeVoteDecoder`) 1. **Spoke re-registration**: - Re-register all spokes on the new `HubEvmSpokeAggregateProposer` 1. **Verification and testing**: - Conduct thorough testing of the new system setup - Verify all cross-chain interactions are functioning correctly 1. **System transition and deprecation**: - Create a proposal to switch the timelock to the new governor - Communicate clearly to the community what changes were made 1. **Monitoring**: - Implement a transition period where the new system is closely monitored - Address any issues that arise promptly ## Important Considerations - Always prioritize system stability, upgrades should only be performed when absolutely necessary - Thoroughly audit all new contract implementations before proposing an upgrade - Account for all affected components across all chains in the upgrade plan - Provide comprehensive documentation for the community about the upgrade process and any changes in functionality - Always test upgrades extensively on testnets before implementing in production --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/multigov/upgrade-solana/ --- BEGIN CONTENT --- --- title: Upgrading MultiGov on Solana description: Learn the process and key considerations for upgrading MultiGov on Solana, ensuring system integrity and careful planning across cross-chain components. categories: MultiGov --- # Upgrade MultiGov Contracts on Solana The MultiGov Staking Program on Solana is designed to be upgradeable while maintaining stability. Upgrades introduce improvements, bug fixes, and new features but must be carefully planned and executed to prevent disruptions. This guide covers the key considerations and step-by-step process for upgrading the MultiGov Staking Program, including updating the program binary, Interface Description Language (IDL), and `HubProposalMetadata` while ensuring cross-chain compatibility. ## Key Considerations for Upgrades - **Program upgradeability** - you can upgrade the MultiGov Staking Program on Solana using the `anchor upgrade` command - You need the program's new bytecode (`.so` file) and an updated IDL file to reflect any changes in the program's interface to complete an upgrade - The program's authority (deployer) must execute the upgrade - **`HubProposalMetadata`** - can be updated without redeploying the entire program. You can do this by invoking the `updateHubProposalMetadata` instruction - You must carefully validate updates to `HubProposalMetadata` to ensure compatibility with the existing system - **Cross-chain compatibility** - ensure any changes to the Solana program do not break compatibility with the Ethereum-based `HubGovernor` - Test upgrades thoroughly on devnet before deploying to mainnet ## Upgrade the MultiGov Program Follow these steps to upgrade the MultiGov Staking Program on Solana: 1. **Prepare the new program binary** - build the updated program using the provided script ```bash ./scripts/build_verifiable_staking_program.sh ``` The new program binary will be located at: ```bash target/deploy/staking.so ``` 2. **Upgrade the program** - use the anchor upgrade command to deploy the new program binary ```bash anchor upgrade --program-id INSERT_PROGRAM_ID --provider.cluster INSERT_CLUSTER_URL INSERT_PATH_TO_PROGRAM_BINARY ``` Your completed anchor upgrade command should resemble the following: ```bash anchor upgrade --program-id DgCSKsLDXXufYeEkvf21YSX5DMnFK89xans5WdSsUbeY --provider.cluster https://api.devnet.solana.com ./target/deploy/staking.so ``` 3. **Update the IDL** - after upgrading the program, update the IDL to reflect any changes in the program's interface ```bash anchor idl upgrade INSERT_PROGRAM_ID --filepath INSERT_PATH_TO_IDL_FILE ``` Your completed IDL upgrade command should resemble the following: ```bash anchor idl upgrade --provider.cluster https://api.devnet.solana.com --filepath ./target/idl/staking.json DgCSKsLDXXufYeEkvf21YSX5DMnFK89xans5WdSsUbeY ``` 4. **Update `HubProposalMetadata`** - if `HubProposalMetadata` requires an update, run the following script to invoke the `updateHubProposalMetadata` instruction and apply the changes ```bash npx ts-node app/deploy/07_update_HubProposalMetadata.ts ``` --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/queries/faqs/ --- BEGIN CONTENT --- --- title: Queries FAQs description: Wormhole Queries FAQ covering available libraries, query examples, response formats, and details about running query proxy servers. categories: Queries --- # Wormhole Queries FAQs ## What libraries are available to handle queries? - The [Query TypeScript SDK](https://npmjs.com/package/@wormhole-foundation/wormhole-query-sdk){target=\_blank} can be used to create query requests, mock query responses for testing, and parse query responses. The SDK also includes utilities for posting query responses - The [Solidity `QueryResponseLib` library](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/libraries/QueryResponse.sol){target=\_blank} can be used to parse and verify query responses on EVM chains. See the [Solana Stake Pool](https://github.com/wormholelabs-xyz/example-queries-solana-stake-pool){target=\_blank} repository as an example use case - [`QueryRequestBuilder.sol`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/testing/QueryRequestBuilder.sol){target=\_blank} can be used for mocking query requests and responses in Forge tests - The [Go query package](https://github.com/wormhole-foundation/wormhole/tree/main/node/pkg/query){target=\_blank} can also be used to create query requests and parse query responses !!! note A Rust SDK for Solana is being actively investigated by the Wormhole contributors. See the [Solana Queries Verification](https://github.com/wormholelabs-xyz/example-queries-solana-verify){target=\_blank} repository as a proof of concept. ## Are there any query examples? Certainly. You can find a complete guide on the [Use Queries page](/docs/build/queries/use-queries/){target=\_blank}. Additionally, you can find full code examples in the following repositories: - [Basic Example Query Demo](https://github.com/wormholelabs-xyz/example-queries-demo/){target=\_blank} - [Solana Stake Pool Example Query](https://github.com/wormholelabs-xyz/example-queries-solana-stake-pool){target=\_blank} - [Solana Program Derived Address (PDA) / Token Account Balance Example Query](https://github.com/wormholelabs-xyz/example-queries-solana-pda){target=\_blank} - [Solana Queries Verification Example](https://github.com/wormholelabs-xyz/example-queries-solana-verify){target=\_blank} ## What is the format of the response signature? The Guardian node calculates an ECDSA signature using [`Sign` function of the crypto package](https://pkg.go.dev/github.com/ethereum/go-ethereum@v1.10.21/crypto#Sign){target=\_blank} where the digest hash is: ```keccak256("query_response_0000000000000000000|"+keccak256(responseBytes))``` See the [Guardian Key Usage](https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0009_guardian_key.md){target=\_blank} white paper for more background. Once this signature is created, the Guardian's index in the Guardian set is appended to the end. !!! note If you are used to `ecrecover` you will notice that the `v` byte is `0` or `1` as opposed to `27` or `28`. The `signaturesToEvmStruct` method in the [Query TypeScript SDK](https://npmjs.com/package/@wormhole-foundation/wormhole-query-sdk){target=\_blank} accounts for this as well as structuring the signatures into an `IWormhole.SignatureStruct[]`. ## Can anyone run a query proxy server? Permissions for Query Proxy are managed by the Guardians. The Guardian nodes are configured to only listen to a set of allow-listed proxies. However, it is possible that this restriction may be lifted in the future and/or more proxies could be added. It is also important to note that the proxies don't impact the verifiability of the request or result, i.e., their role in the process is trustless. ## What Does Queries Offer over an RPC Service Wormhole Queries provides on-demand, attested, on-chain, verifiable RPC results. Each Guardian independently executes the specified query and returns the result and their signature. The proxy handles aggregating the results and signatures, giving you a single result (all within one REST call) with a quorum of signatures suitable for on-chain submission, parsing, and verification using one of our examples or SDKs. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/queries/ --- BEGIN CONTENT --- --- title: Wormhole Queries description: Wormhole Queries offers on-demand access to Guardian-attested on-chain data via a simple REST endpoint to initiate an off-chain request via a proxy. categories: Queries --- # Queries ## Get Started Wormhole Queries offers on-demand access to Guardian-attested on-chain data via a simple REST endpoint to initiate an off-chain request via a proxy.
- :octicons-book-16:{ .lg .middle } **Overview** --- Explore Wormhole Queries, offering real-time access to verified blockchain data via a REST API endpoint, enabling secure cross-chain interactions and verifications. [:custom-arrow: Learn about Queries](/docs/build/queries/overview/) - :octicons-code-16:{ .lg .middle } **Use Queries** --- Explore a simple demo of interacting with Wormhole Queries using an `eth_call` request to query the supply of wETH on Ethereum using a Wormhole query. [:custom-arrow: Get hands-on](/docs/build/queries/use-queries/) - :octicons-book-16:{ .lg .middle } **Query FAQs** --- Explore frequently asked questions about Wormhole Queries. [:custom-arrow: Check out the FAQs](/docs/build/queries/faqs/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/queries/overview/ --- BEGIN CONTENT --- --- title: Queries Overview description: Explore Wormhole Queries, offering real-time access to verified blockchain data via a REST endpoint, enabling secure cross-chain interactions and verifications. categories: Queries --- # Queries Overview {: #queries-overview } Wormhole Guardians, who run full nodes for various connected chains, facilitate a new cross-chain query service that allows for on-demand attested responses to queries, bypassing the inefficiencies of traditional transaction-based data retrieval. This method is faster and cost-effective, eliminating the need for gas payments and transaction finality wait times. !!! note Queries are currently in closed beta, though you can start developing today. Check out [Use Queries](/docs/build/queries/use-queries/){target=\_blank} and reach out to [Join the beta](https://forms.clickup.com/45049775/f/1aytxf-10244/JKYWRUQ70AUI99F32Q){target=\_blank}. Wormhole Queries offers on-demand access to Guardian-attested on-chain data. The current implementation provides integrators with a simple REST endpoint to initiate an off-chain request via a proxy. The proxy then forwards the request to the Guardians and gathers a quorum of responses. The result returns the encoded response, including the request details and the Guardian signatures. The request validation performed by the query module includes a three step process that involves verifying the signature to ensure it has the correct prefix, confirming that the signer is authorized to execute query requests, and validating the legitimacy of all per-chain requests contained in the query. You can read more about Queries in the [white paper](https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0013_ccq.md){target=\_blank}. ## The Flow of a Query {: #the-flow-of-a-query} The general overview of a query's flow is as follows: an off-chain process sends HTTPS query requests to a Query Proxy, which validates and forwards them to the Guardians; these Guardians independently validate, sign, and return the response, with the entire process typically taking less than a second. ![The architecture flow of a query](/docs/images/build/queries/overview/overview-1.webp) The step-by-step flow of a query is as follows: 1. An off-chain process initiates a query request via HTTPS to the query proxy (or Query Server) 2. The query proxy validates the request and forwards it to the Guardians via a gossip network 3. The Guardians independently validate the request, make the requisite RPC calls, verify the results, sign, and gossip a response back to the Query Proxy 4. The Query Proxy aggregates the results and returns a response when it reaches a quorum of two-thirds or more of the current Guardian set - the exact quorum requirements as the core bridge 5. The off-chain process can then submit these requests to an on-chain contract which should verify the signatures and validate the request before processing the result In this flow, the Query Proxy is a permissioned but trustless part of the protocol. In most cases, this entire process takes less than one second. If a request is invalid or cannot be processed by the Guardians, they will retry for up to one minute before timing out. Requests can be batched to have the Guardians make multiple calls to multiple networks. This can further reduce overhead for processing query responses on-chain. Up to 255 queries can be batched together, with certain types allowing for batching themselves. ## Supported Query Types {: #supported-query-types} There are currently five supported types of queries. See [the white paper](https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0013_ccq.md){target=\_blank} for more details on each. ### eth_call {: #eth-call} This query type is effectively an equivalent of [eth_call](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call){target=\_blank} against a block specified by number or hash. Calls are batched to allow specifying multiple calls (even to multiple contracts) against the same block. These calls are included in a single batch RPC call, simplifying on-chain verification. Up to 255 calls may be batched in an single `eth_call` query. The result contains the specified block number, hash, timestamp, and the call result. ### eth_call By Timestamp {: #eth-call-by-timestamp} This query type is similar to `eth_call` but targets a timestamp instead of a specific `block_id`. This can be useful when forming requests based on uncorrelated data, such as requiring data from another chain based on the block timestamp of a given chain. The result also contains the target and block details with the following enforced conditions: `target_block.timestamp <= target_time < following_block.timestamp` and `following_block_num - 1 == target_block_num`. ### eth_call With Finality {: #eth-call-with-finality} This query type is similar to `eth_call` but ensures that the specified block has reached the specified finality before returning the query results. The finality may be `finalized` or `safe.` Note that if a chain doesn't natively support the `safe` tag, this will be equivalent to `finalized.` ### sol_account {: #sol_account} This query is used to read data for one or more accounts on Solana, akin to [`getMultipleAccounts`](https://solana.com/docs/rpc/http/getmultipleaccounts){target=\_blank}. ### sol_pda {: #sol_pda} This query is used to read data for one or more [Program Derived Addresses(PDA)](https://www.anchor-lang.com/docs/pdas){target=\_blank} on Solana, akin to calling [`getMultipleAccounts`](https://solana.com/docs/rpc/http/getmultipleaccounts){target=\_blank} on the result of `PublicKey.findProgramAddressSync(seeds, programId).` This query is helpful for times when you want to more generally read accounts owned by a program and verify the derivation on another chain, like how associated token accounts are all derived from the [Associated Token Account Program](https://spl.solana.com/associated-token-account){target=\_blank}. ## Supported Chains {: #supported-chains} The following table provides expected support based on testing. However, the success of any given query is based on the success of the underlying call on each Guardian’s RPC node. For example, many chains have implementations forked from [Geth](https://github.com/ethereum/go-ethereum){target=\_blank}, which keeps 128 blocks of state in memory by default (without running in archive mode). While this is good for about 25 minutes of history on Ethereum Mainnet, it is only about three minutes on Optimism. While Guardian nodes can be expected to have access to recent state, there are currently no guarantees of how far back in history they have access to. ### Mainnet {: #mainnet} | Chain | Wormhole Chain ID | eth_call | By Timestamp | With Finality | Expected History | |:-------------:|:-----------------:|:--------:|:------------------:|:-------------:|:----------------:| | Ethereum | 2 | ✅ | ✅ | ✅ | 128 blocks | | BSC | 4 | ✅ | ✅ | ✅ | 128 blocks | | Polygon | 5 | ✅ | ✅ | ✅ | 128 blocks | | Avalanche | 6 | ✅ | ✅ | ✅ | 32 blocks | | Oasis Emerald | 7 | ✅ | ✅ | ✅ | archive | | Fantom | 10 | ✅ | ✅ | ✅ | 16 blocks | | Karura | 11 | ✅ | ✅ | ✅ | archive | | Acala | 12 | ✅ | ✅ | ✅ | archive | | Kaia | 13 | ✅ | ✅ | ✅ | 128 blocks | | Celo | 14 | ✅ | ℹ️ hints required\* | ✅ | 128 blocks | | Moonbeam | 16 | ✅ | ℹ️ hints required\* | ✅ | 256 blocks | | Arbitrum One | 23 | ✅ | ✅ | ✅ | ~6742 blocks | | Optimism | 24 | ✅ | ✅ | ❌ | 128 blocks | | Base | 30 | ✅ | ✅ | ✅ | archive | \*`EthCallByTimestamp` arguments for `targetBlock` and `followingBlock` are currently required for requests to be successful on these chains. ## Next Steps {: #next-steps} Remember that Wormhole Queries are currently in beta. You can [register to join the beta](https://forms.clickup.com/45049775/f/1aytxf-10244/JKYWRUQ70AUI99F32Q){target=\_blank} to fully experiment with Wormhole Queries. Be sure to check out the [FAQs](/docs/build/queries/faqs/){target=\_blank} and the [Use Queries guide](/docs/build/queries/use-queries/){target=\_blank}. You can also check out the following examples of applications that make use of Wormhole Queries: - [Basic demo](https://github.com/wormholelabs-xyz/example-queries-demo/){target=\_blank} - [Solana Stake Pool](https://github.com/wormholelabs-xyz/example-queries-solana-stake-pool){target=\_blank} - [Solana Program Derived Address (PDA) / Token Account Balance](https://github.com/wormholelabs-xyz/example-queries-solana-pda){target=\_blank} - [Solana Queries Verification](https://github.com/wormholelabs-xyz/example-queries-solana-verify){target=\_blank} --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/queries/use-queries/ --- BEGIN CONTENT --- --- title: Use Queries description: Explore a simple demo of interacting with Wormhole Queries using an eth_call request to query the supply of wETH on Ethereum using a Wormhole query. categories: Queries --- # Use Queries You can visit the [Example Queries Demo](https://wormholelabs-xyz.github.io/example-queries-demo/){target=\_blank} to view an interactive example of an application interacting with the [Query Demo](https://github.com/wormholelabs-xyz/example-queries-demo/blob/main/src/QueryDemo.sol){target=\_blank} contract. This guide covers using a simple `eth_call` request to get the total supply of WETH on Ethereum. ## RPC Basics Before digging into anything Queries-specific, this page will look at how to make an [`eth_call`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call){target=\_blank} against a public Ethereum RPC. Suppose you'd like to query the WETH contract for its total supply; before making a request, you need some information about the contract you want to call, including: - **To** - the contract to call. WETH is [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2](https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2){target=\_blank} - **Data** - the method identifier and ABI-encoded parameters, which can be obtained as follows: `web3.eth.abi.encodeFunctionSignature("totalSupply()")` which yields `0x18160ddd` - **Block ID** - the block number, hash, or tag. Tag options include `latest,` `safe,` or `finalized` The prepared curl request is as follows: ```bash title="eth_call JSON-RPC request" curl https://ethereum.publicnode.com -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","data":"0x18160ddd"},"latest"],"id":1}' ``` And the corresponding response is: ```bash title="eth_call JSON-RPC reponse" { "jsonrpc":"2.0", "id":1, "result":"0x000000000000000000000000000000000000000000029fd3d129b582d7949e71" } ``` Converting the returned value of the executed call from hexidecimal results in the value `3172615244782286193073777`. You can compare your result to the [**Read Contract**](https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#readContract){target=\_blank} tab in Etherscan. Your result will be different as WETH is minted/burned over time. ## Construct a Query {: #construct-a-query} You can use the [Wormhole Query SDK](https://www.npmjs.com/package/@wormhole-foundation/wormhole-query-sdk){target=\_blank} to construct a query. You will also need an RPC endpoint from the provider of your choice. This example uses [Axios](https://www.npmjs.com/package/axios){target=\_blank} for RPC requests. Ensure that you also have [TypeScript](https://www.typescriptlang.org/download/){target=\_blank} installed. ```jsx npm i @wormhole-foundation/wormhole-query-sdk axios ``` In order to make an `EthCallQueryRequest`, you need a specific block number or hash as well as the call data to request. You can request the latest block from a public node using `eth_getBlockByNumber`. ```jsx await axios.post(rpc, { method: 'eth_getBlockByNumber', params: ['latest', false], id: 1, jsonrpc: '2.0', }) ).data?.result?.number; ``` Then construct the call data. ```jsx to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH data: '0x18160ddd', // web3.eth.abi.encodeFunctionSignature("totalSupply()") }; ``` Finally, put it all together in a `QueryRequest`. ```jsx const request = new QueryRequest( 0, // Nonce [ new PerChainQueryRequest( 2, // Ethereum Wormhole Chain ID new EthCallQueryRequest(latestBlock, [callData]) ), ] ); ``` This request consists of one `PerChainQueryRequest`, which is an `EthCallQueryRequest` to Ethereum. You can use `console.log` to print the JSON object and review the structure. ```jsx // { // "nonce": 0, // "requests": [ // { // "chainId": 2, // "query": { // "callData": [ // { // "to": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // "data": "0x18160ddd" // } // ], // "blockTag": "0x11e9068" // } // } // ], // "version": 1 // } ``` ## Mock a Query For easier testing, the Query SDK provides a `QueryProxyMock` method. This method will perform the request and sign the result with the [Devnet](https://github.com/wormhole-foundation/wormhole/blob/main/DEVELOP.md){target=\_blank} Guardian key. The `mock` call returns the same format as the Query Proxy. ```jsx const mockData = await mock.mock(request); console.log(mockData); // { // signatures: ['...'], // bytes: '...' // } ``` This response is suited for on-chain use, but the SDK also includes a parser to make the results readable via the client. ```jsx const mockQueryResult = ( mockQueryResponse.responses[0].response as EthCallQueryResponse ).results[0]; console.log( `Mock Query Result: ${mockQueryResult} (${BigInt(mockQueryResult)})` ); // Mock Query Result: 0x000000000000000000000000000000000000000000029fd09d4d81addb3ccfee (3172556167631284394053614) ``` Testing this all together might look like the following: ```jsx import { EthCallData, EthCallQueryRequest, EthCallQueryResponse, PerChainQueryRequest, QueryProxyMock, QueryRequest, QueryResponse, } from '@wormhole-foundation/wormhole-query-sdk'; import axios from 'axios'; const rpc = 'https://ethereum.publicnode.com'; const callData: EthCallData = { to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH data: '0x18160ddd', // web3.eth.abi.encodeFunctionSignature("totalSupply()") }; (async () => { const latestBlock: string = ( await axios.post(rpc, { method: 'eth_getBlockByNumber', params: ['latest', false], id: 1, jsonrpc: '2.0', }) ).data?.result?.number; if (!latestBlock) { console.error(`❌ Invalid block returned`); return; } console.log('Latest Block: ', latestBlock, `(${BigInt(latestBlock)})`); const targetResponse = await axios.post(rpc, { method: 'eth_call', params: [callData, latestBlock], id: 1, jsonrpc: '2.0', }); // console.log(finalizedResponse.data); if (targetResponse.data.error) { console.error(`❌ ${targetResponse.data.error.message}`); } const targetResult = targetResponse.data?.result; console.log('Target Result: ', targetResult, `(${BigInt(targetResult)})`); // Form the query request const request = new QueryRequest( 0, // Nonce [ new PerChainQueryRequest( 2, // Ethereum Wormhole Chain ID new EthCallQueryRequest(latestBlock, [callData]) ), ] ); console.log(JSON.stringify(request, undefined, 2)); const mock = new QueryProxyMock({ 2: rpc }); const mockData = await mock.mock(request); console.log(mockData); const mockQueryResponse = QueryResponse.from(mockData.bytes); const mockQueryResult = ( mockQueryResponse.responses[0].response as EthCallQueryResponse ).results[0]; console.log( `Mock Query Result: ${mockQueryResult} (${BigInt(mockQueryResult)})` ); })(); ``` ### Fork Testing It is common to test against a local fork of Mainnet with something like ```jsx anvil --fork-url https://ethereum.publicnode.com ``` In order for mock requests to verify against the Mainnet Core Contract, you need to replace the current Guardian set with the single Devnet key used by the mock. Here's an example for Ethereum Mainnet, where the `-a` parameter is the [Core Contract address](/docs/build/reference/contract-addresses/#core-contracts){target=\_blank} on that chain. ```jsx npx @wormhole-foundation/wormhole-cli evm hijack -a 0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B -g 0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe ``` If you are using `EthCallWithFinality`, you will need to mine additional blocks (32 if using [Anvil](https://book.getfoundry.sh/anvil/){target=\_blank}) after the latest transaction for it to become finalized. Anvil supports [auto-mining](https://book.getfoundry.sh/reference/anvil/#mining-modes){target=\_blank} with the `-b` flag if you want to test code that waits naturally for the chain to advance. For integration tests, you may want to simply `anvil_mine` with `0x20`. ## Make a Query Request The standardized means of making a `QueryRequest` with an API key is as follows: ```jsx const serialized = request.serialize(); const proxyResponse = (await axios.post)( QUERY_URL, { bytes: Buffer.from(serialized).toString('hex'), }, { headers: { 'X-API-Key': YOUR_API_KEY } } ); ``` Remember to always take steps to protect your sensitive API keys, such as defining them in `.env` files and including such files in your `.gitignore`. A Testnet Query Proxy is available at `https://testnet.query.wormhole.com/v1/query` A Mainnet Query Proxy is available at `https://query.wormhole.com/v1/query` ## Verify a Query Response On-Chain A [`QueryResponseLib` library](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/libraries/QueryResponse.sol){target=\_blank} is provided to assist with verifying query responses. You can begin by installing the [Wormhole Solidity SDK](https://github.com/wormhole-foundation/wormhole-solidity-sdk){target=\_blank} with the following command: ```bash forge install wormhole-foundation/wormhole-solidity-sdk ``` Broadly, using a query response on-chain comes down to three main steps: 1. Parse and verify the query response 2. The `parseAndVerifyQueryResponse` handles verifying the Guardian signatures against the current Guardian set stored in the Core bridge contract 3. Validate the request details. This may be different for every integrator depending on their use case, but generally checks the following: - Is the request against the expected chain? - Is the request of the expected type? The `parseEthCall` helpers perform this check when parsing - Is the resulting block number and time expected? Some consumers might require that a block number be higher than the last, or the block time be within the last 5 minutes. `validateBlockNum` and `validateBlockTime` can help with the checks - Is the request for the expected contract and function signature? The `validateMultipleEthCallData` can help with non-parameter-dependent cases - Is the result of the expected length for the expected result type? 4. Run `abi.decode` on the result See the [QueryDemo](https://github.com/wormholelabs-xyz/example-queries-demo/blob/main/src/QueryDemo.sol){target=\_blank} contract for an example and read the docstrings of the preceding methods for detailed usage instructions. ??? code "View the complete `QueryDemo`" ```solidity // contracts/query/QueryDemo.sol // SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.0; import "wormhole-solidity-sdk/libraries/BytesParsing.sol"; import "wormhole-solidity-sdk/interfaces/IWormhole.sol"; import "wormhole-solidity-sdk/QueryResponse.sol"; error InvalidOwner(); // @dev for the onlyOwner modifier error InvalidCaller(); error InvalidCalldata(); error InvalidForeignChainID(); error ObsoleteUpdate(); error StaleUpdate(); error UnexpectedResultLength(); error UnexpectedResultMismatch(); /// @dev QueryDemo is an example of using the QueryResponse library to parse and verify Cross Chain Query (CCQ) responses. contract QueryDemo is QueryResponse { using BytesParsing for bytes; struct ChainEntry { uint16 chainID; address contractAddress; uint256 counter; uint256 blockNum; uint256 blockTime; } address private immutable owner; uint16 private immutable myChainID; mapping(uint16 => ChainEntry) private counters; uint16[] private foreignChainIDs; bytes4 public GetMyCounter = bytes4(hex"916d5743"); constructor(address _owner, address _wormhole, uint16 _myChainID) QueryResponse(_wormhole) { if (_owner == address(0)) { revert InvalidOwner(); } owner = _owner; myChainID = _myChainID; counters[_myChainID] = ChainEntry(_myChainID, address(this), 0, 0, 0); } // updateRegistration should be used to add the other chains and to set / update contract addresses. function updateRegistration(uint16 _chainID, address _contractAddress) public onlyOwner { if (counters[_chainID].chainID == 0) { foreignChainIDs.push(_chainID); counters[_chainID].chainID = _chainID; } counters[_chainID].contractAddress = _contractAddress; } // getMyCounter (call signature 916d5743) returns the counter value for this chain. It is meant to be used in a cross chain query. function getMyCounter() public view returns (uint256) { return counters[myChainID].counter; } // getState() returns this chain's view of all the counters. It is meant to be used in the front end. function getState() public view returns (ChainEntry[] memory) { ChainEntry[] memory ret = new ChainEntry[](foreignChainIDs.length + 1); ret[0] = counters[myChainID]; uint256 length = foreignChainIDs.length; for (uint256 i = 0; i < length;) { ret[i + 1] = counters[foreignChainIDs[i]]; unchecked { ++i; } } return ret; } // @notice Takes the cross chain query response for the other counters, stores the results for the other chains, and updates the counter for this chain. function updateCounters(bytes memory response, IWormhole.Signature[] memory signatures) public { ParsedQueryResponse memory r = parseAndVerifyQueryResponse(response, signatures); uint256 numResponses = r.responses.length; if (numResponses != foreignChainIDs.length) { revert UnexpectedResultLength(); } for (uint256 i = 0; i < numResponses;) { // Create a storage pointer for frequently read and updated data stored on the blockchain ChainEntry storage chainEntry = counters[r.responses[i].chainId]; if (chainEntry.chainID != foreignChainIDs[i]) { revert InvalidForeignChainID(); } EthCallQueryResponse memory eqr = parseEthCallQueryResponse(r.responses[i]); // Validate that update is not obsolete validateBlockNum(eqr.blockNum, chainEntry.blockNum); // Validate that update is not stale validateBlockTime(eqr.blockTime, block.timestamp - 300); if (eqr.result.length != 1) { revert UnexpectedResultMismatch(); } // Validate addresses and function signatures address[] memory validAddresses = new address[](1); bytes4[] memory validFunctionSignatures = new bytes4[](1); validAddresses[0] = chainEntry.contractAddress; validFunctionSignatures[0] = GetMyCounter; validateMultipleEthCallData(eqr.result, validAddresses, validFunctionSignatures); require(eqr.result[0].result.length == 32, "result is not a uint256"); chainEntry.blockNum = eqr.blockNum; chainEntry.blockTime = eqr.blockTime / 1_000_000; chainEntry.counter = abi.decode(eqr.result[0].result, (uint256)); unchecked { ++i; } } counters[myChainID].blockNum = block.number; counters[myChainID].blockTime = block.timestamp; counters[myChainID].counter += 1; } modifier onlyOwner() { if (owner != msg.sender) { revert InvalidOwner(); } _; } } ``` ## Submit a Query Response On-Chain The `QueryProxyQueryResponse` result requires a slight tweak when submitting to the contract to match the format of `function parseAndVerifyQueryResponse(bytes memory response, IWormhole.Signature[] memory signatures)`. A helper function, `signaturesToEvmStruct`, is provided in the SDK for this. This example submits the transaction to the demo contract: ```jsx const tx = await contract.updateCounters( `0x${response.data.bytes}`, signaturesToEvmStruct(response.data.signatures) ); ``` --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/reference/chain-ids/ --- BEGIN CONTENT --- --- title: Chain IDs description: This page documents the Wormhole-specific chain IDs for each chain and contrasts them to the more commonly referenced EVM chain IDs originating in EIP-155. categories: Reference --- # Chain IDs The following table documents the chain IDs used by Wormhole and places them alongside the more commonly referenced [EVM Chain IDs](https://chainlist.org/){target=\_blank}. !!! note Please note, Wormhole chain IDs are different than the more commonly referenced EVM [chain IDs](https://eips.ethereum.org/EIPS/eip-155){target=\_blank}, specified in the Mainnet and Testnet ID columns. === "Mainnet" | Ethereum | 2 | 1 | | Solana | 1 | Mainnet Beta-5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d | | Acala | 12 | 787 | | Algorand | 8 | mainnet-v1.0 | | Aptos | 22 | 1 | | Arbitrum | 23 | Arbitrum One-42161 | | Avalanche | 6 | C-Chain-43114 | | Base | 30 | Base-8453 | | Berachain | 39 | | | Blast | 36 | 81457 | | BNB Smart Chain | 4 | 56 | | Celestia | 4004 | celestia | | Celo | 14 | 42220 | | Cosmos Hub | 4000 | cosmoshub-4 | | Dymension | 4007 | dymension_1100-1 | | Evmos | 4001 | evmos_9001-2 | | Fantom | 10 | 250 | | Gnosis | 25 | 100 | | HyperEVM | 47 | | | Injective | 19 | injective-1 | | Ink | 46 | | | Kaia | 13 | 8217 | | Karura | 11 | 686 | | Kujira | 4002 | kaiyo-1 | | Linea | 38 | 59144 | | Mantle | 35 | 5000 | | Mezo | 50 | | | Monad | 48 | | | Moonbeam | 16 | 1284 | | NEAR | 15 | mainnet | | Neon | 17 | 245022934 | | Neutron | 4003 | neutron-1 | | Noble | 4009 | noble-1 | | Oasis | 7 | 42262 | | Optimism | 24 | 10 | | Osmosis | 20 | osmosis-1 | | Polygon | 5 | 137 | | Provenance | 4008 | pio-mainnet-1 | | Pythnet | 26 | | | Scroll | 34 | 534352 | | SEDA | 4006 | | | Sei | 32 | pacific-1 | | Seievm | 40 | | | SNAXchain | 43 | 2192 | | Stargaze | 4005 | stargaze-1 | | Sui | 21 | 35834a8a | | Terra | 3 | columbus-5 | | Terra 2.0 | 18 | phoenix-1 | | Unichain | 44 | | | World Chain | 45 | 480 | | X Layer | 37 | 196 | | XPLA | 28 | dimension_37-1 | === "Testnet" | Ethereum Holesky | 10006 | Holesky-17000 | | Ethereum Sepolia | 10002 | Sepolia-11155111 | | Solana | 1 | Devnet-EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG | | Acala | 12 | 597 | | Algorand | 8 | testnet-v1.0 | | Aptos | 22 | 2 | | Arbitrum Sepolia | 10003 | Sepolia-421614 | | Avalanche | 6 | Fuji-43113 | | Base Sepolia | 10004 | Base Sepolia-84532 | | Berachain | 39 | 80084 | | Blast | 36 | 168587773 | | BNB Smart Chain | 4 | 97 | | Celestia | 4004 | mocha-4 | | Celo | 14 | Alfajores-44787 | | Cosmos Hub | 4000 | theta-testnet-001 | | Dymension | 4007 | | | Evmos | 4001 | evmos_9000-4 | | Fantom | 10 | 4002 | | Gnosis | 25 | Chiado-10200 | | HyperEVM | 47 | 998 | | Injective | 19 | injective-888 | | Ink | 46 | 763373 | | Kaia | 13 | Kairos-1001 | | Karura | 11 | 596 | | Kujira | 4002 | harpoon-4 | | Linea | 38 | 59141 | | Mantle | 35 | Sepolia-5003 | | Mezo | 50 | 31611 | | Monad | 48 | 10143 | | Moonbeam | 16 | Moonbase-Alphanet-1287 | | NEAR | 15 | testnet | | Neon | 17 | 245022940 | | Neutron | 4003 | pion-1 | | Noble | 4009 | grand-1 | | Oasis | 7 | 42261 | | Optimism Sepolia | 10005 | Optimism Sepolia-11155420 | | Osmosis | 20 | osmo-test-5 | | Polygon Amoy | 10007 | Amoy-80002 | | Provenance | 4008 | | | Pythnet | 26 | | | Scroll | 34 | Sepolia-534351 | | SEDA | 4006 | seda-1-testnet | | Sei | 32 | atlantic-2 | | Seievm | 40 | | | SNAXchain | 43 | 13001 | | Stargaze | 4005 | | | Sui | 21 | 4c78adac | | Terra | 3 | bombay-12 | | Terra 2.0 | 18 | pisco-1 | | Unichain | 44 | Unichain Sepolia-1301 | | World Chain | 45 | 4801 | | X Layer | 37 | 195 | | XPLA | 28 | cube_47-5 | --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/reference/consistency-levels/ --- BEGIN CONTENT --- --- title: Wormhole Finality | Consistency Levels description: This page documents how long to wait for finality before signing, based on each chain’s consistency (finality) level and consensus mechanism. categories: Reference --- # Wormhole Finality The following table documents each chain's `consistencyLevel` values (i.e., finality reached before signing). The consistency level defines how long the Guardians should wait before signing a VAA. The finalization time depends on the specific chain's consensus mechanism. The consistency level is a `u8`, so any single byte may be used. However, a small subset has particular meanings. If the `consistencyLevel` isn't one of those specific values, the `Otherwise` column describes how it's interpreted. | Ethereum | 200 | 201 | | finalized | ~ 19min | Details | | Solana | | 0 | 1 | | ~ 14s | Details | | Acala | 200 | 201 | | finalized | ~ 24s | | | Algorand | | | 0 | | ~ 4s | Details | | Aptos | | | 0 | | ~ 4s | Details | | Arbitrum | 200 | 201 | | finalized | ~ 18min | Details | | Avalanche | 200 | | | finalized | ~ 2s | Details | | Base | 200 | 201 | | finalized | ~ 18min | | | Berachain | 200 | | | finalized | ~ 4s | | | Blast | 200 | 201 | | finalized | ~ 18min | | | BNB Smart Chain | 200 | 201 | | finalized | ~ 48s | Details | | Celestia | | | 0 | | ~ 5s | | | Celo | 200 | | | finalized | ~ 10s | | | Cosmos Hub | | | 0 | | ~ 5s | | | Dymension | | | 0 | | ~ 5s | | | Evmos | | | 0 | | ~ 2s | | | Fantom | 200 | | | finalized | ~ 5s | | | Injective | | | 0 | | ~ 3s | | | Ink | | | 0 | | ~ 9min | | | Kaia | 200 | | | finalized | ~ 1s | | | Karura | 200 | 201 | | finalized | ~ 24s | Details | | Kujira | | | 0 | | ~ 3s | | | Mantle | 200 | 201 | | finalized | ~ 18min | | | Mezo | | | 0 | | ~ 8s | | | Monad | | | 0 | | ~ 2s | | | Moonbeam | 200 | 201 | | finalized | ~ 24s | Details | | NEAR | | | 0 | | ~ 2s | Details | | Neutron | | | 0 | | ~ 5s | | | Oasis | 200 | | | finalized | ~ 12s | | | Optimism | 200 | 201 | | finalized | ~ 18min | | | Osmosis | | | 0 | | ~ 6s | | | Polygon | 200 | | | finalized | ~ 66s | Details | | Scroll | 200 | | | finalized | ~ 16min | | | Sei | | | 0 | | ~ 1s | | | Stargaze | | | 0 | | ~ 5s | | | Sui | | | 0 | | ~ 3s | Details | | Terra | | | 0 | | ~ 6s | | | Terra 2.0 | | | 0 | | ~ 6s | | | Unichain | 200 | 201 | | finalized | ~ 18min | | | World Chain | | | 0 | | ~ 18min | | | X Layer | 200 | 201 | | finalized | ~ 16min | | | XPLA | | | 0 | | ~ 5s | | --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/reference/contract-addresses/ --- BEGIN CONTENT --- --- title: Contract Addresses description: This page documents the deployed contract addresses of the Wormhole contracts on each chain, including Core Contracts, TokenBridge, and more. categories: Reference --- # Contract Addresses ## Core Contracts === "Mainnet" | Ethereum | 0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B | | Solana | worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth | | Acala | 0xa321448d90d4e5b0A732867c18eA198e75CAC48E | | Algorand | 842125965 | | Aptos | 0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625 | | Arbitrum | 0xa5f208e072434bC67592E4C49C1B991BA79BCA46 | | Avalanche | 0x54a8e5f9c4CbA08F9943965859F6c34eAF03E26c | | Base | 0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6 | | Berachain | 0xCa1D5a146B03f6303baF59e5AD5615ae0b9d146D | | Blast | 0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6 | | BNB Smart Chain | 0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B | | Celo | 0xa321448d90d4e5b0A732867c18eA198e75CAC48E | | Fantom | 0x126783A6Cb203a3E35344528B26ca3a0489a1485 | | Gnosis | 0xa321448d90d4e5b0A732867c18eA198e75CAC48E | | Injective | inj17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9l2q74d | | Ink | 0xCa1D5a146B03f6303baF59e5AD5615ae0b9d146D | | Kaia | 0x0C21603c4f3a6387e241c0091A7EA39E43E90bb7 | | Karura | 0xa321448d90d4e5b0A732867c18eA198e75CAC48E | | Mantle | 0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6 | | Moonbeam | 0xC8e2b0cD52Cf01b0Ce87d389Daa3d414d4cE29f3 | | NEAR | contract.wormhole_crypto.near | | Neutron | neutron16rerygcpahqcxx5t8vjla46ym8ccn7xz7rtc6ju5ujcd36cmc7zs9zrunh | | Oasis | 0xfE8cD454b4A1CA468B57D79c0cc77Ef5B6f64585 | | Optimism | 0xEe91C335eab126dF5fDB3797EA9d6aD93aeC9722 | | Polygon | 0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7 | | Pythnet | H3fxXJ86ADW2PNuDDmZJg6mzTtPxkYCpNuQUTgmJ7AjU | | Scroll | 0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6 | | Sei | sei1gjrrme22cyha4ht2xapn3f08zzw6z3d4uxx6fyy9zd5dyr3yxgzqqncdqn | | SNAXchain | 0xc1BA3CC4bFE724A08FbbFbF64F8db196738665f4 | | Sui | 0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c | | Terra | terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5 | | Terra 2.0 | terra12mrnzvhx3rpej6843uge2yyfppfyd3u9c3uq223q8sl48huz9juqffcnhp | | Unichain | 0xCa1D5a146B03f6303baF59e5AD5615ae0b9d146D | | World Chain | 0xcbcEe4e081464A15d8Ad5f58BB493954421eB506 | | X Layer | 0x194B123c5E96B9b2E49763619985790Dc241CAC0 | | XPLA | xpla1jn8qmdda5m6f6fqu9qv46rt7ajhklg40ukpqchkejcvy8x7w26cqxamv3w | === "Testnet" | Ethereum Holesky | 0xa10f2eF61dE1f19f586ab8B6F2EbA89bACE63F7a | | Ethereum Sepolia | 0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78 | | Solana | 3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5 | | Acala | 0x64fb09E405D2043ed7785a29E296C766D56F2056 | | Algorand | 86525623 | | Aptos | 0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625 | | Arbitrum Sepolia | 0x6b9C8671cdDC8dEab9c719bB87cBd3e782bA6a35 | | Avalanche | 0x7bbcE28e64B3F8b84d876Ab298393c38ad7aac4C | | Base Sepolia | 0x79A1027a6A159502049F10906D333EC57E95F083 | | Berachain | 0xBB73cB66C26740F31d1FabDC6b7A46a038A300dd | | Blast | 0x473e002D7add6fB67a4964F13bFd61280Ca46886 | | BNB Smart Chain | 0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D | | Celo | 0x88505117CA88e7dd2eC6EA1E13f0948db2D50D56 | | Fantom | 0x1BB3B4119b7BA9dfad76B0545fb3F531383c3bB7 | | Gnosis | 0xBB73cB66C26740F31d1FabDC6b7A46a038A300dd | | HyperEVM | 0xBB73cB66C26740F31d1FabDC6b7A46a038A300dd | | Injective | inj1xx3aupmgv3ce537c0yce8zzd3sz567syuyedpg | | Ink | 0xBB73cB66C26740F31d1FabDC6b7A46a038A300dd | | Kaia | 0x1830CC6eE66c84D2F177B94D544967c774E624cA | | Karura | 0x64fb09E405D2043ed7785a29E296C766D56F2056 | | Linea | 0x79A1027a6A159502049F10906D333EC57E95F083 | | Mantle | 0x376428e7f26D5867e69201b275553C45B09EE090 | | Mezo | 0x268557122Ffd64c85750d630b716471118F323c8 | | Monad | 0xBB73cB66C26740F31d1FabDC6b7A46a038A300dd | | Moonbeam | 0xa5B7D85a8f27dd7907dc8FdC21FA5657D5E2F901 | | NEAR | wormhole.wormhole.testnet | | Neon | 0x268557122Ffd64c85750d630b716471118F323c8 | | Neutron | neutron1enf63k37nnv9cugggpm06mg70emcnxgj9p64v2s8yx7a2yhhzk2q6xesk4 | | Oasis | 0xc1C338397ffA53a2Eb12A7038b4eeb34791F8aCb | | Optimism Sepolia | 0x31377888146f3253211EFEf5c676D41ECe7D58Fe | | Osmosis | osmo1hggkxr0hpw83f8vuft7ruvmmamsxmwk2hzz6nytdkzyup9krt0dq27sgyx | | Polygon Amoy | 0x6b9C8671cdDC8dEab9c719bB87cBd3e782bA6a35 | | Pythnet | EUrRARh92Cdc54xrDn6qzaqjA77NRrCcfbr8kPwoTL4z | | Scroll | 0x055F47F1250012C6B20c436570a76e52c17Af2D5 | | Sei | sei1nna9mzp274djrgzhzkac2gvm3j27l402s4xzr08chq57pjsupqnqaj0d5s | | Seievm | 0xBB73cB66C26740F31d1FabDC6b7A46a038A300dd | | SNAXchain | 0xBB73cB66C26740F31d1FabDC6b7A46a038A300dd | | Sui | 0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790 | | Terra | terra1pd65m0q9tl3v8znnz5f5ltsfegyzah7g42cx5v | | Terra 2.0 | terra19nv3xr5lrmmr7egvrk2kqgw4kcn43xrtd5g0mpgwwvhetusk4k7s66jyv0 | | Unichain | 0xBB73cB66C26740F31d1FabDC6b7A46a038A300dd | | World Chain | 0xe5E02cD12B6FcA153b0d7fF4bF55730AE7B3C93A | | X Layer | 0xA31aa3FDb7aF7Db93d18DDA4e19F811342EDF780 | | XPLA | xpla1upkjn4mthr0047kahvn0llqx4qpqfn75lnph4jpxfn8walmm8mqsanyy35 | === "Devnet" | Ethereum | 0xC89Ce4735882C9F0f0FE26686c53074E09B0D550 | | Solana | Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o | | Algorand | 1004 | | Aptos | 0xde0036a9600559e295d5f6802ef6f3f802f510366e0c23912b0655d972166017 | | BNB Smart Chain | 0xC89Ce4735882C9F0f0FE26686c53074E09B0D550 | | NEAR | wormhole.test.near | | Sui | 0x5a5160ca3c2037f4b4051344096ef7a48ebf4400b3f385e57ea90e1628a8bde0 | | Terra | terra14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9ssrc8au | | Terra 2.0 | terra14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9ssrc8au | ## Token Bridge === "Mainnet" | Ethereum | 0x3ee18B2214AFF97000D974cf647E7C347E8fa585 | | Solana | wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb | | Acala | 0xae9d7fe007b3327AA64A32824Aaac52C42a6E624 | | Algorand | 842126029 | | Aptos | 0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f | | Arbitrum | 0x0b2402144Bb366A632D14B83F244D2e0e21bD39c | | Avalanche | 0x0e082F06FF657D94310cB8cE8B0D9a04541d8052 | | Base | 0x8d2de8d2f73F1F4cAB472AC9A881C9b123C79627 | | Berachain | 0x3Ff72741fd67D6AD0668d93B41a09248F4700560 | | Blast | 0x24850c6f61C438823F01B7A3BF2B89B72174Fa9d | | BNB Smart Chain | 0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7 | | Celo | 0x796Dff6D74F3E27060B71255Fe517BFb23C93eed | | Fantom | 0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2 | | Injective | inj1ghd753shjuwexxywmgs4xz7x2q732vcnxxynfn | | Ink | 0x3Ff72741fd67D6AD0668d93B41a09248F4700560 | | Kaia | 0x5b08ac39EAED75c0439FC750d9FE7E1F9dD0193F | | Karura | 0xae9d7fe007b3327AA64A32824Aaac52C42a6E624 | | Mantle | 0x24850c6f61C438823F01B7A3BF2B89B72174Fa9d | | Moonbeam | 0xb1731c586ca89a23809861c6103f0b96b3f57d92 | | NEAR | contract.portalbridge.near | | Oasis | 0x5848C791e09901b40A9Ef749f2a6735b418d7564 | | Optimism | 0x1D68124e65faFC907325e3EDbF8c4d84499DAa8b | | Polygon | 0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE | | Scroll | 0x24850c6f61C438823F01B7A3BF2B89B72174Fa9d | | Sei | sei1smzlm9t79kur392nu9egl8p8je9j92q4gzguewj56a05kyxxra0qy0nuf3 | | SNAXchain | 0x8B94bfE456B48a6025b92E11Be393BAa86e68410 | | Sui | 0xc57508ee0d4595e5a8728974a4a93a787d38f339757230d441e895422c07aba9 | | Terra | terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf | | Terra 2.0 | terra153366q50k7t8nn7gec00hg66crnhkdggpgdtaxltaq6xrutkkz3s992fw9 | | Unichain | 0x3Ff72741fd67D6AD0668d93B41a09248F4700560 | | World Chain | 0xc309275443519adca74c9136b02A38eF96E3a1f6 | | X Layer | 0x5537857664B0f9eFe38C9f320F75fEf23234D904 | | XPLA | xpla137w0wfch2dfmz7jl2ap8pcmswasj8kg06ay4dtjzw7tzkn77ufxqfw7acv | === "Testnet" | Ethereum Holesky | 0x76d093BbaE4529a342080546cAFEec4AcbA59EC6 | | Ethereum Sepolia | 0xDB5492265f6038831E89f495670FF909aDe94bd9 | | Solana | DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe | | Acala | 0xe157115ef34c93145Fec2FE53706846853B07F42 | | Algorand | 86525641 | | Aptos | 0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f | | Arbitrum Sepolia | 0xC7A204bDBFe983FCD8d8E61D02b475D4073fF97e | | Avalanche | 0x61E44E506Ca5659E6c0bba9b678586fA2d729756 | | Base Sepolia | 0x86F55A04690fd7815A3D802bD587e83eA888B239 | | Berachain | 0xa10f2eF61dE1f19f586ab8B6F2EbA89bACE63F7a | | Blast | 0x430855B4D43b8AEB9D2B9869B74d58dda79C0dB2 | | BNB Smart Chain | 0x9dcF9D205C9De35334D646BeE44b2D2859712A09 | | Celo | 0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153 | | Fantom | 0x599CEa2204B4FaECd584Ab1F2b6aCA137a0afbE8 | | HyperEVM | 0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78 | | Injective | inj1q0e70vhrv063eah90mu97sazhywmeegp7myvnh | | Ink | 0x376428e7f26D5867e69201b275553C45B09EE090 | | Kaia | 0xC7A13BE098720840dEa132D860fDfa030884b09A | | Karura | 0xe157115ef34c93145Fec2FE53706846853B07F42 | | Linea | 0xC7A204bDBFe983FCD8d8E61D02b475D4073fF97e | | Mantle | 0x75Bfa155a9D7A3714b0861c8a8aF0C4633c45b5D | | Mezo | 0xA31aa3FDb7aF7Db93d18DDA4e19F811342EDF780 | | Monad | 0xF323dcDe4d33efe83cf455F78F9F6cc656e6B659 | | Moonbeam | 0xbc976D4b9D57E57c3cA52e1Fd136C45FF7955A96 | | NEAR | token.wormhole.testnet | | Neon | 0xEe3dB83916Ccdc3593b734F7F2d16D630F39F1D0 | | Oasis | 0x88d8004A9BdbfD9D28090A02010C19897a29605c | | Optimism Sepolia | 0x99737Ec4B815d816c49A385943baf0380e75c0Ac | | Polygon Amoy | 0xC7A204bDBFe983FCD8d8E61D02b475D4073fF97e | | Scroll | 0x22427d90B7dA3fA4642F7025A854c7254E4e45BF | | Sei | sei1jv5xw094mclanxt5emammy875qelf3v62u4tl4lp5nhte3w3s9ts9w9az2 | | Seievm | 0x23908A62110e21C04F3A4e011d24F901F911744A | | SNAXchain | 0xa10f2eF61dE1f19f586ab8B6F2EbA89bACE63F7a | | Sui | 0x6fb10cdb7aa299e9a4308752dadecb049ff55a892de92992a1edbd7912b3d6da | | Terra | terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a | | Terra 2.0 | terra1c02vds4uhgtrmcw7ldlg75zumdqxr8hwf7npseuf2h58jzhpgjxsgmwkvk | | Unichain | 0xa10f2eF61dE1f19f586ab8B6F2EbA89bACE63F7a | | World Chain | 0x430855B4D43b8AEB9D2B9869B74d58dda79C0dB2 | | X Layer | 0xdA91a06299BBF302091B053c6B9EF86Eff0f930D | | XPLA | xpla1kek6zgdaxcsu35nqfsyvs2t9vs87dqkkq6hjdgczacysjn67vt8sern93x | === "Devnet" | Ethereum | 0x0290FB167208Af455bB137780163b7B7a9a10C16 | | Solana | B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE | | Algorand | 1006 | | Aptos | 0x84a5f374d29fc77e370014dce4fd6a55b58ad608de8074b0be5571701724da31 | | BNB Smart Chain | 0x0290FB167208Af455bB137780163b7B7a9a10C16 | | NEAR | token.test.near | | Sui | 0xa6a3da85bbe05da5bfd953708d56f1a3a023e7fb58e5a824a3d4de3791e8f690 | | Terra | terra1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrquka9l6 | | Terra 2.0 | terra1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrquka9l6 | ## Wormhole Relayer === "Mainnet" | Ethereum | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Arbitrum | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Avalanche | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Base | 0x706f82e9bb5b0813501714ab5974216704980e31 | | Berachain | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Blast | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | BNB Smart Chain | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Celo | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Fantom | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Ink | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Kaia | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Mantle | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Moonbeam | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Optimism | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Polygon | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Scroll | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | SNAXchain | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | Unichain | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | | World Chain | 0x1520cc9e779c56dab5866bebfb885c86840c33d3 | | X Layer | 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 | === "Testnet" | Ethereum Sepolia | 0x7B1bD7a6b4E61c2a123AC6BC2cbfC614437D0470 | | Arbitrum Sepolia | 0x7B1bD7a6b4E61c2a123AC6BC2cbfC614437D0470 | | Avalanche | 0xA3cF45939bD6260bcFe3D66bc73d60f19e49a8BB | | Base Sepolia | 0x93BAD53DDfB6132b0aC8E37f6029163E63372cEE | | Berachain | 0x362fca37E45fe1096b42021b543f462D49a5C8df | | BNB Smart Chain | 0x80aC94316391752A193C1c47E27D382b507c93F3 | | Celo | 0x306B68267Deb7c5DfCDa3619E22E9Ca39C374f84 | | Fantom | 0x7B1bD7a6b4E61c2a123AC6BC2cbfC614437D0470 | | Ink | 0x362fca37E45fe1096b42021b543f462D49a5C8df | | Monad | 0x362fca37E45fe1096b42021b543f462D49a5C8df | | Moonbeam | 0x0591C25ebd0580E0d4F27A82Fc2e24E7489CB5e0 | | Optimism Sepolia | 0x93BAD53DDfB6132b0aC8E37f6029163E63372cEE | | Seievm | 0x362fca37E45fe1096b42021b543f462D49a5C8df | | Unichain | 0x362fca37E45fe1096b42021b543f462D49a5C8df | === "Devnet" | Ethereum | 0xcC680D088586c09c3E0E099a676FA4b6e42467b4 | | BNB Smart Chain | 0xcC680D088586c09c3E0E099a676FA4b6e42467b4 | ## CCTP === "Mainnet" | Ethereum | 0xAaDA05BD399372f0b0463744C09113c137636f6a | | Arbitrum | 0x2703483B1a5a7c577e8680de9Df8Be03c6f30e3c | | Avalanche | 0x09Fb06A271faFf70A651047395AaEb6265265F13 | | Base | 0x03faBB06Fa052557143dC28eFCFc63FC12843f1D | | Optimism | 0x2703483B1a5a7c577e8680de9Df8Be03c6f30e3c | | Polygon | 0x0FF28217dCc90372345954563486528aa865cDd6 | === "Testnet" | Ethereum Sepolia | 0x2703483B1a5a7c577e8680de9Df8Be03c6f30e3c | | Arbitrum Sepolia | 0x2703483B1a5a7c577e8680de9Df8Be03c6f30e3c | | Avalanche | 0x58f4c17449c90665891c42e14d34aae7a26a472e | | Base Sepolia | 0x2703483B1a5a7c577e8680de9Df8Be03c6f30e3c | | Optimism Sepolia | 0x2703483B1a5a7c577e8680de9Df8Be03c6f30e3c | === "Devnet" N/A ## Read-Only Deployments === "Mainnet" | Corn | 0xa683c66045ad16abb1bCE5ad46A64d95f9A25785 | | Gnosis | 0xa321448d90d4e5b0A732867c18eA198e75CAC48E | | Goat | 0x352A86168e6988A1aDF9A15Cb00017AAd3B67155 | | LightLink | 0x352A86168e6988A1aDF9A15Cb00017AAd3B67155 | | Rootstock | 0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6 | | Sonic | 0x352A86168e6988A1aDF9A15Cb00017AAd3B67155 | | Telos | 0x352A86168e6988A1aDF9A15Cb00017AAd3B67155 | !!!note Read-only deployments allow Wormhole messages to be received on chains not fully integrated with Wormhole Guardians. These deployments support cross-chain data verification but cannot originate messages. For example, a governance message can be sent from a fully integrated chain and processed on a read-only chain, but the read-only chain cannot send messages back. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/reference/ --- BEGIN CONTENT --- --- title: Reference description: Find essential reference information for development, including canonical contract addresses, Wormhole chain IDs, and Wormhole finality levels for Guardians. categories: Reference --- # Reference ## Get Started In this section, you'll find reference information that is essential for development. This includes Wormhole chain IDs, canonical contract addresses, and finality levels for Guardians for each of the supported blockchains in the Wormhole ecosystem.
- :octicons-list-ordered-16:{ .lg .middle } **Chain IDs** --- Find a mapping of Wormhole chain IDs to the names and network IDs of the supported blockchains. [:custom-arrow: View list of chain IDs](/docs/build/reference/chain-ids/) - :material-timer-sand:{ .lg .middle } **Wormhole Finality** --- See the levels of finality (consistency) a transaction should meet before being signed by a Guardian for each network. [:custom-arrow: View list of finality levels](/docs/build/reference/consistency-levels/) - :octicons-file-code-16:{ .lg .middle } **Contract Addresses** --- Discover the contract addresses for Wormhole-deployed contracts on each of the supported blockchains. This includes the following protocol contracts: - Core Contract - Token Bridge - NFT Bridge - Wormhole relayer - CCTP [:custom-arrow: View list of contract addresses](/docs/build/reference/contract-addresses/) - :octicons-checkbox-16:{ .lg .middle } **Wormhole Formatted Addresses** --- Learn how Wormhole formats addresses into a 32-byte hex format for cross-chain compatibility. This includes converting addresses between their native formats and the Wormhole format across multiple blockchains. [:custom-arrow: View details on Wormhole formatted addresses](/docs/build/reference/wormhole-formatted-addresses/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/reference/wormhole-formatted-addresses/ --- BEGIN CONTENT --- --- title: Wormhole Formatted Addresses description: Explanation of Wormhole formatted 32-byte hex addresses, their conversion, and usage across different blockchain platforms. categories: Reference --- # Wormhole Formatted Addresses ## Introduction Wormhole formatted addresses are 32-byte hex representations of addresses from any supported blockchain. Whether an address originates from EVM, Solana, Cosmos, or another ecosystem, Wormhole standardizes all addresses into this format to ensure cross-chain compatibility. This uniform format is essential for smooth interoperability in token transfers and messaging across chains. Wormhole uses formatted addresses throughout the [Wormhole SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank}, especially in cross-chain transactions, such as transfer functions that utilize the `bytes32` representation for recipient addresses. ## Platform-Specific Address Formats Each blockchain ecosystem Wormhole supports has its method for formatting native addresses. To enable cross-chain compatibility, Wormhole converts these native addresses into the standardized 32-byte hex format. Here’s an overview of the native address formats and how they are normalized to the Wormhole format: | Platform | Native Address Format | Wormhole Formatted Address | |-----------------|----------------------------------|----------------------------| | EVM | Hex (e.g., 0x...) | 32-byte Hex | | Solana | Base58 | 32-byte Hex | | CosmWasm | Bech32 | 32-byte Hex | | Algorand | Algorand App ID | 32-byte Hex | | Sui | Hex | 32-byte Hex | | Aptos | Hex | 32-byte Hex | | Near | SHA-256 | 32-byte Hex | These conversions allow Wormhole to interact seamlessly with various chains using a uniform format for all addresses. ### Address Format Handling The Wormhole SDK provides mappings that associate each platform with its native address format. You can find this mapping in the Wormhole SDK file [`platforms.ts`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/007f61b27c650c1cf0fada2436f79940dfa4f211/core/base/src/constants/platforms.ts#L93-L102){target=\_blank}: ```typescript const platformAddressFormatEntries = [ ['Evm', 'hex'], ['Solana', 'base58'], ['Cosmwasm', 'bech32'], ['Algorand', 'algorandAppId'], ['Sui', 'hex'], ['Aptos', 'hex'], ['Near', 'sha256'], ]; ``` These entries define how the [`UniversalAddress`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/007f61b27c650c1cf0fada2436f79940dfa4f211/core/definitions/src/universalAddress.ts#L23){target=\_blank} class handles different address formats based on the platform. ## Universal Address Methods The `UniversalAddress` class is essential for working with Wormhole formatted addresses. It converts native blockchain addresses into the standardized 32-byte hex format used across Wormhole operations. Key functions: - **`new UniversalAddress()`** - use the `UniversalAddress` constructor to convert native addresses into the Wormhole format ```typescript const universalAddress = new UniversalAddress('0x123...', 'hex'); ``` - **`toUniversalAddress()`** - converts a platform-specific address into the Wormhole formatted 32-byte hex address ```typescript const ethAddress: NativeAddress<'Evm'> = toNative('Ethereum', '0x0C9...'); const universalAddress = ethAddress.toUniversalAddress().toString(); ``` - **`toNative()`** - converts the Wormhole formatted address back to a native address for a specific blockchain platform ```typescript const nativeAddress = universalAddress.toNative('Evm'); ``` - **`toString()`** - returns the Wormhole formatted address as a hex string, which can be used in various SDK operations ```typescript console.log(universalAddress.toString()); ``` These methods allow developers to convert between native addresses and the Wormhole format, ensuring cross-chain compatibility. ## Convert Between Native and Wormhole Formatted Addresses The Wormhole SDK allows developers to easily convert between native addresses and Wormhole formatted addresses when building cross-chain applications. ### Convert a Native Address to a Wormhole Formatted Address Example conversions for EVM and Solana: === "EVM" ```typescript import { toNative } from '@wormhole-foundation/sdk-core'; const ethAddress: NativeAddress<'Evm'> = toNative( 'Ethereum', '0x0C99567DC6f8f1864cafb580797b4B56944EEd28' ); const universalAddress = ethAddress.toUniversalAddress().toString(); console.log('Universal Address (EVM):', universalAddress); ``` === "Solana" ```typescript import { toNative } from '@wormhole-foundation/sdk-core'; const solAddress: NativeAddress<'Solana'> = toNative( 'Solana', '6zZHv9EiqQYcdg52ueADRY6NbCXa37VKPngEHaokZq5J' ); const universalAddressSol = solAddress.toUniversalAddress().toString(); console.log('Universal Address (Solana):', universalAddressSol); ``` The result is a standardized address format that is ready for cross-chain operations. ### Convert Back to Native Addresses Below is how you can convert a Wormhole formatted address back to an EVM or Solana native address: ```typescript const nativeAddressEvm = universalAddress.toNative('Evm'); console.log('EVM Native Address:', nativeAddressEvm); const nativeAddressSolana = universalAddress.toNative('Solana'); console.log('Solana Native Address:', nativeAddressSolana); ``` These conversions ensure that your cross-chain applications can seamlessly handle addresses across different ecosystems. ## Use Cases for Wormhole Formatted Addresses ### Cross-chain Token Transfers Cross-chain token transfers require addresses to be converted into a standard format. For example, when transferring tokens from Ethereum to Solana, the Ethereum address is converted into a Wormhole formatted address to ensure compatibility. After the transfer, the Wormhole formatted address is converted back into the Solana native format. ### Smart Contract Interactions In smart contract interactions, especially when building dApps that communicate across multiple chains, Wormhole formatted addresses provide a uniform way to reference addresses. This ensures that addresses from different blockchains can interact seamlessly, whether you're sending messages or making cross-chain contract calls. ### DApp Development For cross-chain dApp development, Wormhole formatted addresses simplify handling user wallet addresses across various blockchains. This allows developers to manage addresses consistently, regardless of whether they work with EVM, Solana, or another supported platform. ### Relayers and Infrastructure Finally, relayers and infrastructure components, such as Wormhole Guardians, rely on the standardized format to efficiently process and relay cross-chain messages. A uniform address format simplifies operations, ensuring smooth interoperability across multiple blockchains. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/start-building/ --- BEGIN CONTENT --- --- title: Start Building description: This section has all you need to start developing with Wormhole, including a guide to supported networks, tool sets, and code examples. --- # Start Building Wormhole's role as a Generic Message Passing (GMP) protocol means it facilitates interoperability across multiple areas of project development. The following sections will help you locate the tools most relevant to your development needs whether you are focused on building frontend user interfaces or smart contracts and protocols. This section also links to developer resources like references and code examples which are helpful for all builders looking to integrate with Wormhole. ## Get Hands-On
- :octicons-repo-16:{ .lg .middle } **Tutorials** --- Follow in-depth, step-by-step tutorials to learn how to build cross-chain contracts, integrate Wormhole's SDK, and more. [:custom-arrow: Explore tutorials](/docs/tutorials/)
## Essential Resources for Development
- :octicons-broadcast-16:{ .lg .middle } **Supported Networks** --- Explore the blockchains supported by Wormhole for cross-chain communication and asset transfers. Understand which networks are available for both Testnet and Mainnet environments. [:custom-arrow: Discover supported networks](/docs/build/start-building/supported-networks/) - :octicons-goal-16:{ .lg .middle } **Testnet Faucets** --- Get Testnet tokens to start experimenting with cross-chain transfers and contract deployment. [:custom-arrow: Find Testnet faucets](/docs/build/start-building/testnet-faucets/) - :octicons-list-unordered-16:{ .lg .middle } **Reference** --- Access the essential Wormhole chain IDs and smart contract addresses for messaging protocols, token bridges, and other key components. [:custom-arrow: Explore Reference](/docs/build/reference/){target=\_blank}
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/start-building/products/ --- BEGIN CONTENT --- --- title: Compare Wormhole's Cross-Chain Solutions description: Compare Wormhole’s cross-chain solutions for bridging, native transfers, data queries, and governance to enable seamless blockchain interoperability. categories: Transfer, Basics --- # Products Wormhole provides a comprehensive suite of cross-chain solutions, enabling seamless asset transfers, data retrieval, and governance across blockchain ecosystems. Wormhole provides multiple options for asset transfers: Connect for a plug-and-play bridging UI, Native Token Transfers (NTT) for moving native assets without wrapped representations, and Token Bridge for a secure lock-and-mint mechanism. Beyond transfers, Wormhole extends interoperability with tools for cross-chain data access, decentralized governance, and an intent-based protocol through Wormhole Settlement. ## Transfer Products Wormhole offers different solutions for cross-chain asset transfer, each designed for various use cases and integration requirements. - [**Connect**](/docs/build/transfers/connect/overview/){target=\_blank} - a pre-built bridging UI for cross-chain token transfers, requiring minimal setup. Best for projects seeking an easy-to-integrate UI for bridging without modifying contracts - [**Native Token Transfers (NTT)**](/docs/learn/transfers/native-token-transfers/overview/){target=\_blank} - a mechanism to transfer native tokens cross-chain seamlessly without conversion to wrapped asset. Best for projects that require maintaining token fungibility and native chain functionality across multiple networks - [**Token Bridge**](/docs/learn/transfers/token-bridge/){target=\_blank} - a bridging solution that uses a lock and mint mechanism. Best for projects that need cross-chain liquidity using wrapped assets and the ability to send messages - [**Settlement**](/docs/learn/messaging/wormhole-settlement/overview/){target=\_blank} - intent-based protocols enabling fast multichain transfers, optimized liquidity flows, and interoperability without relying on traditional bridging methods
::spantable:: | | Criteria | Connect | NTT | Token Bridge | Settlement | |--------------------------------|---------------------------------------|--------------------|--------------------|--------------------|--------------------| | Supported Transfer Types @span | Token Transfers | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | Token Transfers with Payloads | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Supported Assets @span | Wrapped Assets | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | | | Native Assets | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | | | ERC-721s (NFTs) | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | | Features @span | Out-of-the-Box UI | :white_check_mark: | :x: | :x: | :white_check_mark: | | | Event-Based Actions | :x: | :white_check_mark: | :white_check_mark: | :x: | | | Intent-Based Execution | :x: | :x: | :x: | :white_check_mark: | | | Fast Settlement | :x: | :x: | :x: | :white_check_mark: | | | Liquidity Optimization | :x: | :x: | :x: | :white_check_mark: | | Integration Details @span | | | | | | | Requirements @span | Contract Deployment | :x: | :white_check_mark: | :x: |:x: | | Ecosystem Support | Integrates with Other Products | :white_check_mark: | :white_check_mark: | :white_check_mark: |:white_check_mark: | | Ease of Integration | Implementation Complexity | :green_circle: :white_circle: :white_circle:
Low | :green_circle: :green_circle: :white_circle:
Moderate | :green_circle: :green_circle: :white_circle:
Moderate |:green_circle: :white_circle: :white_circle:
Low | | Technology @span | Supported Languages | JavaScript, TypeScript | Solidity, Rust | Solidity, Rust, TypeScript | TypeScript | ::end-spantable::
Beyond asset transfers, Wormhole provides additional tools for cross-chain data and governance. ## Real-time Data [**Queries**](/docs/build/queries/overview/){target=\_blank} is a data retrieval service to fetch on-chain data from multiple networks. Best for applications that need multichain analytics, reporting, and data aggregation. ## Multichain Governance [**MultiGov**](/docs/learn/governance/overview/){target=\_blank} is a unified governance framework that manages multichain protocol governance through a single mechanism. Best for projects managing multichain governance and protocol updates. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/start-building/supported-networks/ --- BEGIN CONTENT --- --- title: Supported Networks description: Learn about the compatible environments and individual networks that Wormhole supports. Readers can click on each of the blockchain logos for more information. categories: Reference --- # Supported Networks Wormhole supports several different blockchains and environments. Since many of the concepts for using Wormhole within a given blockchain environment are the same, this section is organized by environment, and individual chains are detailed within the environment page. ## Supported Environments - [EVM (Ethereum and compatible chains)](#evm) - [SVM (Solana and compatible chains)](#svm) - [CosmWasm (Cosmos ecosystem chains)](#cosmwasm) - [AVM (Algorand)](#avm) - [NEAR VM (NEAR)](#near-vm) - [Move VM (Aptos)](#move-vm) - [Sui Move VM (Sui)](#sui-move-vm) ## Supported Blockchains by Environment
### EVM | Ethereum | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Acala | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Arbitrum | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Avalanche | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Base | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Berachain | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Blast | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | BNB Smart Chain | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Celo | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Fantom | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Gnosis | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | HyperEVM | EVM | :x: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs | | Ink | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Kaia | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Karura | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs | | Linea | EVM | :x: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Mantle | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Mezo | EVM | :x: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Monad | EVM | :x: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Moonbeam | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Neon | EVM | :x: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Oasis | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Optimism | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Polygon | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Scroll | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Seievm | EVM | :x: | :white_check_mark: | | | SNAXchain | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Unichain | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | World Chain | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | X Layer | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | ### SVM | Solana | SVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Pythnet | SVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | ### AVM | Algorand | AVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | ### CosmWasm | Injective | CosmWasm | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Neutron | CosmWasm | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Osmosis | CosmWasm | :x: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Sei | CosmWasm | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Terra | CosmWasm | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Terra 2.0 | CosmWasm | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | XPLA | CosmWasm | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | ### Move VM | Aptos | Move VM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | ### NEAR VM | NEAR | NEAR VM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | ### Sui Move VM | Sui | Sui Move VM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer |
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/start-building/testnet-faucets/ --- BEGIN CONTENT --- --- title: Testnet Faucets description: This page includes resources to quickly find the Testnet tokens you need to deploy and test applications and contracts on Wormhole's supported networks. categories: Reference --- # Testnet Faucets ## Get Started Don't let the need for testnet tokens get in the way of buildling your next great idea with Wormhole. Use this guide to quickly locate the testnet token faucets you need to deploy and test applications and contracts on Wormhole's supported networks.
### EVM | Ethereum Holesky | EVM | ETH | Alchemy Faucet | | Ethereum Sepolia | EVM | ETH | Alchemy Faucet | | Acala | EVM | ACA | Discord Faucet | | Arbitrum Sepolia | EVM | ETH | List of Faucets | | Avalanche | EVM | AVAX | Official Avalanche Faucet | | Base Sepolia | EVM | ETH | List of Faucets | | Berachain | EVM | BERA | Official Berachain Faucet | | Blast | EVM | ETH | List of Faucets | | BNB Smart Chain | EVM | BNB | Official BNB Faucet | | Celo | EVM | CELO | Official Celo Faucet | | Fantom | EVM | FTM | Official Fantom Faucet | | Gnosis | EVM | xDAI | Official Gnosis Faucet | | HyperEVM | EVM | mock USDC | Official Hyperliquid Faucet | | Ink | EVM | ETH | Official Ink Faucet | | Kaia | EVM | KAIA | Official Kaia Faucet | | Karura | EVM | ACA | Discord Faucet | | Linea | EVM | ETH | List of Faucets | | Mantle | EVM | MNT | Official Mantle Faucet | | Monad | EVM | MON | Official Monad Faucet | | Moonbeam | EVM | DEV | Official Moonbeam Faucet | | Neon | EVM | NEON | Official Neon Faucet | | Oasis | EVM | TEST | Official Oasis Faucet | | Optimism Sepolia | EVM | ETH | Superchain Faucet | | Polygon Amoy | EVM | POL | Official Polygon Faucet | | Scroll | EVM | ETH | List of Faucets | | Unichain | EVM | ETH | QuickNode Faucet | | World Chain | EVM | ETH | Alchemy Faucet | | X Layer | EVM | OKB | X Layer Official Faucet | ### SVM | Pythnet | SVM | ETH | Superchain Faucet | ### AVM | Algorand | AVM | ALGO | Official Algorand Faucet | ### CosmWasm | Celestia | CosmWasm | TIA | Discord Faucet | | Cosmos Hub | CosmWasm | ATOM | Discord Faucet | | Evmos | CosmWasm | TEVMOS | Official Evmos Faucet | | Injective | CosmWasm | INJ | Official Injective Faucet | | Kujira | CosmWasm | KUJI | Discord Faucet | | Neutron | CosmWasm | NTRN | List of Faucets | | Noble | CosmWasm | USDC | Circle Faucet | | Osmosis | CosmWasm | OSMO | Official Osmosis Faucet | | SEDA | CosmWasm | SEDA | Official SEDA Faucet | | Sei | CosmWasm | SEI | Sei Atlantic-2 Faucet | | Terra | CosmWasm | LUNA | Terra Official Faucet | | Terra 2.0 | CosmWasm | LUNA | Terra Official Faucet | | XPLA | CosmWasm | XPLA | XPLA Official Faucet | ### Move VM | Aptos | Move VM | APT | Official Aptos Faucet | ### NEAR VM | NEAR | NEAR VM | NEAR | Official NEAR Faucet | ### Sui Move VM | Sui | Sui Move VM | SUI | List of Faucets |
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/start-building/use-cases/ --- BEGIN CONTENT --- --- title: Use Cases description: Explore Wormhole's use cases, from cross-chain swaps to DeFi, lending, gaming, and more. See how projects integrate Wormhole solutions. categories: Basics --- # Wormhole Use Cases
## Cross-Chain Swaps and Liquidity Aggregation Enable seamless swaps between chains with real-time liquidity routing.
🛠 **Wormhole products used:** - [**Wormhole Connect**](/docs/build/transfers/connect/overview/){target=\_blank} – handles user-friendly asset transfers - [**Native Token Transfer**](/docs/build/transfers/native-token-transfers/){target=\_blank} – moves native assets across chains - [**Queries**](/docs/build/queries/overview/){target=\_blank} – fetches real-time prices for optimal trade execution 🔗 **Used in:** Decentralized exchanges (DEXs) and liquidity aggregators
🏗️ **Used by:** [StellaSwap](https://app.stellaswap.com/exchange/swap){target=\_blank}
## Borrowing and Lending Across Chains Let users borrow assets on one chain using collateral from another.
🛠 **Wormhole products used:** - [**Messaging**](/docs/learn/infrastructure/){target=\_blank} – moves loan requests and liquidations across chains - [**Native Token Transfer**](/docs/build/transfers/native-token-transfers/){target=\_blank} – transfers collateral as native assets - [**Queries**](/docs/build/queries/overview/){target=\_blank} – fetches interest rates and asset prices in real-time 🔗 **Used in:** Lending protocols and yield platforms
🏗️ **Used by:** [Folks Finance](https://wormhole.com/case-studies/folks-finance){target=\_blank}
## Real-Time Price Feeds and Trading Strategies Fetch price feeds across multiple chains for DeFi applications.
🛠 **Wormhole products used:** - [**Queries**](/docs/build/queries/overview/){target=\_blank} – fetches price feeds from oracles and trading platforms - [**Messaging**](/docs/learn/infrastructure/){target=\_blank} – sends signals to execute trades 🔗 **Used in:** Trading bots, arbitrage platforms, and oracles
🏗️ **Used by:** [Infinex](https://wormhole.com/case-studies/infinex){target=\_blank}
## Asset Movement Between Bitcoin and Other Chains Enable direct BTC transfers without wrapped assets.
🛠 **Wormhole products used:** - [**Native Token Transfer**](/docs/build/transfers/native-token-transfers/){target=\_blank} – transfers BTC across chains 🔗 **Used in:** Bitcoin DeFi and lightning network integrations
🏗️ **Used by:** [Synonym](https://wormhole.com/case-studies/synonym){target=\_blank}
## Decentralized Social Platforms Enable seamless communication and asset transfer across decentralized social networks.
🛠 **Wormhole products used:** - [**Messaging**](/docs/learn/infrastructure/){target=\_blank} – facilitates decentralized interactions - [**Token Bridge**](/docs/build/transfers/token-bridge/){target=\_blank} – enables cross-chain tokenized rewards 🔗 **Used in:** Web3 social networks and content monetization
🏗️ **Used by:** [Chingari](https://chingari.io/){target=\_blank}
## Memecoin Launchpads Launch and distribute memecoins across multiple chains, enabling cross-chain fundraising and liquidity access.
🛠 **Wormhole products used:** - [**Native Token Transfer**](/docs/build/transfers/native-token-transfers/){target=\_blank} – enables native asset transfers for seamless fundraising - [**Messaging**](/docs/learn/infrastructure/){target=\_blank} – facilitates cross-chain token distribution and claim processes 🔗 **Used in:** Token launchpads, IDOs, and meme token ecosystems
## Cross-Chain Perpetuals Enable leveraged perpetual trading across chains with seamless collateral and liquidity management.
🛠 **Wormhole products used:** - [**Queries**](/docs/build/queries/overview/){target=\_blank} – fetches real-time asset prices and manages position state across chains - [**Wormhole Settlement**](/docs/learn/transfers/settlement/overview/){target=\_blank} - for quick cross-chain token execution, providing efficient and seamless user experiences 🔗 **Used in:** Perpetual DEXs, trading platforms and cross-chain derivatives
## Gas Abstraction Allow users to pay gas fees with any token across different networks, removing friction in multichain interactions.
🛠 **Wormhole products used:** - [**Messaging**](/docs/learn/infrastructure/){target=\_blank} – routes gas fee payments across chains - [**Native Token Transfer**](/docs/build/transfers/native-token-transfers/){target=\_blank} – facilitates native token conversion for gas payments 🔗 **Used in:** Wallets, dApps, and multichain user experience improvements
## Bridging Intent Library Provide developers with a library of bridging intents and automation functions, enabling plug-and-play interoperability logic.
🛠 **Wormhole products used:** - [**Messaging**](/docs/learn/infrastructure/){target=\_blank} – enables predefined cross-chain actions and triggers. - [**Wormhole Settlement**](/docs/learn/transfers/settlement/overview/){target=\_blank} - provides a framework for executing user-defined bridging intents 🔗 **Used in:** Bridging protocols, DeFi automation, and smart contract libraries
## Multichain Prediction Markets Allow users to place bets, manage positions, and receive payouts seamlessly across different networks.
🛠 **Wormhole products used:** - [**Queries**](/docs/build/queries/overview/){target=\_blank} – fetches real-time market data, tracks collateral, and manages odds across chains - [**Wormhole Settlement**](/docs/learn/transfers/settlement/overview/){target=\_blank} – automates token execution for efficient and seamless cross-chain prediction market interactions 🔗 **Used in:** Decentralized betting, prediction markets, and cross-chain gaming
## Cross-Chain Payment Widgets Allow merchants and platforms to accept payments in any token, auto-converting them into a desired asset.
🛠 **Wormhole products used:** - [**Wormhole Connect**](/docs/build/transfers/connect/overview/){target=\_blank} – facilitates seamless payments in various tokens - [**Native Token Transfer**](/docs/build/transfers/native-token-transfers/){target=\_blank} – ensures direct, native asset transfers 🔗 **Used in:** E-commerce, Web3 payments, and subscription models
## Oracle Networks Fetch and verify cross-chain data, enabling reliable, decentralized Oracle services for multichain applications.
🛠 **Wormhole products used:** - [**Queries**](/docs/build/queries/overview/){target=\_blank} – fetches data from multiple chains and Oracle providers - [**Messaging**](/docs/learn/infrastructure/){target=\_blank} – ensures tamper-proof data relay across networks 🔗 **Used in:** Price feeds, DeFi protocols, and smart contract automation
🏗️ **Used by:** [Pyth](https://wormhole.com/case-studies/pyth){target=\_blank}
## Cross-Chain Staking Enable users to stake assets on one chain while earning rewards or securing networks on another.
🛠 **Wormhole products used:** - [**Messaging**](/docs/learn/infrastructure/){target=\_blank} – moves staking rewards and governance signals across chains - [**Native Token Transfer**](/docs/build/transfers/native-token-transfers/){target=\_blank} – transfers staked assets natively between networks 🔗 **Used in:** Liquid staking, cross-chain governance, and PoS networks
🏗️ **Used by:** [Lido](https://lido.fi/){target=\_blank}
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/toolkit/cli/ --- BEGIN CONTENT --- --- title: Wormhole CLI description: Learn how to install and use the Wormhole CLI, including commands and examples for managing multichain deployments, generating VAAs, and querying contract info. categories: Solidity-SDK, Typescript-SDK --- # Wormhole CLI This tool is a command-line interface to Wormhole, allowing you to perform various actions, such as querying a transaction's status or submitting token transfers. ## Installation Clone the repository and change directories to the appropriate directory: ```bash git clone https://github.com/wormhole-foundation/wormhole && cd wormhole/clients/js ``` Build and install the CLI tool: ```bash make install ``` This installs two binaries, `worm-fetch-governance` and `worm` on your `$PATH`. To use `worm`, set up `$HOME/.wormhole/.env` with your private keys, based on `.env.sample` in this folder. ## Usage You can interact with the Wormhole CLI by typing `worm` and including the `command` and any necessary subcommands and parameters. | Command | Description | |--------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| | `worm aptos INSERT_COMMAND` | Aptos utilities | | `worm edit-vaa INSERT_COMMAND` | Edits or generates a VAA | | `worm evm INSERT_COMMAND` | EVM utilities | | `worm generate INSERT_COMMAND` | Generate VAAs (Devnet and Testnet only) | | `worm info INSERT_COMMAND` | Contract, chain, RPC, and address information utilities | | `worm near INSERT_NETWORK, INSERT_ACCOUNT` | NEAR utilities | | `worm parse INSERT_VAA` | Parse a VAA (can be in either hex or base64 format) | | `worm recover INSERT_DIGEST INSERT_SIGNATURE` | Recover an address from a signature | | `worm status INSERT_NETWORK, INSERT_CHAIN, INSERT_TXN_HASH` | Prints information about the automatic delivery initiated on the specified network, chain, and transaction hash | | `worm submit INSERT_VAA` | Execute a VAA | | `worm sui INSERT_COMMAND` | Sui utilities | | `worm transfer INSERT_SOURCE_CHAIN, INSERT_DESTINATION_CHAIN, INSERT_DESTINATION_ADDRESS, INSERT_AMOUNT, INSERT_NETWORK` | Transfers a token | | `worm verify-vaa INSERT_VAA, INSERT_NETWORK` | Verifies a VAA by querying the Core Contract on Ethereum | You can also refer to the below options, available with all `worm` commands: ```bash Options: --help Show help [boolean] --version Show version number [boolean] ``` ### Subcommands ??? code "Aptos" ```bash worm aptos INSERT_COMMAND Commands: worm aptos init-token-bridge Init token bridge contract worm aptos init-wormhole Init Wormhole core contract worm aptos deploy Deploy an Aptos package worm aptos deploy-resource Deploy an Aptos package using a resource account worm aptos send-example-message Send example message worm aptos derive-resource-account Derive resource account address worm aptos derive-wrapped-address Derive wrapped coin type worm aptos hash-contracts Hash contract bytecodes for upgrade worm aptos upgrade Perform upgrade after VAA has been submitted worm aptos migrate Perform migration after contract upgrade worm aptos faucet Request money from the faucet for a given account worm aptos start-validator Start a local aptos validator Options: --help Show help [boolean] --version Show version number [boolean] ``` ??? code "Edit VAA" ```bash worm edit-vaa INSERT_COMMAND Options: --help Show help [boolean] --version Show version number [boolean] -v, --vaa vaa in hex format [string] [required] -n, --network Network [required] [choices: "mainnet", "testnet", "devnet"] --guardian-set-index, --gsi guardian set index [number] --signatures, --sigs comma separated list of signatures [string] --wormscanurl, --wsu url to wormscan entry for the vaa that includes signatures [string] --wormscan, --ws if specified, will query the wormscan entry for the vaa to get the signatures [boolean] --emitter-chain-id, --ec emitter chain id to be used in the vaa [number] --emitter-address, --ea emitter address to be used in the vaa[string] --nonce, --no nonce to be used in the vaa [number] --sequence, --seq sequence number to be used in the vaa[string] --consistency-level, --cl consistency level to be used in the vaa [number] --timestamp, --ts timestamp to be used in the vaa in unix seconds [number] -p, --payload payload in hex format [string] --guardian-secret, --gs Guardian's secret key [string] ``` ??? code "EVM" ```bash worm evm INSERT_COMMAND Commands: worm evm address-from-secret Compute a 20 byte eth address from a 32 byte private key worm evm storage-update Update a storage slot on an EVM fork during testing (anvil or hardhat) worm evm chains Return all EVM chains worm evm info Query info about the on-chain state of the contract worm evm hijack Override the guardian set of the core bridge contract during testing (anvil or hardhat) worm evm start-validator Start a local EVM validator Options: --help Show help [boolean] --version Show version number [boolean] --rpc RPC endpoint [string] ``` ??? code "Generate" ```bash worm generate INSERT_COMMAND Commands: worm generate registration Generate registration VAA worm generate upgrade Generate contract upgrade VAA worm generate attestation Generate a token attestation VAA worm generate recover-chain-id Generate a recover chain ID VAA worm generate Sets the default delivery provider set-default-delivery-provider for the Wormhole Relayer contract Options: --help Show help [boolean] --version Show version number [boolean] -g, --guardian-secret Guardians' secret keys (CSV) [string] [required] ``` ??? code "Info" ```bash worm info INSERT_COMMAND Commands: worm info chain-id Print the wormhole chain ID integer associated with the specified chain name worm info contract Print contract address worm info emitter
Print address in emitter address format worm info origin
Print the origin chain and address of the asset that corresponds to the given chain and address. worm info registrations Print chain registrations worm info rpc Print RPC address worm info wrapped Print the wrapped address on the target chain that corresponds with the specified origin chain and address. Options: --help Show help [boolean] --version Show version number [boolean]
``` ??? code "NEAR" ```bash worm near INSERT_COMMAND Commands: worm near contract-update Submit a contract update using our specific APIs worm near deploy Submit a contract update using near APIs Options: --help Show help [boolean] --version Show version number [boolean] -m, --module Module to query [choices: "Core", "NFTBridge", "TokenBridge"] -n, --network Network [required] [choices: "mainnet", "testnet", "devnet"] --account Near deployment account [string] [required] --attach Attach some near [string] --target Near account to upgrade [string] --mnemonic Near private keys [string] --key Near private key [string] -r, --rpc Override default rpc endpoint url [string] ``` ??? code "Parse" ```bash worm parse INSERT_VAA Positionals: vaa vaa [string] Options: --help Show help [boolean] --version Show version number [boolean] ``` ??? code "Recover" ```bash worm recover INSERT_DIGEST INSERT_SIGNATURE Positionals: digest digest [string] signature signature [string] Options: --help Show help [boolean] --version Show version number [boolean] ``` ??? code "Status" ```bash worm status INSERT_NETWORK, INSERT_CHAIN, INSERT_TXN_HASH Positionals: network Network [choices: 'mainnet', 'testnet', 'devnet'] chain Source chain [choices: 'unset', 'solana', 'ethereum', 'terra', 'bsc', 'polygon', 'avalanche', 'oasis', 'algorand', 'aurora', 'fantom', 'karura', 'acala', 'klaytn', 'celo', 'near', 'moonbeam', 'neon', 'terra2', 'injective', 'osmosis', 'sui', 'aptos', 'arbitrum', 'optimism', 'gnosis', 'pythnet', 'xpla', 'btc', 'base', 'sei', 'rootstock', 'scroll', 'mantle', 'blast', 'xlayer', 'linea', 'berachain', 'seievm', 'wormchain', 'cosmoshub', 'evmos', 'kujira', 'neutron', 'celestia', 'stargaze', 'seda', 'dymension', 'provenance', 'sepolia', 'arbitrum_sepolia', 'base_sepolia', 'optimism_sepolia', 'holesky', 'polygon_sepolia'] tx Source transaction hash [string] Options: --help Show help [boolean] --version Show version number [boolean] ``` ??? code "Submit" ```bash worm submit INSERT_VAA Positionals: vaa vaa [string] Options: --help Show help [boolean] --version Show version number [boolean] -c, --chain chain name [choices: 'unset', 'solana', 'ethereum', 'terra', 'bsc', 'polygon', 'avalanche', 'oasis', 'algorand', 'aurora', 'fantom', 'karura', 'acala', 'klaytn', 'celo', 'near', 'moonbeam', 'neon', 'terra2', 'injective', 'osmosis', 'sui', 'aptos', 'arbitrum', 'optimism', 'gnosis', 'pythnet', 'xpla', 'btc', 'base', 'sei', 'rootstock', 'scroll', 'mantle', 'blast', 'xlayer', 'linea', 'berachain', 'seievm', 'wormchain', 'cosmoshub', 'evmos', 'kujira', 'neutron', 'celestia', 'stargaze', 'seda', 'dymension', 'provenance', 'sepolia', 'arbitrum_sepolia', 'base_sepolia', 'optimism_sepolia', 'holesky', 'polygon_sepolia'] -n, --network Network [required] [choices: 'mainnet', 'testnet', 'devnet'] -a, --contract-address Contract to submit VAA to (override config) [string] --rpc RPC endpoint [string] --all-chains, --ac Submit the VAA to all chains except for the origin chain specified in the payload [boolean] [default: false] ``` ??? code "Sui" ```bash worm sui INSERT_COMMAND Commands: worm sui build-coin Build wrapped coin and dump bytecode. Example: worm sui build-coin -d 8 -v V__0_1_1 -n testnet -r "https://fullnode.testnet.sui.io:443" worm sui deploy Deploy a Sui package worm sui init-example-message-app Initialize example core message app worm sui init-token-bridge Initialize token bridge contract worm sui init-wormhole Initialize wormhole core contract worm sui publish-example-message Publish message from example app via core bridge worm sui setup-devnet Setup devnet by deploying and initializing core and token bridges and submitting chain registrations. worm sui objects Get owned objects by owner worm sui package-id Get package ID from State object ID worm sui tx Get transaction details Options: --help Show help [boolean] --version Show version number [boolean] ``` ??? code "Transfer" ```bash worm transfer INSERT_SOURCE_CHAIN, INSERT_DESTINATION_CHAIN, INSERT_DESTINATION_ADDRESS, INSERT_AMOUNT, INSERT_NETWORK Options: --help Show help [boolean] --version Show version number [boolean] --src-chain source chain [required] [choices: 'solana', 'ethereum', 'terra', 'bsc', 'polygon', 'avalanche', 'oasis', 'algorand', 'aurora', 'fantom', 'karura', 'acala', 'klaytn', 'celo', 'near', 'moonbeam', 'neon', 'terra2', 'injective', 'osmosis', 'sui', 'aptos', 'arbitrum', 'optimism', 'gnosis', 'pythnet', 'xpla', 'btc', 'base', 'sei', 'rootstock', 'scroll', 'mantle', 'blast', 'xlayer', 'linea', 'berachain', 'seievm', 'wormchain', 'cosmoshub', 'evmos', 'kujira', 'neutron', 'celestia', 'stargaze', 'seda', 'dymension', 'provenance', 'sepolia', 'arbitrum_sepolia', 'base_sepolia', 'optimism_sepolia', 'holesky', 'polygon_sepolia'] --dst-chain destination chain [required] [choices: 'solana', 'ethereum', 'terra', 'bsc', 'polygon', 'avalanche', 'oasis', 'algorand', 'aurora', 'fantom', 'karura', 'acala', 'klaytn', 'celo', 'near', 'moonbeam', 'neon', 'terra2', 'injective', 'osmosis', 'sui', 'aptos', 'arbitrum', 'optimism', 'gnosis', 'pythnet', 'xpla', 'btc', 'base', 'sei', 'rootstock', 'scroll', 'mantle', 'blast', 'xlayer', 'linea', 'berachain', 'seievm', 'wormchain', 'cosmoshub', 'evmos', 'kujira', 'neutron', 'celestia', 'stargaze', 'seda', 'dymension', 'provenance', 'sepolia', 'arbitrum_sepolia', 'base_sepolia', 'optimism_sepolia', 'holesky', 'polygon_sepolia'] --dst-addr destination address [string] [required] --token-addr token address [string] [default: native token] --amount token amount [string] [required] -n, --network Network [required] [choices: "mainnet", "testnet", "devnet"] --rpc RPC endpoint [string] ``` ??? code "Verify VAA" ```bash worm verify-vaa INSERT_VAA, INSERT_NETWORK Options: --help Show help [boolean] --version Show version number [boolean] -v, --vaa vaa in hex format [string] [required] -n, --network Network [required] [choices: "mainnet", "testnet", "devnet"] ``` ## Examples ### VAA generation Use `generate` to create VAAs for testing. For example, use the following command to create an NFT bridge registration VAA: ```bash worm generate registration --module NFTBridge \ --chain bsc \ --contract-address 0x706abc4E45D419950511e474C7B9Ed348A4a716c \ --guardian-secret cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0 ``` The below example generates a token attestation VAA: ```bash worm generate attestation --emitter-chain ethereum \ --emitter-address 11111111111111111111111111111115 \ --chain ethereum \ --token-address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \ --decimals 6 \ --symbol USDC \ --name USDC \ --guardian-secret cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0 ``` ### VAA parsing Use `parse` to parse a VAA into JSON: ```bash worm parse $(worm-fetch-governance 13940208096455381020) ``` This example will fetch governance VAA `13940208096455381020` and print it as JSON: ```bash # ...signatures elided timestamp: 1651416474, nonce: 1570649151, emitterChain: 1, emitterAddress: '0000000000000000000000000000000000000000000000000000000000000004', sequence: 13940208096455381020n, consistencyLevel: 32, payload: { module: 'Core', type: 'GuardianSetUpgrade', chain: 0, newGuardianSetIndex: 2, newGuardianSetLength: 19, newGuardianSet: [ '58cc3ae5c097b213ce3c81979e1b9f9570746aa5', 'ff6cb952589bde862c25ef4392132fb9d4a42157', '114de8460193bdf3a2fcf81f86a09765f4762fd1', '107a0086b32d7a0977926a205131d8731d39cbeb', '8c82b2fd82faed2711d59af0f2499d16e726f6b2', '11b39756c042441be6d8650b69b54ebe715e2343', '54ce5b4d348fb74b958e8966e2ec3dbd4958a7cd', '66b9590e1c41e0b226937bf9217d1d67fd4e91f5', '74a3bf913953d695260d88bc1aa25a4eee363ef0', '000ac0076727b35fbea2dac28fee5ccb0fea768e', 'af45ced136b9d9e24903464ae889f5c8a723fc14', 'f93124b7c738843cbb89e864c862c38cddcccf95', 'd2cc37a4dc036a8d232b48f62cdd4731412f4890', 'da798f6896a3331f64b48c12d1d57fd9cbe70811', '71aa1be1d36cafe3867910f99c09e347899c19c3', '8192b6e7387ccd768277c17dab1b7a5027c0b3cf', '178e21ad2e77ae06711549cfbb1f9c7a9d8096e8', '5e1487f35515d02a92753504a8d75471b9f49edb', '6fbebc898f403e4773e95feb15e80c9a99c8348d' ] } ``` ### Submitting VAAs Use `submit` to submit a VAA to a chain. It first parses the VAA and determines the destination chain and module. For example, a contract upgrade contains both the target chain and module, so the only required argument is the network moniker (`mainnet` or `testnet`): ```bash worm submit $(cat my-nft-registration.txt) --network mainnet ``` The script will ask you to specify the target chain for VAAs that don't have a specific target chain (like registrations or Guardian set upgrades). For example, to submit a Guardian set upgrade on all chains, simply run: ```bash worm-fetch-governance 13940208096455381020 > guardian-upgrade.txt worm submit $(cat guardian-upgrade.txt) --network mainnet --chain oasis worm submit $(cat guardian-upgrade.txt) --network mainnet --chain aurora worm submit $(cat guardian-upgrade.txt) --network mainnet --chain fantom worm submit $(cat guardian-upgrade.txt) --network mainnet --chain karura worm submit $(cat guardian-upgrade.txt) --network mainnet --chain acala worm submit $(cat guardian-upgrade.txt) --network mainnet --chain klaytn worm submit $(cat guardian-upgrade.txt) --network mainnet --chain avalanche worm submit $(cat guardian-upgrade.txt) --network mainnet --chain polygon worm submit $(cat guardian-upgrade.txt) --network mainnet --chain bsc worm submit $(cat guardian-upgrade.txt) --network mainnet --chain solana worm submit $(cat guardian-upgrade.txt) --network mainnet --chain terra worm submit $(cat guardian-upgrade.txt) --network mainnet --chain ethereum worm submit $(cat guardian-upgrade.txt) --network mainnet --chain celo ``` The VAA payload type (Guardian set upgrade) specifies that this VAA should go to the core bridge, and the tool directs it there. ### Getting Info To get info about a contract (only EVM supported at this time), use the following command: ```bash worm evm info -c bsc -n mainnet -m TokenBridge ``` Running this command generates the following output: ```bash { "address": "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7", "wormhole": "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B", "implementation": "0x621199f6beB2ba6fbD962E8A52A320EA4F6D4aA3", "isInitialized": true, "tokenImplementation": "0x7f8C5e730121657E17E452c5a1bA3fA1eF96f22a", "chainId": 4, "finality": 15, "evmChainId": "56", "isFork": false, "governanceChainId": 1, "governanceContract": "0x0000000000000000000000000000000000000000000000000000000000000004", "WETH": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", "registrations": { "Solana": "0xec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5", "Ethereum": "0x0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa585", "Terra": "0x0000000000000000000000007cf7b764e38a0a5e967972c1df77d432510564e2", "Polygon": "0x0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde", "Avalanche": "0x0000000000000000000000000e082f06ff657d94310cb8ce8b0d9a04541d8052", "Oasis": "0x0000000000000000000000005848c791e09901b40a9ef749f2a6735b418d7564", "Algorand": "0x67e93fa6c8ac5c819990aa7340c0c16b508abb1178be9b30d024b8ac25193d45", "Aurora": "0x00000000000000000000000051b5123a7b0f9b2ba265f9c4c8de7d78d52f510f", "Fantom": "0x0000000000000000000000007c9fc5741288cdfdd83ceb07f3ea7e22618d79d2", "Karura": "0x000000000000000000000000ae9d7fe007b3327aa64a32824aaac52c42a6e624", "Acala": "0x000000000000000000000000ae9d7fe007b3327aa64a32824aaac52c42a6e624", "Klaytn": "0x0000000000000000000000005b08ac39eaed75c0439fc750d9fe7e1f9dd0193f", "Celo": "0x000000000000000000000000796dff6d74f3e27060b71255fe517bfb23c93eed", "Near": "0x148410499d3fcda4dcfd68a1ebfcdddda16ab28326448d4aae4d2f0465cdfcb7", "Moonbeam": "0x000000000000000000000000b1731c586ca89a23809861c6103f0b96b3f57d92", "Neon": "0x0000000000000000000000000000000000000000000000000000000000000000", "Terra2": "0xa463ad028fb79679cfc8ce1efba35ac0e77b35080a1abe9bebe83461f176b0a3", "Injective": "0x00000000000000000000000045dbea4617971d93188eda21530bc6503d153313", "Osmosis": "0x0000000000000000000000000000000000000000000000000000000000000000", "Sui": "0xccceeb29348f71bdd22ffef43a2a19c1f5b5e17c5cca5411529120182672ade5", "Aptos": "0x0000000000000000000000000000000000000000000000000000000000000001", "Arbitrum": "0x0000000000000000000000000b2402144bb366a632d14b83f244d2e0e21bd39c", "Optimism": "0x0000000000000000000000001d68124e65fafc907325e3edbf8c4d84499daa8b", "Gnosis": "0x0000000000000000000000000000000000000000000000000000000000000000", "Pythnet": "0x0000000000000000000000000000000000000000000000000000000000000000", "Xpla": "0x8f9cf727175353b17a5f574270e370776123d90fd74956ae4277962b4fdee24c", "Btc": "0x0000000000000000000000000000000000000000000000000000000000000000", "Base": "0x0000000000000000000000008d2de8d2f73f1f4cab472ac9a881c9b123c79627", "Sei": "0x86c5fd957e2db8389553e1728f9c27964b22a8154091ccba54d75f4b10c61f5e", "Rootstock": "0x0000000000000000000000000000000000000000000000000000000000000000", "Scroll": "0x00000000000000000000000024850c6f61c438823f01b7a3bf2b89b72174fa9d", "Mantle": "0x00000000000000000000000024850c6f61c438823f01b7a3bf2b89b72174fa9d", "Blast": "0x00000000000000000000000024850c6f61c438823f01b7a3bf2b89b72174fa9d", "Xlayer": "0x0000000000000000000000005537857664b0f9efe38c9f320f75fef23234d904", "Linea": "0x0000000000000000000000000000000000000000000000000000000000000000", "Berachain": "0x0000000000000000000000000000000000000000000000000000000000000000", "Seievm": "0x0000000000000000000000000000000000000000000000000000000000000000", "Snaxchain": "0x0000000000000000000000000000000000000000000000000000000000000000", "Wormchain": "0xaeb534c45c3049d380b9d9b966f9895f53abd4301bfaff407fa09dea8ae7a924", "Cosmoshub": "0x0000000000000000000000000000000000000000000000000000000000000000", "Evmos": "0x0000000000000000000000000000000000000000000000000000000000000000", "Kujira": "0x0000000000000000000000000000000000000000000000000000000000000000", "Neutron": "0x0000000000000000000000000000000000000000000000000000000000000000", "Celestia": "0x0000000000000000000000000000000000000000000000000000000000000000", "Stargaze": "0x0000000000000000000000000000000000000000000000000000000000000000", "Seda": "0x0000000000000000000000000000000000000000000000000000000000000000", "Dymension": "0x0000000000000000000000000000000000000000000000000000000000000000", "Provenance": "0x0000000000000000000000000000000000000000000000000000000000000000", "Sepolia": "0x0000000000000000000000000000000000000000000000000000000000000000", "ArbitrumSepolia": "0x0000000000000000000000000000000000000000000000000000000000000000", "BaseSepolia": "0x0000000000000000000000000000000000000000000000000000000000000000", "OptimismSepolia": "0x0000000000000000000000000000000000000000000000000000000000000000", "Holesky": "0x0000000000000000000000000000000000000000000000000000000000000000", "PolygonSepolia": "0x0000000000000000000000000000000000000000000000000000000000000000" } } ``` ### Additional Info Examples You can get the contract address for a module as follows: ```bash worm info rpc INSERT_NETWORK INSERT_CHAIN INSERT_MODULE ``` To get the contract address for `NFTBridge` on BSC Mainnet, for example, you can provide the following command: ```bash worm info contract mainnet bsc NFTBridge ``` You can get the RPC address for a chain as follows: ```bash worm info rpc INSERT_NETWORK INSERT_CHAIN ``` To get the RPC address for BSC Mainnet, for example, you can provide the following command: ```bash worm info rpc mainnet bsc ``` --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/toolkit/dev-env/ --- BEGIN CONTENT --- --- title: Local Dev Environment description: Learn how to configure a development environment to build with Wormhole, including using the CLI, local validators, testing on public test networks, and more. categories: Solidity-SDK, Typescript-SDK --- # Development Environment Developers building for smart contract integration will want to set up a development environment to allow testing the full integration, possibly including VAA generation and relaying. ## Tooling Installation The [Wormhole CLI Tool](/docs/build/toolkit/cli/){target=\_blank} should be installed regardless of the environments chosen. Each environment has its own set of recommended tools. To begin working with a specific environment, see the recommended tools on the respective [environment page](/docs/build/start-building/supported-networks/){target=\_blank}. ## Development Stages Different approaches to development and testing are recommended at various stages of application development. ### Initial Development During the initial development of an on-chain application, the best option is to use the native tools available in the environment. You can visit the following resources for more information: - **[Environment](https://github.com/wormhole-foundation/wormhole){target=\_blank}** - select the folder for the desired network to learn about the recommended native toolset - **[Mock Guardian](https://github.com/wormhole-foundation/wormhole/blob/main/sdk/js/src/mock/wormhole.ts){target=\_blank}** - it's recommended to set up a mock Guardian or Emitter to provide signed VAAsFor any program methods that require some message be sent or received. - **[Wormhole Scaffolding repository](https://github.com/wormhole-foundation/wormhole-scaffolding/blob/main/evm/ts-test/01_hello_world.ts){target=\_blank}** - example mock Guardian test Relying on native tools when possible allows for more rapid prototyping and iteration. ### Integration For integration to Wormhole and with multiple chains, the simplest option is to use the chains' Testnets. In choosing which chains to use for integration testing, consider which chains in a given environment provide easy access to Testnet tokens and where block times are fast. Find links for Testnet faucets in the [blockchain details section](/docs/build/start-building/supported-networks/){target=\_blank}. A developer may prefer standing up a set of local validators instead of using the Testnet. For this option, [Tilt](https://github.com/wormhole-foundation/wormhole/blob/main/DEVELOP.md){target=\_blank} is available to run local instances of all the chains Wormhole supports. !!! note Variation in host environments causes unique issues, and the computational intensity of multiple simultaneous local validators can make setting them up difficult or time-consuming. You may prefer Testnets for the simplest integration testing. ### Prepare for Deployment Once you've finished the application's initial development and performed integration testing, you should set up a CI test environment. The best option for that is likely to be [Tilt](https://tilt.dev/){target=\_blank} since it allows you to spin up any chains supported by Wormhole in a consistent environment. ## Validator Setup with Tilt ### Tilt If you'd like to set up a local validator environment, follow the setup guide for Tilt. Tilt is a full-fledged Kubernetes deployment of every chain connected to Wormhole, along with a Guardian node. It usually takes 30 minutes to spin up fully, but it comes with all chains running out of the box. Refer to the [Tilt](https://github.com/wormhole-foundation/wormhole/blob/main/DEVELOP.md){target=\_blank} page for a complete guide to setting up and configuring Tilt. ## Deploying to Public Networks ### Testnet When doing integration testing on Testnets, remember that a single Guardian node is watching for transactions on various test networks. Because Testnets only have a single Guardian, there's a slight chance that your VAAs won't be processed. This rate doesn't indicate performance on Mainnet, where 19 Guardians are watching for transactions. The Testnet contract addresses are available on the page for each [environment](/docs/build/start-building/supported-networks/){target=\_blank}. The [Wormholescan API](https://docs.wormholescan.io){target=\_blank} offers the following Guardian equivalent Testnet endpoint: ```text https://api.testnet.wormholescan.io ``` ### Mainnet The Mainnet contract addresses are available on the page for each [environment](/docs/build/start-building/supported-networks/){target=\_blank}. The [Wormholescan API](https://docs.wormholescan.io){target=\_blank} offers the following Guardian equivalent Mainnet endpoint: ```text https://api.wormholescan.io ``` --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/toolkit/faqs/ --- BEGIN CONTENT --- --- title: Toolkit FAQs description: FAQs on Wormhole Toolkit, covering Wormholescan, CLI, SDKs (TypeScript, Solidity), Tilt, error handling, transaction history, and manual VAA submission. categories: Solidity-SDK, Typescript-SDK --- # Toolkit FAQs ## Why does the `toNative` function in the TypeScript SDK return an error? The `toNative` function may return an error if the platform-specific module (such as Solana or EVM) is not correctly imported or passed into the Wormhole constructor. To fix this, ensure the relevant platform module is imported and included when initializing Wormhole. For example, if you're working with Solana, make sure to import the Solana module and pass it into the Wormhole constructor like this: ```typescript import solana from '@wormhole-foundation/sdk/solana'; const wh = await wormhole('Testnet', [solana]); ``` ## How can I retrieve the history of previously bridged transactions? To retrieve the history of previously bridged transactions, you can use the Wormholescan API. Use the following endpoint to query the transaction history for a specific address: ```bash https://api.wormholescan.io/api/v1/operations?address=INSERT_ADDRESS ``` Simply replace `INSERT_ADDRESS_HERE` with the address you want to query. The API will return a list of operations, including details about previously bridged transactions. ???- example "Fetch transaction history for a specific address" ```bash curl -X GET "https://api.wormholescan.io/api/v1/operations?address=0x05c009C4C1F1983d4B915C145F4E782de23d3A38" -H "accept: application/json" ``` ## How can I manually submit a VAA to a destination chain in the correct format? To manually submit a VAA (Verifiable Action Approval) to a destination chain, follow these steps: 1. **Obtain the VAA in Base64 format** - navigate to the **Advanced** tab in [Wormholescan](https://wormholescan.io/){target=\_blank} to find the VAA associated with the transaction you want to submit and copy the VAA in base64 format ```bash https://wormholescan.io/#/tx/INSERT_TX_HASH?view=advanced ``` 2. **Convert the VAA to hex** - you must convert the base64 VAA into a hexadecimal (hex) format before submitting it to the destination chain. This can be done using various online tools or via command-line utilities like `xxd` or a script in a language like Python 3. **Submit the VAA through Etherscan (for EVM chains)** - once the VAA is in hex format, go to the [Etherscan UI](https://etherscan.io/){target=\_blank} and submit it through the [`TokenBridge`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/interfaces/ITokenBridge.sol){target=\_blank} contract’s method (such as the `CompleteTransfer` function or `CompleteTransferWithPayload`) - The `TokenBridge` contract addresses for each chain are available in the [Wormhole contract addresses](/docs/build/reference/contract-addresses/){target=\_blank} section - Interact with the smart contract through the Etherscan UI by pasting the hex-encoded VAA into the appropriate field Following these steps, you can manually submit a VAA in the proper format to a destination chain. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/toolkit/ --- BEGIN CONTENT --- --- title: Wormhole Tooling description: This page lists key dev tools, including the WormholeScan Explorer, Wormhole CLI, Wormhole SDKs, and APIs for querying network data. categories: Solidity-SDK --- # Wormhole Tooling Regardless of which network development environment you are using, there are a few Wormhole-specific tools you should know about. ## Get Started
- :octicons-telescope-16:{ .lg .middle } **Wormholescan** --- Wormholescan is an explorer for looking at individual transfer statuses on Mainnet and Testnet. [:custom-arrow: Review transactions on Wormholescan](https://wormholescan.io){target=\_blank} - :octicons-plug-16:{ .lg .middle } **Wormholescan API** --- Leverage the Wormholescan API to programmatically access Wormhole network data, including transaction details and VAAs. [:custom-arrow: Explore the Wormholescan API](https://wormholescan.io/#/developers/api-doc){target=\_blank} - :octicons-code-square-16:{ .lg .middle } **Wormhole CLI Tool** --- The Wormhole CLI is a Swiss-Army knife utility command line tool. It is excellent for creating one-off VAAs, parsing VAAs, reading Wormhole contract configurations, and more. [:custom-arrow: Get started with the CLI](/docs/build/toolkit/cli/) - :octicons-code-square-16:{ .lg .middle } **Wormhole SDK** --- Explore Wormhole's TypeScript SDK and learn how to perform different types of transfers, including native, token, and USDC transfers. [:custom-arrow: Get started with the SDK](/docs/build/toolkit/typescript-sdk/) - :octicons-code-square-16:{ .lg .middle } **Solidity SDK** --- Learn about Wormhole's Solidity SDK, including key components, interfaces, and tools for developing cross-chain decentralized applications on EVM-compatible blockchains. [:custom-arrow: Get started with the SDK](/docs/build/toolkit/solidity-sdk/) - :octicons-beaker-16:{ .lg .middle } **Tilt** --- Learn about Tilt, a Wormhole developer environment with a local Kubernetes set up for cross-chain testing with Guardian nodes and relayers for seamless development. [:custom-arrow: Get started with Tilt](https://github.com/wormhole-foundation/wormhole/blob/main/DEVELOP.md){target=\_blank}
## Additional Resources
- :octicons-code-square-16:{ .lg .middle } **Wormhole Spy SDK** --- The Wormhole Spy SDK allows you to listen to all the Guardian Network activity. [:custom-arrow: Check out the Spy SDK repository](https://github.com/wormhole-foundation/wormhole/tree/main/spydk/js){target=\_blank} - :octicons-pencil-16:{ .lg .middle } **VAA Parser** --- The VAA Parser is a resource for parsing out details of an encoded VAA. [:custom-arrow: Try the VAA Parser](https://wormholescan.io/#/developers/vaa-parser){target=\_blank}
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/toolkit/solidity-sdk/ --- BEGIN CONTENT --- --- title: Solidity SDK description: How to use the Wormhole Solidity SDK for cross-chain messaging, token transfers, and integrating decentralized applications on EVM-compatible blockchains. categories: Solidity-SDK --- # Solidity SDK ## Introduction The [Wormhole Solidity SDK](https://github.com/wormhole-foundation/wormhole-solidity-sdk){target=\_blank} simplifies cross-chain messaging on EVM-compatible chains by providing essential Solidity interfaces, utility libraries, and testing tools. It allows developers to build secure and efficient cross-chain decentralized applications (dApps) without manually interacting with Wormhole’s core contracts across multiple chains. By abstracting away complex interactions, the SDK drastically reduces the overhead associated with cross-chain development. It provides: - **Unified interfaces** - developers can use a standardized set of Solidity interfaces to handle cross-chain messaging, token transfers, and verifiable action approvals (VAAs) without needing to manage the underlying infrastructure - **Automated message delivery** - the SDK leverages Wormhole’s relayer infrastructure, automatically delivering messages across chains, reducing the need for manual intervention, and simplifying gas management on the target chain - **Seamless integration with Wormhole services** - the SDK integrates with Wormhole’s `TokenBridge` and Circle’s CCTP, providing built-in mechanisms for cross-chain asset transfers, making token bridges and cross-chain messaging easy to implement - **Testing and development tools** - it comes with comprehensive tools for local testing and simulation, allowing developers to validate their cross-chain logic before deployment, minimizing the risk of errors in production environments These features significantly streamline the development workflow by reducing complexity and offering tools compatible with various EVM versions. This helps developers avoid issues that arise from differences in EVM equivalence across chains. This guide covers installation, key concepts, and usage examples to help you build secure cross-chain applications using the SDK, from token transfers to advanced message passing. ## Installation To install the SDK, use [Foundry and Forge](https://book.getfoundry.sh/getting-started/installation){target=\_blank}. This pulls the necessary libraries into your project: ```bash forge install wormhole-foundation/wormhole-solidity-sdk@v0.1.0 ``` When developing cross-chain applications, ensure that the chains you target support the EVM version you’re using. For instance, the PUSH0 opcode (introduced in Solidity 0.8.20) may not be available on all chains. To avoid compatibility issues, you can set the EVM version in your `foundry.toml` file: ```toml evm_version = "paris" ``` This ensures compatibility across all targeted chains, even if some do not yet support the latest EVM upgrades. ## Key Considerations Before deploying applications using the Wormhole Solidity SDK, keep these considerations in mind: - **Version compatibility** - the SDK is evolving, and using tagged releases for production is crucial, as the main branch may introduce breaking changes - **IERC-20 remapping** - the SDK provides a remapping mechanism to handle potential conflicts between different implementations of IERC20, ensuring seamless integration with other libraries - **Testing** - given the cross-chain dependencies, testing all integrations is critical to avoid issues in production environments ## Concepts and Components The Wormhole Solidity SDK consists of key components that streamline cross-chain communication, allowing developers to securely and efficiently interact with Wormhole’s infrastructure. Below are the critical concepts and contracts you'll encounter when working with the SDK. ### Cross-Chain Messaging with the Wormhole Relayer SDK The [`WormholeRelayerSDK.sol`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayerSDK.sol){target=\_blank} contract simplifies cross-chain messaging and asset transfers by integrating several necessary modules, including the Wormhole relayer. By automating message delivery between chains, the Wormhole relayer removes the need for developers to manage relayer infrastructure or handle gas on the target chain. Delivery providers handle the message payload, ensuring secure and efficient communication. You can refer to the [Wormhole relayer documentation](/docs/build/core-messaging/wormhole-relayers/){target=\_blank} for more details. Key modules in the SDK include: - **`Base.sol`** - the core module for cross-chain messaging. It provides utility functions like `onlyWormholeRelayer()` and `setRegisteredSender()`, ensuring that only messages from trusted relayers are processed - **`TokenBase.sol`** - this module extends the base messaging functionality to support cross-chain token transfers. It includes utilities for securely sending and receiving tokens between EVM-compatible chains - **`CCTPBase.sol`** - designed for Circle’s Cross-Chain Transfer Protocol, this module manages asset transfers such as USDC between chains. It includes functionalities for both sending and receiving CCTP-based assets - **`CCTPAndTokenBase.sol`** - a combined module that supports token and CCTP-based asset transfers in a single implementation. This module simplifies development for applications needing to handle both types of transfers The Wormhole Solidity SDK offers a unified framework for cross-chain communication. Developers can select specific modules based on their application’s requirements, whether for messaging, token transfers, or CCTP. Each module includes built-in security measures, ensuring that only authorized senders or relayers are accepted, thereby protecting the application from unauthorized interactions. Please refer to the complete `WormholeRelayerSDK.sol` file below for further details. ???- code "`WormholeRelayerSDK.sol`" ```solidity // SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.19; import "wormhole-sdk/interfaces/IWormholeReceiver.sol"; import "wormhole-sdk/interfaces/IWormholeRelayer.sol"; import "wormhole-sdk/constants/Chains.sol"; import "wormhole-sdk/Utils.sol"; import {Base} from "wormhole-sdk/WormholeRelayer/Base.sol"; import {TokenBase, TokenReceiver, TokenSender} from "wormhole-sdk/WormholeRelayer/TokenBase.sol"; import {CCTPBase, CCTPReceiver, CCTPSender} from "wormhole-sdk/WormholeRelayer/CCTPBase.sol"; import {CCTPAndTokenBase, CCTPAndTokenReceiver, CCTPAndTokenSender} from "wormhole-sdk/WormholeRelayer/CCTPAndTokenBase.sol"; ``` ### Base Contract Overview The [`Base.sol`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/Base.sol){target=\_blank} contract is a core part of the Wormhole Solidity SDK, providing essential helper functions and modifiers for managing cross-chain messages securely via the Wormhole Relayer. It handles sender registration and message validation, ensuring only authorized senders from specific chains can send messages. - **`onlyWormholeRelayer()`** - a modifier that ensures only authorized messages from the Wormhole relayer contract are processed, restricting access to certain functions ```solidity require( msg.sender == address(wormholeRelayer), "Msg.sender is not Wormhole Relayer" ); _; } ``` - **`setRegisteredSender()`** - restricts message acceptance to a registered sender from a specific chain, ensuring messages are only processed from trusted sources ```solidity uint16 sourceChain, bytes32 sourceAddress ) public { require( msg.sender == registrationOwner, "Not allowed to set registered sender" ); registeredSenders[sourceChain] = sourceAddress; } ``` These security measures ensure messages come from the correct source and are processed securely. Please refer to the complete `Base.sol` contract below for further details. ???- code "`Base.sol`" ```solidity // SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.19; import "wormhole-sdk/interfaces/IWormholeReceiver.sol"; import "wormhole-sdk/interfaces/IWormholeRelayer.sol"; import "wormhole-sdk/interfaces/IWormhole.sol"; import "wormhole-sdk/Utils.sol"; abstract contract Base { IWormholeRelayer public immutable wormholeRelayer; IWormhole public immutable wormhole; address registrationOwner; mapping(uint16 => bytes32) registeredSenders; constructor(address _wormholeRelayer, address _wormhole) { wormholeRelayer = IWormholeRelayer(_wormholeRelayer); wormhole = IWormhole(_wormhole); registrationOwner = msg.sender; } modifier onlyWormholeRelayer() { require( msg.sender == address(wormholeRelayer), "Msg.sender is not Wormhole Relayer" ); _; } modifier isRegisteredSender(uint16 sourceChain, bytes32 sourceAddress) { require( registeredSenders[sourceChain] == sourceAddress, "Not registered sender" ); _; } /** * Sets the registered address for 'sourceChain' to 'sourceAddress' * So that for messages from 'sourceChain', only ones from 'sourceAddress' are valid * * Assumes only one sender per chain is valid * Sender is the address that called 'send' on the Wormhole Relayer contract on the source chain) */ function setRegisteredSender( uint16 sourceChain, bytes32 sourceAddress ) public { require( msg.sender == registrationOwner, "Not allowed to set registered sender" ); registeredSenders[sourceChain] = sourceAddress; } } ``` ### Interface for Cross-Chain Messages The Wormhole Solidity SDK interacts with the Wormhole relayer for sending and receiving messages across EVM-compatible chains. The [`IWormholeRelayer`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/interfaces/IWormholeRelayer.sol){target=\_blank} and [`IWormholeReceiver`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/interfaces/IWormholeReceiver.sol){target=\_blank} interfaces are central to cross-chain communication, enabling secure and efficient message delivery. For detailed information on how to implement these interfaces, refer to the [Wormhole Relayer Interfaces documentation](/docs/build/core-messaging/wormhole-relayers/#wormhole-relayer-interfaces){target=\_blank}. This section covers: - **`IWormholeRelayer`** – methods for sending cross-chain messages, VAAs, and token transfers - **`IWormholeReceiver`** – the required implementation for receiving cross-chain messages - **`quoteEVMDeliveryPrice()`** – how to estimate gas and fees for cross-chain transactions These interfaces reduce the complexity of cross-chain dApp development by abstracting away the details of relayer infrastructure, ensuring that message delivery is handled efficiently. ### Advanced Concepts For developers interested in exploring additional advanced topics, the following sections provide insights into key aspects of the SDK’s functionality. ???- note "Error Handling and Reverts" The SDK defines several custom errors to help developers handle common issues like incorrect gas fees, invalid senders, and more. For example, `InvalidMsgValue` is thrown when the message value for a relayed message is erroneous. ```solidity error InvalidMsgValue(uint256 msgValue, uint256 totalFee); ``` ## Usage This section covers cross-chain messaging and token transfers and shows how to use the Wormhole Solidity SDK in real-world scenarios. ### Send a Cross-Chain Message To send a cross-chain message, inherit from the base contract provided by the SDK and use its helper methods to define your message and sender address. Here’s a basic example: ```solidity pragma solidity ^0.8.19; import "@wormhole-foundation/wormhole-solidity-sdk/src/WormholeRelayer/Base.sol"; contract CrossChainSender is Base { constructor( address _wormholeRelayer, address _wormhole ) Base(_wormholeRelayer, _wormhole) {} function sendMessage( bytes memory message, uint16 targetChain, bytes32 targetAddress ) external payable { // Register sender and send message through WormholeRelayer setRegisteredSender(targetChain, msg.sender); onlyWormholeRelayer().sendPayloadToEvm( targetChain, address(targetAddress), message, 0, 500_000 ); } } ``` This contract extends `Base.sol` and allows sending cross-chain messages securely using the `WormholeRelayer`. ### Send Tokens Across Chains The SDK enables seamless token transfers between EVM-compatible chains in addition to sending messages. To facilitate cross-chain token transfers, you can extend the SDK's `TokenSender` and `TokenReceiver` base contracts. ```solidity pragma solidity ^0.8.19; import "@wormhole-foundation/wormhole-solidity-sdk/src/WormholeRelayer/TokenBase.sol"; contract CrossChainTokenSender is TokenSender { constructor( address _wormholeRelayer, address _wormhole ) TokenSender(_wormholeRelayer, _wormhole) {} function sendToken( address token, uint256 amount, uint16 targetChain, bytes32 targetAddress ) external payable { // Send tokens across chains transferTokenToTarget(token, amount, targetChain, targetAddress); } } ``` In this example, `TokenSender` initiates a token transfer to another chain. The SDK’s built-in utilities securely handle token transfers, ensuring proper VAAs are generated and processed. ### Receive Tokens Across Chains To receive tokens on the target chain, implement a contract that inherits from `TokenReceiver` and overrides the `receiveWormholeMessages` function. ```solidity pragma solidity ^0.8.19; import "@wormhole-foundation/wormhole-solidity-sdk/src/WormholeRelayer/TokenBase.sol"; contract CrossChainTokenReceiver is TokenReceiver { constructor( address _wormholeRelayer, address _wormhole ) TokenReceiver(_wormholeRelayer, _wormhole) {} // Function to handle received tokens from another chain function receiveWormholeMessages( bytes memory payload, bytes[] memory additionalMessages, bytes32 sourceAddress, uint16 sourceChain, bytes32 deliveryHash ) external payable override { // Process the received tokens here receiveTokens(payload); } } ``` In this example, `TokenReceiver` allows the contract to handle tokens sent from the source chain. Once the cross-chain message is received, the `receiveWormholeMessages` function processes the incoming tokens. Always validate the message's authenticity and source. !!! note Always verify the source of incoming messages and tokens to prevent unauthorized access to your contract. Please refer to the [Emitter Verification](/docs/build/core-messaging/core-contracts/#validating-the-emitter/){target=\_blank} section for more details. ## Testing Environment The SDK includes built-in support for Forge-based testing, which allows you to test your cross-chain applications locally before deploying them to production. Testing with the same Solidity compiler version and configuration you plan to use in production is highly recommended to catch any potential issues early. For a detailed example, check out the below repositories: - [Cross chain messaging](/docs/tutorials/solidity-sdk/cross-chain-contracts/){target=\_blank} - [Cross chain token transfer](/docs/tutorials/solidity-sdk/cross-chain-token-contracts/){target=\_blank} --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/toolkit/typescript-sdk/ --- BEGIN CONTENT --- --- title: Wormhole SDK description: The Wormhole SDK provides tools for cross-chain communication, token bridges, and more, enabling developers to integrate with multiple blockchain environments. categories: Typescript-SDK --- # Wormhole SDK ## Get Started The Wormhole SDK provides developers with essential tools for cross-chain communication, token bridges, and more. This SDK enables seamless interaction between different blockchain environments with a focus on performance and usability.
- :octicons-book-16:{ .lg .middle } **Wormhole SDK** --- Learn about the core functionalities of the Wormhole SDK, including how to use its features for building cross-chain applications. [:custom-arrow: Explore the SDK](/docs/build/toolkit/typescript-sdk/wormhole-sdk/) - :octicons-code-16:{ .lg .middle } **Layouts** --- Discover how to define, serialize, and deserialize data structures using the Wormhole SDK's layout system, ensuring efficient cross-chain communication. [:custom-arrow: Learn about layouts](/docs/build/toolkit/typescript-sdk/sdk-layout/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/toolkit/typescript-sdk/protocols-payloads/ --- BEGIN CONTENT --- --- title: Building Protocols and Payloads description: Learn how to build, register, and integrate protocols and payloads in the Wormhole TypeScript SDK with type-safe layouts. categories: Typescript-SDK --- # Building Protocols and Payloads ## Introduction The [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank} provides a flexible and powerful system for integrating cross-chain communication into your applications. A key feature of the SDK is its ability to define protocols—modular units representing distinct functionalities—and their associated payloads, which encapsulate the data required for specific operations within those protocols. This guide will help you understand how to build protocols and payloads in the SDK, covering: - The role of protocols and payloads in cross-chain communication - The mechanics of registering protocols and payloads using the SDK - Best practices for creating strongly typed layouts to ensure compatibility and reliability - Real-world examples using the `TokenBridge` as a reference implementation By the end of this guide, you’ll have a solid understanding of how to define, register, and use protocols and payloads in your projects. ## What is a Protocol? In the Wormhole SDK, a protocol represents a significant feature or functionality that operates across multiple blockchains. Protocols provide the framework for handling specific types of messages, transactions, or operations consistently and standardized. Examples of Protocols: - **`TokenBridge`** - enables cross-chain token transfers, including operations like transferring tokens and relaying payloads - **`NTT (Native Token Transfers)`** - manages native token movements across chains Protocols are defined by: - **A `name`** - a string identifier (e.g., `TokenBridge`, `Ntt`) - **A set of `payloads`** - these represent the specific actions or messages supported by the protocol, such as `Transfer` or `TransferWithPayload` Each protocol is registered in the Wormhole SDK, allowing developers to leverage its predefined features or extend it with custom payloads. ## What is a Payload? A payload is a structured piece of data that encapsulates the details of a specific operation within a protocol. It defines the format, fields, and types of data used in a message or transaction. Payloads ensure consistency and type safety when handling complex cross-chain operations. Each payload is defined as: - **A `layout`** - describes the binary format of the payload fields - **A `literal`** - combines the protocol name and payload name into a unique identifier (e.g., `TokenBridge:Transfer`) By registering payloads, developers can enforce type safety and enable serialization and deserialization for specific protocol operations. ## Register Protocols and Payloads Protocols and payloads work together to enable cross-chain communication with precise type safety. For instance, in the `TokenBridge` protocol: - The protocol is registered under the `TokenBridge` namespace - Payloads like `Transfer` or `AttestMeta` are linked to the protocol to handle specific operations Understanding the connection between these components is important for customizing or extending the SDK to suit your needs. ### Register Protocols Registering a protocol establishes its connection to Wormhole's infrastructure, ensuring it interacts seamlessly with payloads and platforms while maintaining type safety and consistency. #### How Protocol Registration Works Protocol registration involves two key tasks: - **Mapping protocols to interfaces** - connect the protocol to its corresponding interface, defining its expected behavior across networks (`N`) and chains (`C`). This ensures type safety, similar to strong typing, by preventing runtime errors if protocol definitions are incorrect - **Linking protocols to platforms** - specify platform-specific implementations if needed, or use default mappings for platform-agnostic protocols For example, here's the `TokenBridge` protocol registration: ```typescript declare module '../../registry.js' { export namespace WormholeRegistry { interface ProtocolToInterfaceMapping { TokenBridge: TokenBridge; } interface ProtocolToPlatformMapping { TokenBridge: EmptyPlatformMap<'TokenBridge'>; } } } ``` This code snippet: - Maps the `TokenBridge` protocol to its interface to define how it operates - Links the protocol to a default platform mapping via `EmptyPlatformMap` You can view the full implementation in the [`TokenBridge` protocol file](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L14-L70){target=\_blank}. #### Platform-Specific Protocols Some protocols require platform-specific behavior. For instance, the EVM-compatible Wormhole Registry maps native addresses for Ethereum-based chains: ```typescript declare module '@wormhole-foundation/sdk-connect' { export namespace WormholeRegistry { interface PlatformToNativeAddressMapping { Evm: EvmAddress; } } } registerNative(_platform, EvmAddress); ``` This ensures that `EvmAddress` is registered as the native address type for EVM-compatible platforms. See the [EVM platform address file](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/platforms/evm/src/address.ts#L98-L106){target=\_blank} for details. ### Register Payloads [Payload registration](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/dbbbc7c365db602dd3b534f6d615ac80c3d2aaf1/core/definitions/src/vaa/registration.ts){target=\_blank} enables developers to define, serialize, and handle custom message types within their protocols. It establishes the connection between a protocol and its payloads, ensuring seamless integration, type enforcement, and runtime efficiency. This process ties a protocol to its payloads using a combination of: - **Payload literals** - unique identifiers in the format `:`. These literals map each payload to a layout - **Payload layouts** - structures that define the binary representation of payload data - **The payload factory** - a centralized runtime registry that maps payload literals to layouts for dynamic resolution and serialization These components work together to streamline the definition and management of protocol payloads. #### How Payload Registration Works Payload registration involves: 1. **Define payload layouts** - create layouts to structure your payloads. For instance, a protocol might use a `TransferWithPayload` layout: ```typescript export const transferWithPayloadLayout = < const P extends CustomizableBytes = undefined >( customPayload?: P ) => [ payloadIdItem(3), ...transferCommonLayout, { name: 'from', ...universalAddressItem }, customizableBytes({ name: 'payload' }, customPayload), ] as const; ``` 2. **Register payloads** - use `registerPayloadTypes` to map payload literals to their layouts: ```typescript registerPayloadTypes('ProtocolName', protocolNamedPayloads); ``` 3. **Access registered payloads** - use the [`getPayloadLayout`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/9105de290c91babbf8ad031bd89cc75ee38739c8/core/definitions/src/vaa/functions.ts#L19-L23){target=\_blank} function to fetch the layout for a specific payload literal. This method ensures that the correct layout is retrieved dynamically and safely: ```typescript const layout = getPayloadLayout('ProtocolName:PayloadName'); ``` These steps link payload literals and their layouts, enabling seamless runtime handling. #### The Payload Factory At the core of the payload registration process is the `payloadFactory`, a registry that manages the mapping between payload literals and layouts: ```typescript export const payloadFactory = new Map(); export function registerPayloadType( protocol: ProtocolName, name: string, layout: Layout ) { const payloadLiteral = composeLiteral(protocol, name); if (payloadFactory.has(payloadLiteral)) { throw new Error(`Payload type ${payloadLiteral} already registered`); } payloadFactory.set(payloadLiteral, layout); } ``` - The `payloadFactory` ensures each payload literal maps to its layout uniquely - The [`registerPayloadType`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/dbbbc7c365db602dd3b534f6d615ac80c3d2aaf1/core/definitions/src/vaa/registration.ts#L46-L52){target=\_blank} function adds individual payloads, while [`registerPayloadTypes`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/dbbbc7c365db602dd3b534f6d615ac80c3d2aaf1/core/definitions/src/vaa/registration.ts#L62-L64){target=\_blank} supports bulk registration This implementation ensures dynamic, efficient handling of payloads at runtime. ## Integrate Protocols with Payloads Integrating payloads with protocols enables dynamic identification through payload literals, while serialization and deserialization ensure their binary representation is compatible across chains. For more details on these processes, refer to the [Layouts page](/docs/build/toolkit/typescript-sdk/sdk-layout/){target=\_blank}. ### Payload Discriminators Payload discriminators are mechanisms in the Wormhole SDK that dynamically identify and map incoming payloads to their respective layouts at runtime. They are relevant for protocols like `TokenBridge`, enabling efficient handling of diverse payload types while ensuring type safety and consistent integration. #### How Discriminators Work Discriminators evaluate serialized binary data and determine the corresponding payload layout by inspecting fixed fields or patterns within the data. Each payload layout is associated with a payload literal (e.g., `TokenBridge:Transfer` or `TokenBridge:TransferWithPayload`). This system ensures: - **Dynamic runtime identification** - payloads are parsed based on their content, even if a single protocol handles multiple payload types - **Strict type enforcement** - discriminators leverage layout mappings to prevent invalid payloads from being processed Below is an example of how the Wormhole SDK builds a discriminator to distinguish between payload layouts: ```typescript export function layoutDiscriminator( layouts: readonly Layout[], allowAmbiguous?: B ): Discriminator { // Internal logic to determine distinguishable layouts const [distinguishable, discriminator] = internalBuildDiscriminator(layouts); if (!distinguishable && !allowAmbiguous) { throw new Error('Cannot uniquely distinguish the given layouts'); } return ( !allowAmbiguous ? (encoded: BytesType) => { const layout = discriminator(encoded); return layout.length === 0 ? null : layout[0]; } : discriminator ) as Discriminator; } ``` - [`layoutDiscriminator`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/9105de290c91babbf8ad031bd89cc75ee38739c8/core/base/src/utils/layout.ts#L16){target=\_blank} takes a list of layouts and generates a function that can identify the appropriate layout for a given serialized payload - The `allowAmbiguous` parameter determines whether layouts with overlapping characteristics are permitted ### Real-World Example: Token Bridge Protocol Integrating protocols with their respective payloads exemplifies how the Wormhole SDK leverages layouts and type-safe registration mechanisms to ensure efficient cross-chain communication. This section focuses on how protocols like `TokenBridge` use payloads to facilitate specific operations. #### Token Bridge Protocol and Payloads The `TokenBridge` protocol enables cross-chain token transfers through its payloads. Key payloads include: - **`Transfer`** - handles basic token transfer operations - **`TransferWithPayload`** - extends the `Transfer` payload to include custom data, enhancing functionality Payloads are registered to the `TokenBridge` protocol via the `PayloadLiteralToLayoutMapping` interface, which links payload literals (e.g., `TokenBridge:Transfer`) to their layouts. Additionally, the protocol uses reusable layouts like [`transferCommonLayout`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/76b20317b0f68e823d4e6c4a2e41bb2a7705c64f/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts#L29C7-L47){target=\_blank} and extends them in more specialized layouts such as [`transferWithPayloadLayout`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/76b20317b0f68e823d4e6c4a2e41bb2a7705c64f/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts#L49-L57){target=\_blank}: ```typescript export const transferWithPayloadLayout = < const P extends CustomizableBytes = undefined >( customPayload?: P ) => [ payloadIdItem(3), ...transferCommonLayout, { name: 'from', ...universalAddressItem }, customizableBytes({ name: 'payload' }, customPayload), ] as const; ``` This layout includes: - A `payloadIdItem` to identify the payload type - Common fields for token and recipient details - A customizable `payload` field for additional data #### Use the Discriminator To manage multiple payloads, the `TokenBridge` protocol utilizes a discriminator to distinguish between payload types dynamically. For example: ```typescript const tokenBridgePayloads = ['Transfer', 'TransferWithPayload'] as const; export const getTransferDiscriminator = lazyInstantiate(() => payloadDiscriminator([_protocol, tokenBridgePayloads]) ); ``` - The [`getTransferDiscriminator`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/dbbbc7c365db602dd3b534f6d615ac80c3d2aaf1/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L67-L70){target=\_blank} function dynamically evaluates payloads using predefined layouts - This ensures that each payload type is processed according to its unique structure and type-safe layout #### Register Payloads to Protocols Here’s how the `TokenBridge` protocol connects its payloads to the Wormhole SDK: ```typescript declare module '../../registry.js' { export namespace WormholeRegistry { interface PayloadLiteralToLayoutMapping extends RegisterPayloadTypes< 'TokenBridge', typeof tokenBridgeNamedPayloads > {} } } registerPayloadTypes('TokenBridge', tokenBridgeNamedPayloads); ``` This registration links the `TokenBridge` payload literals to their respective layouts, enabling serialization and deserialization at runtime. You can explore the complete `TokenBridge` protocol and payload definitions in the [`TokenBridge` layout file](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts){target=\_blank}. #### Token Bridge Payloads The following payloads are registered for the `TokenBridge` protocol: - **`AttestMeta`** - used for token metadata attestation - **`Transfer`** - facilitates token transfers - **`TransferWithPayload`** - adds a custom payload to token transfers These payloads and their layouts are defined in the [`TokenBridge` layout file](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts){target=\_blank}. ### Other Protocols: Native Token Transfers (NTT) While this guide focuses on the `TokenBridge` protocol, other protocols, like NTT, follow a similar structure. - NTT manages the transfer of native tokens across chains - Payloads such as `WormholeTransfer` and `WormholeTransferStandardRelayer` are registered to the protocol using the same patterns for payload literals and layouts - The same mechanisms for type-safe registration and payload discriminators apply, ensuring reliability and extensibility For more details, you can explore the [NTT implementation in the SDK](https://github.com/wormhole-foundation/example-native-token-transfers/blob/00f83aa215338b1b8fd66f522bd0f45be3e98a5a/sdk/definitions/src/ntt.ts){target=\_blank}. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/toolkit/typescript-sdk/sdk-layout/ --- BEGIN CONTENT --- --- title: Data Layouts description: Learn how to efficiently define, serialize, and deserialize data structures using Wormhole SDK's layout system for cross-chain communication. categories: Typescript-SDK --- # Data Layouts ## Introduction The [Wormhole SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank} uses the [layout package](https://www.npmjs.com/package/binary-layout){target=\_blank} to define, serialize, and deserialize data structures efficiently. This modular system ensures consistent data formatting and cross-environment compatibility, benefiting projects that require robust handling of structured data. By understanding the layout mechanism, you’ll be able to: - Define data structures (numbers, arrays, and custom types) - Efficiently serialize and deserialize data using the SDK’s utilities - Handle protocol-specific layouts with ease This guide is beneficial for developers looking to integrate Wormhole into their applications or protocols, especially those dealing with complex payloads or cross-chain communication. ## Key Concepts ### Layout Items A layout defines how data structures should be serialized (converted into binary format) and deserialized (converted back into their original structure). This ensures consistent data formatting when transmitting information across different blockchain environments. Layouts are composed of [layout items](https://github.com/nonergodic/layout/blob/main/src/items.ts){target=\_blank}, which describe individual fields or sets of fields in your data. Each layout item specifies: - **`name`** - name of the field - **`binary`** - type of data (e.g., `uint`, `bytes`) - **`size`** - byte length for fixed-size fields within uint and bytes items only Layout items can represent: - **Primitive types** - basic data types like unsigned integers (`uint`) or byte arrays (`bytes`) - **Composite types** - more complex structures, such as arrays or nested objects Below is an example of a layout that might be used to serialize a message across the Wormhole protocol: ```typescript const exampleLayout = [ { name: 'sourceChain', binary: 'uint', size: 2 }, { name: 'orderSender', binary: 'bytes', size: 32 }, { name: 'redeemer', binary: 'bytes', size: 32 }, { name: 'redeemerMessage', binary: 'bytes', lengthSize: 4 }, ] as const; ``` In this example: - `sourceChain` is a 2-byte unsigned integer (`uint`) identifying the source blockchain - `orderSender` is a fixed-length 32-byte array representing the sender's address - `redeemer` is another 32-byte array used for the redeemer’s address - `redeemerMessage` is a variable-length byte sequence, with its length specified by a 4-byte integer This layout definition ensures that all necessary data fields are consistently encoded and can be correctly interpreted when they are deserialized. ### Serialization and Deserialization Serialization converts structured data into binary format; deserialization reverses this, reconstructing the original objects. You can serialize data using the `serializeLayout` function: ```typescript const serialized = serializeLayout(fillLayout, exampleFill); ``` To deserialize the binary data back into a structured object, use the `deserializeLayout` function: ```typescript const deserialized = deserializeLayout(fillLayout, serialized); ``` ### Custom Conversions Layouts also allow for custom conversions, which help map complex or custom types (like chain IDs or universal addresses) into a more usable format. This is useful when serializing or deserializing data that doesn’t fit neatly into simple types like integers or byte arrays. For example, consider a custom conversion for a chain ID: ```typescript const chainCustomConversion = { to: (chainId: number) => toChain(chainId), from: (chain: Chain) => chainToChainId(chain), } satisfies CustomConversion; ``` This setup allows Wormhole to convert between human-readable formats and binary-encoded data used in payloads. ### Error Handling The layout system performs error checks during serialization and deserialization. An error is thrown if data is incorrectly sized or in the wrong format. Refer to the below example: ```typescript try { deserializeLayout(fillLayout, corruptedData); } catch (error) { console.error('Error during deserialization:', error.message); } ``` ## Application of Layouts This section will focus on applying the concepts explained earlier through examples. These will help developers better understand how to define layouts, serialize and deserialize data, and use custom conversions where needed. ### Defining Layouts To get started with layouts in Wormhole, you need to define your structure. A layout is simply a list of fields (layout items) describing how each data piece will be serialized. Consider the following layout for a payload: ```typescript const exampleLayout = [ { name: 'sourceChain', binary: 'uint', size: 2 }, { name: 'orderSender', binary: 'bytes', size: 32 }, { name: 'redeemer', binary: 'bytes', size: 32 }, { name: 'redeemerMessage', binary: 'bytes', lengthSize: 4 }, ] as const; ``` In this example: - `sourceChain` is an unsigned integer (uint) of 2 bytes - `orderSender` is a 32-byte fixed-length byte array - `redeemer` is another 32-byte byte array - `redeemerMessage` is a length-prefixed byte array, with the length specified by a 4-byte integer ### Serialize Data Once a layout is defined, the next step is to serialize data according to that structure. You can accomplish this using the `serializeLayout` function from the Wormhole SDK. ```typescript const examplePayload = { sourceChain: 6, orderSender: new Uint8Array(32), redeemer: new Uint8Array(32), redeemerMessage: new Uint8Array([0x01, 0x02, 0x03]), }; const serializedData = serializeLayout(exampleLayout, examplePayload); ``` This takes the data structure (`examplePayload`) and serializes it according to the rules defined in the layout (`exampleLayout`). The result is a `Uint8Array` representing the serialized binary data. ### Deserialize Data Deserialization is the reverse of serialization. Given a serialized `Uint8Array`, we can convert it back into its original structure using the `deserializeLayout` function. ```typescript const deserializedPayload = deserializeLayout(exampleLayout, serializedData); ``` This will output the structured object, making it easy to work with data transmitted or received from another chain. ### Handling Variable-Length Fields One relevant aspect of Wormhole SDK's layout system is the ability to handle variable-length fields, such as arrays and length-prefixed byte sequences. For instance, if you want to serialize or deserialize a message where the length of the content isn't known beforehand, you can define a layout item with a `lengthSize` field. ```typescript { name: 'message', binary: 'bytes', lengthSize: 4 } ``` This tells the SDK to read or write the message's length (in 4 bytes) and then handle the content. ## Nested Layouts and Strong Typing The Wormhole SDK simplifies handling complex structures with nested layouts and strong typing. Nested layouts clearly represent hierarchical data, while strong typing ensures data consistency and catches errors during development. ### Nested Layout In complex protocols, layouts can contain nested structures. Nested layouts become relevant here, allowing you to represent hierarchical data (such as transactions or multi-part messages) in a structured format. Refer to the following nested layout where a message contains nested fields: ```typescript const nestedLayout = [ { name: 'source', binary: 'bytes', layout: [ { name: 'chainId', binary: 'uint', size: 2 }, { name: 'sender', binary: 'bytes', size: 32 }, ], }, { name: 'redeemer', binary: 'bytes', layout: [ { name: 'address', binary: 'bytes', size: 32 }, { name: 'message', binary: 'bytes', lengthSize: 4 }, ], }, ] as const satisfies Layout; ``` In this layout: - `source` is an object with two fields: `chainId` and `sender` - `redeemer` is another object with two fields: `address` and a length-prefixed `message` ### Strong Typing One of the benefits of using the Wormhole SDK in TypeScript is its support for strong typing. This ensures that serialized and deserialized data conform to expected structures, reducing errors during development by enforcing type checks at compile time. Using TypeScript, the `LayoutToType` utility provided by the SDK automatically generates a strongly typed structure based on the layout: ```typescript type NestedMessage = LayoutToType; ``` This ensures that when you serialize or deserialize data, it matches the expected structure. ```typescript const message: NestedMessage = { source: { chainId: 6, sender: new Uint8Array(32), }, redeemer: { address: new Uint8Array(32), message: new Uint8Array([0x01, 0x02, 0x03]), }, }; ``` Attempting to assign data of incorrect types will result in a compile-time error. The Wormhole SDK's layout system enforces strong types, reducing runtime errors and improving code reliability. ### Serialization and Deserialization with Nested Layouts You can serialize and deserialize nested structures in the same way as simpler layouts: ```typescript const serializedNested = serializeLayout(nestedLayout, message); const deserializedNested = deserializeLayout(nestedLayout, serializedNested); ``` Strong typing in TypeScript ensures that the message object conforms to the nested layout structure. This reduces the risk of data inconsistency during cross-chain communication. ## Commonly Used Layouts The Wormhole SDK includes predefined layouts frequently used in cross-chain messaging. These layouts are optimized for standard fields such as chain IDs, addresses, and signatures. You can explore the complete set of predefined layouts in the [`layout-items` directory](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/definitions/src/layout-items){target=\_blank} of the Wormhole SDK. ### Chain ID Layouts Chain ID layouts in the Wormhole SDK derive from a common foundation: `chainItemBase`. This structure defines the binary representation of a chain ID as a 2-byte unsigned integer, ensuring consistency across serialization and deserialization processes. #### Base Structure This simple structure is the blueprint for more specific layouts by standardizing the binary format and size. ```typescript const chainItemBase = { binary: 'uint', size: 2 } as const; ``` #### Dynamic Chain ID Layout The dynamic chain ID layout, [`chainItem`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/layout-items/chain.ts#L13-L40){target=\_blank}, extends `chainItemBase` by adding flexible custom conversion logic. It enables runtime validation of chain IDs, supports optional null values, and restricts chain IDs to a predefined set when needed. ```typescript export const chainItem = < const C extends readonly Chain[] = typeof chains, const N extends boolean = false, >(opts?: { allowedChains?: C; allowNull?: N; }) => ({ ...chainItemBase, // Builds on the base structure custom: { to: (val: number): AllowNull => { ... }, from: (val: AllowNull): number => { ... }, }, }); ``` This layout is versatile. It allows the serialization of human-readable chain names (e.g., `Ethereum`) to numeric IDs (e.g., `1`) and vice versa. This is particularly useful when working with dynamic configurations or protocols supporting multiple chains. #### Fixed Chain ID Layout The fixed chain ID layout, [`fixedChainItem`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/layout-items/chain.ts#L42-L49){target=\_blank}, is more rigid. It also extends `chainItemBase`, but the custom field is hardcoded for a single chain. This eliminates runtime validation and enforces strict adherence to a specific chain. ```typescript export const fixedChainItem = (chain: C) => ({ ...chainItemBase, // Builds on the base structure custom: { to: chain, from: chainToChainId(chain), }, }); ``` This layout allows developers to efficiently serialize and deserialize messages involving a single, fixed chain ID. ### Address Layout The Wormhole SDK uses a Universal Address Layout to serialize and deserialize blockchain addresses into a standardized format. This layout ensures that addresses are always represented as fixed 32-byte binary values, enabling seamless cross-chain communication. #### Base Structure The [`universalAddressItem`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/layout-items/universalAddress.ts#L7-L14){target=\_blank} defines the layout for addresses. It uses the binary type bytes and enforces a fixed size of 32 bytes for consistency. ```typescript export const universalAddressItem = { binary: 'bytes', size: 32, custom: { to: (val: Uint8Array): UniversalAddress => new UniversalAddress(val), from: (val: UniversalAddress): Uint8Array => val.toUint8Array(), } satisfies CustomConversion, } as const satisfies LayoutItem; ``` This layout ensures consistent address handling by defining the following: - **Serialization** - converts a high-level `UniversalAddress` object into raw binary (32 bytes) for efficient storage or transmission - **Deserialization** - converts raw binary back into a `UniversalAddress` object, enabling further interaction in a human-readable or programmatic format ### Signature Layout In the Wormhole SDK, the Signature Layout defines how to serialize and deserialize cryptographic signatures. These signatures verify message authenticity and ensure data integrity, particularly in Guardian-signed VAAs. #### Base Structure The `signatureLayout` specifies the binary structure of a secp256k1 signature. It divides the signature into three components: ```typescript const signatureLayout = [ { name: 'r', binary: 'uint', size: 32 }, { name: 's', binary: 'uint', size: 32 }, { name: 'v', binary: 'uint', size: 1 }, ] as const satisfies Layout; ``` This layout provides a clear binary format for the secp256k1 signature, making it efficient to process within the Wormhole protocol. #### Layout with Custom Conversion The [`signatureItem`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/layout-items/signature.ts#L15-L22){target=\_blank} builds upon the `signatureLayout` by adding custom conversion logic. This conversion transforms raw binary data into a high-level `Signature` object and vice versa. ```typescript export const signatureItem = { binary: 'bytes', layout: signatureLayout, custom: { to: (val: LayoutToType) => new Signature(val.r, val.s, val.v), from: (val: Signature) => ({ r: val.r, s: val.s, v: val.v }), } satisfies CustomConversion, Signature>, } as const satisfies BytesLayoutItem; ``` The `custom` field ensures seamless integration of raw binary data with the `Signature` class, encapsulating signature-specific logic. ## Advanced Use Cases The Wormhole SDK’s layout system is designed to handle various data structures and serialization needs. This section will explore more advanced use cases, such as handling conditional data structures, fixed conversions, and optimizing serialization performance. ???- code "Switch Statements for Conditional Layouts" In some cases, the structure of serialized data might change based on a specific field, such as a payload ID. The switch layout type conditionally defines layouts based on a value. For example, different message types can be identified using a payload ID, and the layout for each message can be determined at runtime: ```typescript const switchLayout = { binary: 'switch', idSize: 1, // size of the payload ID idTag: 'messageType', // tag to identify the type of message layouts: [ [[1, 'messageType1'], fillLayout], // layout for type 1 [[2, 'messageType2'], fastFillLayout], // layout for type 2 ], } as const satisfies Layout; ``` The switch statement helps developers parse multiple payload types using the same structure, depending on a control field like an ID. ???- code "Fixed Conversions and Omitted Fields" Fixed conversions and omitted fields allow developers to handle known, static data without including it in every serialization or deserialization operation. For instance, when specific fields in a layout always hold a constant value, they can be omitted from the deserialized object. **Example: Fixed Conversion** In some cases, a field may always contain a predefined value. The layout system supports fixed conversions, allowing developers to “hard-code” these values: ```typescript const fixedConversionLayout = { binary: 'uint', size: 2, custom: { to: 'Ethereum', from: chainToChainId('Ethereum'), }, } as const satisfies Layout; ``` **Example: Omitted Fields** Omitted fields are useful for handling padding or reserved fields that do not carry meaningful information and can safely be excluded from the deserialized output: ```typescript const omittedFieldLayout = [ { name: 'reserved', binary: 'uint', size: 2, omit: true }, ] as const satisfies Layout; ``` In this example, `reserved` is a padding field with a fixed, non-dynamic value that serves no functional purpose. It is omitted from the deserialized result but still considered during serialization to maintain the correct binary format. Only fields with a fixed, known value, such as padding or reserved fields, should be marked as `omit: true`. Fields with meaningful or dynamic information, such as `sourceChain` or `version`, must remain in the deserialized structure to ensure data integrity and allow seamless round-trip conversions between serialized and deserialized representations. ## Integration with Wormhole Protocol The layout system facilitates seamless interaction with the Wormhole protocol, mainly when dealing with VAAs. These cross-chain messages must be serialized and deserialized to ensure they can be transmitted and processed accurately across different chains. ### VAAs and Layouts VAAs are the backbone of Wormhole’s cross-chain communication. Each VAA is a signed message encapsulating important information such as the originating chain, the emitter address, a sequence number, and Guardian signatures. The Wormhole SDK leverages its layout system to define, serialize, and deserialize VAAs, ensuring data integrity and chain compatibility. #### Base VAA Structure The Wormhole SDK organizes the VAA structure into three key components: - [**Header**](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/vaa/vaa.ts#L37-L41){target=\_blank} - contains metadata such as the Guardian set index and an array of Guardian signatures - [**Envelope**](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/vaa/vaa.ts#L44-L51){target=\_blank} - includes chain-specific details such as the emitter chain, address, sequence, and [consistency (finality) level](/docs/build/reference/consistency-levels/){target=\_blank} - **Payload** - provides application-specific data, such as the actual message or operation being performed **Header layout:** ```typescript const guardianSignatureLayout = [ { name: 'guardianIndex', binary: 'uint', size: 1 }, { name: 'signature', ...signatureItem }, ] as const satisfies Layout; export const headerLayout = [ { name: 'version', binary: 'uint', size: 1, custom: 1, omit: true }, { name: 'guardianSet', ...guardianSetItem }, { name: 'signatures', binary: 'array', lengthSize: 1, layout: guardianSignatureLayout, }, ] as const satisfies Layout; ``` The header defines metadata for validating and processing the VAA, such as the Guardian set index and signatures. Each signature is represented using the `signatureItem` layout, ensuring consistency and compatibility across different platforms. !!! note "Signature Standard Compliance" The signature field uses the `signatureItem` layout, which is explicitly defined as 65 bytes. This layout is aligned with widely used standards such as EIP-2612 and Uniswap's Permit2, ensuring compatibility with cryptographic protocols and applications. **Envelope layout:** ```typescript export const envelopeLayout = [ { name: 'timestamp', binary: 'uint', size: 4 }, { name: 'nonce', binary: 'uint', size: 4 }, { name: 'emitterChain', ...chainItem() }, { name: 'emitterAddress', ...universalAddressItem }, { name: 'sequence', ...sequenceItem }, { name: 'consistencyLevel', binary: 'uint', size: 1 }, ] as const satisfies Layout; ``` The envelope encapsulates the VAA's core message data, including chain-specific information like the emitter address and sequence number. This structured layout ensures that the VAA can be securely transmitted across chains. **Payload Layout:** The Payload contains the user-defined data specific to the application or protocol, such as a token transfer message, governance action, or other cross-chain operation. The layout of the payload is dynamic and depends on the payload type, identified by the `payloadLiteral` field. ```typescript const examplePayloadLayout = [ { name: 'type', binary: 'uint', size: 1 }, { name: 'data', binary: 'bytes', lengthSize: 2 }, ] as const satisfies Layout; ``` This example demonstrates a payload containing: - A type field specifying the operation type (e.g., transfer or governance action) - A data field that is length-prefixed and can store operation-specific information Dynamic payload layouts are selected at runtime using the `payloadLiteral` field, which maps to a predefined layout in the Wormhole SDK. **Combined Base Layout:** The base VAA layout combines the header, envelope, and dynamically selected payload layout: ```typescript export const baseLayout = [...headerLayout, ...envelopeLayout] as const; ``` At runtime, the payload layout is appended to the `baseLayout` to form the complete structure. #### Serializing VAA Data The Wormhole SDK provides the [`serialize`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/vaa/functions.ts#L48-L54){target=\_blank} function to serialize a VAA message. This function combines the base layout (header and envelope) with the appropriate payload layout, ensuring the message’s format is correct for transmission across chains. ```typescript import { serialize } from '@wormhole-foundation/sdk-core/vaa/functions'; const vaaData = { guardianSet: 1, signatures: [{ guardianIndex: 0, signature: new Uint8Array(65).fill(0) }], timestamp: 1633000000, nonce: 42, emitterChain: 2, // Ethereum emitterAddress: new Uint8Array(32).fill(0), sequence: BigInt(1), consistencyLevel: 1, payloadLiteral: 'SomePayloadType', payload: { key: 'value' }, }; const serializedVAA = serialize(vaaData); ``` ???- note "How does it work?" Internally, the serialize function dynamically combines the `baseLayout` (header and envelope) with the payload layout defined by the `payloadLiteral`. The complete layout is then passed to the `serializeLayout` function, which converts the data into binary format. ```typescript const layout = [ ...baseLayout, // Header and envelope layout payloadLiteralToPayloadItemLayout(vaa.payloadLiteral), // Payload layout ] as const; return serializeLayout(layout, vaa as LayoutToType); ``` #### Deserializing VAA Data The Wormhole SDK provides the [`deserialize`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/vaa/functions.ts#L162-L200){target=\_blank} function to parse a VAA from its binary format back into a structured object. This function uses the `baseLayout` and payload discriminator logic to ensure the VAA is correctly interpreted. ```typescript import { deserialize } from '@wormhole-foundation/sdk-core/vaa/functions'; const serializedVAA = new Uint8Array([ /* Serialized VAA binary data */ ]); const vaaPayloadType = 'SomePayloadType'; // The payload type expected for this VAA const deserializedVAA = deserialize(vaaPayloadType, serializedVAA); ``` ???- note "How does it work?" Internally, the `deserialize` function uses the `baseLayout` (header and envelope) to parse the main VAA structure. It then identifies the appropriate payload layout using the provided payload type or discriminator. ```typescript const [header, envelopeOffset] = deserializeLayout(headerLayout, data, { consumeAll: false, }); const [envelope, payloadOffset] = deserializeLayout(envelopeLayout, data, { offset: envelopeOffset, consumeAll: false, }); const [payloadLiteral, payload] = typeof payloadDet === 'string' ? [ payloadDet as PayloadLiteral, deserializePayload(payloadDet as PayloadLiteral, data, payloadOffset), ] : deserializePayload( payloadDet as PayloadDiscriminator, data, payloadOffset ); return { ...header, ...envelope, payloadLiteral, payload, } satisfies VAA; ``` ### Registering Custom Payloads In the Wormhole SDK, payloads rely on layouts to define their binary structure, ensuring consistency and type safety across protocols. Custom payloads extend this functionality, allowing developers to handle protocol-specific features or unique use cases. To learn how to define and register payloads using layouts, refer to the [Building Protocols and Payloads](/docs/build/toolkit/typescript-sdk/protocols-payloads/){target=\_blank} page for a detailed guide. ## Common Pitfalls & Best Practices When working with the Wormhole SDK layout system, it's important to be aware of a few common issues that can arise. Below are some pitfalls to avoid and best practices to ensure smooth integration. ### Pitfalls to Avoid #### Defining Sizes for Data Types When defining sizes for each data type, make sure to match the actual data length to the specified size to prevent serialization and deserialization errors: - **`uint` and `int`** - the specified size must be large enough to accommodate the data value. For instance, storing a value greater than 255 in a single byte (`uint8`) will fail since it exceeds the byte’s capacity. Similarly, an undersized integer (e.g., specifying 2 bytes for a 4-byte integer) can lead to data loss or deserialization failure - **`bytes`** - the data must match the specified byte length in the layout. For example, defining a field as 32 bytes (`size: 32`) requires the provided data to be exactly 32 bytes long; otherwise, serialization will fail ```typescript // Pitfall: Mismatch between the size of data and the defined size in the layout { name: 'orderSender', binary: 'bytes', size: 32 } // If the provided data is not exactly 32 bytes, this will fail ``` #### Incorrectly Defined Arrays Arrays can be fixed-length or length-prefixed, so it’s important to define them correctly. Fixed-length arrays must match the specified length, while length-prefixed arrays need a `lengthSize` field. ```typescript // Pitfall: Array length does not match the expected size { name: 'redeemerMessage', binary: 'bytes', lengthSize: 4 } ``` ### Best Practices These best practices and common pitfalls can help prevent bugs and improve the reliability of your implementation when working with layouts in the Wormhole SDK. #### Reuse Predefined Layout Items Rather than defining sizes or types manually, reuse the predefined layout items provided by the Wormhole SDK. These items ensure consistent formatting and enforce strong typing. For instance, use the `chainItem` layout for chain IDs or `universalAddressItem` for blockchain addresses: ```typescript import { chainItem, universalAddressItem, } from '@wormhole-foundation/sdk-core/layout-items'; const exampleLayout = [ { name: 'sourceChain', ...chainItem() }, // Use predefined chain ID layout { name: 'senderAddress', ...universalAddressItem }, // Use universal address layout ] as const; ``` By leveraging predefined layout items, you reduce redundancy, maintain consistency, and ensure compatibility with Wormhole’s standards. #### Use Class Instances Whenever possible, convert deserialized data into higher-level class instances. This makes it easier to validate, manipulate, and interact with structured data. For example, the [`UniversalAddress`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/universalAddress.ts#L17-L59){target=\_blank} class ensures consistent address handling: ```typescript import { UniversalAddress } from '@wormhole-foundation/sdk-core'; const deserializedAddress = new UniversalAddress(someBinaryData); ``` Focusing on reusing predefined layout items and converting deserialized data into higher-level abstractions can ensure a more robust and maintainable implementation. #### Consistent Error Handling Always handle errors during both serialization and deserialization. Catching exceptions allows you to log or resolve issues gracefully when working with potentially corrupted or invalid data. ```typescript try { const deserialized = deserializeLayout(fillLayout, data); } catch (error) { console.error('Deserialization failed:', error); } ``` #### Leverage Reusable Layouts Creating reusable layouts for commonly repeated structures improves code maintainability and reduces duplication. These layouts can represent fields or combinations of fields frequently encountered in cross-chain communication, such as chain IDs, addresses, and signatures. For example, define a reusable layout for chain IDs and addresses: ```typescript const commonLayout = [ { name: 'chainId', binary: 'uint', size: 2 }, { name: 'address', binary: 'bytes', size: 32 }, ] as const satisfies Layout; // Reuse the common layout in different contexts const exampleLayout = [ ...commonLayout, { name: 'sequence', binary: 'uint', size: 8 }, ]; ``` By abstracting common elements into a single layout, you ensure consistency across different parts of your application and simplify future updates. ## Performance Considerations Efficient serialization and deserialization are crucial when handling large amounts of cross-chain data. Below are some strategies and best practices to ensure optimal performance when using Wormhole SDK layouts. ### Lazy Instantiation Building a discriminator can be resource-intensive for complex or large datasets. The layout structures do not incur significant upfront costs, but deferring the creation of discriminators until needed can improve efficiency. ```typescript const lazyDiscriminator = lazyInstantiate(() => layoutDiscriminator(layouts)); ``` This approach ensures that discriminators are only built when required, helping to optimize performance, especially for complex or conditional layouts. ## Resources For further learning and practical experience, explore the following resources: - **Wormhole TypeScript SDK** - the [Wormhole SDK repository](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank} contains the core implementation of layouts, including predefined layout items and utilities like `serializeLayout` and `deserializeLayout` - **Layout tests repository** - for hands-on experimentation, check out this [layout package repository](https://github.com/nonergodic/layout){target=\_blank}, which provides examples and unit tests to help you better understand serialization, deserialization, and the strong typing mechanism. Running these tests locally is a great way to deepen your understanding of how layouts function in real-world scenarios --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/toolkit/typescript-sdk/vaas-protocols/ --- BEGIN CONTENT --- --- title: VAAs and Protocols description: Understand how VAAs enable cross-chain messaging and how to handle them using Wormhole's TypeScript and Solidity SDKs. categories: Typescript-SDK --- # VAAs and Protocols ## Introduction Wormhole's core functionality revolves around [Verifiable Action Approvals](/docs/learn/infrastructure/vaas/){target=\_blank} (VAAs), which are signed messages enabling secure and decentralized communication across chains. This guide focuses on their practical usage within the Wormhole ecosystem, specifically when working with protocol-specific messages in the [TypeScript](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank} and [Solidity](https://github.com/wormhole-foundation/wormhole-solidity-sdk){target=\_blank} SDKs. For deeper insights into serialization, deserialization, and protocol design, refer to: - [Data Layouts](/docs/build/toolkit/typescript-sdk/sdk-layout/){target=\_blank} for serialization concepts - [Building Protocols and Payloads](/docs/build/toolkit/typescript-sdk/protocols-payloads/){target=\_blank} for designing custom protocol messages This guide will help you understand how to handle VAAs and protocol messages in off-chain and on-chain scenarios. ## VAA Structure Understanding the structure of VAAs is fundamental to working with Wormhole's SDKs. Each section of the VAA—Header, Envelope, and Payload—serves a specific role: | Section | Description | |----------|----------------------------------------------------------------------------------------------------------| | Header | Includes the version and guardian signature information required to verify the VAA | | Envelope | Contains metadata about the emitted message, such as the emitter chain, emitter address, and timestamp | | Payload | Represents the actual message, in raw bytes, without a length prefix | The VAA's body combines the Envelope and Payload. The Wormhole Guardians signed the core data and hashed (using `keccak256`) to generate the VAA's unique identifier. When integrating protocols like Token Bridge or Wormhole Relayer: - The TypeScript SDK handles VAAs off-chain, focusing on deserialization, validation, and payload extraction before submission - The Solidity SDK processes VAAs on-chain, using libraries like `VaaLib` to decode and execute protocol actions ## VAAs in Protocol Contexts ### How VAAs Enable Protocol-Specific Messaging VAAs are the backbone of Wormhole's cross-chain communication, encapsulating critical protocol payloads that drive actions on different blockchains. Each protocol—such as [Token Bridge](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/definitions/src/protocols/tokenBridge){target=\_blank}, [Wormhole Relayer](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/definitions/src/protocols/relayer){target=\_blank}, or [Circle CCTP](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/definitions/src/protocols/circleBridge){target=\_blank}—uses VAAs to securely transmit its messages across chains. Examples of mapping protocols to VAAs: | Protocol | Payload Purpose | Example | |-----------------|-----------------------------------------------------------|------------------------------------| | Token Bridge | Transfers token data and metadata | Token transfer or redemption | | Wormhole Relayer| Manages delivery instructions for messages across chains | Delivery fee or refund handling | | Circle CCTP | Facilitates stablecoin mint-and-burn operations | Circle-issued stablecoin transfer | Each protocol integrates its payload format into the VAA structure, ensuring consistent message validation and execution across the ecosystem. ### TypeScript SDK: Off-Chain Handling of VAAs The TypeScript SDK is designed for off-chain operations like reading, validating, and manipulating VAAs before submitting them to a chain. Developers can easily deserialize VAAs to extract protocol payloads and prepare actions such as initiating token transfers or constructing delivery instructions. In the example below, we use the real [`envelopeLayout`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/dd6bd2463264680597519285ff559f9e92e85ca7/core/definitions/src/vaa/vaa.ts#L44-L51){target=\_blank} from Wormhole's TS SDK to deserialize and extract essential information like the emitter chain, sequence, and [consistency (finality) level](/docs/build/reference/consistency-levels/){target=\_blank}: ```typescript import { deserializeLayout } from '@wormhole-foundation/sdk-base'; import { universalAddressItem, sequenceItem, } from '@wormhole-foundation/core/layout-items/index.js'; export const envelopeLayout = [ { name: 'timestamp', binary: 'uint', size: 4 }, { name: 'nonce', binary: 'uint', size: 4 }, { name: 'emitterChain', binary: 'uint', size: 2 }, { name: 'emitterAddress', ...universalAddressItem }, { name: 'sequence', ...sequenceItem }, { name: 'consistencyLevel', binary: 'uint', size: 1 }, ] as const satisfies Layout; const encodedEnvelope = new Uint8Array([ /* binary envelope data */ ]); const deserializedEnvelope = deserializeLayout(envelopeLayout, encodedEnvelope); ``` For more details, you can refer to the [parseVAA example](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/examples/src/parseVaa.ts){target=\_blank} in the Wormhole SDK repository. ### Solidity SDK: On-Chain Handling of VAAs The Solidity SDK enables on-chain processing of VAAs directly within smart contracts. This is essential for real-time validation, decoding, and execution of protocol-specific payloads. Developers can use libraries like [`VaaLib`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/e19013d08d1fdf5af9e6344c637e36a270422dd9/src/libraries/VaaLib.sol){target=\_blank} to parse the VAA header and payload, ensuring the message is authentic and consistent with Wormhole's validation. Below is an example of parsing an envelope on-chain using the Solidity SDK: ```solidity // SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.19; import {VaaLib} from "wormhole-sdk/libraries/VaaLib.sol"; contract EnvelopeParser { using VaaLib for bytes; function parseEnvelope( bytes memory encodedVaa ) public pure returns ( uint32 timestamp, uint32 nonce, uint16 emitterChainId, bytes32 emitterAddress, uint64 sequence, uint8 consistencyLevel ) { // Skip the header and decode the envelope uint offset = VaaLib.skipVaaHeaderMemUnchecked(encodedVaa, 0); return VaaLib.decodeVaaEnvelopeMemUnchecked(encodedVaa, offset); } } ``` --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/toolkit/typescript-sdk/wormhole-sdk/ --- BEGIN CONTENT --- --- title: Wormhole TS SDK description: Explore Wormhole's TypeScript SDK and learn how to perform different types of transfers, including native, token, and USDC. categories: Typescript-SDK --- # Wormhole TypeScript SDK ## Introduction The Wormhole TypeScript SDK is useful for interacting with the chains Wormhole supports and the [protocols](#protocols) built on top of Wormhole. This package bundles together functions, definitions, and constants that streamline the process of connecting chains and completing transfers using Wormhole. The SDK also offers targeted sub-packages for Wormhole-connected platforms, which allow you to add multichain support without creating outsized dependencies. This section covers all you need to know about the functionality and ease of development offered through the Wormhole TypeScript SDK. Take a tour of the package to discover how it helps make integration easier. Learn more about how the SDK abstracts away complexities around concepts like platforms, contexts, and signers. Finally, you'll find guidance on usage, along with code examples, to show you how to use the tools of the SDK.
- :octicons-download-16:{ .lg .middle } **Installation** --- Find installation instructions for both the meta package and installing specific, individual packages [:custom-arrow: Install the SDK](#installation) - :octicons-book-16:{ .lg .middle } **Concepts** --- Understand key concepts and how the SDK abstracts them away. Learn more about platforms, chain context, addresses, and signers [:custom-arrow: Explore concepts](#concepts) - :octicons-file-code-16:{ .lg .middle } **Usage** --- Guidance on using the SDK to add seamless interchain messaging to your application, including code examples [:custom-arrow: Use the SDK](#usage) - :octicons-code-square-16:{ .lg .middle } **TSdoc for SDK** --- Review the TSdoc for the Wormhole TypeScript SDK for a detailed look at availabel methods, classes, interfaces, and definitions [:custom-arrow: View the TSdoc on GitHub](https://wormhole-foundation.github.io/wormhole-sdk-ts/){target=\_blank}
!!! warning This package is a work in progress. The interface may change, and there are likely bugs. Please [report](https://github.com/wormhole-foundation/connect-sdk/issues){target=\_blank} any issues you find. ## Installation ### Basic To install the meta package using npm, run the following command in the root directory of your project: ```bash npm install @wormhole-foundation/sdk ``` This package combines all the individual packages to make setup easier while allowing for tree shaking. ### Advanced Alternatively, you can install a specific set of published packages individually: ??? interface "`sdk-base` - exposes constants" ```sh npm install @wormhole-foundation/sdk-base ``` ??? interface "`sdk-definitions` - exposes contract interfaces, basic types, and VAA payload definitions" ```sh npm install @wormhole-foundation/sdk-definitions ``` ??? interface "`sdk-evm` - exposes EVM-specific utilities" ```sh npm install @wormhole-foundation/sdk-evm ``` ??? interface "`sdk-evm-tokenbridge` - exposes the EVM Token Bridge protocol client" ```sh npm install @wormhole-foundation/sdk-evm-tokenbridge ``` ## Usage Getting your integration started is simple. First, import Wormhole: ```ts import { wormhole } from '@wormhole-foundation/sdk'; import { Wormhole, amount, signSendWait } from '@wormhole-foundation/sdk'; import algorand from '@wormhole-foundation/sdk/algorand'; import aptos from '@wormhole-foundation/sdk/aptos'; import cosmwasm from '@wormhole-foundation/sdk/cosmwasm'; import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import sui from '@wormhole-foundation/sdk/sui'; import { getSigner } from './helpers/index.js'; (async function () { const wh = await wormhole('Testnet', [ evm, solana, aptos, algorand, cosmwasm, sui, ]); const ctx = wh.getChain('Solana'); const rcv = wh.getChain('Algorand'); const sender = await getSigner(ctx); const receiver = await getSigner(rcv); // Get a Token Bridge contract client on the source const sndTb = await ctx.getTokenBridge(); // Send the native token of the source chain const tokenId = Wormhole.tokenId(ctx.chain, 'native'); // Bigint amount using `amount` module const amt = amount.units(amount.parse('0.1', ctx.config.nativeTokenDecimals)); // Create a transaction stream for transfers const transfer = sndTb.transfer( sender.address.address, receiver.address, tokenId.address, amt ); // Sign and send the transaction const txids = await signSendWait(ctx, transfer, sender.signer); console.log('Sent: ', txids); // Get the Wormhole message ID from the transaction const [whm] = await ctx.parseTransaction(txids[txids.length - 1]!.txid); console.log('Wormhole Messages: ', whm); const vaa = await wh.getVaa( // Wormhole Message ID whm!, // Protocol:Payload name to use for decoding the VAA payload 'TokenBridge:Transfer', // Timeout in milliseconds, depending on the chain and network, the VAA may take some time to be available 60_000 ); // Now get the token bridge on the redeem side const rcvTb = await rcv.getTokenBridge(); // Create a transaction stream for redeeming const redeem = rcvTb.redeem(receiver.address.address, vaa!); // Sign and send the transaction const rcvTxids = await signSendWait(rcv, redeem, receiver.signer); console.log('Sent: ', rcvTxids); // Now check if the transfer is completed according to // the destination token bridge const finished = await rcvTb.isTransferCompleted(vaa!); console.log('Transfer completed: ', finished); })(); ``` Then, import each of the ecosystem [platforms](#platforms) that you wish to support: ```ts import aptos from '@wormhole-foundation/sdk/aptos'; import cosmwasm from '@wormhole-foundation/sdk/cosmwasm'; import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import sui from '@wormhole-foundation/sdk/sui'; ``` To make the [platform](#platforms) modules available for use, pass them to the Wormhole constructor: ```ts evm, solana, aptos, algorand, cosmwasm, sui, ]); ``` With a configured Wormhole object, you can do things like parse addresses for the provided platforms, get a [`ChainContext`](#chain-context) object, or fetch VAAs. ```ts ``` You can retrieve a VAA as follows. In this example, a timeout of `60,000` milliseconds is used. The amount of time required for the VAA to become available will vary by network. ```ts // Wormhole Message ID whm!, // Protocol:Payload name to use for decoding the VAA payload 'TokenBridge:Transfer', // Timeout in milliseconds, depending on the chain and network, the VAA may take some time to be available 60_000 ); ``` ??? code "View the complete script" ```ts import { wormhole } from '@wormhole-foundation/sdk'; import { Wormhole, amount, signSendWait } from '@wormhole-foundation/sdk'; import algorand from '@wormhole-foundation/sdk/algorand'; import aptos from '@wormhole-foundation/sdk/aptos'; import cosmwasm from '@wormhole-foundation/sdk/cosmwasm'; import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import sui from '@wormhole-foundation/sdk/sui'; import { getSigner } from './helpers/index.js'; (async function () { const wh = await wormhole('Testnet', [ evm, solana, aptos, algorand, cosmwasm, sui, ]); const ctx = wh.getChain('Solana'); const rcv = wh.getChain('Algorand'); const sender = await getSigner(ctx); const receiver = await getSigner(rcv); // Get a Token Bridge contract client on the source const sndTb = await ctx.getTokenBridge(); // Send the native token of the source chain const tokenId = Wormhole.tokenId(ctx.chain, 'native'); // Bigint amount using `amount` module const amt = amount.units(amount.parse('0.1', ctx.config.nativeTokenDecimals)); // Create a transaction stream for transfers const transfer = sndTb.transfer( sender.address.address, receiver.address, tokenId.address, amt ); // Sign and send the transaction const txids = await signSendWait(ctx, transfer, sender.signer); console.log('Sent: ', txids); // Get the Wormhole message ID from the transaction const [whm] = await ctx.parseTransaction(txids[txids.length - 1]!.txid); console.log('Wormhole Messages: ', whm); const vaa = await wh.getVaa( // Wormhole Message ID whm!, // Protocol:Payload name to use for decoding the VAA payload 'TokenBridge:Transfer', // Timeout in milliseconds, depending on the chain and network, the VAA may take some time to be available 60_000 ); // Now get the token bridge on the redeem side const rcvTb = await rcv.getTokenBridge(); // Create a transaction stream for redeeming const redeem = rcvTb.redeem(receiver.address.address, vaa!); // Sign and send the transaction const rcvTxids = await signSendWait(rcv, redeem, receiver.signer); console.log('Sent: ', rcvTxids); // Now check if the transfer is completed according to // the destination token bridge const finished = await rcvTb.isTransferCompleted(vaa!); console.log('Transfer completed: ', finished); })(); ``` Optionally, you can override the default configuration with a partial `WormholeConfig` object to specify particular fields, such as a different RPC endpoint. ```ts const wh = await wormhole('Testnet', [solana], { chains: { Solana: { contracts: { coreBridge: '11111111111111111111111111111', }, rpc: 'https://api.devnet.solana.com', }, }, }); ``` ??? code "View the complete script" ```ts import { wormhole } from '@wormhole-foundation/sdk'; import solana from '@wormhole-foundation/sdk/solana'; (async function () { const wh = await wormhole('Testnet', [solana], { chains: { Solana: { contracts: { coreBridge: '11111111111111111111111111111', }, rpc: 'https://api.devnet.solana.com', }, }, }); console.log(wh.config.chains.Solana); })(); ``` ## Concepts Understanding several higher-level Wormhole concepts and how the SDK abstracts them away will help you use the tools most effectively. The following sections will introduce and discuss the concepts of platforms, chain contexts, addresses, signers, and protocols, how they are used in the Wormhole context, and how the SDK helps ease development in each conceptual area. ### Platforms While every chain has unique attributes, chains from the same platform typically have standard functionalities they share. The SDK includes `Platform` modules, which create a standardized interface for interacting with the chains of a supported platform. The contents of a module vary by platform but can include: - Protocols, such as [Wormhole core](#wormhole-core), preconfigured to suit the selected platform - Definitions and configurations for types, signers, addresses, and chains - Helpers configured for dealing with unsigned transactions on the selected platform These modules also import and expose essential functions and define types or constants from the chain's native ecosystem to reduce the dependencies needed to interact with a chain using Wormhole. Rather than installing the entire native package for each desired platform, you can install a targeted package of standardized functions and definitions essential to connecting with Wormhole, keeping project dependencies as slim as possible. Wormhole currently supports the following platforms: - EVM - Solana - Cosmos - Sui - Aptos - Algorand See the [Platforms folder of the TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/platforms){target=\_blank} for an up-to-date list of the platforms supported by the Wormhole TypeScript SDK. ### Chain Context The `definitions` package of the SDK includes the `ChainContext` class, which creates an interface for working with connected chains in a standardized way. This class contains the network, chain, and platform configurations for connected chains and cached RPC and protocol clients. The `ChainContext` class also exposes chain-specific methods and utilities. Much of the functionality comes from the `Platform` methods but some specific chains may have overridden methods via the context. This is also where the `Network`, `Chain`, and `Platform` type parameters which are used throughout the package are defined. ```ts const srcChain = wh.getChain(senderAddress.chain); const dstChain = wh.getChain(receiverAddress.chain); const tb = await srcChain.getTokenBridge(); // => TokenBridge<'Evm'> srcChain.getRpcClient(); // => RpcClient<'Evm'> ``` ### Addresses The SDK uses the `UniversalAddress` class to implement the `Address` interface, which all address types must implement. Addresses from various networks are parsed into their byte representation and modified as needed to ensure they are exactly 32 bytes long. Each platform also has an address type that understands the native address formats, referred to as `NativeAddress.` These abstractions allow you to work with addresses consistently regardless of the underlying chain. ```ts // It's possible to convert a string address to its Native address const ethAddr: NativeAddress<'Evm'> = toNative('Ethereum', '0xbeef...'); // A common type in the SDK is the `ChainAddress` which provides // the additional context of the `Chain` this address is relevant for const senderAddress: ChainAddress = Wormhole.chainAddress( 'Ethereum', '0xbeef...' ); const receiverAddress: ChainAddress = Wormhole.chainAddress( 'Solana', 'Sol1111...' ); // Convert the ChainAddress back to its canonical string address format const strAddress = Wormhole.canonicalAddress(senderAddress); // => '0xbeef...' // Or if the ethAddr above is for an emitter and you need the UniversalAddress const emitterAddr = ethAddr.toUniversalAddress().toString(); ``` ### Tokens Similar to the `ChainAddress` type, the `TokenId` type provides the chain and address of a given token. The following snippet introduces `TokenId`, a way to uniquely identify any token, whether it's a standard token or a blockchain's native currency (like ETH for Ethereum). Wormhole uses their contract address to create a `TokenId` for standard tokens. For native currencies, Wormhole uses the keyword `native` instead of an address. This makes it easy to work with any type of token consistently. Finally, the snippet demonstrates how to convert a `TokenId` back into a regular address format when needed. ```ts const sourceToken: TokenId = Wormhole.tokenId('Ethereum', '0xbeef...'); const gasToken: TokenId = Wormhole.tokenId('Ethereum', 'native'); const strAddress = Wormhole.canonicalAddress(senderAddress); // => '0xbeef...' ``` ### Signers Certain methods of signing transactions require a `Signer` interface in the SDK. Depending on the specific requirements, this interface can be fulfilled by either a `SignOnlySigner` or a `SignAndSendSigner`. A signer can be created by wrapping an offline or web wallet. A `SignOnlySigner` is used when the signer isn't connected to the network or prefers not to broadcast transactions themselves. It accepts an array of unsigned transactions and returns an array of signed and serialized transactions. Before signing, the transactions may be inspected or altered. It's important to note that the serialization process is chain-specific. Refer to the testing signers (e.g., [EVM](https://github.com/wormhole-foundation/connect-sdk/blob/main/platforms/evm/src/signer.ts){target=\_blank} or [Solana](https://github.com/wormhole-foundation/connect-sdk/blob/main/platforms/solana/src/signer.ts){target=\_blank}) for an example of how to implement a signer for a specific chain or platform. Conversely, a `SignAndSendSigner` is appropriate when the signer is connected to the network and intends to broadcast the transactions. This type of signer also accepts an array of unsigned transactions but returns an array of transaction IDs corresponding to the order of the unsigned transactions. ```ts export type Signer = SignOnlySigner | SignAndSendSigner; export interface SignOnlySigner { chain(): ChainName; address(): string; // Accept an array of unsigned transactions and return // an array of signed and serialized transactions. // The transactions may be inspected or altered before // signing. sign(tx: UnsignedTransaction[]): Promise; } export interface SignAndSendSigner { chain(): ChainName; address(): string; // Accept an array of unsigned transactions and return // an array of transaction ids in the same order as the // unsignedTransactions array. signAndSend(tx: UnsignedTransaction[]): Promise; } ``` #### Set Up a Signer with Ethers.js To sign transactions programmatically with the Wormhole SDK, you can use Ethers.js to manage private keys and handle signing. Here's an example of setting up a signer using Ethers.js: ```javascript import { ethers } from 'ethers'; // Update the following variables const rpcUrl = 'INSERT_RPC_URL'; const privateKey = 'INSERT_PRIVATE_KEY'; const toAddress = 'INSERT_RECIPIENT_ADDRESS'; // Set up a provider and signer const provider = new ethers.JsonRpcProvider(rpcUrl); const signer = new ethers.Wallet(privateKey, provider); // Example: Signing and sending a transaction async function sendTransaction() { const tx = { to: toAddress, value: ethers.parseUnits('0.1'), // Sending 0.1 ETH gasPrice: await provider.getGasPrice(), gasLimit: ethers.toBeHex(21000), }; const transaction = await signer.sendTransaction(tx); console.log('Transaction hash:', transaction.hash); } sendTransaction(); ``` - **`provider`** - responsible for connecting to the Ethereum network (or any EVM-compatible network). It acts as a bridge between your application and the blockchain, allowing you to fetch data, check the state of the blockchain, and submit transactions - **`signer`** - represents the account that will sign the transaction. In this case, you’re creating a signer using the private key associated with the account. The signer is responsible for authorizing transactions by digitally signing them with the private key - **`Wallet`** - combines both the provider (for blockchain interaction) and the signer (for transaction authorization), allowing you to sign and send transactions programmatically These components work together to create, sign, and submit a transaction to the blockchain. ???- tip "Managing Private Keys Securely" Handling private keys is unavoidable, so it’s crucial to manage them securely. Here are some best practices: - **Use environment variables** - avoid hardcoding private keys in your code. Use environment variables or secret management tools to inject private keys securely - **Hardware wallets** - for production environments, consider integrating hardware wallets to keep private keys secure while allowing programmatic access through the SDK ### Protocols While Wormhole is a Generic Message Passing (GMP) protocol, several protocols have been built to provide specific functionality. If available, each protocol will have a platform-specific implementation. These implementations provide methods to generate transactions or read state from the contract on-chain. #### Wormhole Core The core protocol underlies all Wormhole activity. This protocol is responsible for emitting the message containing the information necessary to perform bridging, including the [emitter address](https://docs.wormhole.com/wormhole/reference/glossary#emitter){target=\_blank}, the [sequence number](https://docs.wormhole.com/wormhole/reference/glossary#sequence){target=\_blank} for the message, and the payload of the message itself. The following example demonstrates sending and verifying a message using the Wormhole Core protocol on Solana. First, initialize a Wormhole instance for the Testnet environment, specifically for the Solana chain. Then, obtain a signer and its associated address, which will be used to sign transactions. Next, get a reference to the core messaging bridge, which is the main interface for interacting with Wormhole's cross-chain messaging capabilities. The code then prepares a message for publication. This message includes: - The sender's address - The message payload (in this case, the encoded string `lol`) - A nonce (set to `0` here, but can be any user-defined value to uniquely identify the message) - A [consistency (finality) level](/docs/build/reference/consistency-levels/){target=\_blank} (set to `0`, which determines the finality requirements for the message) After preparing the message, the next steps are to generate, sign, and send the transaction or transactions required to publish the message on the Solana blockchain. Once the transaction is confirmed, the Wormhole message ID is extracted from the transaction logs. This ID is crucial for tracking the message across chains. The code then waits for the Wormhole network to process and sign the message, turning it into a Verified Action Approval (VAA). This VAA is retrieved in a `Uint8Array` format, with a timeout of 60 seconds. Lastly, the code will demonstrate how to verify the message on the receiving end. A verification transaction is prepared using the original sender's address and the VAA, and finally, this transaction is signed and sent. ???+ code "View the complete script" ```ts import { encoding, signSendWait, wormhole } from '@wormhole-foundation/sdk'; import { getSigner } from './helpers/index.js'; import solana from '@wormhole-foundation/sdk/solana'; import evm from '@wormhole-foundation/sdk/evm'; (async function () { const wh = await wormhole('Testnet', [solana, evm]); const chain = wh.getChain('Avalanche'); const { signer, address } = await getSigner(chain); // Get a reference to the core messaging bridge const coreBridge = await chain.getWormholeCore(); // Generate transactions, sign and send them const publishTxs = coreBridge.publishMessage( // Address of sender (emitter in VAA) address.address, // Message to send (payload in VAA) encoding.bytes.encode('lol'), // Nonce (user defined, no requirement for a specific value, useful to provide a unique identifier for the message) 0, // ConsistencyLevel (ie finality of the message, see wormhole docs for more) 0 ); // Send the transaction(s) to publish the message const txids = await signSendWait(chain, publishTxs, signer); // Take the last txid in case multiple were sent // The last one should be the one containing the relevant // event or log info const txid = txids[txids.length - 1]; // Grab the wormhole message id from the transaction logs or storage const [whm] = await chain.parseTransaction(txid!.txid); // Wait for the vaa to be signed and available with a timeout const vaa = await wh.getVaa(whm!, 'Uint8Array', 60_000); console.log(vaa); // Note: calling verifyMessage manually is typically not a useful thing to do // As the VAA is typically submitted to the counterpart contract for // A given protocol and the counterpart contract will verify the VAA // This is simply for demo purposes const verifyTxs = coreBridge.verifyMessage(address.address, vaa!); console.log(await signSendWait(chain, verifyTxs, signer)); })(); ``` The payload contains the information necessary to perform whatever action is required based on the protocol that uses it. #### Token Bridge The most familiar protocol built on Wormhole is the Token Bridge. Every chain has a `TokenBridge` protocol client that provides a consistent interface for interacting with the Token Bridge, which includes methods to generate the transactions required to transfer tokens and methods to generate and redeem attestations. `WormholeTransfer` abstractions are the recommended way to interact with these protocols, but it is possible to use them directly. ```ts import { signSendWait } from '@wormhole-foundation/sdk'; const tb = await srcChain.getTokenBridge(); const token = '0xdeadbeef...'; const txGenerator = tb.createAttestation(token); const txids = await signSendWait(srcChain, txGenerator, src.signer); ``` Supported protocols are defined in the [definitions module](https://github.com/wormhole-foundation/connect-sdk/tree/main/core/definitions/src/protocols){target=\_blank}. ## Transfers While using the [`ChainContext`](#chain-context) and [`Protocol`](#protocols) clients directly is possible, the SDK provides some helpful abstractions for transferring tokens. The `WormholeTransfer` interface provides a convenient abstraction to encapsulate the steps involved in a cross-chain transfer. ### Token Transfers Performing a token transfer is trivial for any source and destination chains. You can create a new `Wormhole` object to make objects like `TokenTransfer` and `CircleTransfer`, to transfer tokens between chains. The following example demonstrates the process of initiating and completing a token transfer. It starts by creating a `TokenTransfer` object, which tracks the transfer's state throughout its lifecycle. The code then obtains a quote for the transfer, ensuring the amount is sufficient to cover fees and any requested native gas. The transfer process is divided into three main steps: 1. Initiating the transfer on the source chain 2. Waiting for the transfer to be attested (if not automatic) 3. Completing the transfer on the destination chain For automatic transfers, the process ends after initiation. The code waits for the transfer to be attested for manual transfers and then completes it on the destination chain. ```ts const xfer = await wh.tokenTransfer( route.token, route.amount, route.source.address, route.destination.address, route.delivery?.automatic ?? false, route.payload, route.delivery?.nativeGas ); const quote = await TokenTransfer.quoteTransfer( wh, route.source.chain, route.destination.chain, xfer.transfer ); console.log(quote); if (xfer.transfer.automatic && quote.destinationToken.amount < 0) throw 'The amount requested is too low to cover the fee and any native gas requested.'; // 1) Submit the transactions to the source chain, passing a signer to sign any txns console.log('Starting transfer'); const srcTxids = await xfer.initiateTransfer(route.source.signer); console.log(`Started transfer: `, srcTxids); // If automatic, we're done if (route.delivery?.automatic) return xfer; // 2) Wait for the VAA to be signed and ready (not required for auto transfer) console.log('Getting Attestation'); const attestIds = await xfer.fetchAttestation(60_000); console.log(`Got Attestation: `, attestIds); // 3) Redeem the VAA on the dest chain console.log('Completing Transfer'); const destTxids = await xfer.completeTransfer(route.destination.signer); console.log(`Completed Transfer: `, destTxids); ``` ??? code "View the complete script" ```ts hl_lines="122" import { Chain, Network, TokenId, TokenTransfer, Wormhole, amount, isTokenId, wormhole, } from '@wormhole-foundation/sdk'; import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import { SignerStuff, getSigner, waitLog } from './helpers/index.js'; (async function () { // Init Wormhole object, passing config for which network // to use (e.g. Mainnet/Testnet) and what Platforms to support const wh = await wormhole('Testnet', [evm, solana]); // Grab chain Contexts -- these hold a reference to a cached rpc client const sendChain = wh.getChain('Avalanche'); const rcvChain = wh.getChain('Solana'); // Shortcut to allow transferring native gas token const token = Wormhole.tokenId(sendChain.chain, 'native'); // A TokenId is just a `{chain, address}` pair and an alias for ChainAddress // The `address` field must be a parsed address. // You can get a TokenId (or ChainAddress) prepared for you // by calling the static `chainAddress` method on the Wormhole class. // e.g. // wAvax on Solana // const token = Wormhole.tokenId("Solana", "3Ftc5hTz9sG4huk79onufGiebJNDMZNL8HYgdMJ9E7JR"); // wSol on Avax // const token = Wormhole.tokenId("Avalanche", "0xb10563644a6AB8948ee6d7f5b0a1fb15AaEa1E03"); // Normalized given token decimals later but can just pass bigints as base units // Note: The Token bridge will dedust past 8 decimals // This means any amount specified past that point will be returned // To the caller const amt = '0.05'; // With automatic set to true, perform an automatic transfer. This will invoke a relayer // Contract intermediary that knows to pick up the transfers // With automatic set to false, perform a manual transfer from source to destination // Of the token // On the destination side, a wrapped version of the token will be minted // To the address specified in the transfer VAA const automatic = false; // The Wormhole relayer has the ability to deliver some native gas funds to the destination account // The amount specified for native gas will be swapped for the native gas token according // To the swap rate provided by the contract, denominated in native gas tokens const nativeGas = automatic ? '0.01' : undefined; // Get signer from local key but anything that implements // Signer interface (e.g. wrapper around web wallet) should work const source = await getSigner(sendChain); const destination = await getSigner(rcvChain); // Used to normalize the amount to account for the tokens decimals const decimals = isTokenId(token) ? Number(await wh.getDecimals(token.chain, token.address)) : sendChain.config.nativeTokenDecimals; // Set this to true if you want to perform a round trip transfer const roundTrip: boolean = false; // Set this to the transfer txid of the initiating transaction to recover a token transfer // And attempt to fetch details about its progress. let recoverTxid = undefined; // Finally create and perform the transfer given the parameters set above const xfer = !recoverTxid ? // Perform the token transfer await tokenTransfer( wh, { token, amount: amount.units(amount.parse(amt, decimals)), source, destination, delivery: { automatic, nativeGas: nativeGas ? amount.units(amount.parse(nativeGas, decimals)) : undefined, }, }, roundTrip ) : // Recover the transfer from the originating txid await TokenTransfer.from(wh, { chain: source.chain.chain, txid: recoverTxid, }); const receipt = await waitLog(wh, xfer); // Log out the results console.log(receipt); })(); async function tokenTransfer( wh: Wormhole, route: { token: TokenId; amount: bigint; source: SignerStuff; destination: SignerStuff; delivery?: { automatic: boolean; nativeGas?: bigint; }; payload?: Uint8Array; }, roundTrip?: boolean ): Promise> { // Create a TokenTransfer object to track the state of the transfer over time const xfer = await wh.tokenTransfer( route.token, route.amount, route.source.address, route.destination.address, route.delivery?.automatic ?? false, route.payload, route.delivery?.nativeGas ); const quote = await TokenTransfer.quoteTransfer( wh, route.source.chain, route.destination.chain, xfer.transfer ); console.log(quote); if (xfer.transfer.automatic && quote.destinationToken.amount < 0) throw 'The amount requested is too low to cover the fee and any native gas requested.'; // 1) Submit the transactions to the source chain, passing a signer to sign any txns console.log('Starting transfer'); const srcTxids = await xfer.initiateTransfer(route.source.signer); console.log(`Started transfer: `, srcTxids); // If automatic, we're done if (route.delivery?.automatic) return xfer; // 2) Wait for the VAA to be signed and ready (not required for auto transfer) console.log('Getting Attestation'); const attestIds = await xfer.fetchAttestation(60_000); console.log(`Got Attestation: `, attestIds); // 3) Redeem the VAA on the dest chain console.log('Completing Transfer'); const destTxids = await xfer.completeTransfer(route.destination.signer); console.log(`Completed Transfer: `, destTxids); // If no need to send back, dip if (!roundTrip) return xfer; const { destinationToken: token } = quote; return await tokenTransfer(wh, { ...route, token: token.token, amount: token.amount, source: route.destination, destination: route.source, }); } ``` Internally, this uses the [TokenBridge](#token-bridge) protocol client to transfer tokens. Like other Protocols, the `TokenBridge` protocol provides a consistent set of methods across all chains to generate a set of transactions for that specific chain. ### Native USDC Transfers You can also transfer native USDC using [Circle's CCTP](https://www.circle.com/en/cross-chain-transfer-protocol){target=\_blank}. Please note that if the transfer is set to `Automatic` mode, a fee for performing the relay will be included in the quote. This fee is deducted from the total amount requested to be sent. For example, if the user wishes to receive `1.0` on the destination, the amount sent should be adjusted to `1.0` plus the relay fee. The same principle applies to native gas drop offs. In the following example, the `wh.circleTransfer` function is called with several parameters to set up the transfer. It takes the amount to be transferred (in the token's base units), the sender's chain and address, and the receiver's chain and address. The function also allows specifying whether the transfer should be automatic, meaning it will be completed without further user intervention. An optional payload can be included with the transfer, though it's set to undefined in this case. Finally, if the transfer is automatic, you can request that native gas (the blockchain's native currency used for transaction fees) be sent to the receiver along with the transferred tokens. When waiting for the `VAA`, a timeout of `60,000` milliseconds is used. The amount of time required for the VAA to become available will [vary by network](https://developers.circle.com/stablecoins/docs/required-block-confirmations#mainnet){target=\_blank}. ```ts // Amount as bigint (base units) req.amount, // Sender chain/address src.address, // Receiver chain/address dst.address, // Automatic delivery boolean req.automatic, // Payload to be sent with the transfer undefined, // If automatic, native gas can be requested to be sent to the receiver req.nativeGas ); // Note, if the transfer is requested to be Automatic, a fee for performing the relay // will be present in the quote. The fee comes out of the amount requested to be sent. // If the user wants to receive 1.0 on the destination, the amount to send should be 1.0 + fee. // The same applies for native gas dropoff const quote = await CircleTransfer.quoteTransfer( src.chain, dst.chain, xfer.transfer ); console.log('Quote', quote); console.log('Starting Transfer'); const srcTxids = await xfer.initiateTransfer(src.signer); console.log(`Started Transfer: `, srcTxids); if (req.automatic) { const relayStatus = await waitForRelay(srcTxids[srcTxids.length - 1]!); console.log(`Finished relay: `, relayStatus); return; } console.log('Waiting for Attestation'); const attestIds = await xfer.fetchAttestation(60_000); console.log(`Got Attestation: `, attestIds); console.log('Completing Transfer'); const dstTxids = await xfer.completeTransfer(dst.signer); console.log(`Completed Transfer: `, dstTxids); } ``` ??? code "View the complete script" ```ts import { Chain, CircleTransfer, Network, Signer, TransactionId, TransferState, Wormhole, amount, wormhole, } from '@wormhole-foundation/sdk'; import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import { SignerStuff, getSigner, waitForRelay } from './helpers/index.js'; /* Notes: Only a subset of chains are supported by Circle for CCTP, see core/base/src/constants/circle.ts for currently supported chains AutoRelayer takes a 0.1 USDC fee when transferring to any chain beside Goerli, which is 1 USDC */ // (async function () { // Init the Wormhole object, passing in the config for which network // to use (e.g. Mainnet/Testnet) and what Platforms to support const wh = await wormhole('Testnet', [evm, solana]); // Grab chain Contexts const sendChain = wh.getChain('Avalanche'); const rcvChain = wh.getChain('Solana'); // Get signer from local key but anything that implements // Signer interface (e.g. wrapper around web wallet) should work const source = await getSigner(sendChain); const destination = await getSigner(rcvChain); // 6 decimals for USDC (except for BSC, so check decimals before using this) const amt = amount.units(amount.parse('0.2', 6)); // Choose whether or not to have the attestation delivered for you const automatic = false; // If the transfer is requested to be automatic, you can also request that // during redemption, the receiver gets some amount of native gas transferred to them // so that they may pay for subsequent transactions // The amount specified here is denominated in the token being transferred (USDC here) const nativeGas = automatic ? amount.units(amount.parse('0.0', 6)) : 0n; await cctpTransfer(wh, source, destination, { amount: amt, automatic, nativeGas, }); })(); async function cctpTransfer( wh: Wormhole, src: SignerStuff, dst: SignerStuff, req: { amount: bigint; automatic: boolean; nativeGas?: bigint; } ) { const xfer = await wh.circleTransfer( // Amount as bigint (base units) req.amount, // Sender chain/address src.address, // Receiver chain/address dst.address, // Automatic delivery boolean req.automatic, // Payload to be sent with the transfer undefined, // If automatic, native gas can be requested to be sent to the receiver req.nativeGas ); // Note, if the transfer is requested to be Automatic, a fee for performing the relay // will be present in the quote. The fee comes out of the amount requested to be sent. // If the user wants to receive 1.0 on the destination, the amount to send should be 1.0 + fee. // The same applies for native gas dropoff const quote = await CircleTransfer.quoteTransfer( src.chain, dst.chain, xfer.transfer ); console.log('Quote', quote); console.log('Starting Transfer'); const srcTxids = await xfer.initiateTransfer(src.signer); console.log(`Started Transfer: `, srcTxids); if (req.automatic) { const relayStatus = await waitForRelay(srcTxids[srcTxids.length - 1]!); console.log(`Finished relay: `, relayStatus); return; } console.log('Waiting for Attestation'); const attestIds = await xfer.fetchAttestation(60_000); console.log(`Got Attestation: `, attestIds); console.log('Completing Transfer'); const dstTxids = await xfer.completeTransfer(dst.signer); console.log(`Completed Transfer: `, dstTxids); } export async function completeTransfer( wh: Wormhole, txid: TransactionId, signer: Signer ): Promise { const xfer = await CircleTransfer.from(wh, txid); const attestIds = await xfer.fetchAttestation(60 * 60 * 1000); console.log('Got attestation: ', attestIds); const dstTxIds = await xfer.completeTransfer(signer); console.log('Completed transfer: ', dstTxIds); } ``` ### Recovering Transfers It may be necessary to recover an abandoned transfer before it is completed. To do this, instantiate the `Transfer` class with the `from` static method and pass one of several types of identifiers. A `TransactionId` or `WormholeMessageId` may be used to recover the transfer. ```ts const attestIds = await xfer.fetchAttestation(60 * 60 * 1000); console.log('Got attestation: ', attestIds); const dstTxIds = await xfer.completeTransfer(signer); console.log('Completed transfer: ', dstTxIds); ``` ??? code "View the complete script" ```ts hl_lines="130" import { Chain, CircleTransfer, Network, Signer, TransactionId, TransferState, Wormhole, amount, wormhole, } from '@wormhole-foundation/sdk'; import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import { SignerStuff, getSigner, waitForRelay } from './helpers/index.js'; /* Notes: Only a subset of chains are supported by Circle for CCTP, see core/base/src/constants/circle.ts for currently supported chains AutoRelayer takes a 0.1 USDC fee when transferring to any chain beside Goerli, which is 1 USDC */ // (async function () { // Init the Wormhole object, passing in the config for which network // to use (e.g. Mainnet/Testnet) and what Platforms to support const wh = await wormhole('Testnet', [evm, solana]); // Grab chain Contexts const sendChain = wh.getChain('Avalanche'); const rcvChain = wh.getChain('Solana'); // Get signer from local key but anything that implements // Signer interface (e.g. wrapper around web wallet) should work const source = await getSigner(sendChain); const destination = await getSigner(rcvChain); // 6 decimals for USDC (except for BSC, so check decimals before using this) const amt = amount.units(amount.parse('0.2', 6)); // Choose whether or not to have the attestation delivered for you const automatic = false; // If the transfer is requested to be automatic, you can also request that // during redemption, the receiver gets some amount of native gas transferred to them // so that they may pay for subsequent transactions // The amount specified here is denominated in the token being transferred (USDC here) const nativeGas = automatic ? amount.units(amount.parse('0.0', 6)) : 0n; await cctpTransfer(wh, source, destination, { amount: amt, automatic, nativeGas, }); })(); async function cctpTransfer( wh: Wormhole, src: SignerStuff, dst: SignerStuff, req: { amount: bigint; automatic: boolean; nativeGas?: bigint; } ) { const xfer = await wh.circleTransfer( // Amount as bigint (base units) req.amount, // Sender chain/address src.address, // Receiver chain/address dst.address, // Automatic delivery boolean req.automatic, // Payload to be sent with the transfer undefined, // If automatic, native gas can be requested to be sent to the receiver req.nativeGas ); // Note, if the transfer is requested to be Automatic, a fee for performing the relay // will be present in the quote. The fee comes out of the amount requested to be sent. // If the user wants to receive 1.0 on the destination, the amount to send should be 1.0 + fee. // The same applies for native gas dropoff const quote = await CircleTransfer.quoteTransfer( src.chain, dst.chain, xfer.transfer ); console.log('Quote', quote); console.log('Starting Transfer'); const srcTxids = await xfer.initiateTransfer(src.signer); console.log(`Started Transfer: `, srcTxids); if (req.automatic) { const relayStatus = await waitForRelay(srcTxids[srcTxids.length - 1]!); console.log(`Finished relay: `, relayStatus); return; } console.log('Waiting for Attestation'); const attestIds = await xfer.fetchAttestation(60_000); console.log(`Got Attestation: `, attestIds); console.log('Completing Transfer'); const dstTxids = await xfer.completeTransfer(dst.signer); console.log(`Completed Transfer: `, dstTxids); } export async function completeTransfer( wh: Wormhole, txid: TransactionId, signer: Signer ): Promise { const xfer = await CircleTransfer.from(wh, txid); const attestIds = await xfer.fetchAttestation(60 * 60 * 1000); console.log('Got attestation: ', attestIds); const dstTxIds = await xfer.completeTransfer(signer); console.log('Completed transfer: ', dstTxIds); } ``` ## Routes While a specific `WormholeTransfer`, such as `TokenTransfer` or `CCTPTransfer`, may be used, the developer must know exactly which transfer type to use for a given request. To provide a more flexible and generic interface, the `Wormhole` class provides a method to produce a `RouteResolver` that can be configured with a set of possible routes to be supported. The following section demonstrates setting up and validating a token transfer using Wormhole's routing system. ```ts const resolver = wh.resolver([ routes.TokenBridgeRoute, // manual token bridge routes.AutomaticTokenBridgeRoute, // automatic token bridge routes.CCTPRoute, // manual CCTP routes.AutomaticCCTPRoute, // automatic CCTP routes.AutomaticPorticoRoute, // Native eth transfers ]); ``` Once created, the resolver can be used to provide a list of input and possible output tokens. ```ts const srcTokens = await resolver.supportedSourceTokens(sendChain); console.log( 'Allowed source tokens: ', srcTokens.map((t) => canonicalAddress(t)) ); const sendToken = Wormhole.tokenId(sendChain.chain, 'native'); // Given the send token, what can we possibly get on the destination chain? const destTokens = await resolver.supportedDestinationTokens( sendToken, sendChain, destChain ); console.log( 'For the given source token and routes configured, the following tokens may be receivable: ', destTokens.map((t) => canonicalAddress(t)) ); // Grab the first one for the example const destinationToken = destTokens[0]!; ``` Once the tokens are selected, a `RouteTransferRequest` may be created to provide a list of routes that can fulfill the request. Creating a transfer request fetches the token details since all routes will need to know about the tokens. ```ts // Since all routes will need to know about the tokens const tr = await routes.RouteTransferRequest.create(wh, { source: sendToken, destination: destinationToken, }); // Resolve the transfer request to a set of routes that can perform it const foundRoutes = await resolver.findRoutes(tr); console.log( 'For the transfer parameters, we found these routes: ', foundRoutes ); ``` Choosing the best route is currently left to the developer, but strategies might include sorting by output amount or expected time to complete the transfer (no estimate is currently provided). After choosing the best route, extra parameters like `amount`, `nativeGasDropoff`, and `slippage` can be passed, depending on the specific route selected. A quote can be retrieved with the validated request. After successful validation, the code requests a transfer quote. This quote likely includes important details such as fees, estimated time, and the final amount to be received. If the quote is generated successfully, it's displayed for the user to review and decide whether to proceed with the transfer. This process ensures that all transfer details are properly set up and verified before any actual transfer occurs. ```ts 'This route offers the following default options', bestRoute.getDefaultOptions() ); // Specify the amount as a decimal string const amt = '0.001'; // Create the transfer params for this request const transferParams = { amount: amt, options: { nativeGas: 0 } }; // Validate the transfer params passed, this returns a new type of ValidatedTransferParams // which (believe it or not) is a validated version of the input params // This new var must be passed to the next step, quote const validated = await bestRoute.validate(tr, transferParams); if (!validated.valid) throw validated.error; console.log('Validated parameters: ', validated.params); // Get a quote for the transfer, this too returns a new type that must // be passed to the next step, execute (if you like the quote) const quote = await bestRoute.quote(tr, validated.params); if (!quote.success) throw quote.error; console.log('Best route quote: ', quote); ``` Finally, assuming the quote looks good, the route can initiate the request with the quote and the `signer`. ```ts tr, sender.signer, quote, receiver.address ); console.log('Initiated transfer with receipt: ', receipt); ``` ??? code "View the complete script" ```ts import { Wormhole, canonicalAddress, routes, wormhole, } from '@wormhole-foundation/sdk'; import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import { getSigner } from './helpers/index.js'; (async function () { // Setup const wh = await wormhole('Testnet', [evm, solana]); // Get chain contexts const sendChain = wh.getChain('Avalanche'); const destChain = wh.getChain('Solana'); // Get signers from local config const sender = await getSigner(sendChain); const receiver = await getSigner(destChain); // Create new resolver, passing the set of routes to consider const resolver = wh.resolver([ routes.TokenBridgeRoute, // manual token bridge routes.AutomaticTokenBridgeRoute, // automatic token bridge routes.CCTPRoute, // manual CCTP routes.AutomaticCCTPRoute, // automatic CCTP routes.AutomaticPorticoRoute, // Native eth transfers ]); // What tokens are available on the source chain? const srcTokens = await resolver.supportedSourceTokens(sendChain); console.log( 'Allowed source tokens: ', srcTokens.map((t) => canonicalAddress(t)) ); const sendToken = Wormhole.tokenId(sendChain.chain, 'native'); // Given the send token, what can we possibly get on the destination chain? const destTokens = await resolver.supportedDestinationTokens( sendToken, sendChain, destChain ); console.log( 'For the given source token and routes configured, the following tokens may be receivable: ', destTokens.map((t) => canonicalAddress(t)) ); // Grab the first one for the example const destinationToken = destTokens[0]!; // Creating a transfer request fetches token details // Since all routes will need to know about the tokens const tr = await routes.RouteTransferRequest.create(wh, { source: sendToken, destination: destinationToken, }); // Resolve the transfer request to a set of routes that can perform it const foundRoutes = await resolver.findRoutes(tr); console.log( 'For the transfer parameters, we found these routes: ', foundRoutes ); const bestRoute = foundRoutes[0]!; console.log('Selected: ', bestRoute); console.log( 'This route offers the following default options', bestRoute.getDefaultOptions() ); // Specify the amount as a decimal string const amt = '0.001'; // Create the transfer params for this request const transferParams = { amount: amt, options: { nativeGas: 0 } }; // Validate the transfer params passed, this returns a new type of ValidatedTransferParams // which (believe it or not) is a validated version of the input params // This new var must be passed to the next step, quote const validated = await bestRoute.validate(tr, transferParams); if (!validated.valid) throw validated.error; console.log('Validated parameters: ', validated.params); // Get a quote for the transfer, this too returns a new type that must // be passed to the next step, execute (if you like the quote) const quote = await bestRoute.quote(tr, validated.params); if (!quote.success) throw quote.error; console.log('Best route quote: ', quote); // If you're sure you want to do this, set this to true const imSure = false; if (imSure) { // Now the transfer may be initiated // A receipt will be returned, guess what you gotta do with that? const receipt = await bestRoute.initiate( tr, sender.signer, quote, receiver.address ); console.log('Initiated transfer with receipt: ', receipt); // Kick off a wait log, if there is an opportunity to complete, this function will do it // See the implementation for how this works await routes.checkAndCompleteTransfer(bestRoute, receipt, receiver.signer); } else { console.log('Not initiating transfer (set `imSure` to true to do so)'); } })(); ``` See the `router.ts` example in the [examples directory](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/examples){target=\_blank} for a full working example. ### Routes as Plugins Routes can be imported from any npm package that exports them and configured with the resolver. Custom routes must extend [`Route`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/0c57292368146c460abc9ce9e7f6a42be8e0b903/connect/src/routes/route.ts#L21-L64){target=\_blank} and implement [`StaticRouteMethods`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/0c57292368146c460abc9ce9e7f6a42be8e0b903/connect/src/routes/route.ts#L101){target=\_blank}. ```ts import { Network, routes } from '@wormhole-foundation/sdk-connect'; export class CustomRoute extends routes.Route implements routes.StaticRouteMethods { static meta = { name: 'CustomRoute', }; // implementation... } ``` A noteworthy example of a route exported from a separate npm package is Wormhole Native Token Transfers (NTT). See the [`NttAutomaticRoute`](https://github.com/wormhole-foundation/native-token-transfers/blob/66f8e414223a77f5c736541db0a7a85396cab71c/sdk/route/src/automatic.ts#L48){target=\_blank} route implementation. ## See Also The TSdoc is available [on GitHub](https://wormhole-foundation.github.io/wormhole-sdk-ts/){target=\_blank}. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/cctp/ --- BEGIN CONTENT --- --- title: Interacting with CCTP Contracts description: Learn how to interact directly with Circle's CCTP Bridge contracts, including TokenMessenger, TokenMinter, and MessageTransmitter. categories: Transfer --- # Get Started with CCTP ## Introduction Circle's [Cross-Chain Transfer Protocol (CCTP)](/docs/learn/transfers/cctp/){target=\_blank} by Circle is a permissionless utility that facilitates secure and efficient USDC transfers across blockchain networks through native burning and minting mechanisms. As decentralized finance (DeFi) protocols evolve, the need for flexible, secure cross-chain messaging has expanded, requiring solutions beyond simple asset transfers. Wormhole enhances CCTP's capabilities by allowing developers to compose more complex cross-chain interactions. With Wormhole's generic messaging, applications can execute smart contract logic alongside native USDC transfers, enabling richer, more versatile cross-chain experiences. This guide will walk you through getting started with Wormhole's CCTP contracts and show you how to integrate CCTP into your smart contracts, enabling the composition of advanced cross-chain functions with native USDC transfers. ## Prerequisites To interact with the Wormhole CCTP, you'll need the following: - [The address of the CCTP contract](/docs/build/reference/contract-addresses/#cctp){target=\_blank} on the chains you're deploying your contract on - [The Wormhole chain ID](/docs/build/reference/chain-ids/){target=\_blank} of the chains you're deploying your contract on ## Wormhole's CCTP Integration Contract Wormhole's Circle Integration contract, `CircleIntegration.sol`, is the contract you'll interact with directly. It burns and mints Circle-supported tokens by using [Circle's CCTP contracts](#circles-cctp-contracts). The Circle Integration contract emits Wormhole messages with arbitrary payloads to allow additional composability when performing cross-chain transfers of Circle-supported assets. This contract can be found in [Wormhole's `wormhole-circle-integration` repository](https://github.com/wormhole-foundation/wormhole-circle-integration/){target=\_blank} on GitHub. !!! note Wormhole supports all CCTP-supported chains, but Circle currently supports only a [handful of chains](https://developers.circle.com/stablecoins/docs/supported-domains){target=\_blank}. Please refer to the [CCTP section of the Contract Addresses](/docs/build/reference/contract-addresses/#cctp){target=\_blank} reference page to view the complete list of supported chains. ??? code "Circle Integration contract" ```solidity // SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.19; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IWormhole} from "wormhole/interfaces/IWormhole.sol"; import {BytesLib} from "wormhole/libraries/external/BytesLib.sol"; import {ICircleBridge} from "../interfaces/circle/ICircleBridge.sol"; import {CircleIntegrationGovernance} from "./CircleIntegrationGovernance.sol"; import {CircleIntegrationMessages} from "./CircleIntegrationMessages.sol"; /** * @notice This contract burns and mints Circle-supported tokens by using Circle's Cross-Chain Transfer Protocol. It also emits * Wormhole messages with arbitrary payloads to allow for additional composability when performing cross-chain * transfers of Circle-suppored assets. */ contract CircleIntegration is CircleIntegrationMessages, CircleIntegrationGovernance, ReentrancyGuard { using BytesLib for bytes; /** * @notice Emitted when Circle-supported assets have been minted to the mintRecipient * @param emitterChainId Wormhole chain ID of emitter contract on source chain * @param emitterAddress Address (bytes32 zero-left-padded) of emitter on source chain * @param sequence Sequence of Wormhole message used to mint tokens */ event Redeemed( uint16 indexed emitterChainId, bytes32 indexed emitterAddress, uint64 indexed sequence ); /** * @notice `transferTokensWithPayload` calls the Circle Bridge contract to burn Circle-supported tokens. It emits * a Wormhole message containing a user-specified payload with instructions for what to do with * the Circle-supported assets once they have been minted on the target chain. * @dev reverts if: * - user passes insufficient value to pay Wormhole message fee * - `token` is not supported by Circle Bridge * - `amount` is zero * - `targetChain` is not supported * - `mintRecipient` is bytes32(0) * @param transferParams Struct containing the following attributes: * - `token` Address of the token to be burned * - `amount` Amount of `token` to be burned * - `targetChain` Wormhole chain ID of the target blockchain * - `mintRecipient` The recipient wallet or contract address on the target chain * @param batchId ID for Wormhole message batching * @param payload Arbitrary payload to be delivered to the target chain via Wormhole * @return messageSequence Wormhole sequence number for this contract */ function transferTokensWithPayload( TransferParameters memory transferParams, uint32 batchId, bytes memory payload ) public payable nonReentrant returns (uint64 messageSequence) { // cache wormhole instance and fees to save on gas IWormhole wormhole = wormhole(); uint256 wormholeFee = wormhole.messageFee(); // confirm that the caller has sent enough ether to pay for the wormhole message fee require(msg.value == wormholeFee, "insufficient value"); // Call the circle bridge and `depositForBurnWithCaller`. The `mintRecipient` // should be the target contract (or wallet) composing on this contract. (uint64 nonce, uint256 amountReceived) = _transferTokens( transferParams.token, transferParams.amount, transferParams.targetChain, transferParams.mintRecipient ); // encode DepositWithPayload message bytes memory encodedMessage = encodeDepositWithPayload( DepositWithPayload({ token: addressToBytes32(transferParams.token), amount: amountReceived, sourceDomain: localDomain(), targetDomain: getDomainFromChainId(transferParams.targetChain), nonce: nonce, fromAddress: addressToBytes32(msg.sender), mintRecipient: transferParams.mintRecipient, payload: payload }) ); // send the DepositWithPayload wormhole message messageSequence = wormhole.publishMessage{value: wormholeFee}( batchId, encodedMessage, wormholeFinality() ); } function _transferTokens( address token, uint256 amount, uint16 targetChain, bytes32 mintRecipient ) internal returns (uint64 nonce, uint256 amountReceived) { // sanity check user input require(amount > 0, "amount must be > 0"); require(mintRecipient != bytes32(0), "invalid mint recipient"); require(isAcceptedToken(token), "token not accepted"); require( getRegisteredEmitter(targetChain) != bytes32(0), "target contract not registered" ); // take custody of tokens amountReceived = custodyTokens(token, amount); // cache Circle Bridge instance ICircleBridge circleBridge = circleBridge(); // approve the Circle Bridge to spend tokens SafeERC20.safeApprove( IERC20(token), address(circleBridge), amountReceived ); // burn tokens on the bridge nonce = circleBridge.depositForBurnWithCaller( amountReceived, getDomainFromChainId(targetChain), mintRecipient, token, getRegisteredEmitter(targetChain) ); } function custodyTokens( address token, uint256 amount ) internal returns (uint256) { // query own token balance before transfer (, bytes memory queriedBalanceBefore) = token.staticcall( abi.encodeWithSelector(IERC20.balanceOf.selector, address(this)) ); uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256)); // deposit tokens SafeERC20.safeTransferFrom( IERC20(token), msg.sender, address(this), amount ); // query own token balance after transfer (, bytes memory queriedBalanceAfter) = token.staticcall( abi.encodeWithSelector(IERC20.balanceOf.selector, address(this)) ); uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256)); return balanceAfter - balanceBefore; } /** * @notice `redeemTokensWithPayload` verifies the Wormhole message from the source chain and * verifies that the passed Circle Bridge message is valid. It calls the Circle Bridge * contract by passing the Circle message and attestation to mint tokens to the specified * mint recipient. It also verifies that the caller is the specified mint recipient to ensure * atomic execution of the additional instructions in the Wormhole message. * @dev reverts if: * - Wormhole message is not properly attested * - Wormhole message was not emitted from a registered contrat * - Wormhole message was already consumed by this contract * - msg.sender is not the encoded mintRecipient * - Circle Bridge message and Wormhole message are not associated * - `receiveMessage` call to Circle Transmitter fails * @param params Struct containing the following attributes: * - `encodedWormholeMessage` Wormhole message emitted by a registered contract including * information regarding the token burn on the source chain and an arbitrary message. * - `circleBridgeMessage` Message emitted by Circle Bridge contract with information regarding * the token burn on the source chain. * - `circleAttestation` Serialized EC Signature attesting the cross-chain transfer * @return depositInfo Struct containing the following attributes: * - `token` Address (bytes32 left-zero-padded) of token to be minted * - `amount` Amount of tokens to be minted * - `sourceDomain` Circle domain for the source chain * - `targetDomain` Circle domain for the target chain * - `nonce` Circle sequence number for the transfer * - `fromAddress` Source CircleIntegration contract caller's address * - `mintRecipient` Recipient of minted tokens (must be caller of this contract) * - `payload` Arbitrary Wormhole message payload */ function redeemTokensWithPayload( RedeemParameters calldata params ) public returns (DepositWithPayload memory depositInfo) { // verify the wormhole message IWormhole.VM memory verifiedMessage = verifyWormholeRedeemMessage( params.encodedWormholeMessage ); // Decode the message payload into the DepositWithPayload struct. Call the Circle TokenMinter // contract to determine the address of the encoded token on this chain. depositInfo = decodeDepositWithPayload(verifiedMessage.payload); depositInfo.token = fetchLocalTokenAddress( depositInfo.sourceDomain, depositInfo.token ); // confirm that circle gave us a valid token address require(depositInfo.token != bytes32(0), "invalid local token address"); // confirm that the caller is the `mintRecipient` to ensure atomic execution require( addressToBytes32(msg.sender) == depositInfo.mintRecipient, "caller must be mintRecipient" ); // confirm that the caller passed the correct message pair require( verifyCircleMessage( params.circleBridgeMessage, depositInfo.sourceDomain, depositInfo.targetDomain, depositInfo.nonce ), "invalid message pair" ); // call the circle bridge to mint tokens to the recipient bool success = circleTransmitter().receiveMessage( params.circleBridgeMessage, params.circleAttestation ); require(success, "CIRCLE_INTEGRATION: failed to mint tokens"); // emit Redeemed event emit Redeemed( verifiedMessage.emitterChainId, verifiedMessage.emitterAddress, verifiedMessage.sequence ); } function verifyWormholeRedeemMessage( bytes memory encodedMessage ) internal returns (IWormhole.VM memory) { require(evmChain() == block.chainid, "invalid evm chain"); // parse and verify the Wormhole core message ( IWormhole.VM memory verifiedMessage, bool valid, string memory reason ) = wormhole().parseAndVerifyVM(encodedMessage); // confirm that the core layer verified the message require(valid, reason); // verify that this message was emitted by a trusted contract require(verifyEmitter(verifiedMessage), "unknown emitter"); // revert if this message has been consumed already require( !isMessageConsumed(verifiedMessage.hash), "message already consumed" ); consumeMessage(verifiedMessage.hash); return verifiedMessage; } function verifyEmitter( IWormhole.VM memory vm ) internal view returns (bool) { // verify that the sender of the wormhole message is a trusted return (getRegisteredEmitter(vm.emitterChainId) == vm.emitterAddress && vm.emitterAddress != bytes32(0)); } function verifyCircleMessage( bytes memory circleMessage, uint32 sourceDomain, uint32 targetDomain, uint64 nonce ) internal pure returns (bool) { // parse the circle bridge message inline uint32 circleSourceDomain = circleMessage.toUint32(4); uint32 circleTargetDomain = circleMessage.toUint32(8); uint64 circleNonce = circleMessage.toUint64(12); // confirm that both the Wormhole message and Circle message share the same transfer info return (sourceDomain == circleSourceDomain && targetDomain == circleTargetDomain && nonce == circleNonce); } /** * @notice Fetches the local token address given an address and domain from * a different chain. * @param sourceDomain Circle domain for the sending chain. * @param sourceToken Address of the token for the sending chain. * @return Address bytes32 formatted address of the `sourceToken` on this chain. */ function fetchLocalTokenAddress( uint32 sourceDomain, bytes32 sourceToken ) public view returns (bytes32) { return addressToBytes32( circleTokenMinter().remoteTokensToLocalTokens( keccak256(abi.encodePacked(sourceDomain, sourceToken)) ) ); } /** * @notice Converts type address to bytes32 (left-zero-padded) * @param address_ Address to convert to bytes32 * @return Address bytes32 */ function addressToBytes32(address address_) public pure returns (bytes32) { return bytes32(uint256(uint160(address_))); } } ``` The functions provided by the Circle Integration contract are as follows: - **`transferTokensWithPayload`** - calls the Circle Bridge contract to burn Circle-supported tokens. It emits a Wormhole message containing a user-specified payload with instructions for what to do with the Circle-supported assets once they have been minted on the target chain ??? interface "Parameters" `transferParams` ++"TransferParameters"++ A tuple containing the parameters for the transfer. ??? child "`TransferParameters` struct" `token` ++"address"++ Address of the token to be burned. --- `amount` ++"uint256"++ Amount of the token to be burned. --- `targetChain` ++"uint16"++ Wormhole chain ID of the target blockchain. --- `mintRecipient` ++"bytes32"++ The recipient wallet or contract address on the target chain. --- `batchId` ++"uint32"++ The ID for Wormhole message batching. --- `payload` ++"bytes"++ Arbitrary payload to be delivered to the target chain via Wormhole. ??? interface "Returns" `messageSequence` ++"uint64"++ Wormhole sequence number for this contract. - `redeemTokensWithPayload` - verifies the Wormhole message from the source chain and verifies that the passed Circle Bridge message is valid. It calls the Circle Bridge contract by passing the Circle message and attestation to the `receiveMessage` function, which is responsible for minting tokens to the specified mint recipient. It also verifies that the caller is the specified mint recipient to ensure atomic execution of the additional instructions in the Wormhole message ??? interface "Parameters" `params` ++"RedeemParameters"++ A tuple containing the parameters for the redemption. ??? child "`RedeemParameters` struct" `encodedWormholeMessage` ++"bytes"++ Wormhole message emitted by a registered contract including information regarding the token burn on the source chain and an arbitrary message. --- `circleBridgeMessage` ++"bytes"++ Message emitted by Circle Bridge contract with information regarding the token burn on the source chain. --- `circleAttestation` ++"bytes"++ Serialized EC signature attesting the cross-chain transfer. ??? interface "Returns" `depositInfo` ++"DepositWithPayload"++ Information about the deposit. ??? child "`DepositWithPayload` struct" `token` ++"bytes32"++ Address (`bytes32` left-zero-padded) of token to be minted. --- `amount` ++"uint256"++ Amount of tokens to be minted. --- `sourceDomain` ++"uint32"++ Circle domain for the source chain. --- `targetDomain` ++"uint32"++ Circle domain for the target chain. --- `nonce` ++"uint64"++ Circle sequence number for the transfer. --- `fromAddress` ++"bytes32"++ Source Circle Integration contract caller's address. --- `mintRecipient` ++"bytes32"++ Recipient of minted tokens (must be caller of this contract). --- `payload` ++"bytes"++ Arbitrary Wormhole message payload. ??? interface "Emits" `Redeemed` - event emitted when Circle-supported assets have been minted to the `mintRecipient` ??? child "Event arguments" `emitterChainId` ++"uint16"++ Wormhole chain ID of emitter contract on source chain. --- `emitterAddress` ++"bytes32"++ Address (`bytes32` zero-left-padded) of emitter on source chain. --- `sequence` ++"uint64"++ Sequence of Wormhole message used to mint tokens. ## Circle's CCTP Contracts Three key contracts power Circle's CCTP: - **`TokenMessenger`** - the entry point for cross-chain USDC transfers, routing messages to initiate USDC burns on the source chain, and mint USDC on the destination chain - **`MessageTransmitter`** - handles generic message passing, sending messages from the source chain and receiving them on the destination chain - **`TokenMinter`** - responsible for the actual minting and burning of USDC, utilizing chain-specific settings for both the burners and minters across different networks The following sections will examine these contracts in-depth, focusing on the methods invoked indirectly through function calls in the Wormhole Circle Integration contract. !!! note When using Wormhole's CCTP integration, you will not directly interact with these contracts. You will indirectly interact with them through the Wormhole Circle Integration contract. These contracts can be found in [Circle's `evm-cctp-contracts` repository](https://github.com/circlefin/evm-cctp-contracts/){target=\_blank} on GitHub. ### Token Messenger Contract The Token Messenger contract enables cross-chain USDC transfers by coordinating message exchanges between blockchains. It works alongside the Message Transmitter contract to relay messages for burning USDC on a source chain and minting it on a destination chain. The contract emits events to track both the burning of tokens and their subsequent minting on the destination chain. To ensure secure communication, the Token Messenger restricts message handling to registered remote Token Messenger contracts only. It verifies the proper conditions for token burning and manages local and remote minters using chain-specific settings. Additionally, the contract provides methods for updating or replacing previously sent burn messages, adding or removing remote Token Messenger contracts, and managing the minting process for cross-chain transfers. ??? code "Token Messenger contract" ```solidity /* * Copyright (c) 2022, Circle Internet Financial Limited. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ pragma solidity 0.7.6; import "./interfaces/IMessageHandler.sol"; import "./interfaces/ITokenMinter.sol"; import "./interfaces/IMintBurnToken.sol"; import "./interfaces/IMessageTransmitter.sol"; import "./messages/BurnMessage.sol"; import "./messages/Message.sol"; import "./roles/Rescuable.sol"; /** * @title TokenMessenger * @notice Sends messages and receives messages to/from MessageTransmitters * and to/from TokenMinters */ contract TokenMessenger is IMessageHandler, Rescuable { // ============ Events ============ /** * @notice Emitted when a DepositForBurn message is sent * @param nonce unique nonce reserved by message * @param burnToken address of token burnt on source domain * @param amount deposit amount * @param depositor address where deposit is transferred from * @param mintRecipient address receiving minted tokens on destination domain as bytes32 * @param destinationDomain destination domain * @param destinationTokenMessenger address of TokenMessenger on destination domain as bytes32 * @param destinationCaller authorized caller as bytes32 of receiveMessage() on destination domain, if not equal to bytes32(0). * If equal to bytes32(0), any address can call receiveMessage(). */ event DepositForBurn( uint64 indexed nonce, address indexed burnToken, uint256 amount, address indexed depositor, bytes32 mintRecipient, uint32 destinationDomain, bytes32 destinationTokenMessenger, bytes32 destinationCaller ); /** * @notice Emitted when tokens are minted * @param mintRecipient recipient address of minted tokens * @param amount amount of minted tokens * @param mintToken contract address of minted token */ event MintAndWithdraw( address indexed mintRecipient, uint256 amount, address indexed mintToken ); /** * @notice Emitted when a remote TokenMessenger is added * @param domain remote domain * @param tokenMessenger TokenMessenger on remote domain */ event RemoteTokenMessengerAdded(uint32 domain, bytes32 tokenMessenger); /** * @notice Emitted when a remote TokenMessenger is removed * @param domain remote domain * @param tokenMessenger TokenMessenger on remote domain */ event RemoteTokenMessengerRemoved(uint32 domain, bytes32 tokenMessenger); /** * @notice Emitted when the local minter is added * @param localMinter address of local minter * @notice Emitted when the local minter is added */ event LocalMinterAdded(address localMinter); /** * @notice Emitted when the local minter is removed * @param localMinter address of local minter * @notice Emitted when the local minter is removed */ event LocalMinterRemoved(address localMinter); // ============ Libraries ============ using TypedMemView for bytes; using TypedMemView for bytes29; using BurnMessage for bytes29; using Message for bytes29; // ============ State Variables ============ // Local Message Transmitter responsible for sending and receiving messages to/from remote domains IMessageTransmitter public immutable localMessageTransmitter; // Version of message body format uint32 public immutable messageBodyVersion; // Minter responsible for minting and burning tokens on the local domain ITokenMinter public localMinter; // Valid TokenMessengers on remote domains mapping(uint32 => bytes32) public remoteTokenMessengers; // ============ Modifiers ============ /** * @notice Only accept messages from a registered TokenMessenger contract on given remote domain * @param domain The remote domain * @param tokenMessenger The address of the TokenMessenger contract for the given remote domain */ modifier onlyRemoteTokenMessenger(uint32 domain, bytes32 tokenMessenger) { require( _isRemoteTokenMessenger(domain, tokenMessenger), "Remote TokenMessenger unsupported" ); _; } /** * @notice Only accept messages from the registered message transmitter on local domain */ modifier onlyLocalMessageTransmitter() { // Caller must be the registered message transmitter for this domain require(_isLocalMessageTransmitter(), "Invalid message transmitter"); _; } // ============ Constructor ============ /** * @param _messageTransmitter Message transmitter address * @param _messageBodyVersion Message body version */ constructor(address _messageTransmitter, uint32 _messageBodyVersion) { require( _messageTransmitter != address(0), "MessageTransmitter not set" ); localMessageTransmitter = IMessageTransmitter(_messageTransmitter); messageBodyVersion = _messageBodyVersion; } // ============ External Functions ============ /** * @notice Deposits and burns tokens from sender to be minted on destination domain. * Emits a `DepositForBurn` event. * @dev reverts if: * - given burnToken is not supported * - given destinationDomain has no TokenMessenger registered * - transferFrom() reverts. For example, if sender's burnToken balance or approved allowance * to this contract is less than `amount`. * - burn() reverts. For example, if `amount` is 0. * - MessageTransmitter returns false or reverts. * @param amount amount of tokens to burn * @param destinationDomain destination domain * @param mintRecipient address of mint recipient on destination domain * @param burnToken address of contract to burn deposited tokens, on local domain * @return _nonce unique nonce reserved by message */ function depositForBurn( uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken ) external returns (uint64 _nonce) { return _depositForBurn( amount, destinationDomain, mintRecipient, burnToken, // (bytes32(0) here indicates that any address can call receiveMessage() // on the destination domain, triggering mint to specified `mintRecipient`) bytes32(0) ); } /** * @notice Deposits and burns tokens from sender to be minted on destination domain. The mint * on the destination domain must be called by `destinationCaller`. * WARNING: if the `destinationCaller` does not represent a valid address as bytes32, then it will not be possible * to broadcast the message on the destination domain. This is an advanced feature, and the standard * depositForBurn() should be preferred for use cases where a specific destination caller is not required. * Emits a `DepositForBurn` event. * @dev reverts if: * - given destinationCaller is zero address * - given burnToken is not supported * - given destinationDomain has no TokenMessenger registered * - transferFrom() reverts. For example, if sender's burnToken balance or approved allowance * to this contract is less than `amount`. * - burn() reverts. For example, if `amount` is 0. * - MessageTransmitter returns false or reverts. * @param amount amount of tokens to burn * @param destinationDomain destination domain * @param mintRecipient address of mint recipient on destination domain * @param burnToken address of contract to burn deposited tokens, on local domain * @param destinationCaller caller on the destination domain, as bytes32 * @return nonce unique nonce reserved by message */ function depositForBurnWithCaller( uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken, bytes32 destinationCaller ) external returns (uint64 nonce) { // Destination caller must be nonzero. To allow any destination caller, use depositForBurn(). require(destinationCaller != bytes32(0), "Invalid destination caller"); return _depositForBurn( amount, destinationDomain, mintRecipient, burnToken, destinationCaller ); } /** * @notice Replace a BurnMessage to change the mint recipient and/or * destination caller. Allows the sender of a previous BurnMessage * (created by depositForBurn or depositForBurnWithCaller) * to send a new BurnMessage to replace the original. * The new BurnMessage will reuse the amount and burn token of the original, * without requiring a new deposit. * @dev The new message will reuse the original message's nonce. For a * given nonce, all replacement message(s) and the original message are * valid to broadcast on the destination domain, until the first message * at the nonce confirms, at which point all others are invalidated. * Note: The msg.sender of the replaced message must be the same as the * msg.sender of the original message. * @param originalMessage original message bytes (to replace) * @param originalAttestation original attestation bytes * @param newDestinationCaller the new destination caller, which may be the * same as the original destination caller, a new destination caller, or an empty * destination caller (bytes32(0), indicating that any destination caller is valid.) * @param newMintRecipient the new mint recipient, which may be the same as the * original mint recipient, or different. */ function replaceDepositForBurn( bytes calldata originalMessage, bytes calldata originalAttestation, bytes32 newDestinationCaller, bytes32 newMintRecipient ) external { bytes29 _originalMsg = originalMessage.ref(0); _originalMsg._validateMessageFormat(); bytes29 _originalMsgBody = _originalMsg._messageBody(); _originalMsgBody._validateBurnMessageFormat(); bytes32 _originalMsgSender = _originalMsgBody._getMessageSender(); // _originalMsgSender must match msg.sender of original message require( msg.sender == Message.bytes32ToAddress(_originalMsgSender), "Invalid sender for message" ); require( newMintRecipient != bytes32(0), "Mint recipient must be nonzero" ); bytes32 _burnToken = _originalMsgBody._getBurnToken(); uint256 _amount = _originalMsgBody._getAmount(); bytes memory _newMessageBody = BurnMessage._formatMessage( messageBodyVersion, _burnToken, newMintRecipient, _amount, _originalMsgSender ); localMessageTransmitter.replaceMessage( originalMessage, originalAttestation, _newMessageBody, newDestinationCaller ); emit DepositForBurn( _originalMsg._nonce(), Message.bytes32ToAddress(_burnToken), _amount, msg.sender, newMintRecipient, _originalMsg._destinationDomain(), _originalMsg._recipient(), newDestinationCaller ); } /** * @notice Handles an incoming message received by the local MessageTransmitter, * and takes the appropriate action. For a burn message, mints the * associated token to the requested recipient on the local domain. * @dev Validates the local sender is the local MessageTransmitter, and the * remote sender is a registered remote TokenMessenger for `remoteDomain`. * @param remoteDomain The domain where the message originated from. * @param sender The sender of the message (remote TokenMessenger). * @param messageBody The message body bytes. * @return success Bool, true if successful. */ function handleReceiveMessage( uint32 remoteDomain, bytes32 sender, bytes calldata messageBody ) external override onlyLocalMessageTransmitter onlyRemoteTokenMessenger(remoteDomain, sender) returns (bool) { bytes29 _msg = messageBody.ref(0); _msg._validateBurnMessageFormat(); require( _msg._getVersion() == messageBodyVersion, "Invalid message body version" ); bytes32 _mintRecipient = _msg._getMintRecipient(); bytes32 _burnToken = _msg._getBurnToken(); uint256 _amount = _msg._getAmount(); ITokenMinter _localMinter = _getLocalMinter(); _mintAndWithdraw( address(_localMinter), remoteDomain, _burnToken, Message.bytes32ToAddress(_mintRecipient), _amount ); return true; } /** * @notice Add the TokenMessenger for a remote domain. * @dev Reverts if there is already a TokenMessenger set for domain. * @param domain Domain of remote TokenMessenger. * @param tokenMessenger Address of remote TokenMessenger as bytes32. */ function addRemoteTokenMessenger(uint32 domain, bytes32 tokenMessenger) external onlyOwner { require(tokenMessenger != bytes32(0), "bytes32(0) not allowed"); require( remoteTokenMessengers[domain] == bytes32(0), "TokenMessenger already set" ); remoteTokenMessengers[domain] = tokenMessenger; emit RemoteTokenMessengerAdded(domain, tokenMessenger); } /** * @notice Remove the TokenMessenger for a remote domain. * @dev Reverts if there is no TokenMessenger set for `domain`. * @param domain Domain of remote TokenMessenger */ function removeRemoteTokenMessenger(uint32 domain) external onlyOwner { // No TokenMessenger set for given remote domain. require( remoteTokenMessengers[domain] != bytes32(0), "No TokenMessenger set" ); bytes32 _removedTokenMessenger = remoteTokenMessengers[domain]; delete remoteTokenMessengers[domain]; emit RemoteTokenMessengerRemoved(domain, _removedTokenMessenger); } /** * @notice Add minter for the local domain. * @dev Reverts if a minter is already set for the local domain. * @param newLocalMinter The address of the minter on the local domain. */ function addLocalMinter(address newLocalMinter) external onlyOwner { require(newLocalMinter != address(0), "Zero address not allowed"); require( address(localMinter) == address(0), "Local minter is already set." ); localMinter = ITokenMinter(newLocalMinter); emit LocalMinterAdded(newLocalMinter); } /** * @notice Remove the minter for the local domain. * @dev Reverts if the minter of the local domain is not set. */ function removeLocalMinter() external onlyOwner { address _localMinterAddress = address(localMinter); require(_localMinterAddress != address(0), "No local minter is set."); delete localMinter; emit LocalMinterRemoved(_localMinterAddress); } // ============ Internal Utils ============ /** * @notice Deposits and burns tokens from sender to be minted on destination domain. * Emits a `DepositForBurn` event. * @param _amount amount of tokens to burn (must be non-zero) * @param _destinationDomain destination domain * @param _mintRecipient address of mint recipient on destination domain * @param _burnToken address of contract to burn deposited tokens, on local domain * @param _destinationCaller caller on the destination domain, as bytes32 * @return nonce unique nonce reserved by message */ function _depositForBurn( uint256 _amount, uint32 _destinationDomain, bytes32 _mintRecipient, address _burnToken, bytes32 _destinationCaller ) internal returns (uint64 nonce) { require(_amount > 0, "Amount must be nonzero"); require(_mintRecipient != bytes32(0), "Mint recipient must be nonzero"); bytes32 _destinationTokenMessenger = _getRemoteTokenMessenger( _destinationDomain ); ITokenMinter _localMinter = _getLocalMinter(); IMintBurnToken _mintBurnToken = IMintBurnToken(_burnToken); require( _mintBurnToken.transferFrom( msg.sender, address(_localMinter), _amount ), "Transfer operation failed" ); _localMinter.burn(_burnToken, _amount); // Format message body bytes memory _burnMessage = BurnMessage._formatMessage( messageBodyVersion, Message.addressToBytes32(_burnToken), _mintRecipient, _amount, Message.addressToBytes32(msg.sender) ); uint64 _nonceReserved = _sendDepositForBurnMessage( _destinationDomain, _destinationTokenMessenger, _destinationCaller, _burnMessage ); emit DepositForBurn( _nonceReserved, _burnToken, _amount, msg.sender, _mintRecipient, _destinationDomain, _destinationTokenMessenger, _destinationCaller ); return _nonceReserved; } /** * @notice Sends a BurnMessage through the local message transmitter * @dev calls local message transmitter's sendMessage() function if `_destinationCaller` == bytes32(0), * or else calls sendMessageWithCaller(). * @param _destinationDomain destination domain * @param _destinationTokenMessenger address of registered TokenMessenger contract on destination domain, as bytes32 * @param _destinationCaller caller on the destination domain, as bytes32. If `_destinationCaller` == bytes32(0), * any address can call receiveMessage() on destination domain. * @param _burnMessage formatted BurnMessage bytes (message body) * @return nonce unique nonce reserved by message */ function _sendDepositForBurnMessage( uint32 _destinationDomain, bytes32 _destinationTokenMessenger, bytes32 _destinationCaller, bytes memory _burnMessage ) internal returns (uint64 nonce) { if (_destinationCaller == bytes32(0)) { return localMessageTransmitter.sendMessage( _destinationDomain, _destinationTokenMessenger, _burnMessage ); } else { return localMessageTransmitter.sendMessageWithCaller( _destinationDomain, _destinationTokenMessenger, _destinationCaller, _burnMessage ); } } /** * @notice Mints tokens to a recipient * @param _tokenMinter address of TokenMinter contract * @param _remoteDomain domain where burned tokens originate from * @param _burnToken address of token burned * @param _mintRecipient recipient address of minted tokens * @param _amount amount of minted tokens */ function _mintAndWithdraw( address _tokenMinter, uint32 _remoteDomain, bytes32 _burnToken, address _mintRecipient, uint256 _amount ) internal { ITokenMinter _minter = ITokenMinter(_tokenMinter); address _mintToken = _minter.mint( _remoteDomain, _burnToken, _mintRecipient, _amount ); emit MintAndWithdraw(_mintRecipient, _amount, _mintToken); } /** * @notice return the remote TokenMessenger for the given `_domain` if one exists, else revert. * @param _domain The domain for which to get the remote TokenMessenger * @return _tokenMessenger The address of the TokenMessenger on `_domain` as bytes32 */ function _getRemoteTokenMessenger(uint32 _domain) internal view returns (bytes32) { bytes32 _tokenMessenger = remoteTokenMessengers[_domain]; require(_tokenMessenger != bytes32(0), "No TokenMessenger for domain"); return _tokenMessenger; } /** * @notice return the local minter address if it is set, else revert. * @return local minter as ITokenMinter. */ function _getLocalMinter() internal view returns (ITokenMinter) { require(address(localMinter) != address(0), "Local minter is not set"); return localMinter; } /** * @notice Return true if the given remote domain and TokenMessenger is registered * on this TokenMessenger. * @param _domain The remote domain of the message. * @param _tokenMessenger The address of the TokenMessenger on remote domain. * @return true if a remote TokenMessenger is registered for `_domain` and `_tokenMessenger`, * on this TokenMessenger. */ function _isRemoteTokenMessenger(uint32 _domain, bytes32 _tokenMessenger) internal view returns (bool) { return _tokenMessenger != bytes32(0) && remoteTokenMessengers[_domain] == _tokenMessenger; } /** * @notice Returns true if the message sender is the local registered MessageTransmitter * @return true if message sender is the registered local message transmitter */ function _isLocalMessageTransmitter() internal view returns (bool) { return address(localMessageTransmitter) != address(0) && msg.sender == address(localMessageTransmitter); } } ``` This contract and the interfaces, contracts, and libraries it relies on are stored in [Circle's `evm-cctp-contracts` repository](https://github.com/circlefin/evm-cctp-contracts/blob/master/src/TokenMessenger.sol){target=\_blank} on GitHub. The functions provided by the Token Messenger contract are as follows: - **`depositForBurn`** - deposits and burns tokens from the sender to be minted on the destination domain. Minted tokens will be transferred to `mintRecipient` ??? interface "Parameters" `amount` ++"uint256"++ The amount of tokens to burn. --- `destinationDomain` ++"uint32"++ The network where the token will be minted after burn. --- `mintRecipient` ++"bytes32"++ Address of mint recipient on destination domain. --- `burnToken` ++"address"++ Address of contract to burn deposited tokens, on local domain. ??? interface "Returns" `_nonce` ++"uint64"++ Unique nonce reserved by message. ??? interface "Emits" `DepositForBurn` - event emitted when `depositForBurn` is called. The `destinationCaller` is set to `bytes32(0)` to allow any address to call `receiveMessage` on the destination domain ??? child "Event Arguments" `nonce` ++"uint64"++ Unique nonce reserved by message (indexed). --- `burnToken` ++"address"++ Address of token burnt on source domain. --- `amount` ++"uint256"++ The deposit amount. --- `depositor` ++"address"++ Address where deposit is transferred from. --- `mintRecipient` ++"bytes32"++ Address receiving minted tokens on destination domain. --- `destinationDomain` ++"uint32"++ - Destination domain. --- `destinationTokenMessenger` ++"bytes32"++ Address of `TokenMessenger` on destination domain. --- `destinationCaller` ++"bytes32"++ Authorized caller of the `receiveMessage` function on the destination domain, if not equal to `bytes32(0)`. If equal to `bytes32(0)`, any address can call `receiveMessage`. - **`depositForBurnWithCaller`** - deposits and burns tokens from the sender to be minted on the destination domain. This method differs from `depositForBurn` in that the mint on the destination domain can only be called by the designated `destinationCaller` address ??? interface "Parameters" `amount` ++"uint256"++ The amount of tokens to burn. --- `destinationDomain` ++"uint32"++ The network where the token will be minted after burn. --- `mintRecipient` ++"bytes32"++ Address of mint recipient on destination domain. --- `burnToken` ++"address"++ Address of contract to burn deposited tokens, on local domain. --- `destinationCaller` ++"bytes32"++ Address of the caller on the destination domain who will trigger the mint. ??? interface "Returns" `_nonce` ++"uint64"++ Unique nonce reserved by message. ??? interface "Emits" `DepositForBurn` - event emitted when `depositForBurnWithCaller` is called ??? child "Event Arguments" `nonce` ++"uint64"++ Unique nonce reserved by message (indexed). --- `burnToken` ++"address"++ Address of token burnt on source domain. --- `amount` ++"uint256"++ The deposit amount. --- `depositor` ++"address"++ Address where deposit is transferred from. --- `mintRecipient` ++"bytes32"++ Address receiving minted tokens on destination domain. --- `destinationDomain` ++"uint32"++ - Destination domain. --- `destinationTokenMessenger` ++"bytes32"++ Address of `TokenMessenger` on destination domain. --- `destinationCaller` ++"bytes32"++ Authorized caller of the `receiveMessage` function on the destination domain, if not equal to `bytes32(0)`. If equal to `bytes32(0)`, any address can call `receiveMessage`. - **`replaceDepositForBurn`** — replaces a previous `BurnMessage` to modify the mint recipient and/or the destination caller. The replacement message reuses the `_nonce` created by the original message, which allows the original message's sender to update the details without requiring a new deposit ??? interface "Parameters" `originalMessage` ++"bytes"++ The original burn message to be replaced. --- `originalAttestation` ++"bytes"++ The attestation of the original message. --- `newDestinationCaller` ++"bytes32"++ The new caller on the destination domain, can be the same or updated. --- `newMintRecipient` ++"bytes32"++ The new recipient for the minted tokens, can be the same or updated. ??? interface "Returns" None. ??? interface "Emits" `DepositForBurn` - event emitted when `replaceDepositForBurn` is called. Note that the `destinationCaller` will reflect the new destination caller, which may be the same as the original destination caller, a new destination caller, or an empty destination caller (`bytes32(0)`), indicating that any destination caller is valid ??? child "Event Arguments" `nonce` ++"uint64"++ Unique nonce reserved by message (indexed). --- `burnToken` ++"address"++ Address of token burnt on source domain. --- `amount` ++"uint256"++ The deposit amount. --- `depositor` ++"address"++ Address where deposit is transferred from. --- `mintRecipient` ++"bytes32"++ Address receiving minted tokens on destination domain. --- `destinationDomain` ++"uint32"++ - Destination domain. --- `destinationTokenMessenger` ++"bytes32"++ Address of `TokenMessenger` on destination domain. --- `destinationCaller` ++"bytes32"++ Authorized caller of the `receiveMessage` function on the destination domain, if not equal to `bytes32(0)`. If equal to `bytes32(0)`, any address can call `receiveMessage`. - **`handleReceiveMessage`** - handles an incoming message received by the local `MessageTransmitter` and takes the appropriate action. For a burn message, it mints the associated token to the requested recipient on the local domain. ???+ note Though this function can only be called by the local `MessageTransmitter`, it is included here as it emits the essential event for minting tokens and withdrawing to send to the recipient. ??? interface "Parameters" `remoteDomain` ++"uint32"++ The domain where the message originated. --- `sender` ++"bytes32"++ The address of the sender of the message. --- `messageBody` ++"bytes"++ The bytes making up the body of the message. ??? interface "Returns" `success` ++"boolean"++ Returns `true` if successful, otherwise, it returns `false`. ??? interface "Emits" `MintAndWithdraw` - event emitted when tokens are minted ??? child "Event arguments" `localMinter` ++"address"++ Minter responsible for minting and burning tokens on the local domain. --- `remoteDomain` ++"uint32"++ The domain where the message originated from. --- `burnToken` ++"address"++ Address of contract to burn deposited tokens, on local domain. --- `mintRecipient` ++"address"++ Recipient address of minted tokens (indexed). --- `amount` ++"uint256"++ Amount of minted tokens. ### Message Transmitter Contract The Message Transmitter contract ensures secure messaging across blockchain domains by managing message dispatch and tracking communication with events like `MessageSent` and `MessageReceived`. It uses a unique nonce for each message, which ensures proper validation, verifies attestation signatures, and prevents replay attacks. The contract supports flexible delivery options, allowing messages to be sent to a specific `destinationCaller` or broadcast more generally. It also includes domain-specific configurations to manage communication between chains. Additional features include replacing previously sent messages, setting maximum message body sizes, and verifying that messages are received only once per nonce to maintain network integrity. ??? code "Message Transmitter contract" ```solidity /* * Copyright (c) 2022, Circle Internet Financial Limited. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ pragma solidity 0.7.6; import "@memview-sol/contracts/TypedMemView.sol"; import "./interfaces/IMessageTransmitter.sol"; import "./interfaces/IMessageHandler.sol"; import "./messages/Message.sol"; import "./roles/Pausable.sol"; import "./roles/Rescuable.sol"; import "./roles/Attestable.sol"; /** * @title MessageTransmitter * @notice Contract responsible for sending and receiving messages across chains. */ contract MessageTransmitter is IMessageTransmitter, Pausable, Rescuable, Attestable { // ============ Events ============ /** * @notice Emitted when a new message is dispatched * @param message Raw bytes of message */ event MessageSent(bytes message); /** * @notice Emitted when a new message is received * @param caller Caller (msg.sender) on destination domain * @param sourceDomain The source domain this message originated from * @param nonce The nonce unique to this message * @param sender The sender of this message * @param messageBody message body bytes */ event MessageReceived( address indexed caller, uint32 sourceDomain, uint64 indexed nonce, bytes32 sender, bytes messageBody ); /** * @notice Emitted when max message body size is updated * @param newMaxMessageBodySize new maximum message body size, in bytes */ event MaxMessageBodySizeUpdated(uint256 newMaxMessageBodySize); // ============ Libraries ============ using TypedMemView for bytes; using TypedMemView for bytes29; using Message for bytes29; // ============ State Variables ============ // Domain of chain on which the contract is deployed uint32 public immutable localDomain; // Message Format version uint32 public immutable version; // Maximum size of message body, in bytes. // This value is set by owner. uint256 public maxMessageBodySize; // Next available nonce from this source domain uint64 public nextAvailableNonce; // Maps a bytes32 hash of (sourceDomain, nonce) -> uint256 (0 if unused, 1 if used) mapping(bytes32 => uint256) public usedNonces; // ============ Constructor ============ constructor( uint32 _localDomain, address _attester, uint32 _maxMessageBodySize, uint32 _version ) Attestable(_attester) { localDomain = _localDomain; maxMessageBodySize = _maxMessageBodySize; version = _version; } // ============ External Functions ============ /** * @notice Send the message to the destination domain and recipient * @dev Increment nonce, format the message, and emit `MessageSent` event with message information. * @param destinationDomain Domain of destination chain * @param recipient Address of message recipient on destination chain as bytes32 * @param messageBody Raw bytes content of message * @return nonce reserved by message */ function sendMessage( uint32 destinationDomain, bytes32 recipient, bytes calldata messageBody ) external override whenNotPaused returns (uint64) { bytes32 _emptyDestinationCaller = bytes32(0); uint64 _nonce = _reserveAndIncrementNonce(); bytes32 _messageSender = Message.addressToBytes32(msg.sender); _sendMessage( destinationDomain, recipient, _emptyDestinationCaller, _messageSender, _nonce, messageBody ); return _nonce; } /** * @notice Replace a message with a new message body and/or destination caller. * @dev The `originalAttestation` must be a valid attestation of `originalMessage`. * Reverts if msg.sender does not match sender of original message, or if the source domain of the original message * does not match this MessageTransmitter's local domain. * @param originalMessage original message to replace * @param originalAttestation attestation of `originalMessage` * @param newMessageBody new message body of replaced message * @param newDestinationCaller the new destination caller, which may be the * same as the original destination caller, a new destination caller, or an empty * destination caller (bytes32(0), indicating that any destination caller is valid.) */ function replaceMessage( bytes calldata originalMessage, bytes calldata originalAttestation, bytes calldata newMessageBody, bytes32 newDestinationCaller ) external override whenNotPaused { // Validate each signature in the attestation _verifyAttestationSignatures(originalMessage, originalAttestation); bytes29 _originalMsg = originalMessage.ref(0); // Validate message format _originalMsg._validateMessageFormat(); // Validate message sender bytes32 _sender = _originalMsg._sender(); require( msg.sender == Message.bytes32ToAddress(_sender), "Sender not permitted to use nonce" ); // Validate source domain uint32 _sourceDomain = _originalMsg._sourceDomain(); require( _sourceDomain == localDomain, "Message not originally sent from this domain" ); uint32 _destinationDomain = _originalMsg._destinationDomain(); bytes32 _recipient = _originalMsg._recipient(); uint64 _nonce = _originalMsg._nonce(); _sendMessage( _destinationDomain, _recipient, newDestinationCaller, _sender, _nonce, newMessageBody ); } /** * @notice Send the message to the destination domain and recipient, for a specified `destinationCaller` on the * destination domain. * @dev Increment nonce, format the message, and emit `MessageSent` event with message information. * WARNING: if the `destinationCaller` does not represent a valid address, then it will not be possible * to broadcast the message on the destination domain. This is an advanced feature, and the standard * sendMessage() should be preferred for use cases where a specific destination caller is not required. * @param destinationDomain Domain of destination chain * @param recipient Address of message recipient on destination domain as bytes32 * @param destinationCaller caller on the destination domain, as bytes32 * @param messageBody Raw bytes content of message * @return nonce reserved by message */ function sendMessageWithCaller( uint32 destinationDomain, bytes32 recipient, bytes32 destinationCaller, bytes calldata messageBody ) external override whenNotPaused returns (uint64) { require( destinationCaller != bytes32(0), "Destination caller must be nonzero" ); uint64 _nonce = _reserveAndIncrementNonce(); bytes32 _messageSender = Message.addressToBytes32(msg.sender); _sendMessage( destinationDomain, recipient, destinationCaller, _messageSender, _nonce, messageBody ); return _nonce; } /** * @notice Receive a message. Messages with a given nonce * can only be broadcast once for a (sourceDomain, destinationDomain) * pair. The message body of a valid message is passed to the * specified recipient for further processing. * * @dev Attestation format: * A valid attestation is the concatenated 65-byte signature(s) of exactly * `thresholdSignature` signatures, in increasing order of attester address. * ***If the attester addresses recovered from signatures are not in * increasing order, signature verification will fail.*** * If incorrect number of signatures or duplicate signatures are supplied, * signature verification will fail. * * Message format: * Field Bytes Type Index * version 4 uint32 0 * sourceDomain 4 uint32 4 * destinationDomain 4 uint32 8 * nonce 8 uint64 12 * sender 32 bytes32 20 * recipient 32 bytes32 52 * messageBody dynamic bytes 84 * @param message Message bytes * @param attestation Concatenated 65-byte signature(s) of `message`, in increasing order * of the attester address recovered from signatures. * @return success bool, true if successful */ function receiveMessage(bytes calldata message, bytes calldata attestation) external override whenNotPaused returns (bool success) { // Validate each signature in the attestation _verifyAttestationSignatures(message, attestation); bytes29 _msg = message.ref(0); // Validate message format _msg._validateMessageFormat(); // Validate domain require( _msg._destinationDomain() == localDomain, "Invalid destination domain" ); // Validate destination caller if (_msg._destinationCaller() != bytes32(0)) { require( _msg._destinationCaller() == Message.addressToBytes32(msg.sender), "Invalid caller for message" ); } // Validate version require(_msg._version() == version, "Invalid message version"); // Validate nonce is available uint32 _sourceDomain = _msg._sourceDomain(); uint64 _nonce = _msg._nonce(); bytes32 _sourceAndNonce = _hashSourceAndNonce(_sourceDomain, _nonce); require(usedNonces[_sourceAndNonce] == 0, "Nonce already used"); // Mark nonce used usedNonces[_sourceAndNonce] = 1; // Handle receive message bytes32 _sender = _msg._sender(); bytes memory _messageBody = _msg._messageBody().clone(); require( IMessageHandler(Message.bytes32ToAddress(_msg._recipient())) .handleReceiveMessage(_sourceDomain, _sender, _messageBody), "handleReceiveMessage() failed" ); // Emit MessageReceived event emit MessageReceived( msg.sender, _sourceDomain, _nonce, _sender, _messageBody ); return true; } /** * @notice Sets the max message body size * @dev This value should not be reduced without good reason, * to avoid impacting users who rely on large messages. * @param newMaxMessageBodySize new max message body size, in bytes */ function setMaxMessageBodySize(uint256 newMaxMessageBodySize) external onlyOwner { maxMessageBodySize = newMaxMessageBodySize; emit MaxMessageBodySizeUpdated(maxMessageBodySize); } // ============ Internal Utils ============ /** * @notice Send the message to the destination domain and recipient. If `_destinationCaller` is not equal to bytes32(0), * the message can only be received on the destination chain when called by `_destinationCaller`. * @dev Format the message and emit `MessageSent` event with message information. * @param _destinationDomain Domain of destination chain * @param _recipient Address of message recipient on destination domain as bytes32 * @param _destinationCaller caller on the destination domain, as bytes32 * @param _sender message sender, as bytes32 * @param _nonce nonce reserved for message * @param _messageBody Raw bytes content of message */ function _sendMessage( uint32 _destinationDomain, bytes32 _recipient, bytes32 _destinationCaller, bytes32 _sender, uint64 _nonce, bytes calldata _messageBody ) internal { // Validate message body length require( _messageBody.length <= maxMessageBodySize, "Message body exceeds max size" ); require(_recipient != bytes32(0), "Recipient must be nonzero"); // serialize message bytes memory _message = Message._formatMessage( version, localDomain, _destinationDomain, _nonce, _sender, _recipient, _destinationCaller, _messageBody ); // Emit MessageSent event emit MessageSent(_message); } /** * @notice hashes `_source` and `_nonce`. * @param _source Domain of chain where the transfer originated * @param _nonce The unique identifier for the message from source to destination * @return hash of source and nonce */ function _hashSourceAndNonce(uint32 _source, uint64 _nonce) internal pure returns (bytes32) { return keccak256(abi.encodePacked(_source, _nonce)); } /** * Reserve and increment next available nonce * @return nonce reserved */ function _reserveAndIncrementNonce() internal returns (uint64) { uint64 _nonceReserved = nextAvailableNonce; nextAvailableNonce = nextAvailableNonce + 1; return _nonceReserved; } } ``` This contract and the interfaces, contracts, and libraries it relies on are stored in [Circle's `evm-cctp-contracts` repository](https://github.com/circlefin/evm-cctp-contracts/blob/master/src/MessageTransmitter.sol){target=\_blank} on GitHub. The functions provided by the Message Transmitter contract are as follows: - **`receiveMessage`** — processes and validates an incoming message and its attestation. If valid, it triggers further action based on the message body ??? interface "Parameters" `message` ++"bytes"++ The message to be processed, including details such as sender, recipient, and message body. --- `attestation` ++"bytes"++ Concatenated 65-byte signature(s) that attest to the validity of the `message`. ??? interface "Returns" `success` ++"boolean"++ Returns `true` if successful, otherwise, returns `false`. ??? interface "Emits" `MessageReceived` - event emitted when a new message is received ??? child "Event arguments" `caller` ++"address"++ Caller on destination domain. --- `sourceDomain` ++"uint32"++ The source domain this message originated from. --- `nonce` ++"uint64"++ Nonce unique to this message (indexed). --- `sender` ++"bytes32"++ Sender of this message. --- `messageBody` ++"bytes"++ The body of the message. - **`sendMessage`** — sends a message to the destination domain and recipient. It increments the `nonce`, assigns a unique `nonce` to the message, and emits a `MessageSent` event ??? interface "Parameters" `destinationDomain` ++"uint32"++ The target blockchain network where the message is to be sent. --- `recipient` ++"bytes32"++ The recipient's address on the destination domain. --- `messageBody` ++"bytes"++ The raw bytes content of the message. ??? interface "Returns" `nonce` ++"uint64"++ Nonce unique to this message. ??? interface "Emits" `MessageSent` - event emitted when a new message is dispatched ??? child "Event arguments" `message` ++"bytes"++ The raw bytes of the message. - **`sendMessageWithCaller`** — sends a message to the destination domain and recipient, requiring a specific caller to trigger the message on the target chain. It increments the `nonce`, assigns a unique `nonce` to the message, and emits a `MessageSent` event ??? interface "Parameters" `destinationDomain` ++"uint32"++ The target blockchain network where the message is to be sent. --- `recipient` ++"bytes32"++ The recipient's address on the destination domain. --- `destinationCaller` ++"bytes32"++ The caller on the destination domain. --- `messageBody` ++"bytes"++ The raw bytes content of the message. ??? interface "Returns" `nonce` ++"uint64"++ Nonce unique to this message. ??? interface "Emits" `MessageSent` - event emitted when a new message is dispatched ??? child "Event arguments" `message` ++"bytes"++ The raw bytes of the message. - **`replaceMessage`** — replaces an original message with a new message body and/or updates the destination caller. The replacement message reuses the `_nonce` created by the original message ??? interface "Parameters" `originalMessage` ++"bytes"++ The original message to be replaced. --- `originalAttestation` ++"bytes"++ Attestation verifying the original message. --- `newMessageBody` ++"bytes"++ The new content for the replaced message. --- `newDestinationCaller` ++"bytes32"++ The new destination caller, which may be the same as the original destination caller, a new destination caller, or an empty destination caller (`bytes32(0)`), indicating that any destination caller is valid. ??? interface "Returns" None. ??? interface "Emits" `MessageSent` - event emitted when a new message is dispatched ??? child "Event arguments" `message` ++"bytes"++ The raw bytes of the message. ### Token Minter Contract The Token Minter contract manages the minting and burning of tokens across different blockchain domains. It maintains a registry that links local tokens to their corresponding remote tokens, ensuring that tokens maintain a 1:1 exchange rate across domains. The contract restricts minting and burning functions to a designated Token Messenger, which ensures secure and reliable cross-chain operations. When tokens are burned on a remote domain, an equivalent amount is minted on the local domain for a specified recipient, and vice versa. To enhance control and flexibility, the contract includes mechanisms to pause operations, set burn limits, and update the Token Controller, which governs token minting permissions. Additionally, it provides functionality to add or remove the local Token Messenger and retrieve the local token address associated with a remote token. ??? code "Token Minter contract" ```solidity /* * Copyright (c) 2022, Circle Internet Financial Limited. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ pragma solidity 0.7.6; import "./interfaces/ITokenMinter.sol"; import "./interfaces/IMintBurnToken.sol"; import "./roles/Pausable.sol"; import "./roles/Rescuable.sol"; import "./roles/TokenController.sol"; import "./TokenMessenger.sol"; /** * @title TokenMinter * @notice Token Minter and Burner * @dev Maintains registry of local mintable tokens and corresponding tokens on remote domains. * This registry can be used by caller to determine which token on local domain to mint for a * burned token on a remote domain, and vice versa. * It is assumed that local and remote tokens are fungible at a constant 1:1 exchange rate. */ contract TokenMinter is ITokenMinter, TokenController, Pausable, Rescuable { // ============ Events ============ /** * @notice Emitted when a local TokenMessenger is added * @param localTokenMessenger address of local TokenMessenger * @notice Emitted when a local TokenMessenger is added */ event LocalTokenMessengerAdded(address localTokenMessenger); /** * @notice Emitted when a local TokenMessenger is removed * @param localTokenMessenger address of local TokenMessenger * @notice Emitted when a local TokenMessenger is removed */ event LocalTokenMessengerRemoved(address localTokenMessenger); // ============ State Variables ============ // Local TokenMessenger with permission to call mint and burn on this TokenMinter address public localTokenMessenger; // ============ Modifiers ============ /** * @notice Only accept messages from the registered message transmitter on local domain */ modifier onlyLocalTokenMessenger() { require(_isLocalTokenMessenger(), "Caller not local TokenMessenger"); _; } // ============ Constructor ============ /** * @param _tokenController Token controller address */ constructor(address _tokenController) { _setTokenController(_tokenController); } // ============ External Functions ============ /** * @notice Mints `amount` of local tokens corresponding to the * given (`sourceDomain`, `burnToken`) pair, to `to` address. * @dev reverts if the (`sourceDomain`, `burnToken`) pair does not * map to a nonzero local token address. This mapping can be queried using * getLocalToken(). * @param sourceDomain Source domain where `burnToken` was burned. * @param burnToken Burned token address as bytes32. * @param to Address to receive minted tokens, corresponding to `burnToken`, * on this domain. * @param amount Amount of tokens to mint. Must be less than or equal * to the minterAllowance of this TokenMinter for given `_mintToken`. * @return mintToken token minted. */ function mint( uint32 sourceDomain, bytes32 burnToken, address to, uint256 amount ) external override whenNotPaused onlyLocalTokenMessenger returns (address mintToken) { address _mintToken = _getLocalToken(sourceDomain, burnToken); require(_mintToken != address(0), "Mint token not supported"); IMintBurnToken _token = IMintBurnToken(_mintToken); require(_token.mint(to, amount), "Mint operation failed"); return _mintToken; } /** * @notice Burn tokens owned by this TokenMinter. * @param burnToken burnable token address. * @param burnAmount amount of tokens to burn. Must be * > 0, and <= maximum burn amount per message. */ function burn(address burnToken, uint256 burnAmount) external override whenNotPaused onlyLocalTokenMessenger onlyWithinBurnLimit(burnToken, burnAmount) { IMintBurnToken _token = IMintBurnToken(burnToken); _token.burn(burnAmount); } /** * @notice Add TokenMessenger for the local domain. Only this TokenMessenger * has permission to call mint() and burn() on this TokenMinter. * @dev Reverts if a TokenMessenger is already set for the local domain. * @param newLocalTokenMessenger The address of the new TokenMessenger on the local domain. */ function addLocalTokenMessenger(address newLocalTokenMessenger) external onlyOwner { require( newLocalTokenMessenger != address(0), "Invalid TokenMessenger address" ); require( localTokenMessenger == address(0), "Local TokenMessenger already set" ); localTokenMessenger = newLocalTokenMessenger; emit LocalTokenMessengerAdded(localTokenMessenger); } /** * @notice Remove the TokenMessenger for the local domain. * @dev Reverts if the TokenMessenger of the local domain is not set. */ function removeLocalTokenMessenger() external onlyOwner { address _localTokenMessengerBeforeRemoval = localTokenMessenger; require( _localTokenMessengerBeforeRemoval != address(0), "No local TokenMessenger is set" ); delete localTokenMessenger; emit LocalTokenMessengerRemoved(_localTokenMessengerBeforeRemoval); } /** * @notice Set tokenController to `newTokenController`, and * emit `SetTokenController` event. * @dev newTokenController must be nonzero. * @param newTokenController address of new token controller */ function setTokenController(address newTokenController) external override onlyOwner { _setTokenController(newTokenController); } /** * @notice Get the local token address associated with the given * remote domain and token. * @param remoteDomain Remote domain * @param remoteToken Remote token * @return local token address */ function getLocalToken(uint32 remoteDomain, bytes32 remoteToken) external view override returns (address) { return _getLocalToken(remoteDomain, remoteToken); } // ============ Internal Utils ============ /** * @notice Returns true if the message sender is the registered local TokenMessenger * @return True if the message sender is the registered local TokenMessenger */ function _isLocalTokenMessenger() internal view returns (bool) { return address(localTokenMessenger) != address(0) && msg.sender == address(localTokenMessenger); } } ``` This contract and the interfaces and contracts it relies on are stored in [Circle's `evm-cctp-contracts` repository](https://github.com/circlefin/evm-cctp-contracts/blob/master/src/TokenMinter.sol){target=\_blank} on GitHub. Most of the methods of the Token Minter contract can be called only by the registered Token Messenger. However, there is one publicly accessible method, a public view function that allows anyone to query the local token associated with a remote domain and token. - **`getLocalToken`** — a read-only function that returns the local token address associated with a given remote domain and token ??? interface "Parameters" `remoteDomain` ++"uint32"++ The remote blockchain domain where the token resides. --- `remoteToken` ++"bytes32"++ The address of the token on the remote domain. ??? interface "Returns" ++"address"++ The local token address. ## How to Interact with CCTP Contracts Before writing your own contracts, it's essential to understand the key functions and events of the Wormhole CCTP contracts. The primary functionality revolves around the following: - **Sending tokens with a message payload** - initiating a cross-chain transfer of Circle-supported assets along with a message payload to a specific target address on the target chain - **Receiving tokens with a message payload** - validating messages received from other chains via Wormhole and then minting the tokens for the recipient ### Sending Tokens and Messages To initiate a cross-chain transfer, you must call the `transferTokensWithPayload` method of Wormhole's Circle Integration (CCTP) contract. Once you have initiated a transfer, you must fetch the attested Wormhole message and parse the transaction logs to locate a transfer message emitted by the Circle Bridge contract. Then, a request must be sent to Circle's off-chain process with the transfer message to grab the attestation from the process's response, which validates the token mint on the target chain. To streamline this process, you can use the [Wormhole Solidity SDK](https://github.com/wormhole-foundation/wormhole-solidity-sdk/tree/main){target=\_blank}, which exposes the [`WormholeRelayerSDK.sol` contract](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayerSDK.sol){target=\_blank}, including the [`CCTPSender` abstract contract](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/CCTPBase.sol){target=\_blank}. By inheriting this contract, you can transfer USDC while automatically relaying the message payload to the destination chain via a Wormhole-deployed relayer. ??? code "CCTP Sender contract" ```solidity abstract contract CCTPSender is CCTPBase { uint8 internal constant CONSISTENCY_LEVEL_FINALIZED = 15; using CCTPMessageLib for *; mapping(uint16 => uint32) public chainIdToCCTPDomain; /** * Sets the CCTP Domain corresponding to chain 'chain' to be 'cctpDomain' * So that transfers of USDC to chain 'chain' use the target CCTP domain 'cctpDomain' * * This action can only be performed by 'cctpConfigurationOwner', who is set to be the deployer * * Currently, cctp domains are: * Ethereum: Wormhole chain id 2, cctp domain 0 * Avalanche: Wormhole chain id 6, cctp domain 1 * Optimism: Wormhole chain id 24, cctp domain 2 * Arbitrum: Wormhole chain id 23, cctp domain 3 * Base: Wormhole chain id 30, cctp domain 6 * * These can be set via: * setCCTPDomain(2, 0); * setCCTPDomain(6, 1); * setCCTPDomain(24, 2); * setCCTPDomain(23, 3); * setCCTPDomain(30, 6); */ function setCCTPDomain(uint16 chain, uint32 cctpDomain) public { require( msg.sender == cctpConfigurationOwner, "Not allowed to set CCTP Domain" ); chainIdToCCTPDomain[chain] = cctpDomain; } function getCCTPDomain(uint16 chain) internal view returns (uint32) { return chainIdToCCTPDomain[chain]; } /** * transferUSDC wraps common boilerplate for sending tokens to another chain using IWormholeRelayer * - approves the Circle TokenMessenger contract to spend 'amount' of USDC * - calls Circle's 'depositForBurnWithCaller' * - returns key for inclusion in WormholeRelayer `additionalVaas` argument * * Note: this requires that only the targetAddress can redeem transfers. * */ function transferUSDC( uint256 amount, uint16 targetChain, address targetAddress ) internal returns (MessageKey memory) { IERC20(USDC).approve(address(circleTokenMessenger), amount); bytes32 targetAddressBytes32 = addressToBytes32CCTP(targetAddress); uint64 nonce = circleTokenMessenger.depositForBurnWithCaller( amount, getCCTPDomain(targetChain), targetAddressBytes32, USDC, targetAddressBytes32 ); return MessageKey( CCTPMessageLib.CCTP_KEY_TYPE, abi.encodePacked(getCCTPDomain(wormhole.chainId()), nonce) ); } // Publishes a CCTP transfer of 'amount' of USDC // and requests a delivery of the transfer along with 'payload' to 'targetAddress' on 'targetChain' // // The second step is done by publishing a wormhole message representing a request // to call 'receiveWormholeMessages' on the address 'targetAddress' on chain 'targetChain' // with the payload 'abi.encode(amount, payload)' // (and we encode the amount so it can be checked on the target chain) function sendUSDCWithPayloadToEvm( uint16 targetChain, address targetAddress, bytes memory payload, uint256 receiverValue, uint256 gasLimit, uint256 amount ) internal returns (uint64 sequence) { MessageKey[] memory messageKeys = new MessageKey[](1); messageKeys[0] = transferUSDC(amount, targetChain, targetAddress); bytes memory userPayload = abi.encode(amount, payload); address defaultDeliveryProvider = wormholeRelayer .getDefaultDeliveryProvider(); (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( targetChain, receiverValue, gasLimit ); sequence = wormholeRelayer.sendToEvm{value: cost}( targetChain, targetAddress, userPayload, receiverValue, 0, gasLimit, targetChain, address(0x0), defaultDeliveryProvider, messageKeys, CONSISTENCY_LEVEL_FINALIZED ); } function addressToBytes32CCTP(address addr) private pure returns (bytes32) { return bytes32(uint256(uint160(addr))); } } ``` The `CCTPSender` abstract contract exposes the `sendUSDCWithPayloadToEvm` function. This function publishes a CCTP transfer of the provided `amount` of USDC and requests that the transfer be delivered along with a `payload` to the specified `targetAddress` on the `targetChain`. ```solidity function sendUSDCWithPayloadToEvm( uint16 targetChain, address targetAddress, bytes memory payload, uint256 receiverValue, uint256 gasLimit, uint256 amount ) internal returns (uint64 sequence) ``` ??? interface "Parameters" `targetChain` ++"uint16"++ The target chain for the transfer. --- `targetAddress` ++"address"++ The target address for the transfer. --- `payload` ++"bytes"++ Arbitrary payload to be delivered to the target chain via Wormhole. --- `gasLimit` ++"uint256"++ The gas limit with which to call `targetAddress`. --- `amount` ++"uint256"++ The amount of USDC to transfer. --- ??? interface "Returns" `sequence` ++"uint64"++ Sequence number of the published VAA containing the delivery instructions. When the `sendUSDCWithPayloadToEvm` function is called, the following series of actions are executed: 1. **USDC transfer initiation**: - The Circle Token Messenger contract is approved to spend the specified amount of USDC. - The `depositForBurnWithCaller` function of the Token Messenger contract is invoked - A key is returned, which is to be provided to the Wormhole relayer for message delivery 2. **Message encoding** - the message `payload` is encoded for transmission via the Wormhole relayer. The encoded value also includes the `amount` so that it can be checked on the target chain 3. **Retrieving delivery provider** - the current default delivery provider's address is retrieved 4. **Cost calculation** - the transfer cost is calculated using the Wormhole relayer's `quoteEVMDeliveryPrice` function 5. **Message dispatch**: - The `sendToEvm` function of the Wormhole relayer is called with the encoded payload, the delivery provider's address, and the arguments passed to `sendUSDCWithPayloadToEvm` - The function must be called with `msg.value` set to the previously calculated cost (from step 4) - This function publishes an instruction for the delivery provider to relay the payload and VAAs specified by the key (from step 1) to the target address on the target chain A simple example implementation is as follows: ```solidity function sendCrossChainDeposit( uint16 targetChain, address targetAddress, address recipient, uint256 amount, uint256, gasLimit ) public payable { uint256 cost = quoteCrossChainDeposit(targetChain); require( msg.value == cost, "msg.value must be quoteCrossChainDeposit(targetChain)" ); IERC20(USDC).transferFrom(msg.sender, address(this), amount); bytes memory payload = abi.encode(recipient); sendUSDCWithPayloadToEvm( targetChain, targetAddress, // address (on targetChain) to send token and payload to payload, 0, // receiver value gasLimit, amount ); } ``` The above example sends a specified amount of USDC and the recipient's address as a payload to a target contract on another chain, ensuring that the correct cost is provided for the cross-chain transfer. ### Receiving Tokens and Messages To complete the cross-chain transfer, you must invoke the `redeemTokensWithPayload` function on the target Wormhole Circle Integration contract. This function verifies the message's authenticity, decodes the payload, confirms the recipient and sender, checks message delivery, and then calls the `receiveMessage` function of the [Message Transmitter](#message-transmitter-contract) contract. Using the Wormhole-deployed relayer automatically triggers the `receiveWormholeMessages` function. This function is defined in the [`WormholeRelayerSDK.sol` contract](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayerSDK.sol){target=\_blank} from the [Wormhole Solidity SDK](https://github.com/wormhole-foundation/wormhole-solidity-sdk/tree/main){target=\_blank} and is implemented within the [`CCTPReceiver` abstract contract](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/CCTPBase.sol){target=\_blank}. ??? code "CCTP Receiver contract" ```solidity abstract contract CCTPReceiver is CCTPBase { function redeemUSDC( bytes memory cctpMessage ) internal returns (uint256 amount) { (bytes memory message, bytes memory signature) = abi.decode( cctpMessage, (bytes, bytes) ); uint256 beforeBalance = IERC20(USDC).balanceOf(address(this)); circleMessageTransmitter.receiveMessage(message, signature); return IERC20(USDC).balanceOf(address(this)) - beforeBalance; } function receiveWormholeMessages( bytes memory payload, bytes[] memory additionalMessages, bytes32 sourceAddress, uint16 sourceChain, bytes32 deliveryHash ) external payable { // Currently, 'sendUSDCWithPayloadToEVM' only sends one CCTP transfer // That can be modified if the integrator desires to send multiple CCTP transfers // in which case the following code would have to be modified to support // redeeming these multiple transfers and checking that their 'amount's are accurate require( additionalMessages.length <= 1, "CCTP: At most one Message is supported" ); uint256 amountUSDCReceived; if (additionalMessages.length == 1) amountUSDCReceived = redeemUSDC(additionalMessages[0]); (uint256 amount, bytes memory userPayload) = abi.decode( payload, (uint256, bytes) ); // Check that the correct amount was received // It is important to verify that the 'USDC' sent in by the relayer is the same amount // that the sender sent in on the source chain require(amount == amountUSDCReceived, "Wrong amount received"); receivePayloadAndUSDC( userPayload, amountUSDCReceived, sourceAddress, sourceChain, deliveryHash ); } // Implement this function to handle in-bound deliveries that include a CCTP transfer function receivePayloadAndUSDC( bytes memory payload, uint256 amountUSDCReceived, bytes32 sourceAddress, uint16 sourceChain, bytes32 deliveryHash ) internal virtual {} } ``` Although you do not need to interact with the `receiveWormholeMessages` function directly, it's important to understand what it does. This function processes cross-chain messages and USDC transfers via Wormhole's Circle (CCTP) Bridge. Here's a summary of what it does: 1. **Validate additional messages** - the function checks that there is at most one CCTP transfer message in the `additionalMessages` array, as it currently only supports processing a single CCTP transfer 2. **Redeem USDC**: - If there is a CCTP message, it calls the `redeemUSDC` function of the `CCTPReceiver` contract to decode and redeem the USDC - This results in the call of the `receiveMessage` function of Circle's Message Transmitter contract to redeem the USDC based on the provided message and signature - The amount of USDC received is calculated by subtracting the contract's previous balance from the current balance after redeeming the USDC 3. **Decode payload** - the incoming payload is decoded, extracting both the expected amount of USDC and a `userPayload` (which could be any additional data) 4. **Verify the amount** - it ensures that the amount of USDC received matches the amount encoded in the payload. If the amounts don't match, the transaction is reverted 5. **Handle the payload and USDC** - after verifying the amounts, `receivePayloadAndUSDC` is called, which is meant to handle the actual logic for processing the received payload and USDC transfer You'll need to implement the `receivePayloadAndUSDC` function to transfer the USDC and handle the payload as your application needs. A simple example implementation is as follows: ```solidity function receivePayloadAndUSDC( bytes memory payload, uint256 amountUSDCReceived, bytes32, // sourceAddress uint16, // sourceChain bytes32 // deliveryHash ) internal override onlyWormholeRelayer { address recipient = abi.decode(payload, (address)); IERC20(USDC).transfer(recipient, amountUSDCReceived); } ``` ## Complete Example To view a complete example of creating a contract that integrates with Wormhole's CCTP contracts to send and receive USDC cross-chain, check out the [Hello USDC](https://github.com/wormhole-foundation/hello-usdc){target=\_blank} repository on GitHub. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/connect/configuration-v0/ --- BEGIN CONTENT --- --- title: Configure Your Connect Widget v0 description: Configure Wormhole Connect v0 for React or HTML, set themes, define tokens, networks, and customize RPC endpoints for optimized blockchain interactions. --- # Configure Your Connect Widget ## Introduction {: #introduction } Wormhole Connect is a flexible React widget that streamlines cross-chain asset transfers and enables seamless interoperability by leveraging Wormhole's powerful infrastructure. Designed for easy integration into decentralized applications (dApps), Wormhole Connect abstracts the complexities of cross-chain communication, providing a user-friendly experience for both developers and end users. This guide provides detailed instructions on configuring Wormhole Connect and highlights the many ways it can be customized to fit your specific needs, from integrating supported blockchains and tokens to tailoring the user interface. !!! note For documentation on the latest version of Connect, please refer to the current [configuration documentation](/docs/build/transfers/connect/configuration/){target=\_blank}. If you are looking to upgrade from Wormhole Connect v0 to v1, please refer to the [migration guide](/docs/build/transfers/connect/upgrade/){target=\_blank} for detailed instructions. ## Get Started Configure the Wormhole Connect React component by passing a `WormholeConnectConfig` object as the `config` attribute. If using the hosted version, provide `config` and `theme` as JSON-serialized strings on the mount point. === "React" ```ts import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { networks: ['ethereum', 'polygon', 'solana'], tokens: ['ETH', 'WETH', 'MATIC', 'WMATIC'], rpcs: { ethereum: 'https://rpc.ankr.com/eth', solana: 'https://rpc.ankr.com/solana', } } ``` === "HTML Tags" ```html
``` ## Examples {: #examples } Below are some examples of different ways you can configure Connect. See `WormholeConnectConfig` in the below file for a full view of the supported configuration parameters. ??? code "View `WormholeConnectConfig`" ```ts import { ChainName, WormholeContext, WormholeConfig, ChainResourceMap, } from 'sdklegacy'; import MAINNET from './mainnet'; import TESTNET from './testnet'; import DEVNET from './devnet'; import type { WormholeConnectConfig } from './types'; import { Network, InternalConfig, Route, WrappedTokenAddressCache, } from './types'; import { mergeCustomTokensConfig, mergeNttGroups, validateDefaults, } from './utils'; import { wrapEventHandler } from './events'; import { SDKConverter } from './converter'; import { wormhole as getWormholeV2, Wormhole as WormholeV2, Network as NetworkV2, Token as TokenV2, Chain as ChainV2, ChainTokens as ChainTokensV2, WormholeConfigOverrides as WormholeConfigOverridesV2, } from '@wormhole-foundation/sdk'; import '@wormhole-foundation/sdk/addresses'; import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import aptos from '@wormhole-foundation/sdk/aptos'; import sui from '@wormhole-foundation/sdk/sui'; import cosmwasm from '@wormhole-foundation/sdk/cosmwasm'; import algorand from '@wormhole-foundation/sdk/algorand'; export function buildConfig( customConfig?: WormholeConnectConfig ): InternalConfig { const network = ( customConfig?.network || customConfig?.env || // TODO remove; deprecated import.meta.env.REACT_APP_CONNECT_ENV?.toLowerCase() || 'mainnet' ).toLowerCase() as Network; if (!['mainnet', 'testnet', 'devnet'].includes(network)) throw new Error(`Invalid env "${network}"`); const networkData = { MAINNET, DEVNET, TESTNET }[network.toUpperCase()]!; const tokens = mergeCustomTokensConfig( networkData.tokens, customConfig?.tokensConfig ); const sdkConfig = WormholeContext.getConfig(network); const rpcs = Object.assign( {}, sdkConfig.rpcs, networkData.rpcs, customConfig?.rpcs ); const wh = getWormholeContext(network, sdkConfig, rpcs); if (customConfig?.bridgeDefaults) { validateDefaults(customConfig.bridgeDefaults, networkData.chains, tokens); } const sdkConverter = new SDKConverter(wh); return { wh, sdkConfig, sdkConverter, v2Network: sdkConverter.toNetworkV2(network), network, isMainnet: network === 'mainnet', // External resources rpcs, rest: Object.assign( {}, sdkConfig.rest, networkData.rest, customConfig?.rest ), graphql: Object.assign({}, networkData.graphql, customConfig?.graphql), wormholeApi: { mainnet: 'https://api.wormholescan.io/', testnet: 'https://api.testnet.wormholescan.io/', devnet: '', }[network], wormholeRpcHosts: { mainnet: [ 'https://wormhole-v2-mainnet-api.mcf.rocks', 'https://wormhole-v2-mainnet-api.chainlayer.network', 'https://wormhole-v2-mainnet-api.staking.fund', ], testnet: [ 'https://guardian.testnet.xlabs.xyz', 'https://guardian-01.testnet.xlabs.xyz', 'https://guardian-02.testnet.xlabs.xyz', ], devnet: ['http://localhost:7071'], }[network], coinGeckoApiKey: customConfig?.coinGeckoApiKey, // Callbacks triggerEvent: wrapEventHandler(customConfig?.eventHandler), validateTransfer: customConfig?.validateTransferHandler, // White lists chains: networkData.chains, chainsArr: Object.values(networkData.chains).filter((chain) => { return customConfig?.networks ? customConfig.networks!.includes(chain.key) : true; }), tokens, tokensArr: Object.values(tokens).filter((token) => { return customConfig?.tokens ? customConfig.tokens!.includes(token.key) : true; }), // For token bridge ^_^ wrappedTokenAddressCache: new WrappedTokenAddressCache( tokens, sdkConverter ), gasEstimates: networkData.gasEstimates, // TODO: routes that aren't supported yet are disabled routes: (customConfig?.routes ?? Object.values(Route)).filter((r) => [ Route.Bridge, Route.Relay, Route.NttManual, Route.NttRelay, Route.CCTPManual, Route.CCTPRelay, ].includes(r as Route) ), // UI details cta: customConfig?.cta, explorer: customConfig?.explorer, attestUrl: { mainnet: 'https://portalbridge.com/advanced-tools/#/register', devnet: '', testnet: 'https://wormhole-foundation.github.io/example-token-bridge-ui/#/register', }[network], bridgeDefaults: customConfig?.bridgeDefaults, cctpWarning: customConfig?.cctpWarning?.href || '', pageHeader: customConfig?.pageHeader, pageSubHeader: customConfig?.pageSubHeader, menu: customConfig?.menu ?? [], searchTx: customConfig?.searchTx, moreTokens: customConfig?.moreTokens, moreNetworks: customConfig?.moreNetworks, partnerLogo: customConfig?.partnerLogo, walletConnectProjectId: customConfig?.walletConnectProjectId ?? import.meta.env.REACT_APP_WALLET_CONNECT_PROJECT_ID, showHamburgerMenu: customConfig?.showHamburgerMenu ?? false, previewMode: !!customConfig?.previewMode, // Route options ethBridgeMaxAmount: customConfig?.ethBridgeMaxAmount ?? 5, wstETHBridgeMaxAmount: customConfig?.wstETHBridgeMaxAmount ?? 5, // NTT config nttGroups: mergeNttGroups( tokens, networkData.nttGroups, customConfig?.nttGroups ), // Guardian set guardianSet: networkData.guardianSet, // Render redesign views useRedesign: customConfig?.useRedesign, }; } // Running buildConfig with no argument generates the default configuration const config = buildConfig(); export default config; // TODO SDKV2: REMOVE export function getWormholeContext( network: Network, sdkConfig: WormholeConfig, rpcs: ChainResourceMap ): WormholeContext { const wh: WormholeContext = new WormholeContext(network, { ...sdkConfig, ...{ rpcs }, }); return wh; } export function getDefaultWormholeContext(network: Network): WormholeContext { const sdkConfig = WormholeContext.getConfig(network); const networkData = { mainnet: MAINNET, devnet: DEVNET, testnet: TESTNET }[ network ]!; const rpcs = Object.assign({}, sdkConfig.rpcs, networkData.rpcs); return getWormholeContext(network, sdkConfig, rpcs); } export async function getWormholeContextV2(): Promise> { if (config.v2Wormhole) return config.v2Wormhole; config.v2Wormhole = await newWormholeContextV2(); return config.v2Wormhole; } export async function newWormholeContextV2(): Promise> { const v2Config: WormholeConfigOverridesV2 = { chains: {} }; for (const key in config.chains) { const chainV1 = key as ChainName; const chainConfigV1 = config.chains[chainV1]!; const chainContextV1 = chainConfigV1.context; const chainV2 = config.sdkConverter.toChainV2( chainV1 as ChainName ) as ChainV2; const rpc = config.rpcs[chainV1]; const tokenMap: ChainTokensV2 = {}; for (const token of config.tokensArr) { const nativeChainV2 = config.sdkConverter.toChainV2(token.nativeChain); const tokenV2: Partial = { key: token.key, chain: chainV2, symbol: token.symbol, }; if (nativeChainV2 == chainV2) { const decimals = token.decimals[chainContextV1] ?? token.decimals.default; if (!decimals) { continue; } else { tokenV2.decimals = decimals; } const address = config.sdkConverter.getNativeTokenAddressV2(token); if (!address) throw new Error('Token must have address'); tokenV2.address = address; } else { tokenV2.original = nativeChainV2; if (token.foreignAssets) { const fa = token.foreignAssets[chainV1]!; if (!fa) { continue; } else { tokenV2.address = fa.address; tokenV2.decimals = fa.decimals; } } else { continue; } } tokenMap[token.key] = tokenV2 as TokenV2; } v2Config.chains![chainV2] = { rpc, tokenMap }; } return await getWormholeV2( config.v2Network, [evm, solana, aptos, cosmwasm, sui, algorand], v2Config ); } // setConfig can be called afterwards to override the default config with integrator-provided config export function setConfig(customConfig?: WormholeConnectConfig) { const newConfig: InternalConfig = buildConfig(customConfig); // We overwrite keys in the existing object so the references to the config // imported elsewhere point to the new values for (const key in newConfig) { /* @ts-ignore */ config[key] = newConfig[key]; } } // TODO: add config validation step to buildConfig //validateConfigs(); ``` ### Custom Networks and RPC Endpoints {: #custom-networks-and-rpc-endpoints } Specify supported networks, tokens, and custom RPC endpoints. Your users may encounter rate limits using public RPC endpoints if you don't provide your own. === "Mainnet" ```js import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { env: 'mainnet', networks: ['ethereum', 'polygon', 'solana'], tokens: ['ETH', 'WETH', 'MATIC', 'WMATIC'], rpcs: { ethereum: 'https://rpc.ankr.com/eth', solana: 'https://rpc.ankr.com/solana', }, }; function App() { return ; } ``` === "Testnet" ```js import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { env: 'testnet', networks: ['sepolia', 'arbitrum_sepolia', 'base_sepolia', 'fuji'], rpcs: { fuji: 'https://rpc.ankr.com/avalanche_fuji', base_sepolia: 'https://base-sepolia-rpc.publicnode.com', }, }; function App() { return ; } ``` !!! note For a complete list of testnet chain names that can be manually added, see the [Testnet Chains List](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/fa4ba4bc349a7caada809f209090d79a3c5962fe/tokenRegistry/src/scripts/importConnect.ts#L44-L55){target=\_blank}. ### Fully Customized Theme {: #fully-customized-theme } Wormhole Connect offers a high level of customizability that suits and integrates with your application's design, including various options for buttons, backgrounds, popovers, fonts, and more. The following example demonstrates a variety of appearance customizations. Remember, if you prefer a visual to aid in designing your widget, you can use the [no code style interface](https://connect-in-style.wormhole.com/){target=\_blank}. ```jsx import WormholeConnect, { WormholeConnectTheme, } from '@wormhole-foundation/wormhole-connect'; import red from '@mui/material/colors/red'; import lightblue from '@mui/material/colors/lightBlue'; import grey from '@mui/material/colors/grey'; import green from '@mui/material/colors/green'; import orange from '@mui/material/colors/orange'; const customTheme: WormholeConnectTheme = { mode: 'dark', primary: grey, secondary: grey, divider: 'rgba(255, 255, 255, 0.2)', background: { default: '#232323', }, text: { primary: '#ffffff', secondary: grey[500], }, error: red, info: lightblue, success: green, warning: orange, button: { primary: 'rgba(255, 255, 255, 0.2)', primaryText: '#ffffff', disabled: 'rgba(255, 255, 255, 0.1)', disabledText: 'rgba(255, 255, 255, 0.4)', action: orange[300], actionText: '#000000', hover: 'rgba(255, 255, 255, 0.7)', }, options: { hover: '#474747', select: '#5b5b5b', }, card: { background: '#333333', secondary: '#474747', elevation: 'none', }, popover: { background: '#1b2033', secondary: 'rgba(255, 255, 255, 0.5)', elevation: 'none', }, modal: { background: '#474747', }, font: { primary: 'Impact', header: 'Impact', }, }; export default function App() { return ; } ``` ### Environment {: #environment } You can configure Connect to be used in Testnet environments, too. You can toggle between Mainnet and Testnet environments by defining the `WormholeConnectConfig` as follows: === "Mainnet" ```ts const config: WormholeConnectConfig = { env: 'mainnet', }; ``` === "Testnet" ```ts const config: WormholeConnectConfig = { env: 'testnet', }; ``` ### Custom RPC Endpoint {: #custom-rpc-endpoint } You can define a custom RPC provider for your Connect widget to use. This can be especially helpful if you'd like to replace public endpoints with dedicated or private endpoints. ```ts const config: WormholeConnectConfig = { rpcs: { solana: 'https://rpc.ankr.com/solana/ee827255553bb0fa9e0aaeab27e988707e60ea06ae36be0658b778072e94979e', }, }; ``` ### Arbitrary Token {: #arbitrary-token } The following section shows how to add an arbitrary token to your deployment of Connect. !!! note You will need to [register](https://portalbridge.com/advanced-tools/#/register){target=\_blank} your token with the Token Bridge to get the contract addresses necessary for it to work with Connect. This example configuration limits Connect to the Solana and Ethereum networks and a handful of tokens, including `BSKT`, which isn't built in by default and provided under the `tokensConfig` key. See [`src/config/types.ts`](https://github.com/wormhole-foundation/wormhole-connect/blob/development/wormhole-connect/src/config/types.ts){target=\_blank} for the type definition of `TokensConfig`. ```json const config: WormholeConnectConfig = { networks: ['solana', 'ethereum'], tokens: ['ETH', 'WETH', 'MATIC', 'WMATIC', 'BSKT'], tokensConfig: { BSKT: { key: 'BSKT', symbol: 'BSKT', nativeChain: 'solana', tokenId: { chain: 'solana', address: '6gnCPhXtLnUD76HjQuSYPENLSZdG8RvDB1pTLM5aLSJA', }, coinGeckoId: 'basket', icon: 'https://assets.coingecko.com/coins/images/34661/standard/BSKT_Logo.png?1705636891', color: '#2894EE', decimals: { default: 5, }, }, }, }; ``` ## More Configuration Options {: #more-configuration-options } ### Whitelisting Tokens {: #whitelisting-tokens } By default, Connect will offer its complete built-in list of assets, but you can restrict the displayed assets by defining a subset of tokens under `tokens`. The default full list is as follows: | Mainnet | Testnet | |:--------------:|:----------------------------------:| | ETH | ETH, ETHsepolia | | WETH | WETH, WETHsepolia | | USDCeth | USDCeth | | WBTC | - | | USDT | - | | DAI | - | | BUSD | - | | MATIC | MATIC | | WMATIC | WMATIC | | USDCpolygon | - | | BNB | BNB | | WBNB | WBNB | | USDCbnb | - | | AVAX | AVAX | | WAVAX | WAVAX | | USDCavax | USDCavax | | FTM | FTM | | WFTM | WFTM | | CELO | CELO | | GLMR | GLMR | | WGLMR | WGLMR | | SOL | WSOL | | PYTH | - | | SUI | SUI | | USDCsol | - | | APT | APT | | ETHarbitrum | ETHarbitrum, ETHarbitrum_sepolia | | WETHarbitrum | WETHarbitrum, WETHarbitrum_sepolia | | USDCarbitrum | USDCarbitrum | | ETHoptimism | ETHoptimism, ETHoptimism_sepolia | | WETHoptimism | WETHoptimism, WETHoptimism_sepolia | | USDCoptimism | USDCoptimism | | ETHbase | ETHbase, ETHbase_sepolia | | WETHbase | WETHbase, WETHbase_sepolia | | tBTC | tBTC | | tBTCpolygon | tBTCpolygon | | tBTCoptimism | tBTCoptimism | | tBTCarbitrum | tBTCarbitrum | | tBTCbase | tBTCbase | | tBTCsol | tBTCsol | | WETHpolygon | - | | WETHbsc | - | | wstETH | wstETH | | wstETHarbitrum | - | | wstETHoptimism | - | | wstETHpolygon | - | | wstETHbase | - | ### Routes {: #routes } By default, Connect will offer its complete built-in list of routes, but you can restrict the possible route assets by defining a subset under `routes.` By default, Connect will offer its complete built-in list: | Mainnet | Testnet | |:------------:|:----------:| | bridge | bridge | | relay | relay | | cctpManual | cctpManual | | cctpRelay | cctpRelay | | nttManual | nttManual | | nttRelay | nttRelay | | ethBridge | - | | wstETHBridge | - | | usdtBridge | - | | tBTC | tBTC | ### Wallet Set Up {: #wallet-connect-project-id } When using Wormhole Connect, your selected blockchain network determines the available wallet options. - For EVM chains, wallets like MetaMask and WalletConnect are supported - For Solana, you'll see options such as Phantom, Torus, and Coin98 The wallet options automatically adjust based on the selected chain, providing a seamless user experience without additional configuration. If you would like to offer WalletConnect as a supported wallet option, you'll need to obtain a project ID on the [WalletConnect cloud dashboard](https://cloud.walletconnect.com/){target=\_blank}. ### Toggle Hamburger Menu {: #toggle-hamburger-menu } By setting the `showHamburgerMenu` option to **false**, you can deactivate the hamburger menu, causing the links to be positioned at the bottom. #### Add Extra Menu Entry {: #add-extra-menu-entry } By setting the `showHamburgerMenu` option to `false,` you can add extra links. The following properties are accessed through the `menu[]` property (e.g., `menu[].label`): | Property | Description | |:--------:|:-------------------------------------------:| | `label` | Link name to show up | | `href` | Target URL or URN | | `target` | Anchor standard target, by default `_blank` | | `order` | Order where the new item should be injected | #### Sample Configuration {: #sample-configuration } ```json { "showHamburgerMenu": false, "menu": [ { "label": "Advance Tools", "href": "https://portalbridge.com", "target": "_self", "order": 1 } ] } ``` ### CoinGecko API Key {: #coingecko-api-key } The CoinGecko API can be used to fetch token price data. If you have a [CoinGecko API Plan](https://apiguide.coingecko.com/getting-started/getting-started){target=\_blank}, you can include the API key in the configuration. Remember to always take steps to protect your sensitive API keys, such as defining them in `.env` files and including such files in your `.gitignore`. ### More Networks {: #more-networks } Specify a set of extra networks to be displayed on the network selection modal, each linking to a different page, dApp, or mobile app the user will be redirected to. The following properties are accessed through the `moreNetworks` property (e.g., `moreNetworks.href`): |
Property
| Description | |:--------------------------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------:| | `href` | **Required**. Default value for missing network hrefs | | `target` | Default value for missing network link targets. Defaults to `_self` | | `description` | Brief description that should be displayed as a tooltip when the user hovers over a more network icon. Used as default for missing network descriptions | | `networks[].icon` | **Required**. URL data encoded icon to display | | `networks[].href` | Network href to redirect to. If present, the values `sourceChain` and `targetChain` are replaced with the currently selected chains before redirecting | | `networks[].label` | **Required**. Display text | | `networks[].name` | Unique network key. Defaults to a snake_case version of the label | | `networks[].description` | Description value. Defaults to `moreNetworks.description` | | `networks[].target` | href target value. Defaults to `moreNetworks.target` | | `networks[].showOpenInNewIcon` | Disable top right open in new icon. Defaults to **true** if target is `_blank` or **false** if target is `_self` | ??? code "View full configuration" ```json { ... "moreNetworks": { "href": "https://example.com", "target": "_blank", "description": "brief description that should be displayed as tooltip when the user hovers over a more network icon", "networks": [ { "icon": "https://assets.coingecko.com/coins/images/34661/standard/BSKT_Logo.png?1705636891", "name": "more", "label": "More networks", "href": "https://portalbridge.com/#/transfer", "showOpenInNewIcon": false } ] } ... } ``` ### More Tokens {: #more-tokens } Show a particular entry on the select tokens modal, redirecting the user to a different page, dApp, or mobile app. The following properties are accessed through the `moreTokens` property (e.g., `moreTokens.label`): | Property | Description | |:--------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------:| | `label` | **Required**. Display text | | `href` | **Required**. URL to redirect to. If present, the values `sourceChain` and `targetChain` are replaced with the currently selected chains before redirecting | | `target` | href target. Defaults to `_self` | ### Explorer {: #explorer } Enable the explorer button to allow users to search for their transactions on a given explorer, filtering by their wallet address. The following properties are accessed through the `explorer` property (e.g., `explorer.label`): | Property | Description | |:--------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| | `label` | Display text. Defaults to `Transactions` | | `href` | **Required**. URL of the explorer, for instance [https://wormholescan.io/](https://wormholescan.io/){target=\_blank}. If present, the value `address` is replaced with the connected wallet address | | `target` | `href` target. Defaults to `_blank` | --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/connect/configuration/configure-data/ --- BEGIN CONTENT --- --- title: Connect Data Configuration description: Configure Wormhole Connect v1 (latest) with custom chains, tokens, routes, and more for enhanced blockchain interoperability. categories: Connect, Transfer --- ## Data Configuration This page explains how to configure Wormhole Connect's core functionality, from choosing supported chains and tokens to bridging routes to setting up wallets and enabling price lookups. By the end, you'll know how to specify custom networks and RPC endpoints, integrate different bridging protocols, add new tokens, and more. ## Get Started Configure Wormhole Connect by passing a `WormholeConnectConfig` object as the `config` prop. === "React integration" ```ts import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { chains: ['Ethereum', 'Polygon', 'Solana'], tokens: ['ETH', 'WETH', 'MATIC', 'WMATIC'], rpcs: { Ethereum: 'https://rpc.ankr.com/eth', Solana: 'https://rpc.ankr.com/solana', } } ``` === "Hosted integration" ```ts import WormholeConnect, { wormholeConnectHosted, WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { chains: ['Ethereum', 'Polygon', 'Solana'], tokens: ['ETH', 'WETH', 'MATIC', 'WMATIC'], rpcs: { Ethereum: 'https://rpc.ankr.com/eth', Solana: 'https://rpc.ankr.com/solana', }, }; const container = document.getElementById('bridge-container'); wormholeConnectHosted(container, { config, }); ``` !!! note The complete type definition of `WormholeConnectConfig` is available in the [Wormhole Connect repository](https://github.com/wormhole-foundation/wormhole-connect/blob/development/wormhole-connect/src/config/types.ts){target=\_blank}. ## Examples {: #examples } ### Configuring Chains and RPC Endpoints {: #chains-and-rpc-endpoints } Connect lets you customize the available chains to match your project's needs. You should provide your own RPC endpoints, as the default public ones may not support essential functions like balance fetching. === "Mainnet" ```js import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { chains: ['Ethereum', 'Polygon', 'Solana'], rpcs: { Ethereum: 'https://rpc.ankr.com/eth', Solana: 'https://rpc.ankr.com/solana', }, }; function App() { return ; } ``` === "Testnet" ```js import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { // You can use Connect with testnet chains by specifying "network": network: 'Testnet', chains: ['Sepolia', 'ArbitrumSepolia', 'BaseSepolia', 'Avalanche'], rpcs: { Avalanche: 'https://rpc.ankr.com/avalanche_fuji', BaseSepolia: 'https://base-sepolia-rpc.publicnode.com', }, }; function App() { return ; } ``` !!! note For a complete list of available chain names, see the [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/base/src/constants/chains.ts){target=\_blank}. ### Configuring Routes By default, Connect offers two bridging protocols: Token Bridge (for Wormhole-wrapped tokens) and Circle's CCTP (for native USDC). For most use cases, integrators require more than these default routes. The `routes` property allows you to specify which protocols to include and exclude any routes unnecessary for your application, including default and third-party routes. #### Available Route Plugins The `@wormhole-foundation/wormhole-connect` package offers a variety of `route` plugins to give you flexibility in handling different protocols. You can choose from the following `route` exports for your integration: - [**`TokenBridgeRoute`**](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/connect/src/routes/tokenBridge/manual.ts){target=\_blank} - manually redeemed Wormhole Token Bridge route - [**`AutomaticTokenBridgeRoute`**](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/connect/src/routes/tokenBridge/automatic.ts){target=\_blank} - automatically redeemed (relayed) Token Bridge route - [**`CCTPRoute`**](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/connect/src/routes/cctp/manual.ts){target=\_blank} - manually redeemed CCTP route - [**`AutomaticCCTPRoute`**](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/connect/src/routes/cctp/automatic.ts){target=\_blank} - automatically redeemed (relayed) CCTP route - **`DEFAULT_ROUTES`** - array containing the four preceding routes (`TokenBridgeRoute`, `AutomaticTokenBridgeRoute`, `CCTPRoute`, `AutomaticCCTPRoute`) - [**`nttAutomaticRoute(nttConfig)`**](https://github.com/wormhole-foundation/native-token-transfers/blob/main/sdk/route/src/automatic.ts){target=\_blank} - function that returns the automatically-redeemed (relayed) Native Token Transfer (NTT) route - [**`nttManualRoute(nttConfig)`**](https://github.com/wormhole-foundation/native-token-transfers/blob/main/sdk/route/src/manual.ts){target=\_blank}- function that returns the manually-redeemed NTT route - **`nttRoutes(nttConfig)`** - function that returns both NTT routes as an array - [**`MayanRoute`**](https://github.com/mayan-finance/wormhole-sdk-route/blob/main/src/index.ts#L57){target=\_blank} - route that offers multiple Mayan protocols - [**`MayanRouteSWIFT`**](https://github.com/mayan-finance/wormhole-sdk-route/blob/main/src/index.ts#L528){target=\_blank} - route for Mayan's Swift protocol only - [**`MayanRouteMCTP`**](https://github.com/mayan-finance/wormhole-sdk-route/blob/main/src/index.ts#L539){target=\_blank} - route for Mayan's MCTP protocol only - [**`MayanRouteWH`**](https://github.com/mayan-finance/wormhole-sdk-route/blob/main/src/index.ts#L550){target=\_blank} - route for Mayan's original Wormhole transfer protocol In addition to these routes, developers can create custom routes for their Wormhole-based protocols. For examples, refer to the [NTT](https://github.com/wormhole-foundation/native-token-transfers/tree/main/sdk/route){target=\_blank} and the [Mayan](https://github.com/mayan-finance/wormhole-sdk-route){target=\_blank} example GitHub repositories. For further details on the `route` plugin interface, refer to the [Wormhole TypeScript SDK route code](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/connect/src/routes/route.ts){target=\_blank}. #### Example: Offer Only CCTP Transfers To configure Wormhole Connect to offer only USDC transfers via the CCTP route, use the following configuration: ```typescript import WormholeConnect, { AutomaticCCTPRoute, WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { routes: [AutomaticCCTPRoute], }; ; ``` #### Example: Offer All Default Routes and Third-Party Plugins In this example, Wormhole Connect is configured with routes for both default protocols (Token Bridge and CCTP), as well as third-party protocols like [Native Token Transfers (NTT)](/docs/build/transfers/native-token-transfers/){target=\_blank} and [Mayan Swap](https://swap.mayan.finance/){target=\_blank}. ```typescript import WormholeConnect, { DEFAULT_ROUTES, nttRoutes, MayanRouteSWIFT, WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; import { myNttConfig } from './consts'; // Custom NTT configuration const config: WormholeConnectConfig = { routes: [...DEFAULT_ROUTES, ...nttRoutes(myNttConfig), MayanRouteSWIFT], }; ; ``` This flexible plugin allows you to combine default routes (such as Token Bridge and CCTP) with third-party protocols, offering complete control over which routes are available in your application. ### Adding Custom Tokens {: #custom-tokens } The following section shows how to add an arbitrary token to your deployment of Connect. !!! note You will need to [register](https://portalbridge.com/advanced-tools/#/register){target=\_blank} your token with the Token Bridge to get the contract addresses necessary for it to work with that protocol. This example configuration adds the BONK token to Connect. Note the `wrappedTokens` property, which is required for use with the Token Bridge. See the [Connect source code](https://github.com/wormhole-foundation/wormhole-connect/blob/development/wormhole-connect/src/config/types.ts){target=\_blank} for the type definition of `TokensConfig`. ```typescript import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { tokensConfig: { BONK: { key: 'BONK', symbol: 'BONK', nativeChain: 'Ethereum', icon: Icon.ETH, tokenId: { chain: 'Ethereum', address: '0x1151CB3d861920e07a38e03eEAd12C32178567F6', }, coinGeckoId: 'bonk', decimals: 18, }, }, wrappedTokens: { BONK: { Solana: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', }, }, }; ``` ### Whitelisting Tokens {: #whitelisting-tokens } Connect offers a list of built-in tokens by default. You can see it below: - [Mainnet tokens](https://github.com/wormhole-foundation/wormhole-connect/blob/development/wormhole-connect/src/config/mainnet/tokens.ts){target=\_blank} - [Testnet tokens](https://github.com/wormhole-foundation/wormhole-connect/blob/development/wormhole-connect/src/config/testnet/tokens.ts){target=\_blank} You can customize the tokens shown in the UI using the `tokens` property. The following example adds a custom token and limits Connect to showing only that token, along with the native gas tokens ETH and SOL. ```jsx import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { chains: ['Ethereum', 'Solana'], tokens: ['ETH', 'SOL', 'BONK'], rpcs: { Ethereum: 'https://rpc.ankr.com/eth', Solana: 'https://rpc.ankr.com/solana', }, tokensConfig: { BONK: { key: 'BONK', symbol: 'BONK', icon: 'https://assets.coingecko.com/coins/images/28600/large/bonk.jpg?1696527587', tokenId: { chain: 'Ethereum', address: '0x1151CB3d861920e07a38e03eEAd12C32178567F6', }, decimals: 18, }, }, wrappedTokens: { BONK: { Solana: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', }, }, }; function App() { return ; } ``` You can whitelist tokens by symbol or by specifying tuples of [chain, address]. For example, this would show only BONK token (on all chains you've whitelisted) as well as [`EPjFW...TDt1v`](https://solscan.io/token/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v){target=\_blank} on Solana, which is USDC. ```jsx import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { chains: ['Ethereum', 'Solana'], tokens: [ // Whitelist BONK on every whitelisted chain 'BONK', // Also whitelist USDC, specifically on Solana ['Solana', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'] ], ... }; function App() { return ; } ``` ### User-Inputted Tokens {: #user-inputted-tokens } As of version 2.0, Connect allows users to paste token addresses to bridge any token they want. As an integrator, you may want to disable this feature if you are deploying Connect for use only with a specific token(s). If you provide a token whitelist (see above), this is turned off automatically. However, you can also disable it explicitly like this: ```jsx import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { ui: { disableUserInputtedTokens: true } }; function App() { return ; } ``` Setting `ui.disableUserInputtedTokens` to `true` will disable the ability to paste in token addresses. ### Transaction Settings {: #transaction-settings } Landing transactions on Solana can require finely tuned priority fees when there is congestion. You can tweak how Connect determines these with `transactionSettings`. All of the parameters in this configuration are optional; you can provide any combination of them. ```jsx import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { transactionSettings: { Solana: { priorityFee: { // Number between 0-1, defaults to 0.9. Higher percentile yields higher fees. // For example, you can set percentile to 0.95 to make Connect compute the // 95th percentile priority fee amount based on recent transactions percentile: 0.95, // Any number, defaults to 1.0. The fee amount is multiplied by this number. // This can be used to further raise or lower the fees Connect is using. // For example, percentile=0.95 and percentileMultiple=1.1 would use // the 95th percentile fee, with a 10% increase percentileMultiple: 1.1, // Minimum fee you want to use in microlamports, regardless of recent transactions // Defaults to 1 min: 200_000, // Maximum fee you want to use in microlamports, regardless of recent transactions // Defaults to 100,000,000 max: 5_000_000, } } } }; function App() { return ; } ``` !!! note Connect can calculate fees more accurately if you are using a [Triton](https://triton.one){target=\_blank} RPC endpoint. ### Wallet Set Up {: #reown-cloud-project-id } Your selected blockchain network determines the available wallet options when using Wormhole Connect. - For EVM chains, wallets like MetaMask and Reown Cloud (formerly WalletConnect) are supported - For Solana, you'll see options such as Phantom, Torus, and Coin98 The wallet options automatically adjust based on the selected chain, providing a seamless user experience without additional configuration. If you would like to offer Reown Cloud (formerly WalletConnect) as a supported wallet option, you'll need to obtain a project ID on the [Reown Cloud dashboard](https://cloud.reown.com/){target=\_blank}. ### CoinGecko API Key {: #coingecko-api-key } The CoinGecko API can be used to fetch token price data. If you have a [CoinGecko API Plan](https://apiguide.coingecko.com/getting-started/getting-started){target=\_blank}, you can include the API key in the configuration. ```jsx import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { coinGeckoApiKey: 'INSERT_API_KEY', }; function App() { return ; } ``` --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/connect/configuration/configure-theme/ --- BEGIN CONTENT --- --- title: Connect Theme & UI Customization description: Learn how to style Wormhole Connect with custom color schemes, fonts, layouts, and menus for a streamlined user experience. categories: Connect, Transfer --- ## Theme & UI Customization This page focuses on how to style the Wormhole Connect widget, covering color schemes, fonts, layout changes (like toggling the hamburger menu), and adding extra menu entries. You'll learn how to customize Connect's look and feel to match your application's branding. ### Changing the Color Scheme You can customize Connect's color scheme by providing a `theme` prop. === "React integration" ```ts import WormholeConnect, { WormholeConnectConfig, WormholeConnectTheme, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { /* Your config... */ }; const theme: WormholeConnectTheme = { mode: 'dark', primary: '#78c4b6', font: 'Comic Sans; sans-serif', }; function App() { return ; } ``` === "Hosted integration" ```ts import WormholeConnect, { WormholeConnectConfig, WormholeConnectTheme, wormholeConnectHosted, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { /* Your config... */ }; const theme: WormholeConnectTheme = { mode: 'dark', primary: '#78c4b6', font: 'Comic Sans; sans-serif', }; const container = document.getElementById('bridge-container'); wormholeConnectHosted(container, { config, theme, }); ``` The `WormholeConnectTheme` type supports the following properties: |
Property
| Description | Example | |:--------------------------------------:|:---------------------------------------------------------------------:|:---------------------:| | `mode` | Dark mode or light mode. **Required** | `"dark"` or `"light"` | | `input` | Color used for input fields, dropdowns | `"#AABBCC"` | | `primary` | Primary color used for buttons | `"#AABBCC"` | | `secondary` | Secondary color used for some UI elements | `"#AABBCC"` | | `text` | Primary color used for text | `"#AABBCC"` | | `textSecondary` | Secondary color used for dimmer text | `"#AABBCC"` | | `error` | Color to display errors in, usually some shade of red | `"#AABBCC"` | | `success` | Color to display success messages in | `"#AABBCC"` | | `font` | Font used in the UI, can be custom font available in your application | `"Arial; sans-serif"` | ### Toggle Hamburger Menu {: #toggle-hamburger-menu } By setting the `showHamburgerMenu` option to **false**, you can deactivate the hamburger menu, which will position the links at the bottom. #### Add Extra Menu Entry {: #add-extra-menu-entry } By setting the `showHamburgerMenu` option to `false,` you can add extra links. The following properties are accessed through the `menu[]` property (e.g., `menu[].label`): | Property | Description | |:--------:|:-------------------------------------------:| | `label` | Link name to show up | | `href` | Target URL or URN | | `target` | Anchor standard target, by default `_blank` | | `order` | Order where the new item should be injected | ```jsx import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { ui: { showHamburgerMenu: false, menu: [ { label: 'Advance Tools', href: 'https://portalbridge.com', target: '_self', order: 1, }, ], }, }; function App() { return ; } ``` --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/connect/configuration/ --- BEGIN CONTENT --- --- title: Wormhole Connect description: Wormhole Connect is a React widget offering an easy-to-use interface to facilitate cross-chain asset transfers via Wormhole directly in a web application. categories: Connect, Transfer --- # Wormhole Connect ## Configure Connect Wormhole Connect is a flexible React widget that streamlines cross-chain asset transfers and enables seamless interoperability by leveraging Wormhole's powerful infrastructure. Designed for easy integration into decentralized applications (dApps), Wormhole Connect abstracts the complexities of cross-chain communication, providing a user-friendly experience for both developers and end users. This guide provides detailed instructions on configuring Wormhole Connect and highlights the many ways it can be customized to fit your specific needs, from integrating supported blockchains and tokens to tailoring the user interface. !!! note To upgrade from Wormhole Connect v0 to v1, please refer to the [migration guide](/docs/build/transfers/connect/upgrade/){target=\_blank} for instructions. If you're using an older version of Wormhole Connect (v0.x), please refer to the [v0.x configuration documentation](/docs/build/transfers/connect/configuration-v0/){target=\_blank}.
- :octicons-database-16:{ .lg .middle } **Data** --- Learn how to configure the networks, tokens, and routes supported by Wormhole Connect. Set up RPC endpoints, whitelist tokens, and leverage multiple bridging protocols to meet your dApp's needs. [:custom-arrow: Get started](/docs/build/transfers/connect/configuration/configure-data/) - :octicons-apps-16:{ .lg .middle } **Theme** --- Discover how to style the Wormhole Connect widget to align with your brand. Customize colors, fonts, and UI elements to deliver a seamless user experience. [:custom-arrow: Explore routes](/docs/build/transfers/connect/configuration/configure-theme/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/connect/faqs/ --- BEGIN CONTENT --- --- title: Connect FAQs description: Common questions and detailed answers about using Wormhole Connect, including supported assets, chains, customization, and integration options. categories: Connect, Transfer --- # Wormhole Connect FAQs ## What types of assets does Connect support? Wormhole Connect supports both native and wrapped assets across all Wormhole-supported blockchains. This includes: - Major stablecoins like USDT and USDC (via CCTP) - Native gas tokens such as ETH, SOL, etc. - Cross-chain asset swaps through integrators like Mayan When bridging assets through the Wormhole Token Bridge, depending on the chain and token, assets may arrive as Wormhole-wrapped tokens on the destination chain. ## What chains does Connect support? Connect supports around 30 chains, spanning various blockchain runtimes: - EVM-based chains (Ethereum, Base, Arbitrum, BSC, etc.) - Solana - Move-based chains (Sui, Aptos) For a complete list of supported chains, see the [Connect-supported chains list](/docs/build/transfers/connect/features/){target=\_blank}. ## What is gas dropoff? Gas dropoff allows users to receive gas for transaction fees on the destination chain, eliminating the need to acquire the native gas token from a centralized exchange. The relayer automatically swaps part of the transferred assets into the native gas token, enabling seamless entry into new ecosystems. ## Can I customize Connect inside my application? Connect can be [fully customized](https://connect-in-style.wormhole.com/){target=\_blank} to choose the chains and assets you wish to support. You may also select different themes and colors to tailor Connect for your decentralized application. For details, see the [GitHub readme](https://github.com/wormhole-foundation/wormhole-connect){target=\_blank}. ## Which functions or events does Connect rely on for NTT integration? Connect relies on the NTT SDK for integration, with platform-specific implementations for Solana and EVM. The critical methods involved include initiate and redeem functions and rate capacity methods. These functions ensure Connect can handle token transfers and manage chain-rate limits. ## Do integrators need to enable wallets like Phantom or Backpack in Wormhole Connect? Integrators don’t need to explicitly enable wallets like Phantom or Backpack in Wormhole Connect. However, the wallet must be installed and enabled in the user's browser to appear as an option in the interface. ## Which function should be modified to set priority fees for Solana transactions? In [Wormhole Connect](https://github.com/wormhole-foundation/wormhole-connect){target=\_blank}, you can modify the priority fees for Solana transactions by updating the `computeBudget/index.ts` file. This file contains the logic for adjusting the compute unit limit and priority fees associated with Solana transactions. To control the priority fee applied to your transactions, you can modify the `feePercentile` and `minPriorityFee` parameters in the `addComputeBudget` and `determineComputeBudget` functions. The relevant file can be found in the Connect codebase: [`computeBudget/index.ts`](https://github.com/wormhole-foundation/wormhole-connect/blob/62f1ba8ee5502ac6fd405680e6b3816c9aa54325/sdk/src/contexts/solana/utils/computeBudget/index.ts){target=\_blank}. ## Is there a minimum amount for bridging with CCTP or the Connect SDK? There is no minimum amount for bridging via CCTP if the user covers the gas fees on both the source and destination chains. However, if the transfer is automatically relayed, a minimum amount is required to cover relay fees on the destination chain. The relay provider charges these fees at cost. Current relay fees: - Ethereum L1: ~4.2 USDC - Base, Optimism, Arbitrum, Avalanche: 0.3 USDC Additional notes: - **USDC to Solana** - Wormhole's native CCTP route does not currently support automatic relaying of USDC to Solana. However, you can transfer USDC to Solana using the [Mayan plugin](https://github.com/mayan-finance/wormhole-sdk-route){target=\_blank} for the SDK. Mayan is a protocol that integrates Wormhole and CCTP to enable this functionality - **Frontend integrations** - **Connect** - A pre-built UI available via [@wormhole-foundation/wormhole-connect](https://www.npmjs.com/package/@wormhole-foundation/wormhole-connect){target=\_blank} - **TypeScript SDK** - A lower-level integration option, available via [@wormhole-foundation/sdk](https://www.npmjs.com/package/@wormhole-foundation/sdk){target=\_blank}, allowing developers to build custom UIs !!!note The TypeScript SDK was previously referred to as the "Connect SDK," but this naming has since been discontinued. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/connect/features/ --- BEGIN CONTENT --- --- title: Features description: Explore a comprehensive Feature Support matrix and explain Wormhole's capabilities across networks for Token Bridge, CCTP, ETH Bridge, and more. categories: Connect, Transfer --- ## Feature Support Matrix {: #feature-support-matrix} *Scroll down for details about each column.* | **Network** | **Token Bridge** | **Token Bridge Relayer** | **Circle CCTP** | **ETH Bridge** | **Gas Drop Off** | |:-----------:|:----------------:|:------------------------:|:---------------:|:--------------:|:----------------:| | Solana | ✅ | ✅ | ✅ | ❌ | ✅ | | Ethereum | ✅ | ✅ | ✅ | ✅ | ✅ | | BSC | ✅ | ✅ | ❌ | ✅ | ✅ | | Polygon | ✅ | ✅ | ✅ | ✅ | ✅ | | Avalanche | ✅ | ✅ | ✅ | ✅ | ✅ | | Fantom | ✅ | ✅ | ❌ | ❌ | ✅ | | Kaia | ✅ | ❌ | ❌ | ❌ | ❌ | | Celo | ✅ | ✅ | ❌ | ❌ | ✅ | | Moonbeam | ✅ | ✅ | ❌ | ❌ | ✅ | | Injective | ✅ | ❌ | ❌ | ❌ | ❌ | | Sui | ✅ | ✅ | ❌ | ❌ | ✅ | | Aptos | ✅ | ❌ | ❌ | ❌ | ❌ | | Arbitrum | ✅ | ✅ | ✅ | ✅ | ✅ | | Optimism | ✅ | ✅ | ✅ | ✅ | ✅ | | Base | ✅ | ✅ | ✅ | ✅ | ✅ | | Sei | ✅ | ❌ | ❌ | ❌ | ❌ | | Scroll | ✅ | ❌ | ❌ | ❌ | ❌ | | Blast | ✅ | ❌ | ❌ | ❌ | ❌ | | X Layer | ✅ | ❌ | ❌ | ❌ | ❌ | ## Feature Explanation {: #feature-explanation} ### Token Bridge {: #token-bridge} Wormhole is best known for its Token Bridge transfer method. It locks assets on the source chain and mints Wormhole-wrapped "IOU" tokens on the destination chain. To transfer the assets back, the Wormhole-wrapped tokens are burned, unlocking the tokens on their original chain. This route appears if both of the following conditions are satisfied: - Both the origin and destination chains support Token Bridge - No non-Token Bridge routes are available for the selected token ### Token Bridge Relayer {: #token-bridge-relayer} On the [routes](/docs/build/transfers/connect/routes/){target=\_blank} page, this is referred to as the automatic route in the Token Bridge section. Trustless relayers can execute the second transaction on behalf of the user, so the user only needs to perform one transaction on the origin chain to have the tokens delivered to the destination automatically—for a small fee. This route appears if all of the following conditions are satisfied: - Both the origin and destination chains support Token Bridge - Both the origin and destination chains support Token Bridge relayer - No non-Token Bridge routes are available for the selected token - The relayer supports the selected token on the origin chain ### Circle CCTP {: #circle-cctp} [Circle](https://www.circle.com/en/){target=\_blank}, the issuer of USDC, provides a native way for native USDC to be transferred between [CCTP-enabled](https://www.circle.com/en/cross-chain-transfer-protocol){target=\_blank} chains. This route appears if all of the following conditions are satisfied: - Both the origin and destination chains support Circle CCTP - The selected token is native Circle-issued USDC ### ETH Bridge {: #eth-bridge} [Powered by Uniswap liquidity pools](https://github.com/wormhole-foundation/example-uniswap-liquidity-layer){target=\_blank}, this route can transfer native ETH or wstETH between certain EVMs without going through the native bridges. This route appears if all of the following conditions are satisfied: - Both the origin and destination chains support the ETH Bridge - The selected token is native ETH, wstETH, or canonical wETH ### Gas Drop Off {: #gas-drop-off} A relayer can drop off some gas tokens on the destination chain by swapping some of the assets transferred to the native gas token. This is useful if the user wishes to transfer assets to a chain where they don't already have gas. This way, they don't need to onboard into the ecosystem from a centralized exchange. This route appears if all of the following conditions are satisfied: - Both the origin and destination chains support gas drop off - An automatic route is selected - The relayer accepts the selected token to swap into the gas token --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/connect/ --- BEGIN CONTENT --- --- title: Wormhole Connect description: Wormhole Connect is a React widget offering an easy-to-use interface to facilitate multichain asset transfers via Wormhole directly in a web application. categories: Connect, Transfer --- # Wormhole Connect Wormhole Connect is a customizable widget that brings wrapped and native token cross-chain asset transfers into your dApp in as few as 3 lines of code. Connect is available as a React component or hosted version via CDN so you can easily configure any application to transfer tokens via Wormhole. ## Build with Connect [timeline left(wormhole-docs/.snippets/text/build/transfers/connect/connect-timeline.json)] ## See It In Action Wormhole Connect is deployed live in several production apps. Here are a few: - [Portal Bridge](https://portalbridge.com/){target=\_blank} - [Jupiter](https://jup.ag/onboard/cctp){target=\_blank} - [Pancake Swap](https://bridge.pancakeswap.finance/wormhole){target=\_blank} Visit the [Use Cases](/docs/build/start-building/use-cases/){target=\_blank} page to learn how to combine Connect with other Wormhole products, including Native Token Transfer (NTT). ## Next Steps
- :octicons-tools-16:{ .lg .middle} **Get Started Now** --- Follow this series of how-to guides to integrate Connect into your React dApp and configure options to fit your user's needs. [:custom-arrow: Get started](/docs/build/transfers/connect/overview/#integrate-connect) - :octicons-tools-16:{ .lg .middle } **Multichain Swap** --- This tutorial guides you step-by-step through integrating Connect into your React dApp to transfer tokens from Sui to Avalanche Fuji. This tutorial is readily adaptable to work with other [supported networks](/docs/build/start-building/supported-networks/){target=\_blank}. [:custom-arrow: Get started](/docs/tutorials/connect/react-dapp/) - :octicons-tools-16:{ .lg .middle } **Connect FAQs** --- Common questions and detailed answers about using Wormhole Connect, including supported assets, chains, customization, and integration options. [:custom-arrow: Visit FAQs](/docs/build/transfers/connect/faqs/) - :octicons-tools-16:{ .lg .middle } **Supported Features by Chain** --- Get a more detailed look at Wormhole Connect features with a breakdown of supported features by chain. [:custom-arrow: Supported Features](/docs/build/transfers/connect/features/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/connect/overview/ --- BEGIN CONTENT --- --- title: Overview description: Explore Wormhole Connect, the React widget that allows you to offer an easy-to-use UI for cross-chain asset transfers via Wormhole in a web application. categories: Connect, Transfer --- # Wormhole Connect ## Introduction {: #introduction } Wormhole Connect is a React widget that lets developers offer an easy-to-use interface to facilitate cross-chain asset transfers via Wormhole directly in a web application. Check out the [Wormhole Connect GitHub repository](https://github.com/wormhole-foundation/wormhole-connect){target=\_blank}. The [Wormhole TypeScript SDK](https://docs.wormhole.com/wormhole/reference/sdk-docs){target=\_blank} allows you to implement the same functionality as the Connect widget but in your own UI. Check out the docs for more information on using the SDK instead of Connect. ## Features {: #features } Wormhole Connect is easy to customize to suit your application's needs. You can specify technical details like supported assets and custom RPCs or forgo customization and have a full-featured widget. The widget UI is highly customizable, with extensive styling options available, including a user-friendly no code styling interface for those who prefer a more visual approach to design. The features of Wormhole Connect include: - Multiple ways to bridge assets ([routes](/docs/build/transfers/connect/routes/){target=\_blank}) - Extensive ways to style the UI (including the [no code styling interface](https://connect-in-style.wormhole.com/){target=\_blank}) - Ways to [configure](/docs/build/transfers/connect/configuration/){target=\_blank} what feature set to offer - Ability to configure any token to bridge via Wormhole - [Ability to drop off some gas](/docs/build/transfers/connect/features/){target=\_blank} at the destination For more details about the features of Wormhole Connect and a breakdown of supported features by chain, be sure to check [the features page](/docs/build/transfers/connect/features/){target=\_blank}. ## Integrate Connect {: #integrate-connect } ### Import Directly into a React App {: #import-directly-into-a-react-app} First, install the Wormhole Connect npm package. You can read more about the package by clicking on the following button: [![npm version](https://img.shields.io/npm/v/@wormhole-foundation/wormhole-connect.svg)](https://www.npmjs.com/package/@wormhole-foundation/wormhole-connect){target=\_blank} ```bash npm i @wormhole-foundation/wormhole-connect ``` Now you can import the React component: ```ts import WormholeConnect from '@wormhole-foundation/wormhole-connect'; function App() { return ; } ``` ### Use Hosted Version via CDN {: #use-hosted-version-via-cdn} If you're not using React, you can still embed Connect on your website by using the hosted version. This uses pre-built packages (which include React) served from NPM by jsdelivr.net. ```ts title="v1.x" import { wormholeConnectHosted } from '@wormhole-foundation/wormhole-connect'; // Existing DOM element where you want to mount Connect const container = document.getElementById('bridge-container'); wormholeConnectHosted(container); ``` For help migrating from Connect v0.x to v1.x, see the [v1 Migration](/docs/build/transfers/connect/upgrade/){target=\_blank} guide. ???- code "v0.x" Simply copy and paste the following into your HTML body, and replace the ```INSERT_WORMHOLE_CONNECT_VERSION``` in the links with the most recent production version of Wormhole Connect. You can check what the most recent version is on [NPM](https://www.npmjs.com/package/@wormhole-foundation/wormhole-connect/v/latest){target=\_blank}. ```html
``` For example, for [0.3.13](https://www.npmjs.com/package/@wormhole-foundation/wormhole-connect/v/0.3.13){target=\_blank}: ```html
``` It is important to periodically update your Wormhole Connect instance to the latest version, as there are frequent functionality and security releases. ## Configuration {: #configuration} This is just an overview of what's possible. Check the [Configuration docs](/docs/build/transfers/connect/configuration/){target=\_blank} for details about all the configuration options. The default configuration of Wormhole Connect may not be exactly what you're looking for. You may want to: - Use custom styles - Restrict the chains that you allow in your app - Add support for your project's token, and eliminate tokens you don't want to reduce noise - Configuring custom RPC URLs (This is highly recommended as default public RPCs are heavily throttled) - Restrict the [routes](/docs/build/transfers/connect/routes/){target=\_blank} that are available For additional information on the preceding options, check the [configuration options](/docs/build/transfers/connect/configuration/){target=\_blank} and customize your widget however you like. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/connect/routes/ --- BEGIN CONTENT --- --- title: Routes description: Explore Wormhole Connect's routing capabilities for asset transfers, featuring Token Bridge, CCTP, NTT, and various blockchain-specific routes for optimal UX. categories: Connect, Transfer --- ## Routes Overview {: #routes-overview} This page explains the concept of routes in Wormhole Connect. To configure routes for your widget, check the [Wormhole Connect Configuration](/docs/build/transfers/connect/configuration/){target=\_blank}. Routes are methods by which the widget will transfer the assets. Wormhole Connect supports Token Bridge transfers for any arbitrary token, and for specific tokens, it also supports more advanced transfer methods that provide superior UX. When you select the source chain, source token, and destination chain, Wormhole Connect will display the best routes available for that particular combination. In practice, if routes other than the Token Bridge are available, only those will be displayed. Check the [feature matrix](/docs/build/transfers/connect/features/){target=\_blank} to see under which exact conditions the routes appear. ## Token Bridge Routes {: #token-bridge-routes} The Token Bridge is Wormhole's best-known transfer method. It locks assets on the source chain and mints Wormhole-wrapped "IOU" tokens on the destination chain. To transfer the assets back, the Wormhole-wrapped tokens are burned, unlocking the tokens on their original chain. #### Manual Route {: #manual-route} The manual route transfer method requires two transactions: one on the origin chain to lock the tokens (or burn the Wormhole-wrapped tokens) and one on the destination chain to mint the Wormhole-wrapped tokens (or unlock the original tokens). To offer this option, enable the `bridge` route in the configuration. #### Automatic Route {: #automatic-route} Trustless relayers can execute the second transaction on the user's behalf, so the user only needs to perform one transaction on the origin chain to have the tokens delivered to the destination automatically - for a small fee. Wormhole Connect automatically detects whether the relayer supports a token and will display the option if the `relay` route is enabled in the configuration. ## CCTP Routes (USDC) {: #cctp-routes-usdc} [Circle](https://www.circle.com/en/){target=\_blank}, the issuer of USDC, provides a native way for native USDC to be transferred between [CCTP-enabled](https://www.circle.com/en/cross-chain-transfer-protocol){target=\_blank} chains. Wormhole Connect can facilitate such transfers. Note that if native USDC is transferred from the CCTP-enabled chains to any other outside of this list, the transfer will be routed through the Token Bridge, and the resulting asset will be a Wormhole-wrapped token instead of native USDC. #### Manual Route {: #manual-route-cctp} This transfer method requires two transactions: one on the origin chain to burn the USDC and one on the destination chain to mint the USDC. The manual CCTP route relies on CCTP only and doesn't use Wormhole messaging in the background. Enable the `cctpManual` route in the configuration to offer this option. #### Automatic Route {: #automatic-route-cctp} Trustless relayers can execute the second transaction on the user's behalf. Therefore, the user only needs to perform one transaction on the origin chain to have the tokens delivered to the destination automatically—for a small fee. To offer this option, enable the `cctpRelay` route in the configuration. ## Native Token Transfers (NTT) Routes {: #native-token-transfers-ntt-routes} [Wormhole's Native Token Transfer (NTT) framework](https://github.com/wormhole-foundation/native-token-transfers/){target=\_blank} enables token issuers to retain full ownership of their tokens across any number of chains, unlike the Token Bridge. The token issuer must deploy NTT contracts, and Wormhole Connect needs to be [configured](/docs/build/transfers/connect/configuration/){target=\_blank} with the appropriate `nttGroups` before such tokens are recognized as transferrable via NTT. Refer to the [documentation in the NTT repository](https://github.com/wormhole-foundation/native-token-transfers?tab=readme-ov-file#overview){target=\_blank} for more information about the contracts needed and the framework in general. #### Manual Route {: #manual-route-ntt} This transfer method requires two transactions: one on the origin chain to burn or lock the tokens and one on the destination chain to mint them. To offer this option, enable the `nttManual` route in the configuration. #### Automatic Route {: #automatic-route-ntt} Trustless relayers can execute the second transaction on the user's behalf, so the user only needs to perform one transaction on the origin chain to have the tokens delivered to the destination automatically—for a small fee. Wormhole Connect automatically detects whether the relayer supports a token and will display the option if the `nttRelay` route is enabled in the configuration. ## ETH Bridge Route for Native ETH and wstETH {: #eth-bridge-route-for-native-eth-and-wsteth} [Powered by Uniswap liquidity pools](https://github.com/wormhole-foundation/example-uniswap-liquidity-layer){target=\_blank}, this route can transfer native ETH or wstETH between certain EVMs without going through the native bridges. For example, you can transfer native ETH from Arbitrum to Optimism and end up with Optimism ETH all in one go. Supported chains are Ethereum, Arbitrum, Optimism, Base, Polygon (canonical wETH), BSC (canonical wETH), and Avalanche (canonical wETH). #### Automatic Route {: #automatic-route-eth} Only the relayed route is available due to the complexity of the transaction that needs to be executed at the destination. To offer this option, enable the `ethBridge` and/or `wstETHBridge` route in the configuration to provide this option. ## USDT Bridge Route {: #usdt-bridge-route} Operating on the same technology as the ETH Bridge, this route can transfer USDT between certain EVMs without going through the native bridges. The resulting token will be the canonical USDT token on the destination instead of the Wormhole-wrapped variant. Supported chains are Ethereum, Polygon, Avalanche, Arbitrum, Optimism, BSC, and Base. #### Automatic Route {: #automatic-route-usdt} Only the relayed route is available due to the complexity of the transaction that needs to be executed on the destination. Enable the `usdtBridge` route in the configuration to offer this option. ## tBTC Route {: #tbtc-route} You can bridge [Threshold's Bitcoin](https://threshold.network/){target=\_blank} via this hybrid solution that combines the Token Bridge and Threshold's contracts. Native tBTC is first locked in the Wormhole Token Bridge, transferred to the destination in the form of Wormhole-wrapped tBTC, which is then immediately locked in Threshold's contract that mints native tBTC for it. The net result is that the user ends up with native tBTC on chains where this Threshold contract is deployed (e.g., Solana, Polygon, Arbitrum, Optimism, or Base). Note that if native tBTC is transferred out of these chains to any other outside of this list, the transfer will be routed through the Token Bridge, and the resulting asset will be a Wormhole-wrapped token instead of native tBTC. #### Manual Route {: #manual-route-tbtc} This transfer method requires two transactions: one on the origin chain to burn or lock the tokens and one on the destination chain to mint them. To provide this option, enable the `tbtc` route in the configuration. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/connect/upgrade/ --- BEGIN CONTENT --- --- title: Wormhole Connect v1.0 Migration Guide description: Learn how to migrate to Wormhole Connect v1.0, with step-by-step guidance on updating your package and configuration. categories: Connect, Transfer --- # Wormhole Connect v1.0 Migration Guide ## Overview The Wormhole Connect feature has been updated to **version 1.0**, introducing a modernized design and improved routing for faster native-to-native token transfers. This stable release comes with several breaking changes in how to configure the application, requiring minor updates to your integration. This guide will help you migrate to the new version in just a few simple steps. By following this migration guide, you'll learn how to: - Update to the latest Connect package - Apply configuration changes to the **`WormholeConnectConfig`** object - Understand new routing capabilities and plugin options These updates ensure better performance and a smoother integration experience. For complete documentation on the previous version of Wormhole Connect, please refer to the [Wormhole Connect guide](/docs/build/transfers/connect/){target=\_blank}. ## Update the Connect Package To begin the migration process, update the Wormhole Connect [**npm package**](https://www.npmjs.com/package/@wormhole-foundation/wormhole-connect/v/1.0.0-beta.6-development?activeTab=readme){target=\_blank} to the latest version 1.0. Updating to the latest version provides access to the newest features and improvements, including the modernized design and enhanced routing capabilities. Run the following command in your terminal: ```bash npm install @wormhole-foundation/wormhole-connect@^1.0 ``` This command installs the latest stable version of Wormhole Connect and prepares your environment for the new configuration changes. ## Update the `WormholeConnectConfig` Object In version 1.0, the `WormholeConnectConfig` object underwent several breaking changes. Most of these changes are minor and can be applied quickly. Below is a summary of the key changes, followed by detailed examples. ### Summary of Breaking Changes - Chain names are now capitalized: `solana` → `Solana` - `env` renamed to `network` and is now capitalized: `mainnet` → `Mainnet` - `networks` renamed to `chains`, with capitalized names - `routes` updated to use route plugins - `nttGroups` removed in favor of route plugin configuration - `tokensConfig` updated, with a new key `wrappedTokens` added - Many UI-related properties consolidated under a top-level `ui` key - `customTheme` and `mode` were removed, replaced by a top-level `theme` property These changes are explained in more detail below, with examples for easy reference. ### Capitalize Chain Names In version 1.0, chain names are now consistent with the `Chain` type from the [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank}, and must be capitalized. This affects all config properties where a chain is referenced, including `rpcs`, `rest`, `graphql`, and `chains`. === "v0.x" ```typescript const config: WormholeConnectConfig = { rpcs: { ethereum: 'INSERT_ETH_RPC_URL', solana: 'INSERT_SOLANA_RPC_URL', }, }; ``` === "v1.x" ```typescript const config: WormholeConnectConfig = { rpcs: { Ethereum: 'INSERT_ETH_RPC_URL', Solana: 'INSERT_SOLANA_RPC_URL', }, }; ``` You can find the complete list of supported chain names in the [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/fa4ba4bc349a7caada809f209090d79a3c5962fe/core/base/src/constants/chains.ts#L12-L66){target=\_blank}. ### Rename `env` to `network` The `env` property has been renamed to `network`, with capitalized values. This change affects how you configure Testnet and Mainnet environments. === "v0.x" ```typescript const config: WormholeConnectConfig = { env: 'testnet', }; ``` === "v1.x" ```typescript const config: WormholeConnectConfig = { network: 'Testnet', }; ``` If you don’t explicitly set the `network` value, Connect will default to `Mainnet`. ```typescript // Defaults to Mainnet const config: WormholeConnectConfig = {}; ``` For more information, refer to the [network constants list](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/base/src/constants/networks.ts){target=\_blank}. ### Rename `networks` to `chains` The `networks` property, which allowed whitelisting chains, is now renamed `chains`, and the chain names are capitalized. === "v0.x" ```typescript const config: WormholeConnectConfig = { networks: ['solana', 'ethereum'], }; ``` === "v1.x" ```typescript const config: WormholeConnectConfig = { chains: ['Solana', 'Ethereum'], }; ``` ### Update `routes` to Use Route Plugins The `routes` property in Wormhole Connect version 1.0 has significantly improved. Previously, `routes` was a simple array of strings. The latest version has been transformed into a flexible plugin system, allowing you to include specific routes for various protocols. By default, if no `routes` property is set, Wormhole Connect will provide routes for two core protocols: - [Wormhole Token Bridge](/docs/learn/transfers/token-bridge/){target=\_blank} - [CCTP](/docs/learn/transfers/cctp/){target=\_blank} For most use cases, integrators require more than the default routes. The new `routes` property allows you to specify which protocols to include and exclude any routes unnecessary for your application, including both default and third-party routes. #### Available `route` Plugins The `@wormhole-foundation/wormhole-connect` package offers a variety of `route` plugins to give you flexibility in handling different protocols. You can choose from the following `route` exports for your integration: ???- tip "`route` Plugins" - **`TokenBridgeRoute`** - manually redeemed Wormhole Token Bridge route - **`AutomaticTokenBridgeRoute`** - automatically redeemed (relayed) Token Bridge route - **`CCTPRoute`** - manually redeemed CCTP route - **`AutomaticCCTPRoute`** - automatically redeemed (relayed) CCTP route - **`DEFAULT_ROUTES`** - array containing the four preceding routes (TokenBridgeRoute, AutomaticTokenBridgeRoute, CCTPRoute, AutomaticCCTPRoute) - **`nttAutomaticRoute(nttConfig)`** - function that returns the automatically-redeemed (relayed) Native Token Transfer (NTT) route - **`nttManualRoute(nttConfig)`** - function that returns the manually-redeemed NTT route - **`nttRoutes(nttConfig)`** - function that returns both NTT routes as an array - **`MayanRoute`** - route that offers multiple Mayan protocols - **`MayanRouteSWIFT`** - route for Mayan’s Swift protocol only - **`MayanRouteMCTP`** - route for Mayan’s MCTP protocol only - **`MayanRouteWH`** - route for Mayan’s original Wormhole transfer protocol In addition to these routes, developers can create custom routes for their own Wormhole-based protocols. For examples, refer to the [NTT](https://github.com/wormhole-foundation/native-token-transfers/tree/main/sdk/route){target=\_blank} and the [Mayan](https://github.com/mayan-finance/wormhole-sdk-route){target=\_blank} example GitHub repositories. For further details on the Route plugin interface, refer to the [Wormhole TypeScript SDK route code](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/connect/src/routes/route.ts){target=\_blank}. Now that you know the available `route` plugins, let's explore some examples of configuring them. #### Example: Offer Only CCTP Transfers To configure Wormhole Connect to offer only USDC transfers via the CCTP route, use the following configuration: ```typescript import WormholeConnect, { AutomaticCCTPRoute, WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { routes: [AutomaticCCTPRoute], }; ; ``` #### Example: Offer All Default Routes and Third-Party Plugins In this example, Wormhole Connect is configured with routes for both default protocols (Token Bridge & CCTP), as well as third-party protocols like [Native Token Transfers (NTT)](/docs/build/transfers/native-token-transfers/){target=\_blank} and [Mayan Swap](https://swap.mayan.finance/){target=\_blank}. ```typescript import WormholeConnect, { DEFAULT_ROUTES, nttRoutes, MayanRouteSWIFT, WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; import { myNttConfig } from './consts'; // Custom NTT configuration const config: WormholeConnectConfig = { routes: [...DEFAULT_ROUTES, ...nttRoutes(myNttConfig), MayanRouteSWIFT], }; ; ``` This flexible plugin allows you to combine default routes (such as Token Bridge and CCTP) with third-party protocols, offering complete control over which routes are available in your application. ### Update the `tokensConfig` Structure In Wormhole Connect version 1.0, the `tokensConfig` property has been updated to simplify the structure and improve flexibility for token handling across chains. The previous configuration has been streamlined, and a new key, `wrappedTokens,` has been introduced to handle foreign assets more effectively. Key Changes to `tokensConfig`: - **Capitalized chain names** - all chain names, like `ethereum`, must now be capitalized, such as `Ethereum`, to maintain consistency with the rest of the Wormhole SDK - **`wrappedTokens`** - this new key replaces `foreignAssets` and defines the wrapped token addresses on foreign chains, making it easier to manage cross-chain transfers. It consolidates the wrapped token addresses into a cleaner structure. These addresses must be specified to enable token transfers to and from the foreign chain via token bridge routes - **Simplified decimals** - instead of using a map of decimal values for different chains, you now only need to provide a single decimals value for the token's native chain === "v0.x" In the old structure, the `foreignAssets` field defined the token’s presence on other chains, and `decimals` were mapped across multiple chains. ```typescript import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { tokensConfig: { WETH: { key: 'WETH', symbol: 'WETH', nativeChain: 'ethereum', icon: Icon.ETH, tokenId: { chain: 'ethereum', address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', }, coinGeckoId: 'ethereum', color: '#62688F', decimals: { Ethereum: 18, default: 8 }, foreignAssets: { Solana: { address: '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs', decimals: 8, }, }, }, }, }; ``` === "v1.x" In v1.0, `foreignAssets` has been replaced with `wrappedTokens`, simplifying token transfers across chains by directly mapping wrapped token addresses. The `decimals` value is now a simple number representing the token’s decimals on its native chain. ```typescript import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { tokensConfig: { WETH: { key: 'WETH', symbol: 'WETH', nativeChain: 'Ethereum', // Chain name now capitalized icon: Icon.ETH, tokenId: { chain: 'Ethereum', address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', }, coinGeckoId: 'ethereum', color: '#62688F', decimals: 18, // Simplified decimals field }, }, wrappedTokens: { WETH: { Solana: '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs', /* additional chains */ }, }, }; ``` ### Update NTT Configuration In Wormhole Connect version 1.0, the `nttGroups` property, which was used to configure Native Token Transfers (NTT), has been removed. Instead, the NTT configuration is passed directly to the NTT route constructor. This update simplifies the setup and provides more flexibility for defining NTT routes. Key changes: - **Removed `nttGroups`** - the `nttGroups` property has been removed from the configuration and is now passed as an argument to the `nttRoutes` function - **Direct NTT route configuration** - NTT routes are now defined more explicitly, allowing for a more organized structure when specifying tokens, chains, and managers This change simplifies the configuration process by providing a cleaner, more flexible way to handle NTT routes across different chains. === "v0.x" In the previous version, `nttGroups` defined the NTT managers and transceivers for different tokens across multiple chains. ```typescript import WormholeConnect, { nttRoutes, WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { nttGroups: { Lido_wstETH: { nttManagers: [ { chainName: 'ethereum', address: '0xb948a93827d68a82F6513Ad178964Da487fe2BD9', tokenKey: 'wstETH', transceivers: [ { address: '0xA1ACC1e6edaB281Febd91E3515093F1DE81F25c0', type: 'wormhole', }, ], }, { chainName: 'bsc', address: '0x6981F5621691CBfE3DdD524dE71076b79F0A0278', tokenKey: 'wstETH', transceivers: [ { address: '0xbe3F7e06872E0dF6CD7FF35B7aa4Bb1446DC9986', type: 'wormhole', }, ], }, ], }, }, }; ``` === "v1.x" In v1.0, `nttGroups` has been removed, and the configuration is passed to the NTT route constructor as an argument. The tokens and corresponding transceivers are now clearly defined within the `nttRoutes` configuration. ```typescript import WormholeConnect, { nttRoutes, WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { routes: [ ...nttRoutes({ tokens: { Lido_wstETH: [ { chain: 'Ethereum', manager: '0xb948a93827d68a82F6513Ad178964Da487fe2BD9', token: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', transceiver: [ { address: '0xA1ACC1e6edaB281Febd91E3515093F1DE81F25c0', type: 'wormhole', }, ], }, { chain: 'Bsc', manager: '0x6981F5621691CBfE3DdD524dE71076b79F0A0278', token: '0x26c5e01524d2E6280A48F2c50fF6De7e52E9611C', transceiver: [ { address: '0xbe3F7e06872E0dF6CD7FF35B7aa4Bb1446DC9986', type: 'wormhole', }, ], }, ], }, }), /* other routes */ ], }; ``` In this new structure, NTT routes are passed directly through the `nttRoutes` function, with the `token`, `chain`, `manager` and `transceiver` clearly defined for each supported asset. ### Update UI Configuration In Wormhole Connect version 1.0, the user interface configuration has been significantly updated. Several previously scattered UI properties have now been consolidated under a new `ui` key, making the UI configuration cleaner and easier to manage. Key UI changes: - **Consolidated UI properties** - many UI-related properties moved under a new top-level ui key for better organization - **Removed `customTheme` and `mode`** - these properties have been removed in favor of a new top-level prop called `theme`, which simplifies theming and allows dynamic switching between themes #### UI Properties The following properties that were previously defined at the root level of the configuration are now part of the `ui` key: - `explorer` → `ui.explorer` - specifies the explorer to use for viewing transactions - `bridgeDefaults` → `ui.defaultInputs` - sets default input values for the bridge, such as the source and destination chains and token - `pageHeader` → `ui.pageHeader` - sets the title and header for the page - `menu` → `ui.menu` - defines the menu items displayed in the interface - `searchTx` → `ui.searchTx` - configures the transaction search functionality - `partnerLogo` → `ui.partnerLogo` - displays a partner's logo on the interface - `walletConnectProjectId` → `ui.walletConnectProjectId` - integrates WalletConnect into the UI - `showHamburgerMenu` → `ui.showHamburgerMenu` - enables or disables the hamburger menu for navigation Additionally, there are two new properties under `ui`: - **`ui.title`** - sets the title rendered in the top left corner of the UI. The default is "Wormhole Connect" - **`ui.getHelpUrl`** - URL that Connect will render when an unknown error occurs, allowing users to seek help. This can link to a Discord server or any other support channel ```typescript import WormholeConnect, { WormholeConnectConfig, } from '@wormhole-foundation/wormhole-connect'; const config: WormholeConnectConfig = { ui: { title: 'My Custom Bridge Example', getHelpUrl: 'https://examplehelp.com/', menu: [ { label: 'Support', href: 'https://examplehelp.com/support', target: '_blank', order: 1, // Order of appearance in the menu }, { label: 'About', href: 'https://examplehelp.com/about', target: '_blank', order: 2, }, ], showHamburgerMenu: false, }, }; ``` #### UI Configuration In the old structure, UI-related settings like `explorer` and `bridgeDefaults` were defined at the root level of the configuration. In version 1.0, these properties are now organized under the `ui` key, improving the configuration's readability and manageability. === "v0.x" ```typescript const config: WormholeConnectConfig = { bridgeDefaults: { fromNetwork: 'solana', toNetwork: 'ethereum', tokenKey: 'USDC', requiredNetwork: 'solana', }, showHamburgerMenu: true, }; ``` === "v1.x" ```typescript const config: WormholeConnectConfig = { ui: { defaultInputs: { fromChain: 'Solana', // Chain names now capitalized toChain: 'Ethereum', tokenKey: 'USDC', requiredChain: 'Solana', }, showHamburgerMenu: true, }, }; ``` #### Remove `customTheme` and `mode` Properties In version 1.0, the `customTheme` and `mode` properties, which were previously used to set themes, have been removed. They have been replaced by a new top-level prop called `theme`, which allows for more flexibility and dynamic updates to themes. Important details: - The `theme` prop is not part of the `config` object and is passed separately to Wormhole Connect - `config` cannot be modified after Connect has mounted, but the `theme` can be updated dynamically to support changes such as switching between light and dark modes or updating color schemes === "v0.x" ```typescript const config: WormholeConnectConfig = { customTheme: { primaryColor: '#4266f5', secondaryColor: '#ff5733', }, mode: 'dark', }; ; ``` === "v1.x" ```typescript const theme: WormholeConnectTheme = { mode: 'dark', // Can be dynamically changed font: 'Arial', button: { primary: '#4266f5', }, }; ; ``` ### Removed Configuration Properties Several configuration properties have been removed in Wormhole Connect version 1.0. These keys no longer have any effect, and providing values for them in the configuration will not result in any changes. Removed config keys: - `cta` - `cctpWarning` - `pageSubHeader` - `moreTokens` - `moreChains` - `ethBridgeMaxAmount` - `wstETHBridgeMaxAmount` - `customTheme` - `mode` If your current setup includes any of these properties, you can safely remove them, as they are no longer supported in v1.0. ## Use the CDN-Hosted Version of Wormhole Connect For those using the CDN-hosted version of Wormhole Connect, the package's installation and integration have been updated. You must install the Connect package from npm and use the new `wormholeConnectHosted` utility function. ### Install and Integrate the Hosted Version 1. Install the Connect package via npm: ```bash npm install @wormhole-foundation/wormhole-connect@^1.0 ``` 2. After installing the package, you can embed Wormhole Connect into your page by adding the following code: ```typescript import { wormholeConnectHosted } from '@wormhole-foundation/wormhole-connect'; const container = document.getElementById('connect')!; wormholeConnectHosted(container); ``` ### Example: Custom Configuration for Hosted Version The `wormholeConnectHosted` function accepts two parameters: `config` and `theme`. This allows you to customize the routes and apply a theme directly within the hosted version. Here’s an example of how you can pass a custom configuration: ```typescript import { wormholeConnectHosted, MayanRoute, } from '@wormhole-foundation/wormhole-connect'; const container = document.getElementById('connect')!; wormholeConnectHosted(container, { config: { routes: [MayanRoute], eventHandler: (e) => { console.log('Connect event', e); }, }, theme: { background: { default: '#004547', }, }, }); ``` In this example, the `config` object defines the routes (in this case, using the Mayan route), while the `theme` object allows customization of the Connect interface (e.g., background color). --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/ --- BEGIN CONTENT --- --- title: Multichain Transfers description: This section guides you through using Wormhole products to securely and efficiently transfer assets and messages across multiple blockchains. --- # Multichain Transfers Wormhole transfer products offer multiple asset transfer options to meet developer needs and use cases. These sections include guides to integrate and use Wormhole transfer products to securely and efficiently transfer assets and messages across multiple blockchains. Use the following links to jump directly to each Wormhole transfer product page or continue on for a feature-based product comparison. - [**Connect**](/docs/build/transfers/connect/) - a pre-built bridging UI for cross-chain token transfers, requiring minimal setup - [**Native Token Transfers (NTT)**](/docs/build/transfers/native-token-transfers/) - a mechanism to transfer native tokens cross-chain seamlessly without converting to a wrapped asset - [**Token Bridge**](/docs/learn/transfers/token-bridge/) - a bridging solution that uses a lock and mint mechanism - [**Settlement**](/docs/build/transfers/settlement/) - an intent protocol suite for fast multichain transfers, optimizing liquidity flows and interoperability without relying on traditional bridging methods ## Products By Feature While all of these products handle token transfers, there are additional features to consider when selecting the best fit for your project needs.
- :octicons-file-code-16:{ .lg .middle } **Message Payload** --- Send message payloads for social platforms, NFTs, and more with Token Bridge's lock and mint wrapped token mechanism. [:custom-arrow: Discover Token Bridge](/docs/build/transfers/token-bridge/) - :octicons-file-code-16:{ .lg .middle } **Native Assets** --- Eliminate the need for wrapped tokens and issue native multichain assets with Native Token Transfers (NTT). [:custom-arrow: Discover Native Token Transfers](/docs/build/transfers/native-token-transfers/) - :octicons-file-code-16:{ .lg .middle } **Speed at Scale** --- Unleash institutional-scale digital asset settlement with Wormhole Settlement's intent-based asset transfers and liquidity layer. [:custom-arrow: Discover Settlement](/docs/build/transfers/settlement/) - :octicons-gear-16:{ .lg .middle } **Plug-and-Play UI** --- Add Wormhole's bridge UI to your dApp, no smart contract development required, with Connect. [:custom-arrow: Discover Connect](/docs/build/transfers/connect/)
## Additional Resources
- :octicons-tools-16:{ .lg .middle } **Product Comparison** --- Compare Wormhole's cross-chain solutions for bridging, native transfers, data queries, and governance to enable seamless blockchain interoperability. [:custom-arrow: Compare Products](/docs/build/start-building/products/) - :octicons-book-16:{ .lg .middle } **Use Cases** --- Explore Wormhole's use cases, from cross-chain swaps to DeFi, lending, gaming, and more. See how projects integrate Wormhole solutions. [:custom-arrow: Discover Use Cases](/docs/build/start-building/use-cases/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/cli-commands/ --- BEGIN CONTENT --- --- title: NTT CLI Commands description: A comprehensive guide to the Native Token Transfers (NTT) CLI, detailing commands for managing token transfers across chains within the Wormhole ecosystem. categories: NTT, Transfer --- # NTT CLI Commands ## Introduction The NTT Command-Line Interface (CLI) is a powerful tool for managing native token transfers across multiple blockchain networks within the Wormhole ecosystem. This page provides a comprehensive list of available commands, their descriptions, and examples to help you interact with and configure the NTT system effectively. Whether initializing deployments, updating configurations, or working with specific chains, the NTT CLI simplifies these operations through its intuitive commands. If you haven't installed the NTT CLI yet, follow the [NTT Installation Guide](/docs/build/transfers/native-token-transfers/deployment-process/installation/#installation){target=\_blank} to set it up before proceeding. ## Table of Commands The following table lists the available NTT CLI commands, descriptions, and examples. To explore detailed information about any NTT CLI command, including its options and examples, you can append `--help` to the command. This will display a comprehensive guide for the specific command. ### General Commands | Command | Description | Examples | |-----------------------------------------|-------------------------------------------------------|--------------------------| | `ntt update` | update the NTT CLI | `ntt update` | | `ntt new ` | create a new NTT project | `ntt new my-ntt-project` | | `ntt add-chain ` | add a chain to the deployment file | `ntt add-chain Ethereum --token 0x1234... --mode burning --latest`| | `ntt upgrade ` | upgrade the contract on a specific chain | `ntt upgrade Solana --ver 1.1.0`| | `ntt clone
` | initialize a deployment file from an existing contract| `ntt clone Mainnet Solana Sol5678...`| | `ntt init ` | initialize a deployment file | `ntt init devnet` | | `ntt pull` | pull the remote configuration | `ntt pull` | | `ntt push` | push the local configuration | `ntt push` | | `ntt status` | check the status of the deployment | `ntt status` | ### Configuration Commands | Command | Description | Examples | |---------------------------------------------|----------------------------------------|-------------------------------------| | `ntt config set-chain `| set a configuration value for a chain | `ntt config set-chain Ethereum scan_api_key`| | `ntt config unset-chain ` | unset a configuration value for a chain| `ntt config unset-chain Ethereum scan_api_key`| | `ntt config get-chain ` | get a configuration value for a chain | `ntt config get-chain Ethereum scan_api_key`| ### Solana Commands | Command | Description | Examples | |-----------------------------------------------|---------------------------------------------------------|------------------| | `ntt solana key-base58 ` | print private key in base58 | `ntt solana key-base58 /path/to/keypair.json`| | `ntt solana token-authority ` | print the token authority address for a given program ID| `ntt solana token-authority Sol1234...`| | `ntt solana ata `| print the token authority address for a given program ID| `ntt solana ata Mint123... Owner123... token22`| ## Where to Go Next
- :octicons-gear-16:{ .lg .middle } **Configure NTT** --- Find information on configuring NTT, including guidance on setting Owner and Pauser access control roles and management of rate-limiting. [:custom-arrow: Configure your NTT deployment](/docs/build/transfers/native-token-transfers/configuration/) - :octicons-question-16:{ .lg .middle } **NTT FAQs** --- Frequently asked questions about Wormhole Native Token Transfers, including cross-chain lending, SDK usage, custom RPCs, and integration challenges. [:custom-arrow: Check out the FAQs](/docs/build/transfers/native-token-transfers/faqs/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/configuration/access-control/ --- BEGIN CONTENT --- --- title: Native Token Transfers Access Control description: Learn about the owner and pauser access roles for the NTT manager contract, which can be used to pause and un-pause token transfers. categories: NTT, Transfer --- ## Owner and Pauser Roles Pausing the Native Toke Transfer (NTT) Manager Contract will disallow initiating new token transfers. While the contract is paused, in-flight transfers can still be redeemed (subject to rate limits if configured). NTT can be paused on a particular chain by updating the `paused` parameter on the deployment to `true` via the NTT CLI, then performing `ntt push` to sync the local configuration with the on-chain deployment. - **Owner** - full control over NTT contracts, can perform administrative functions. Has the ability to un-pause contracts if they have been paused - **Pauser** - can pause NTT contracts to halt token transfers temporarily. This role is crucial for responding quickly to adverse events without a prolonged governance process. Cannot un-pause contracts You may verify the current owner, pauser, and paused status of the NTT Manager contract on the `deployment.json` file in your NTT project directory. ```json { "network": "Testnet", "chains": { "Sepolia": { "version": "1.1.0", "mode": "burning", "paused": true, // set to true to pause the contract "owner": "0x0088DFAC40029f266e0FF62B82E47A07467A0345", "manager": "0x5592809cf5352a882Ad5E9d435C6B7355B716357", //... "pauser": "0x0088DFAC40029f266e0FF62B82E47A07467A0345" } } } ``` !!! note While the `Pauser` can pause contracts, the ability to un-pause contracts is callable only by the `Owner`. The `Owner` and the `Pauser` addresses can each pause the contract. Since the contract `Owner` address is typically a multisig or a more complex DAO governance contract, and pausing the contract only affects the availability of token transfers, protocols can choose to set the `Pauser` address to be a different address. Creating a separate `Pauser` helps protocols respond quickly to potential risks without going through a drawn-out process. Consider separating `Owner` and `Pauser` roles for your multichain deployment. `Owner` and `Pauser` roles are defined directly on the `NttManager` contract. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/configuration/ --- BEGIN CONTENT --- --- title: Native Token Transfers (NTT) - Configuration description: This section contains information on configuring Native Token Transfers (NTT), including guidance on setting Owner and Pauser access control roles and management of rate-limiting. categories: NTT, Transfer --- # Configure Native Token Transfers (NTT) ## Get Started This section contains information on configuring Native Token Transfers (NTT), including guidance on setting Owner and Pauser access control roles and management of rate-limiting.
- :octicons-clock-16:{ .lg .middle } **Rate Limiting** --- Discover options for configuring rate limits and how queueing effects transaction flow. [:custom-arrow: Explore rate limit options](/docs/build/transfers/native-token-transfers/configuration/rate-limiting/) - :octicons-unlock-16:{ .lg .middle } **Access Control** --- Learn more about access control, including why you should consider setting a separate Pauser address as part of your development security plan. [:custom-arrow: Explore access control roles](/docs/build/transfers/native-token-transfers/configuration/access-control/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/configuration/rate-limiting/ --- BEGIN CONTENT --- --- title: Native Token Transfers Rate Limiting description: Learn about rate limits in Wormhole NTT by configuring send/receive limits, queuing, and canceling flows to manage multichain token transfers efficiently. categories: NTT, Transfer --- ## Introduction The Native Token Transfer (NTT) framework provides configurable per-chain rate limits for sending and receiving token transfers. Integrators can manage these limits via their own governance processes to quickly adapt to on-chain activity. If a transfer is rate-limited on the source chain and queueing is enabled via `shouldQueue = true`, the transfer is placed into an outbound queue and can be released after the rate limit expires. You can configure the following limits on every chain where NTT is deployed directly using the manager: - **Sending limit** - a single outbound limit for sending tokens from the chain - **Per-chain receiving limits** - the maximum receiving limit, which can be configured on a per-chain basis. For example, allowing 100 tokens to be received from Ethereum but only 50 tokens to be received from Arbitrum Rate limits are replenished every second over a fixed duration. While the default duration is 24 hours, the value is configurable at contract creation. Rate-limited transfers on the destination chain are added to an inbound queue with a similar release delay. ## Update Rate Limits To configure or update the sending and receiving rate limits, follow these steps: 1. **Locate the deployment file** - open the `deployment.json` file in your NTT project directory. This file contains the configuration for your deployed contracts 2. **Modify the limits section** - for each chain, locate the limits field and update the outbound and inbound values as needed ```json "limits": { "outbound": "1000.000000000000000000", "inbound": { "Ethereum": "100.000000000000000000", "Arbitrum": "50.000000000000000000" } } ``` - **`outbound`** - sets the maximum tokens allowed to leave the chain - **`inbound`** - configures per-chain receiving limits for tokens arriving from specific chains 3. **Push the configuration** - use the NTT CLI to synchronize the updated configuration with the blockchain ```bash ntt push ``` 4. **Verify the changes** - after pushing, confirm the new rate limits by checking the deployment status ```bash ntt status ``` ???- note "`deployment.json` example" ```json { "network": "Testnet", "chains": { "Sepolia": { "version": "1.1.0", "mode": "burning", "paused": false, "owner": "0x0088DFAC40029f266e0FF62B82E47A07467A0345", "manager": "0x5592809cf5352a882Ad5E9d435C6B7355B716357", "token": "0x5CF5D6f366eEa7123BeECec1B7c44B2493569995", "transceivers": { "threshold": 1, "wormhole": { "address": "0x91D4E9629545129D427Fd416860696a9659AD6a1", "pauser": "0x0088DFAC40029f266e0FF62B82E47A07467A0345" } }, "limits": { "outbound": "184467440737.095516150000000000", "inbound": { "ArbitrumSepolia": "500.000000000000000000" } }, "pauser": "0x0088DFAC40029f266e0FF62B82E47A07467A0345" } } } ``` ## Queuing Mechanism When a transfer exceeds the rate limit, it is held in a queue and can be released after the set rate limit duration has expired. The sending and receiving queuing behavior is as follows: - **Sending** - if an outbound transfer violates rate limits, users can either revert and try again later or queue their transfer. Users must return after the queue duration has expired to complete sending their transfer - **Receiving** - if an inbound transfer violates rate limits, it is in a queue. Users or relayers must return after the queue duration has expired to complete receiving their transfer on the destination chain Queuing is configured dynamically during each transfer by passing the `shouldQueue` parameter to the [`transfer` function](https://github.com/wormhole-foundation/native-token-transfers/blob/5e7ceaef9a5e7eaa13e823a67c611dc684cc0c1d/evm/src/NttManager/NttManager.sol#L171-L182){target=\_blank} in the `NttManager` contract. ## Cancel Flows If users bridge frequently between a given source chain and destination chain, the capacity could be exhausted quickly. Loss of capacity can leave other users rate-limited, potentially delaying their transfers. The outbound transfer cancels the inbound rate limit on the source chain to avoid unintentional delays. This allows for refilling the inbound rate limit by an amount equal to the outbound transfer amount and vice-versa, with the inbound transfer canceling the outbound rate limit on the destination chain and refilling the outbound rate limit with an amount. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-evm/ --- BEGIN CONTENT --- --- title: Native Token Transfers EVM Deployment description: Deploy and configure Wormhole’s Native Token Transfers (NTT) for EVM chains, including setup, token compatibility, mint/burn modes, and CLI usage. categories: NTT, Transfer --- # Native Token Transfers (NTT) EVM Development ## Deploy Your Token and Ensure Compatibility If you still need to do so, deploy the token contract to the destination or spoke chains. ### Requirements for Token Deployment Wormhole’s NTT is an open framework that supports various deployment modes. The NTT CLI currently supports two deployment modes: burn-and-mint and hub-and-spoke. These modes differ in how tokens are managed across chains. #### Burn-and-Mint Mode Tokens integrated with `NttManager` in `burning` mode require the following two functions to be present: - `burn(uint256 amount)` - `mint(address account, uint256 amount)` These functions aren't part of the standard ERC-20 interface. The [`INttToken` interface](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/INttToken.sol){target=\_blank} documents the required functions and convenience methods, errors, and events. ??? code "View the complete `INttToken` Interface`" ```solidity // SPDX-License-Identifier: Apache 2 pragma solidity >=0.8.8 <0.9.0; interface INttToken { /// @notice Error when the caller is not the minter. /// @dev Selector 0x5fb5729e. /// @param caller The caller of the function. error CallerNotMinter(address caller); /// @notice Error when the minter is the zero address. /// @dev Selector 0x04a208c7. error InvalidMinterZeroAddress(); /// @notice Error when insufficient balance to burn the amount. /// @dev Selector 0xcf479181. /// @param balance The balance of the account. /// @param amount The amount to burn. error InsufficientBalance(uint256 balance, uint256 amount); /// @notice The minter has been changed. /// @dev Topic0 /// 0x0b5e7be615a67a819aff3f47c967d1535cead1b98db60fafdcbf22dcaa8fa5a9. /// @param newMinter The new minter. event NewMinter(address previousMinter, address newMinter); // NOTE: the `mint` method is not present in the standard ERC20 interface. function mint(address account, uint256 amount) external; // NOTE: the `setMinter` method is not present in the standard ERC20 interface. function setMinter(address newMinter) external; // NOTE: NttTokens in `burn` mode require the `burn` method to be present. // This method is not present in the standard ERC20 interface, but is // found in the `ERC20Burnable` interface. function burn(uint256 amount) external; } ``` Later, you set mint authority to the corresponding `NttManager` contract. You can also follow the scripts in the [example NTT token](https://github.com/wormhole-foundation/example-ntt-token){target=\_blank} repository to deploy a token contract. #### Hub-and-Spoke Mode A central hub chain (e.g., Ethereum) manages the total token supply in hub-and-spoke mode. Other chains (spokes) mint or burn tokens during cross-chain transfers, ensuring consistency with the locked tokens on the hub chain. - **Hub chain** - tokens are locked on the hub chain when transferring to spoke chains - **Spoke chains** - tokens are native to the spoke chains and are either minted or burned during cross-chain transfers !!! note The only requirement for using the NTT framework is an ERC20 token, which can be newly deployed or existing. Steps like setting mint authority apply only to spoke chains. For example, when transferring tokens from Ethereum (hub) to Polygon (spoke), the NTT Manager locks tokens on Ethereum, and the corresponding amount is minted on Polygon. Similarly, transferring tokens back from Polygon to Ethereum burns the tokens on Polygon and unlocks the equivalent tokens on Ethereum. This process ensures that the total token supply remains consistent across all chains, with the hub chain acting as the source of truth. For more detailed information, see the [Deployment Models](/docs/learn/transfers/native-token-transfers/deployment/){target=\_blank} page. ### Key Differences Between Modes - **Burn-and-mint** - tokens must implement custom `mint` and `burn` functions, allowing each chain to manage token issuance independently - **Hub-and-spoke** - tokens only need to be ERC20 compliant, with the hub chain acting as the source of truth for supply consistency ## Deploy NTT Create a new NTT project: ```bash ntt new my-ntt-deployment cd my-ntt-deployment ``` Initialize a new `deployment.json` file specifying the network: === "Testnet" ```bash ntt init Testnet ``` === "Mainnet" ```bash ntt init Mainnet ``` Ensure you have set up your environment correctly: ```bash export ETH_PRIVATE_KEY=INSERT_PRIVATE_KEY ``` Add each chain you'll be deploying to. The following example demonstrates configuring NTT in burn-and-mint mode on Ethereum Sepolia and Arbitrum Sepolia: ```bash # Set scanner API Keys as environment variables export SEPOLIA_SCAN_API_KEY=INSERT_ETHERSCAN_SEPOLIA_API_KEY export ARBITRUMSEPOLIA_SCAN_API_KEY=INSERT_ARBISCAN_SEPOLIA_API_KEY # Add each chain # The contracts will be automatically verified using the scanner API keys above ntt add-chain Sepolia --latest --mode burning --token INSERT_YOUR_TOKEN_ADDRESS ntt add-chain ArbitrumSepolia --latest --mode burning --token INSERT_YOUR_TOKEN_ADDRESS ``` While not recommended, you can pass the `-skip-verify` flag to the `ntt add-chain` command if you want to skip contract verification. The `ntt add-chain` command takes the following parameters: - Name of each chain - Version of NTT to deploy (use `--latest` for the latest contract versions) - Mode (either `burning` or `locking`) - Your token contract address The NTT CLI prints detailed logs and transaction hashes, so you can see exactly what's happening under the hood. ## Configure NTT The NTT CLI takes inspiration from [git](https://git-scm.com/){target=\_blank}. You can run: - `ntt status` - checks whether your `deployment.json` file is consistent with what is on-chain - `ntt pull` - syncs your `deployment.json` file with the on-chain configuration and set up rate limits with the appropriate number of decimals, depending on the specific chain. For example: For Solana, the limits are set with 9 decimal places: ```json "inbound": { "Sepolia": "1000.000000000" // inbound limit from Sepolia to Solana } ``` For Sepolia (Ethereum Testnet), the limits are set with 18 decimal places: ```json "inbound": { "Solana": "1000.000000000000000000" // inbound limit from Solana to Sepolia } ``` This initial configuration ensures that the rate limits are correctly represented for each chain's token precision - `ntt push` - syncs the on-chain configuration with local changes made to your `deployment.json` file After you deploy the NTT contracts, ensure that the deployment is properly configured and your local representation is consistent with the actual on-chain state by running `ntt status` and following the instructions shown on the screen. ## Set Token Minter to NTT Manager The final step in the deployment process is to set the NTT Manager as a minter of your token on all chains you have deployed to in `burning` mode. When performing a hub-and-spoke deployment, it is only necessary to set the NTT Manager as a minter of the token on each spoke chain. !!! note The required NTT Manager address can be found in the `deployment.json` file. - If you followed the [`INttToken`](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/interfaces/INttToken.sol){target=\_blank} interface, you can execute the `setMinter(address newMinter)` function ```json cast send $TOKEN_ADDRESS "setMinter(address)" $NTT_MANAGER_ADDRESS --private-key $ETH_PRIVATE_KEY --rpc-url $YOUR_RPC_URL ``` - If you have a custom process to manage token minters, you should now follow that process to add the corresponding NTT Manager as a minter By default, NTT transfers to EVM blockchains support automatic relaying via the Wormhole relayer, which doesn't require the user to perform a transaction on the destination chain to complete the transfer. !!!important To proceed with testing and find integration examples, check out the [NTT Post Deployment](/docs/build/transfers/native-token-transfers/deployment-process/post-deployment/){target=\_blank} page. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-solana/ --- BEGIN CONTENT --- --- title: Native Token Transfers Solana Deployment description: Deploy and configure Wormhole's Native Token Transfers (NTT) for Solana, including setup, token compatibility, mint/burn modes, and CLI usage. categories: NTT, Transfer --- # Deploy Native Token Transfers on Solana [Native Token Transfers (NTT)](/docs/learn/transfers/native-token-transfers/overview/){target=\_blank} enable seamless multichain transfers of SPL tokens on Solana using Wormhole's messaging protocol. Instead of creating wrapped tokens, NTT allows native assets to move across chains while maintaining their original properties. This guide walks you through deploying NTT on Solana, including setting up dependencies, configuring token compatibility, and using the NTT CLI to deploy in hub-and-spoke or burn-and-mint mode. By the end, a fully deployed NTT will be set up, allowing your token to transfer between Solana and other supported chains. ## Prerequisites Before deploying NTT on Solana, ensure you have the following: - [Rust](https://www.rust-lang.org/tools/install){target=\_blank} - [Solana](https://docs.solanalabs.com/cli/install){target=\_blank} **`{{ ntt.solana_cli_version }}`** - [Anchor](https://www.anchor-lang.com/docs/installation){target=\_blank} **`{{ ntt.anchor_version }}`** Use the Solana and Anchor versions listed above to avoid compatibility issues while following this guide. ## Overview of the Deployment Process Deploying NTT with the CLI on Solana follows a structured process: 1. **Choose your token setup**: - **Use an existing SPL token** - if your token is already deployed on Solana, you can skip token creation and move directly to the [Set Up NTT](#set-up-ntt) section - **Create a new SPL token** - if you don't already have an SPL token deployed, you'll need to deploy and configure it on Solana before integrating with Wormhole's NTT ???- interface "Create and Mint SPL Tokens" This section walks you through generating a Solana wallet, deploying an SPL token, creating a token account, and minting tokens. 1. **Generate a Solana key pair** - run the following command to create a new wallet: ```bash solana-keygen grind --starts-with w:1 --ignore-case ``` 2. **Set Solana configuration** - configure the Solana CLI to use the generated key pair using the following command: ```bash solana config set --keypair INSERT_PATH_TO_KEYPAIR_JSON ``` 3. **Select an RPC URL** - configure Solana to use the appropriate network using one of the following commands: === "Mainnet" ```bash solana config set -um ``` === "Testnet" ```bash solana config set -ut ``` === "Devnet" ```bash solana config set -ud ``` 4. **Fund your wallet** - ensure you have enough SOL to create a token. If deploying on devnet, request an airdrop with the following commands: ```bash solana airdrop 2 solana balance ``` 5. **Install SPL Token CLI** - install or update the required [CLI tool](https://spl.solana.com/token){target=\_blank} ```bash cargo install spl-token-cli ``` 6. **Create a new SPL token** - initialize the token on Solana ```bash spl-token create-token ``` 7. **Create a token account** - generate an account to hold the token ```bash spl-token create-account INSERT_TOKEN_ADDRESS ``` 8. **Mint tokens** - send 1000 tokens to the created account ```bash spl-token mint INSERT_TOKEN_ADDRESS 1000 ``` !!! note NTT versions `>=v2.0.0+solana` support SPL tokens with [transfer hooks](https://spl.solana.com/transfer-hook-interface){target=\_blank}. 2. **Choose your [deployment model](/docs/learn/transfers/native-token-transfers/deployment/){target=\_blank}**: - **Hub-and-spoke** - tokens are locked on a hub chain and minted on destination spoke chains. Since the token supply remains controlled by the hub chain, no changes to the minting authority are required - **Burn-and-mint** - tokens are burned on the source chain and minted on the destination chain. This requires transferring the SPL token's minting authority to the Program Derived Address (PDA) controlled by the NTT program 3. **Deploy and configure NTT** - use the NTT CLI to initialize and deploy the NTT program, specifying your SPL token and deployment mode Following this process, your token will fully integrate with NTT, enabling seamless transfers between Solana and other chains. By default, NTT transfers to Solana require manual [relaying](/docs/learn/infrastructure/relayer/){target=\_blank}, meaning users must complete a transaction on Solana to finalize the transfer. For automatic relaying, where transactions are completed without user intervention, additional setup is required. [Contact Wormhole contributors](https://forms.clickup.com/45049775/f/1aytxf-10244/JKYWRUQ70AUI99F32Q){target=\_blank} to enable automatic relaying support for your deployment. ## Set Up NTT To integrate your token with NTT on Solana, you must initialize the deployment and configure its parameters. This process sets up the required contracts and may generate key pairs if they don't exist. These key pairs are used to sign transactions and authorize actions within the NTT deployment. The [NTT CLI](/docs/build/transfers/native-token-transfers/deployment-process/installation/){target=\_blank} manages deployments, configures settings, and interacts with the NTT system. Follow these steps to set up NTT using the CLI tool: 1. **Create a new NTT project** - set up a deployment workspace ```bash ntt new INSERT_PROJECT_NAME cd INSERT_PROJECT_NAME ``` 2. **Initialize the deployment** - generate a `deployment.json` file with your deployment settings === "Mainnet" ```bash ntt init Mainnet ``` === "Testnet" ```bash ntt init Testnet ``` !!! note Testnet deployment settings work for both Solana Testnet and Devnet networks. ### Set Mint Authority If you use burn-and-mint mode, follow these steps to enable the NTT program to mint tokens on Solana. This involves deriving the PDA as the token authority and updating the SPL token's minting permissions. If you want to use hub-and-spoke, skip this section and proceed to [Deploy and Configure NTT](#deploy-and-configure-ntt). Before updating the mint authority, you must create metadata for your SPL token. You can visit this repository to see an example of [how to create metadata for your SPL token](https://github.com/wormhole-foundation/demo-metaplex-metadata/blob/main/src/token-metadata.ts){target=\_blank}. Follow these steps to set the mint authority using the NTT CLI: 1. **Generate an NTT program key pair** - create a unique key pair for the NTT program. The key pair must start with "ntt" to identify it as belonging to the NTT deployment ```bash solana-keygen grind --starts-with ntt:1 --ignore-case ``` 2. **Derive the token authority** - generate the PDA, which will manage token minting ```bash ntt solana token-authority INSERT_YOUR_NTT_PROGRAM_KEY_PAIR ``` 3. **Set SPL token mint authority** - delegate minting control to the derived PDA ```bash spl-token authorize INSERT_TOKEN_ADDRESS mint INSERT_DERIVED_PDA ``` ## Deploy and Configure NTT After setting up your deployment, finalize the configuration and deploy the NTT program on Solana by following these steps: 1. **Deploy NTT to Solana** - run the appropriate command based on your deployment mode: === "Burn-and-Mint" ```bash ntt add-chain Solana --latest --mode burning --token INSERT_TOKEN_ADDRESS --payer INSERT_YOUR_KEYPAIR_JSON --program-key INSERT_YOUR_NTT_PROGRAM_KEYPAIR_JSON ``` === "Hub-and-Spoke" ```bash ntt add-chain Solana --latest --mode locking --token INSERT_TOKEN_ADDRESS --payer INSERT_YOUR_KEYPAIR_JSON --program-key INSERT_YOUR_NTT_PROGRAM_KEYPAIR_JSON ``` !!! tip The `add-chain` command accepts an optional `--solana-priority-fee` flag, which sets the priority fee in microlamports. The default is `50000`. 2. **Verify deployment status** - after deployment, check if your `deployment.json` file matches the on-chain configuration using the following command: ```bash ntt status ``` If needed, sync your local configuration with the on-chain state: ```bash ntt pull ``` 3. **Configure inbound and outbound rate limits** - by default, the inbound and outbound limits are set to `0` and must be updated before deployment. For EVM chains, values must be set using 18 decimals, while Solana uses nine decimals. Open your `deployment.json` file and adjust the values based on your use case: ```json "inbound": { "Sepolia": "1000.000000000" // inbound limit from Sepolia to Solana }, "outbound": { "Sepolia": "1000.000000000" // outbound limit from Solana to Sepolia } ``` 4. **Push the final deployment** - once rate limits are set, push the deployment to Solana using the specified key pair to cover gas fees ```bash ntt push --payer INSERT_YOUR_KEYPAIR_JSON ``` ### Troubleshoot Deployment Issues If your deployment fails, it may be due to leftover program buffer accounts taking up storage on Solana. These temporary accounts are created during deployment but may persist if interrupted. Refer to the [Solana program deployment guide](https://solana.com/docs/programs/deploying#program-buffer-accounts){target=\_blank} for instructions on finding and closing these buffer accounts to free up space and allow redeployment. ## Where to Go Next
- :octicons-globe-16:{ .lg .middle } **Deploy NTT on EVM Chains** --- After deploying NTT on Solana, deploy and integrate it on EVM chains to enable seamless multichain transfers. [:custom-arrow: Deploy NTT on EVM](/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-evm/){target=\_blank} - :octicons-tools-16:{ .lg .middle } **Test Your Deployment** --- Follow the NTT Post Deployment Guide for integration examples and testing instructions. [:custom-arrow: Test Your NTT deployment](/docs/build/transfers/native-token-transfers/deployment-process/post-deployment/){target=\_blank} - :octicons-tools-16:{ .lg .middle } **Add NTT to Your dApp** --- Configure Wormhole Connect, a plug-and-play bridging UI, to enable multichain transfers for your token. [:custom-arrow: Use Connect to Integrate NTT](/docs/build/transfers/connect/){target=\_blank}
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ --- BEGIN CONTENT --- --- title: Deploy Native Token Transfers with Launchpad description: Deploy a new token or extend an existing one across multiple chains with the NTT Launchpad. Manage transfers, supply, and settings—all from a single platform. categories: NTT, Transfer --- # Deploy Native Token Transfers with Launchpad ## Introduction The [Native Token Transfers (NTT) Launchpad](https://ntt.wormhole.com/){target=\_blank} is a Wormhole-managed UI application that provides a step-by-step interface for deploying NTT across multiple blockchains. Instead of manually deploying contracts on each chain, configuring relayers, and managing cross-chain communication, you can quickly launch or expand tokens with just a few clicks. The Launchpad automates deployment, reducing complexity and saving time. This guide covers: - Launching a new cross-chain token - Expanding an existing token for NTT - Managing tokens via the dashboard and settings ## Prerequisites - An EVM-compatible wallet (e.g., [MetaMask](https://metamask.io/){target=\_blank}, [Phantom](https://phantom.com/){target=\_blank}, etc.) - Minimum ETH (or equivalent) for gas fees per deployment ## Supported Blockchains The NTT Launchpad currently supports deployments on the following mainnet chains: - Ethereum - Arbitrum One - Base - Berachain - Blast - BNB Smart Chain - Ink - Optimism Mainnet - Polygon ## Choose Your Path Once ready, choose an option to proceed: - [**Launch a Cross-Chain Token**](#launch-a-cross-chain-token) - deploy a brand-new token that is NTT-ready from day one, enabling seamless transfers across multiple blockchains - [**Expand Your Existing Token**](#expand-your-existing-token) - if you already have a token deployed on different chains, integrate it with NTT to enable NTT without modifying its original contract ## Launch a Cross-Chain Token Deploy a new NTT-compatible token that can be transferred across multiple chains. This process sets up your token on a home network and deploys it to additional blockchains. Follow the below steps to get started: 1. Open the [NTT Launchpad](https://ntt.wormhole.com/){target=\_blank}, connect your wallet, and click **Get Started** ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-1.webp) 2. Select **Launch a Cross-Chain Token** ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-2.webp) 3. Set the token details: 1. Select the **home network** from the dropdown menu 2. Enter the **name** for the token 3. Enter the **symbol** of the token 4. Provide the **initial supply** 5. To the token details, click **Next** ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-3.webp) 4. Select the deployment chains: 1. The home network where your token will be deployed will be populated (e.g., Optimism) 2. Choose any additional chains to deploy your token to (e.g., Base) 3. To continue, click **Next** ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-4.webp) 5. To deploy on the first chain (Optimism), click on **Deploy**; if prompted, switch your wallet to the correct network and confirm the transaction ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-5.webp) 6. Once deployed, you can view the transaction in a block explorer and add the token to your wallet ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-6.webp) 7. Repeat the previous step to deploy the token on the second chain (Base). The supply of tokens on Base will be zero since the tokens were all minted on Optimism in the previous step 8. Once both deployments are completed, proceed to the [**Dashboard**](#explore-the-launchpad-dashboard) to manage your token. ## Expand Your Existing Token Expand an existing token to support NTT across multiple chains. This process integrates your deployed token with NTT without modifying its original contract. Follow the steps below to get started: 1. Open the [NTT Launchpad](https://ntt.wormhole.com/){target=\_blank}, connect your wallet, and click **Get Started** ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-1.webp) 2. Select **Expand Your Existing Token** ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-7.webp) 3. Enter the token details: 1. Choose the home network where your token is already deployed (e.g., Optimism) 2. Choose any additional chains to deploy your token to (e.g., Base) 3. To continue, click **Next** ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-8.webp) 4. Select the chains to deploy your token to: 1. The home network where your token is already deployed will be populated (e.g., Optimism) 2. Choose any additional chains to deploy your token to (e.g., Base) 1. Click **Next** ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-9.webp) 5. To deploy on the first chain (Optimism), click on **Deploy**; if prompted, switch your wallet to the correct network and confirm the transaction ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-5.webp) 6. Once deployed, you can view the transaction in a block explorer and add the token to your wallet ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-6.webp) 7. Repeat the previous step to deploy the token on the second chain (Base). The supply of tokens on Base will be zero since the tokens were all minted on Optimism in the previous step 8. Now that your token has been deployed on multiple chains click [**Dashboard**](#explore-the-launchpad-dashboard) to review its details ## Explore the Launchpad Dashboard To access the **Dashboard** from the [Launchpad home page](https://ntt.wormhole.com/){target=\_blank}, click on **Manage Deployment**. Here, you can view deployment status, monitor supply across chains, and configure transfer settings. ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-10.webp) The dashboard provides a high-level view of your token across all deployed chains, including: - Token addresses for each chain - Supply distribution visualization - List of deployed chains, including inbound and outbound transfer limits, which can be modified in [**Settings**](#settings) ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-11.webp) ## Settings The **Settings** page allows you to configure security parameters, role management, and transfer limits for your deployed token. You can switch between chains to manage these settings independently for each deployment. ### Chain Management Use the drop-down menu at the top to select the chain you want to configure. The available options correspond to the chains where your token has already been deployed. Once selected, the page displays token details specific to that chain. From this section, you can also: - **Pause the token** – temporarily turn off transfers on the selected chain - **Deploy to a new chain** – expand your token by deploying it to an additional chain ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-12.webp) ### Role Management This section displays key [roles](/docs/build/transfers/native-token-transfers/configuration/access-control/){target=\_blank} involved in token governance. You can view and modify these roles by selecting a new address and confirming the update. - **Manager’s Owner** – the owner through the `NTTOwner` proxy - **Pauser** – the address authorized to pause transfers ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-13.webp) ### Security Threshold Determine and update how transceivers interact with the token. [Transceivers](/docs/build/transfers/native-token-transfers/managers-transceivers/#transceivers){target=\_blank} route NTT transfers between blockchains, ensuring tokens are correctly sent and received across networks. A higher transceiver threshold increases security by requiring more approvals before processing a transfer, but it may also slow down transactions. A lower threshold allows faster transfers but reduces redundancy in message verification. - **Registered Transceivers** – displays the number of registered transceivers and their addresses - **Transceivers Threshold** – a configurable value that must be less than or equal to the number of transceivers ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-14.webp) ### Peer Chains Limits Define the transfer restrictions for each connected network. You can adjust: - **Sending Limits** – the maximum amount of tokens that can be sent from the home chain - **Receiving Limits** – the maximum amount of tokens that can be received for each of the supported peer chains Enter a new value to adjust limits and click **Update**. The changes will take effect immediately. ![](/docs/images/build/transfers/native-token-transfers/deployment-process/evm-launchpad/ntt-launchpad-15.webp) --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/deployment-process/ --- BEGIN CONTENT --- --- title: Native Token Transfers (NTT) - Deployment description: This section provides information on installing Wormhole's Native Token Transfer framework, deployment to EVM and Solana, and post deployment NTT maintenance. categories: NTT, Transfer --- # Deploy Native Token Transfers (NTT) ## Get Started This section provides information on installing Wormhole's Native Token Transfer framework, deployment to EVM and Solana, and post deployment NTT maintenance.
- :octicons-download-16:{ .lg .middle } **Installation** --- Prerequisites and commands for installing the NTT CLI and working with the NTT framework. [:custom-arrow: Install the NTT CLI](/docs/build/transfers/native-token-transfers/deployment-process/installation/) - :octicons-rocket-16:{ .lg .middle } **Deploy to EVM** --- Find information on preparing for NTT deployment to EVM, including an example NTT token repository. [:custom-arrow: Deploy token and NTT contracts](/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-evm/) - :octicons-rocket-16:{ .lg .middle } **Deploy to EVM Chains via Launchpad** --- Deploy a new token or extend an existing one across multiple chains with the NTT Launchpad. Manage transfers, supply, and settings—all from a single platform. [:custom-arrow: Deploy via Launchpad](/docs/build/transfers/native-token-transfers/deployment-process/evm-launchpad/) - :octicons-rocket-16:{ .lg .middle } **Deploy to Solana** --- Your guide to NTT deployment to Solana, including setup, token compatibility, mint/burn modes, and CLI usage. [:custom-arrow: Deploy token and NTT contracts](/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-solana/) - :octicons-search-16:{ .lg .middle } **Post Deployment** --- Learn how to best monitor and maintain your NTT deployment to get the most out of your Wormhole integration while providing security for users. [:custom-arrow: Explore next steps](/docs/build/transfers/native-token-transfers/deployment-process/post-deployment/) - :octicons-alert-16:{ .lg .middle } **Troubleshooting** --- Explore solutions and detailed guidance in our troubleshooting guide to resolve issues with NTT deployment. [:custom-arrow: Get help](/docs/build/transfers/native-token-transfers/deployment-process/troubleshooting/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/deployment-process/installation/ --- BEGIN CONTENT --- --- title: Native Token Transfers Installation description: Learn how to Install Wormhole’s Native Token Transfers (NTT) framework, a flexible and composable framework for transferring tokens across blockchains. categories: NTT, Transfer --- # Install the Native Token Transfers CLI In this video, the Wormhole team walks you through installing the [Native Token Transfers (NTT) CLI](https://github.com/wormhole-foundation/native-token-transfers/tree/main/cli){target=\_blank}. You’ll see a practical demonstration of running commands, verifying your installation, and addressing common issues that might arise. If you prefer to follow written instructions or want a quick reference for each step, scroll down for the detailed installation guide. To start using the NTT CLI, you can generate test tokens for development with the [`example-ntt-token`](https://github.com/wormhole-foundation/example-ntt-token){target=\_blank} GitHub repository by following the README instructions.
## Install NTT CLI The fastest way to deploy Native Token Transfers (NTT) is using the NTT CLI. As prerequisites, ensure you have the following installed: - Install [Bun](https://bun.sh/docs/installation){target=\_blank} Follow these steps to install the NTT CLI: 1. Run the installation command in your terminal: ```bash curl -fsSL https://raw.githubusercontent.com/wormhole-foundation/native-token-transfers/main/cli/install.sh | bash ``` 2. Verify the NTT CLI is installed: ```bash ntt --version ``` 3. Once installed, check out the available [NTT CLI Commands](/docs/build/transfers/native-token-transfers/cli-commands/){target=\_blank} to start using the CLI ## Update NTT CLI To update an existing NTT CLI installation, run the following command in your terminal: ```bash ntt update ``` NTT CLI installations and updates will always pick up the latest tag with name vX.Y.Z+cli and verify that the underlying commit is included in main. For local development, you can update your CLI version from a specific branch or install from a local path. To install from a specific branch, run: ```bash ntt update --branch foo ``` To install locally, run: ```bash ntt update --path path/to/ntt/repo ``` Git branch and local installations enable a fast iteration loop as changes to the CLI code will immediately be reflected in the running binary without having to run any build steps. ## Where to Go Next
- :octicons-tools-16:{ .lg .middle } **Deploy to EVM Chains** --- Deploy and configure Wormhole’s Native Token Transfers (NTT) for EVM chains, including setup, token compatibility, mint/burn modes, and CLI usage. [:custom-arrow: Deploy NTT to EVM chains](/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-evm/) - :octicons-tools-16:{ .lg .middle } **Deploy to Solana** --- Deploy and configure Wormhole's Native Token Transfers (NTT) for Solana, including setup, token compatibility, mint/burn modes, and CLI usage. [:custom-arrow: Deploy NTT to Solana](/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-solana/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/deployment-process/post-deployment/ --- BEGIN CONTENT --- --- title: Native Token Transfers Post Deployment description: Learn post-deployment guidelines for optimizing Wormhole NTT, which include testing, security, frontend integration, ecosystem coordination, and monitoring. categories: NTT, Transfer --- # Native Token Transfers Post Deployment To offer the best user experience and ensure the most robust deployment, Wormhole contributors recommend the following after you have deployed Native Token Transfers (NTT): - Implement a robust testing plan for your multichain token before launching - Ensure comprehensive, documented security measures are followed for custody of contract ownership, control of keys, and access control roles. Check the [NTT configuration](/docs/build/transfers/native-token-transfers/configuration/){target=\_blank} for more details on ownership and rate limits - Consider a streamlined, customizable frontend such as [Wormhole Connect](/docs/build/transfers/connect/){target=\_blank} for an optimized user experience - Alternatively, the [Wormhole TypeScript SDK](/docs/build/toolkit/typescript-sdk/){target=\_blank} allows for a direct integration into your infrastructure - Ensure ecosystem actors such as block explorers, automated security tools (such as BlockAid and Blowfish), and wallets (such as MetaMask, Backpack, and Phantom) are aware of your multichain deployment and that it is labeled appropriately - Monitor and maintain your multichain deployment ## Manual Relaying for Solana Transfers By default, NTT transfers to Solana require manual relaying, meaning that after initiating a cross-chain transfer, the recipient must submit an on-chain transaction to claim the tokens. This step ensures that tokens are properly minted or unlocked on Solana and prevents unauthorized claims. [Wormhole Connect](/docs/build/applications/connect/){target=\_blank} support this process automatically. ## Where to Go Next
- :octicons-code-16:{ .lg .middle } **Wormhole NTT Connect Demo** --- Check out an example project that uses a Vite-React TypeScript application and integrates it with Wormhole Connect, a customizable widget for cross-chain asset transfers. [:custom-arrow: Explore the NTT Connect demo](https://github.com/wormhole-foundation/demo-ntt-connect) - :octicons-code-16:{ .lg .middle } **Wormhole NTT TypeScript SDK Demo** --- Reference an example project that uses the Wormhole TypeScript SDK to facilitate token transfers between different blockchain networks after deploying the NTT framework. [:custom-arrow: Explore the NTT TypeScript SDK demo](https://github.com/wormhole-foundation/demo-ntt-ts-sdk)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/deployment-process/troubleshooting/ --- BEGIN CONTENT --- --- title: Troubleshooting NTT Deployment description: Resolve common issues in NTT deployment with this troubleshooting guide covering Solana, EVM, mint authority, decimals, and rate limits. categories: NTT, Transfer --- # Troubleshooting NTT Deployment If you encounter issues during the NTT deployment process, check the following common points: - **Solana and Anchor versions** - ensure you are using the expected versions of Solana and Anchor as outlined in the [deployment page](/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-solana/#install-dependencies){target=\_blank} - [Solana](https://docs.solanalabs.com/cli/install){target=\_blank} **`{{ ntt.solana_cli_version }}`** - [Anchor](https://www.anchor-lang.com/docs/installation){target=\_blank} **`{{ ntt.anchor_version }}`** - **Token compliance on EVM** - verify that your token is an ERC20 token on the EVM chain - **Mint authority transfer** - **For burn or spoke tokens on Solana** - ensure the token mint authority was transferred as described in the [set SPL Token Mint Authority](/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-solana/#set-spl-token-mint-authority){target=\_blank} section - **For EVM tokens** - confirm the token minter was set to the NTT Manager. Refer to the [set Token Minter to NTT Manager](/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-evm/#set-token-minter-to-ntt-manager){target=\_blank} section for details - **Decimal configuration** - run `ntt pull` to correctly configure the decimals in your `deployment.json` file. More details in the [configure NTT](/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-solana/#configure-ntt){target=\_blank} section - **Rate limit configuration** - increase your rate limits to a value greater than zero. A rate limit of zero can cause transactions to get stuck. Learn more on how to [configure rate limits](/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-evm/#configure-ntt){target=\_blank} - **Docker environment based on Ubuntu 20.04 with all dependencies required for Wormhole NTT CLI development** - run `docker compose up -d` to start the container in your terminal from the directory containing the `docker-compose.yml` file ???- interface "Dockerfile" ```Dockerfile FROM ubuntu:20.04 # Set environment variables to prevent interactive prompts during installation ENV DEBIAN_FRONTEND=noninteractive # Update and install necessary dependencies RUN apt-get update && apt-get install -y \ curl \ wget \ git \ build-essential \ libssl-dev \ libudev-dev \ pkg-config \ python3 \ python3-pip \ software-properties-common \ ca-certificates \ unzip \ clang \ cmake \ protobuf-compiler \ && apt-get clean && rm -rf /var/lib/apt/lists/* # Install Rust RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y ENV PATH="/root/.cargo/bin:$PATH" # Install Solana CLI ({{ntt.solana_cli_version}}) RUN sh -c "$(curl -sSfL https://release.solana.com/{{ntt.solana_cli_version}}/install)" ENV PATH="/root/.local/share/solana/install/active_release/bin:$PATH" # Install Anchor using avm RUN cargo install --git https://github.com/coral-xyz/anchor avm --locked --force \ && avm install 0.29.0 \ && avm use 0.29.0 ENV PATH="/root/.avm/bin:$PATH" ENV NVM_DIR=/root/.nvm RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash \ && . "$NVM_DIR/nvm.sh" \ && nvm install 22 \ && nvm use 22 \ && nvm alias default 22 ENV PATH="$NVM_DIR/versions/node/v22.12.0/bin:$PATH" # Install Bun RUN curl -fsSL https://bun.sh/install | bash ENV PATH="/root/.bun/bin:$PATH" # Install Foundry RUN curl -L https://foundry.paradigm.xyz | bash ENV PATH="/root/.foundry/bin:${PATH}" RUN /bin/bash -c "source /root/.bashrc && foundryup" # Install Wormhole NTT CLI RUN curl -fsSL https://raw.githubusercontent.com/wormhole-foundation/native-token-transfers/main/cli/install.sh | bash # Add a default working directory WORKDIR /app # Expose port for development if needed EXPOSE 8899 # Entry point for the container CMD ["bash"] ``` ???- interface "docker-compose.yml" ```yml services: portal-ntt: build: context: . dockerfile: Dockerfile platform: linux/amd64 volumes: - ./src:/app working_dir: /app tty: true ``` --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/faqs/ --- BEGIN CONTENT --- --- title: Native Token Transfers FAQs description: Frequently asked questions about Wormhole Native Token Transfers, including cross-chain lending, SDK usage, custom RPCs, and integration challenges. categories: NTT, Transfer --- # Wormhole NTT FAQs ## Do you have an example of how cross-chain lending can be implemented using Wormhole? Yes, we have an example of cross-chain lending that leverages [Wormhole’s Token Bridge](/docs/learn/transfers/token-bridge/){target=\_blank}. In this example, collateral deposits (such as ETH on Ethereum) are bridged to a hub chain. Once the collateral is deposited, the borrowed assets, like wrapped BNB, are bridged to Binance Smart Chain. You can explore the full implementation in the [Wormhole Lending Examples repository](https://github.com/wormhole-foundation/example-wormhole-lending){target=_blank} on GitHub. Alternatively, you can also implement cross-chain lending using [Wormhole’s core messaging](/docs/learn/transfers/native-token-transfers/){target=\_blank} instead of the Token Bridge, which avoids the limitations imposed by governor limits. ETH would be custodied on Ethereum, and BNB on the Binance spoke during this setup. When a user deposits ETH on Ethereum, a core bridge message is sent to the hub for accounting purposes. The hub then emits a message that can be redeemed on Binance to release the BNB. This approach allows for more direct asset control across chains while reducing reliance on Token Bridge limits. ## What causes the "No protocols registered for Evm" error in Wormhole SDK? This error typically occurs when the [Wormhole SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank} cannot recognize or register the necessary EVM protocols, which are required for interacting with Ethereum-based networks. The most common reason for this error is that the relevant EVM package for Wormhole's NTT has not been imported correctly. To resolve this issue, ensure you have imported the appropriate Wormhole SDK package for EVM environments. The necessary package for handling NTT on EVM chains is `@wormhole-foundation/sdk-evm-ntt`. Here's the correct import statement: ```rust import '@wormhole-foundation/sdk-evm-ntt'; ``` By importing this package, the Wormhole SDK can register and utilize the required protocols for EVM chains, enabling cross-chain token transfers using the NTT framework. Ensure to include this import at the start of your code, especially before attempting any interactions with EVM chains in your project. ## How can I transfer ownership of NTT to a multisig? Transferring ownership of Wormhole's NTT to a multisig is a two-step process for safety. This ensures that ownership is not transferred to an address that cannot claim it. Refer to the `transfer_ownership` method in the [NTT Manager Contract](https://github.com/wormhole-foundation/native-token-transfers/blob/main/solana/programs/example-native-token-transfers/src/instructions/admin/transfer_ownership.rs#L55){target=\_blank} to initiate the transfer. 1. **Initiate transfer** - use the `transfer_ownership` method on the NTT Manager contract to set the new owner (the multisig) 2. **Claim ownership** - the multisig must then claim ownership via the `claim_ownership` instruction. If not claimed, the current owner can cancel the transfer 3. **Single-step transfer (Riskier)** - you can also use the `transfer_ownership_one_step_unchecked` method to transfer ownership in a single step, but if the new owner cannot sign, the contract may become locked. Be cautious and ensure the new owner is a Program Derived Address (PDA) For a practical demonstration of transferring ownership of Wormhole's NTT to a multisig on Solana, visit the [GitHub demo](https://github.com/wormhole-foundation/demo-ntt-solana-multisig-tools){target=\_blank} providing scripts and guidance for managing an NTT program using Squads multisig functionality, including ownership transfer procedures. ## How can I specify a custom RPC for NTT? To specify a custom RPC for Wormhole's NTT, create an `overrides.json` file in the root of your deployment directory. This file allows you to define custom RPC endpoints, which can be helpful when you need to connect to specific nodes or networks for better performance, security, or control over the RPC connection. Below’s an example of how the `overrides.json` file should be structured: ???- code "`overrides.json`" ```json { "chains": { "Bsc": { "rpc": "http://127.0.0.1:8545" }, "Sepolia": { "rpc": "http://127.0.0.1:8546" }, "Solana": { "rpc": "http://127.0.0.1:8899" } } } ``` ## How can I redeem tokens if NTT rate limits block them on the target chain? If the rate limits on Wormhole's NTT block tokens from being received on the target chain, the transaction will typically be paused until the rate limits are adjusted. Rate limits are implemented to manage congestion and prevent chain abuse, but they can occasionally delay token redemptions. To resolve this: 1. **Adjust rate limits** - the rate limits must be modified by an administrator or through the appropriate configuration tools to allow the blocked transaction to proceed 2. **Resume transaction flow** - once the rate limits are adjusted, you can resume the flow, which should be visible in the UI. The tokens will then be redeemable on the target chain In most cases, the transaction will resume automatically once the rate limits are adjusted, and the UI will guide you through the redemption process. ## What are the challenges of deploying NTT to non-EVM chains? NTT requires the same transceiver for all routes, limiting flexibility when deploying across EVM and non-EVM chains. For example, if you're deploying to Ethereum, Arbitrum, and Solana, you can't use Wormhole and Axelar as transceivers because Axelar doesn't support Solana. This constraint forces integrators to use a single transceiver (e.g., Wormhole) for all chains, reducing flexibility in optimizing cross-chain transfers. ## Does the NTT manager function as an escrow account for a hub chain? Yes, the NTT manager acts like an escrow account for non-transferable tokens on a hub chain. To manage non-transferable tokens, you would add the NTT manager to the allowlist, ensuring that only the NTT manager can hold and control the tokens as they are transferred across chains. ## Which functions or events does Connect rely on for NTT integration? Connect relies on the NTT SDK for integration, with platform-specific implementations for both [Solana](https://github.com/wormhole-foundation/native-token-transfers/blob/main/solana/ts/sdk/ntt.ts){target=\_blank} and [EVM](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/ts/src/ntt.ts){target=\_blank}. The key methods involved include: - **Initiate and redeem functions** - these functions are essential for initiating token transfers and redeeming them on the destination chain - **Rate capacity methods** - methods for fetching inbound and outbound rate limits are also critical for controlling the flow of tokens and preventing congestion These functions ensure Connect can handle token transfers and manage chain-rate limits. ## How does the relayer contract determine which transceiver to call? The source chain's transceiver includes the destination chain's transceiver in the message via the relayer contract. The admin configures each transceiver's mapping of its peers on other chains. This mapping allows the destination transceiver to verify that the message came from a trusted source. ## How do I create a verifier or transceiver? To run your verifier, you need to implement a transceiver. This involves approximately 200 lines of code, leveraging the base functionality provided by the [abstract transceiver contract](https://github.com/wormhole-foundation/example-native-token-transfers/blob/main/evm/src/Transceiver/Transceiver.sol){target=\_blank}. For reference, you can review the [Axelar transceiver implementation](https://github.com/wormhole-foundation/example-wormhole-axelar-wsteth/blob/main/src/axelar/AxelarTransceiver.sol){target=\_blank}. ## Can I use Hetzner for the NTT deployment? No, using Hetzner servers for Solana deployments is not recommended. Hetzner has blocked Solana network activity on its servers, leading to connection issues. Hetzner nodes will return a `ConnectionRefused: Unable to connect` error for Solana deployments. Therefore, choosing alternative hosting providers that support Solana deployments is advisable to ensure seamless operation. ## How can I transfer tokens with NTT with an additional payload? You can include an extra payload in NTT messages by overriding specific methods in the [NttManager contract](https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/src/NttManager/NttManager.sol){target=\_blank}. - On the source chain, override the [`_handleMsg` function](https://github.com/wormhole-foundation/example-native-token-transfers/blob/main/evm/src/NttManager/NttManager.sol#L216-L226){target=\_blank} to query any additional data you need for the transfer. The extra payload can then be added to the message - On the destination chain override the [`_handleAdditionalPayload` function](https://github.com/wormhole-foundation/example-native-token-transfers/blob/main/evm/src/NttManager/NttManager.sol#L262-L275){target=\_blank} to process and utilize the extra payload sent in the message !!!Important You cannot pass the additional data as part of the entry point directly. Instead, the data must be queried on-chain via the `_handleMsg` method, ensuring the payload is properly included and processed. ## Why use NTT over xERC20? Shortcomings of xERC20: - **Single point of failure** - xERC20 relies on multiple bridges, but a compromise in any single bridge can jeopardize the token. It enforces a 1-of-n design rather than a more robust m-of-n approach - **No pausing** - xERC20 lacks mechanisms to pause operations during emergencies - **No access control** - there are no built-in access controls for managing token transfers securely - **Limited rate limiting** - rate limits are bridge-specific and cannot be set per chain, reducing flexibility and security - **No integration with relaying systems** - xERC20 does not natively support relayer systems, limiting its usability in automated or dynamic setups While xERC20 is an extension of the ERC20 standard, NTT is designed as a framework rather than a rigid standard. It is compatible with any token that supports `burn` and `mint` functions and allows the NTT manager to act as a minter. ## How can I start transferring tokens to a chain that is in burning mode, if no tokens are locked yet? To begin transferring tokens to a chain in burning mode when no tokens are locked, you must first send tokens to the NTT manager to back the supply. The address of the NTT manager can be found in the `deployment.json` file. ## Is there a way to use NTT tokens with chains that don't currently support NTT? Yes. NTT tokens can be used with chains that do not support NTT by leveraging the [Token Bridge](/docs/learn/transfers/token-bridge/){target=\_blank}. For example: - **Wrapped token scenario** - a token, such as the W token, can be bridged to non-NTT networks using the Token Bridge. When the token is bridged to a chain like Sui, a wrapped version of the token is created (e.g., Wrapped W token) - **Unwrapping requirement** - tokens bridged using the Token Bridge cannot be directly transferred to NTT-supported chains. To transfer them, they must first be unwrapped on the non-NTT chain and then transferred via the appropriate mechanism - **Messaging consistency** - the Token Bridge exclusively uses Wormhole messaging, ensuring consistent communication across all chains, whether or not they support NTT This approach ensures interoperability while maintaining the integrity of the token's cross-chain movement. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/ --- BEGIN CONTENT --- --- title: Native Token Transfers (NTT) description: This section provides comprehensive guidance on configuring, deploying, and managing your Native Token Transfers (NTT) integration. categories: NTT, Transfer --- # Native Token Transfers Native Token Transfers (NTT) simplifies and enables seamless, flexible token transfers across blockchains. This section provides comprehensive guidance on configuring, deploying, and managing your NTT integration. It includes information relevant to both new token deployments and existing token management. Visit the [Use Cases](/docs/build/start-building/use-cases/){target=\_blank} and [Product Comparison](/docs/build/start-building/products/){target=\_blank} pages for help determining if NTT will meet the needs of your project. ## Quickstart If needed, you can generate test tokens for development with the [`example-ntt-token`](https://github.com/wormhole-foundation/example-ntt-token){target=\_blank} GitHub repository by following the README instructions. The process for creating, deploying, and monitoring NTTs is as follows. Select the title of each step to view the associated guide: [timeline left(wormhole-docs/.snippets/text/build/transfers/ntt/ntt-deployment-process-timeline.json)] ## Deploy NTTs with Launchpad If you are deploying to EVM blockchains, the [Native Token Transfers (NTT) Launchpad](https://ntt.wormhole.com/){target=\_blank} is a Wormhole-managed UI application that provides a step-by-step interface for deploying NTT. NTT Launchpad replaces manually deploying contracts or configuring relayers for each supported EVM chain. Follow the [Deploy NTT with Launchpad](/docs/build/transfers/native-token-transfers/deployment-process/evm-launchpad/){target=\_blank} guide to create new multichain tokens or integrate existing tokens with just a few clicks. ## Additional Resources
- :octicons-gear-16:{ .lg .middle } **NTT CLI Commands** --- The NTT CLI tool provides a comprehensive set of commands for creating, configuring, deploying, and monitoring NTTs. This page provides a comprehensive list of available NTT CLI commands, their descriptions, and examples to help you interact effectively with the NTT system. [:custom-arrow: NTT CLI Commands](/docs/build/transfers/native-token-transfers/cli-commands/) - :octicons-question-16:{ .lg .middle } **NTT FAQs** --- Frequently asked questions about Wormhole Native Token Transfers, including cross-chain lending, SDK usage, custom RPCs, and integration challenges. [:custom-arrow: Check out the FAQs](/docs/build/transfers/native-token-transfers/faqs/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/native-token-transfers/managers-transceivers/ --- BEGIN CONTENT --- --- title: Managers and Transceivers description: Explore the roles of Managers and Transceivers in NTT cross-chain token transfers, including key functions, lifecycle events, and rate-limiting mechanisms. categories: NTT, Transfer --- # Managers and Transceivers ## Managers _Managers_ oversee the token transfer process and handle rate-limiting and message attestation. They manage interactions with multiple transceivers and ensure that tokens are locked or burned on the source chain before being minted or unlocked on the destination chain. Each NTT manager corresponds to a single token but can control multiple transceivers. Key functions include: - **`transfer`** - initiate the transfer process where tokens on the source chain are locked or burned. This process ensures that an equivalent amount of tokens can be minted or unlocked on the destination chain ```solidity function transfer( uint256 amount, // amount (in atomic units) uint16 recipientChain, // chain ID (Wormhole formatted) bytes32 recipient // recipient address (Wormhole formatted) ) external payable nonReentrant whenNotPaused returns (uint64) ``` - **`quoteDeliveryPrice`** - calculate the cost of sending messages across chains by querying the transceivers for estimates on message delivery fees, allowing users to know the price before initiating a transfer ```solidity function quoteDeliveryPrice( uint16 recipientChain, // chain ID (Wormhole formatted) bytes memory transceiverInstructions // extra instructions for transceivers (Transceiver-dependent on whether extra instructions are used/accepted) ) public view returns (uint256[] memory, uint256) ``` - **`setPeer`** - to maintain secure cross-chain communication, managers establish trust relationships between different instances of NTT manager contracts across chains. By recognizing each other as peers, they ensure that the token transfers happen securely and that rate limits on inbound transactions are respected ```solidity function setPeer( uint16 peerChainId, // chain ID (Wormhole formatted) bytes32 peerContract, // peer NTT Manager address (Wormhole formatted) uint8 decimals, // token decimals on the peer chain uint256 inboundLimit // inbound rate limit (in atomic units) ) public onlyOwner ``` ## Transceivers _Transceivers_ are responsible for routing NTT transfers through the manager on the source chain and ensuring they are delivered to the corresponding manager on the recipient chain. They work with managers to ensure that messages are accurately processed and tokens are correctly transferred, providing a reliable system for cross-chain token transfers. Transceivers can be defined independently of the Wormhole core and modified to support any verification backend. Key functions: - **`sendMessage`** - this external function sends token transfer messages to a specified recipient chain. It encodes the token transfer details into a message format recognized by the system ```solidity function sendMessage( uint16 recipientChain, // chain ID (Wormhole formatted) TransceiverStructs.TransceiverInstruction memory instruction, // extra instruction for the Transceiver (optional, dependent on whether extra instructions are used/accepted for this Transceiver) bytes memory nttManagerMessage, // serialized NTT Manager message, provided by the NTT Manager bytes32 recipientNttManagerAddress, // NTT Manager address on the recipient chain (Wormhole formatted) bytes32 refundAddress // address to receive refunds on the destination chain in case of excess quotes (Wormhole formatted) ) external payable nonReentrant onlyNttManager ``` - **`quoteDeliveryPrice`** - provides an estimation of the cost associated with delivering a message to a target chain and gauges transaction fees ```solidity function quoteDeliveryPrice( uint16 targetChain, // chain ID (Wormhole formatted) TransceiverStructs.TransceiverInstruction memory instruction // extra instruction for the Transceiver (optional, dependent on whether extra instructions are used/accepted for this Transceiver) ) external view returns (uint256) ``` ## Lifecycle of a Message ### EVM #### Transfer A client calls on `transfer` to initiate an NTT transfer. The client must specify, at minimum, the transfer amount, the recipient chain, and the recipient address on the recipient chain. `transfer` also supports a flag to specify whether the `NttManager` should queue rate-limited transfers or revert. Clients can also include additional instructions to forward along to the transceiver on the source chain. Depending on the mode set in the initial configuration of the `NttManager` contract, transfers are either "locked" or "burned." Once the transfer has been forwarded to the transceiver, the `NttManager` emits the `TransferSent` event. **Events** ```ts /// @notice Emitted when a message is sent from the nttManager. /// @dev Topic0 /// 0x9716fe52fe4e02cf924ae28f19f5748ef59877c6496041b986fbad3dae6a8ecf /// @param recipient The recipient of the message. /// @param amount The amount transferred. /// @param fee The amount of ether sent along with the tx to cover the delivery fee. /// @param recipientChain The chain ID of the recipient. /// @param msgSequence The unique sequence ID of the message. event TransferSent( bytes32 recipient, uint256 amount, uint256 fee, uint16 recipientChain, uint64 msgSequence ); ``` #### Rate Limit A transfer can be rate-limited on both the source and destination chains. 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 expiry of the `rateLimitDuration`, 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. **Events** ```ts /// @notice Emitted whenn an outbound transfer is queued. /// @dev Topic0 /// 0x69add1952a6a6b9cb86f04d05f0cb605cbb469a50ae916139d34495a9991481f. /// @param queueSequence The location of the transfer in the queue. event OutboundTransferQueued(uint64 queueSequence); ``` ```ts /// @notice Emitted when an outbound transfer is rate limited. /// @dev Topic0 /// 0x754d657d1363ee47d967b415652b739bfe96d5729ccf2f26625dcdbc147db68b. /// @param sender The initial sender of the transfer. /// @param amount The amount to be transferred. /// @param currentCapacity The capacity left for transfers within the 24-hour window. event OutboundTransferRateLimited( address indexed sender, uint64 sequence, uint256 amount, uint256 currentCapacity ); ``` ```ts /// @notice Emitted when an inbound transfer is queued /// @dev Topic0 /// 0x7f63c9251d82a933210c2b6d0b0f116252c3c116788120e64e8e8215df6f3162. /// @param digest The digest of the message. event InboundTransferQueued(bytes32 digest); ``` #### Send Once the `NttManager` forwards the message to the transceiver, the message is transmitted via the `sendMessage method`. The method signature is enforced by the transceiver but transceivers are free to determine their own 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. **Events** ```ts /// @notice Emitted when a message is sent from the transceiver. /// @dev Topic0 /// 0x53b3e029c5ead7bffc739118953883859d30b1aaa086e0dca4d0a1c99cd9c3f5. /// @param recipientChain The chain ID of the recipient. /// @param message The message. event SendTransceiverMessage( uint16 recipientChain, TransceiverStructs.TransceiverMessage message ); ``` #### Receive 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 `TransceiverAlreadyAttestedToMessage` error. NTT also implements replay protection against re-executing messages. This check also acts as reentrancy protection as well. 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 of transceivers (i.e. threshold < totalTransceivers) and notifies the client (off-chain process) so they don't attempt redundant message delivery. **Events** ```ts /// @notice Emitted when a relayed message is received. /// @dev Topic0 /// 0xf557dbbb087662f52c815f6c7ee350628a37a51eae9608ff840d996b65f87475 /// @param digest The digest of the message. /// @param emitterChainId The chain ID of the emitter. /// @param emitterAddress The address of the emitter. event ReceivedRelayedMessage(bytes32 digest, uint16 emitterChainId, bytes32 emitterAddress); ``` ```ts /// @notice Emitted when a message is received. /// @dev Topic0 /// 0xf6fc529540981400dc64edf649eb5e2e0eb5812a27f8c81bac2c1d317e71a5f0. /// @param digest The digest of the message. /// @param emitterChainId The chain ID of the emitter. /// @param emitterAddress The address of the emitter. /// @param sequence The sequence of the message. event ReceivedMessage( bytes32 digest, uint16 emitterChainId, bytes32 emitterAddress, uint64 sequence ); ``` ```ts /// @notice Emitted when a message has already been executed to notify client of against retries. /// @dev Topic0 /// 0x4069dff8c9df7e38d2867c0910bd96fd61787695e5380281148c04932d02bef2. /// @param sourceNttManager The address of the source nttManager. /// @param msgHash The keccak-256 hash of the message. event MessageAlreadyExecuted(bytes32 indexed sourceNttManager, bytes32 indexed msgHash); ``` #### Mint or Unlock 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. **Events** ```ts /// @notice Emitted when a transfer has been redeemed /// (either minted or unlocked on the recipient chain). /// @dev Topic0 /// 0x504e6efe18ab9eed10dc6501a417f5b12a2f7f2b1593aed9b89f9bce3cf29a91. /// @param digest The digest of the message. event TransferRedeemed(bytes32 indexed digest); ``` ### Solana #### 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 were to be 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: ```ts Program log: Instruction: TransferLock Program log: Instruction: TransferBurn ``` 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 Limit 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 with `release_timestamp` set to the current timestamp plus the configured `RATE_LIMIT_DURATION`. - If `shouldQueue = false`, revert with a `TransferExceedsRateLimit` error #### Send 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 will send by calling `post_message` on the Wormhole program, so that the Wormhole Guardians can 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: ```ts Program log: Instruction: ReleaseOutbound ``` #### Receive 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` entrypoint instruction. Callers can use the `receive_wormhole_message` Anchor library function to execute this instruction. The instruction verifies the Wormhole Verified Action Approvals (VAAs) and stores it in a `VerifiedTransceiverMessage` account. The following will be produced in the program logs: ```ts Program log: Instruction: ReceiveMessage ``` `redeem` checks the inbound rate limit and places the message in an Inbox. Logic works the same as the outbound rate limit mentioned previously. The following will be produced in the program logs: ```ts Program log: Instruction: Redeem ``` #### 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 `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: ```ts Program log: Instruction: ReleaseInboundMint Program log: Instruction: ReleaseInboundUnlock ``` --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/settlement/faqs/ --- BEGIN CONTENT --- --- title: Wormhole Settlement FAQs description: Frequently asked questions about Wormhole Settlement, including smart contract usage, auction fallback, and message execution. categories: Settlement, Transfer --- # Wormhole Settlement FAQs ## Can I use Wormhole Settlement from a smart contract? If so, how is a message signed and relayed? Yes, Wormhole Settlement can be used from a smart contract. The composing protocol's relayer relays the message. For example, Mayan Shuttle (formerly Swap Layer) has a relayer that redeems the VAA on the destination chain to mint USDC and execute the `callData` contained in the payload. ## What happens if no solver participates in the auction? If an auction does not start within the specified deadline, a standard CCTP transfer will proceed directly from the source chain to the destination chain. This is why parameters like `deadline` exist in the token router interface, ensuring a fallback mechanism in case no solver participates. ## What guarantees does Wormhole Settlement provide for message execution? After the user receives the token upfront, the execution of additional contract calls depends on the relayer of the composing protocol. For example, in Mayan Shuttle, the relayer will attempt the swap multiple times, but its success is subject to the parameters defined in the `callData` (e.g., slippage). If the slippage tolerance is set too low, the user may receive USDC on the destination chain instead of the intended swap outcome. However, the four basis points (bps) fee is non-refundable, as the service provided by Liquidity Layer (LL) solvers (ensuring front-finality) is separate from the composing protocol's services, such as swaps or deposits. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/settlement/ --- BEGIN CONTENT --- --- title: Wormhole Settlement description: Start building with Wormhole Settlement; integrate with the Liquidity Layer and set up Solvers to enable seamless cross-chain asset transfers. categories: Settlement, Transfer --- # Wormhole Settlement ## Get Started This section provides resources to build with Wormhole Settlement, including integrating the Liquidity Layer into your application and running a Solver for efficient cross-chain asset transfers.
- :octicons-code-16:{ .lg .middle } **Build on the Liquidity Layer** --- Integrate seamlessly with Wormhole's Liquidity Layer, learn key EVM contract functions for fast and secure cross-chain transfers. [:custom-arrow: Build on the Liquidity layer](/docs/build/transfers/settlement/liquidity-layer/) - :octicons-code-16:{ .lg .middle } **Run a Settlement Solver** --- Set up, configure, and run a Wormhole Settlement Solver on Solana's Matching Engine to fulfill cross-chain transfers efficiently and securely. [:custom-arrow: Run a Solver](/docs/build/transfers/settlement/solver/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/settlement/liquidity-layer/ --- BEGIN CONTENT --- --- title: Wormhole Settlement Liquidity Layer description: Learn how to build on the Wormhole Liquidity Layer, the underlying chain abstraction infrastructure layer for protocols across Wormhole-connected ecosystems. categories: Settlement, Transfer --- # Build on the Wormhole Liquidity Layer ## Introduction The Wormhole Liquidity Layer is the underlying chain abstraction infrastructure layer for protocols across Wormhole-connected ecosystems. It allows these protocols to bundle call data containing arbitrary actions that can be executed atomically alongside each transfer. This feature enables developers to create fully chain-abstracted user experiences, including constructing natively cross-chain decentralized exchanges (DEXs), borrow-lend protocols, payment protocols, and other applications atop this layer. The following section describes the key smart contract components for teams seeking to build atop Wormhole Settlement. ## EVM Functions The EVM Token Router is a simple interface against which to integrate. For an integrator, the contracts have two main entry points: `placeMarketOrder` and `placeFastMarketOrder`. ### Fast Market Order The `placeFastMarketOrder` function allows the caller to elect for a _faster-than-finality_ transfer of USDC (with an arbitrary message payload) to the destination chain by setting the `maxFee` and `deadline` parameters. Using this interface does not guarantee that the caller's transfer will be delivered faster than finality; however, any willing market participants can compete for the specified `maxFee` by participating in an auction on the Solana `MatchingEngine` ```solidity title="`placeFastMarketOrder` Interface" function placeFastMarketOrder( uint128 amountIn, uint16 targetChain, bytes32 redeemer, bytes calldata redeemerMessage, uint128 maxFee, uint32 deadline ) external payable returns (uint64 sequence, uint64 fastSequence); ``` ??? interface "Parameters `placeFastMarketOrder()`" `amountIn` ++"uint128"++ The amount to transfer. --- `targetChain` ++"uint16"++ Target chain ID. --- `redeemer` ++"bytes32"++ Redeemer contract address. --- `redeemerMessage` ++"bytes"++ An arbitrary payload for the redeemer. --- `maxFee` ++"uint128"++ The maximum fee the user wants to pay to execute a fast transfer. --- `deadline` ++"uint32"++ The deadline for the fast transfer auction to start. Note: This timestamp should be for the `MatchingEngine` chain (such as Solana) to avoid any clock drift issues between different blockchains. Integrators can set this value to `0` if they don't want to use a deadline. The `placeFastMarketOrder` function returns a sequence number for the Wormhole Fill message. This function requires the caller to provide a `msg.value` equal to the amount returned by the `messageFee()` function on the `IWormhole.sol` interface. ### Market Order The `placeMarketOrder` function is a _wait-for-full-finality_ USDC transfer with an arbitrary message payload. The Swap Layer, built on top of the Wormhole Settlement, uses this function if the auction on the matching engine for `placeFastMarketOrder` doesn't start within a specific deadline. ```solidity title="`placeMarketOrder` Interface" function placeMarketOrder( uint128 amountIn, uint16 targetChain, bytes32 redeemer, bytes calldata redeemerMessage, ) external payable returns (uint64 sequence, uint64 protocolSequence); ``` ??? interface "Parameters `placeMarketOrder()`" `amountIn` ++"uint128"++ The amount to transfer. --- `targetChain` ++"uint16"++ Target chain ID. --- `redeemer` ++"bytes32"++ Redeemer contract address. --- `redeemerMessage` ++"bytes"++ An arbitrary payload for the redeemer. The `placeMarketOrder` function returns a sequence number for the Wormhole Fill message. This function requires the caller to provide a `msg.value` equal to the amount returned by the `messageFee()` function on the `IWormhole.sol` interface. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/settlement/solver/ --- BEGIN CONTENT --- --- title: Wormhole Settlement Solver description: Set up, configure, and run a Wormhole Settlement Solver on Solana's Matching Engine to fulfill cross-chain transfers efficiently and securely. categories: Settlement, Transfer --- # Run a Wormhole Settlement Solver ## Introduction This page provides instructions on how to set up, configure, and run a Solver for Wormhole Settlement using the [example solver](https://github.com/wormholelabs-xyz/example-liquidity-layer/tree/update-solver-example/solver){target=\_blank}. A Solver is an off-chain agent responsible for: - Listening to cross-chain transfer requests sent over Wormhole - Bidding in auctions (on Solana) to fulfill each request - Facilitating the actual cross-chain transfer by locking/burning assets on Solana and minting/unlocking them on the destination chain - Rebalancing once the origin chain transaction finalizes and is redeemed back on Solana For information on how the protocol functions and its core features, please visit the [Wormhole Settlement](/docs/learn/transfers/settlement/overview/){target=\_blank} page. ## Background The Solana Matching Engine's permissionless English auction is a central component of Wormhole Settlement protocol architecture. The Matching Engine contract allows any third-party solver to interact with the matching engine to place bids or improve existing ones. The contract includes four key instructions: 1. `initialize_auction` - creates a new auction account on-chain and sets basic parameters like the auction's token mint, the amount required, and the bidding period details 2. `bid` - allows a solver to place or update a bid on the active auction 3. `finalize_auction` - following the conclusion of the auction, this instruction completes the fast transfer by sending funds to the recipient on the target chain. This instruction may call the Circle CCTP contract or release an NTT contract in the future, depending on the shuttle asset in question. Failure to execute this message within a predefined grace period may result in a penalty for the winning bidder. 4. `cancel_auction` - cancels an open auction when the auction is no longer valid or was created by mistake. The program returns all locked funds to their respective owners These instructions work together to carry out the auction as follows: - The solver transfers the bid amount to the program escrow account, which ensures they have liquidity - With each successful call of `bid`, the program updates the auction to the new highest bidder, and the prior bid is atomically sent back to the originating solver - The originating solver can repurpose the returned funds and use them to improve their bid - Following the auction, the winning solver has to call an instruction on the matching engine to execute the intent When placing a bid, whether initial or improved, the solver must deposit the required funds plus a security deposit into the matching engine contract. In this permissionless auction, the requirement of this principal amount plus the security deposit ensures a solver's credible commitment to fulfill the transfer. Malicious actors could place hollow bids without this safeguard, undermining the auction's credibility and hindering true price discovery. If the winning solver fails to call the `finalize_auction` instruction, other competing solvers may permissionlessly 'slash' the solver by executing the instruction on their behalf and collecting a portion of the original security deposit as a reward. The remaining portion is routed to the user as compensation for the unanticipated delay. This mechanism properly incentivizes timely execution through solver redundancy and competition. ## Testnet Example Solver You can clone the Wormhole [`example-liquidity-layer`](https://github.com/wormholelabs-xyz/example-liquidity-layer){target=\_blank} repository to use the included [`solver`](https://github.com/wormholelabs-xyz/example-liquidity-layer/tree/main/solver){target=\_blank} directory as an example solver to fulfill fast orders by interacting with the Matching Engine on Solana. !!!warning This example is not optimized for performance, has only been tested on Solana devnet, and is not intended for production use. Any assumptions made in this example may not translate to mainnet. ### Prerequisites In order to build and install dependencies locally in this repo, you will need: - node v20.18.1 - npmv - get started by installing `nvm` using this [installation guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating){target=\_blank} Navigate into the `solver` directory, then run the command below to set up your environment and install the node dependencies and Matching Engine package: ```sh make dependencies ``` ### Set up Config The following is an example of a `config.json` file for Solana devnet. The keys here are required for both the publisher and example solver processes. ```json title="config.json" { "environment": "Testnet", "zmqChannels": { "fastVaa": "tcp://localhost:6001", "finalizedVaa": "tcp://localhost:6002" }, "publisher": { "log": { "level": "info" }, "vaaSpy": { "host": "localhost:7073", "enableObservationCleanup": true, "observationSeenThresholdMs": 1500000, "observationCleanupIntervalMs": 500, "observationsToRemovePerInterval": 5, "delayedThresholdMs": 60000 } }, "solver": { "log": { "level": "info", "filename": "logs/solver.log" }, "connection": { "rpc": "", "maxTransactionsPerSecond": 5, "commitment": "processed", "addressLookupTable": "YourAddressLookupTab1eHere11111111111111111", "matchingEngine": "mPydpGUWxzERTNpyvTKdvS7v8kvw5sgwfiP8WQFrXVS", "mint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU", "knownAtaOwners": [ "Payer11111111111111111111111111111111111111", "Payer11111111111111111111111111111111111112", "Payer11111111111111111111111111111111111113" ] } }, "routerEndpoints": [ { "chain": "Sepolia", "endpoint": "0xE57D917bf955FedE2888AAbD056202a6497F1882", "rollbackRisk": 0.0069, "offerEdge": 0.042 }, { "chain": "Avalanche", "endpoint": "0x8Cd7D7C980cd72eBD16737dC3fa04469dcFcf07A", "rollbackRisk": 0.0069, "offerEdge": 0.042 }, { "chain": "OptimismSepolia", "endpoint": "0x6BAa7397c18abe6221b4f6C3Ac91C88a9faE00D8", "rollbackRisk": 0.0069, "offerEdge": 0.042 }, { "chain": "ArbitrumSepolia", "endpoint": "0xe0418C44F06B0b0D7D1706E01706316DBB0B210E", "rollbackRisk": 0.0069, "offerEdge": 0.042 }, { "chain": "BaseSepolia", "endpoint": "0x824Ea687CD1CC2f2446235D33Ae764CbCd08e18C", "rollbackRisk": 0.0069, "offerEdge": 0.042 }, { "chain": "Polygon", "endpoint": "0xa098368AaaDc0FdF3e309cda710D7A5f8BDEeCD9", "rollbackRisk": 0.0069, "offerEdge": 0.042 } ] } ``` The rollback risks and offer edges configured in the sample config are arbitrary placeholders. You should use historical data and your risk tolerance, to determine appropriate values for your project. ### Listen to Activity The example solver listens to attested Wormhole messages (VAAs) published on the Wormhole Guardian gossip network. To listen to this gossip network and run the VAA publisher, run the command below. Docker compose is used to listen to the Pyth Beacon and start the [`publishActivity`](https://github.com/wormholelabs-xyz/example-liquidity-layer/blob/update-solver-example/solver/app/publishActivity.ts){target=\_blank} process. ```sh NETWORK=testnet CONFIG=path/to/config.json make run-publisher ``` You should see output resembling:
Start logging with info level. 2025-01-21 16:38:28.145 [publisher] info: Environment: Testnet 2025-01-21 16:38:36.631 [publisher] info: Fast VAA. chain=OptimismSepolia, sequence=33635, vaaTime=1737499116 2025-01-21 16:38:51.044 [publisher] info: Fast VAA. chain=OptimismSepolia, sequence=33637, vaaTime=1737499130 2025-01-21 16:40:24.890 [publisher] info: Fast VAA. chain=OptimismSepolia, sequence=33639, vaaTime=1737499224
To set up the Pyth Beacon (which is run using make `run-publisher`), you may need to increase the UDP buffer size for the OS: === "Linux" ```sh sudo sysctl -w net.core.rmem_max=2097152 sudo sysctl -w net.core.rmem_default=2097152 ``` === "MacOS" ```sh sudo sysctl -w net.inet.udp.recvspace=2097152 ``` ### Running the Example Solver Using the same config for your publisher, run the example solver with the command below. ```sh CONFIG=path/to/config.json make run-solver ``` It is recommended you write log output to a file so errors can be tracked. The example config above specifies an example log filename. This process reads the following environment variables: ```sh SOLANA_PRIVATE_KEY_1= SOLANA_PRIVATE_KEY_2= SOLANA_PRIVATE_KEY_3= SOLANA_PRIVATE_KEY_4= SOLANA_PRIVATE_KEY_5= ``` At least one of these environment variables must be defined as a keypair encoded in base64 format. These payers must have SOL to send transactions on Solana devnet. If they need funds, they can request them from the [Solana devnet faucet](https://faucet.solana.com/){target=\_blank}. The example solver assumes that these payers own USDC Associated Token Accounts(ATAs), which will be used to fulfill fast transfers. These ATAs must be funded with Solana Devnet USDC. If your ATAs need funds, request some at the [Circle testnet faucet](https://faucet.circle.com/){target=\_blank}. Wallets and their corresponding ATA will be disabled if there are insufficient funds to pay for transactions or fulfill fast transfers. These constraints can be modified using the `updatePayerMinimumLamports` and `updateTokenMinimumBalance` methods. An address lookup table is required to execute some transactions. Use the command below to create one. ```sh CONFIG=path/to/config.json make create-lut ``` `SOLANA_PRIVATE_KEY_1` must be defined for this script to work. The example solver has the following toggles depending on which orders you want to fulfill: - `enableCctpOrderPipeline()` - `enableLocalOrderPipeline()` - `enablePlaceInitialOffer()` - `enableImproveOffer()` See the comments in [runExampleSolver](https://github.com/wormholelabs-xyz/example-liquidity-layer/blob/update-solver-example/solver/app/runExampleSolver.ts){target=\_blank} for more information. This example solver does NOT do the following: - Discriminate between the CCTP source networks. You must add logic to determine whether you want to constrain fulfilling orders from specific networks. This solver will try to fulfill all orders as long as `enableCctpOrderPipeline()` is called - Discriminate among fulfillment sizes. No logic determines how small or large fast order transfer sizes should be. This solver will try to fulfill anything as long as your balance can handle it - Add auctions to auction history. We recommend that after settling a complete auction (one that you have won), you write the auction pubkey to a database and have a separate process to add auction history entries to reclaim rent from these auction accounts. The auction history time delay is two hours after the VAA timestamp. This example does not prescribe any specific database, so add whichever you want --- END CONTENT --- Doc-Content: https://wormhole.com/docs/build/transfers/token-bridge/ --- BEGIN CONTENT --- --- title: Get Started with Token Bridge description: Learn how to integrate Wormhole's Token Bridge for seamless multichain token transfers with a lock-and-mint mechanism and cross-chain asset management. categories: Token-Bridge, Transfer --- # Token Bridge ## Introduction Wormhole's Token Bridge enables seamless cross-chain token transfers using a lock-and-mint mechanism. The bridge locks tokens on the source chain and mints them as wrapped assets on the destination chain. Additionally, the Token Bridge supports [Token Transfers with Messages](/docs/learn/infrastructure/vaas/#token-transfer-with-message){target=\_blank}, where arbitrary byte payloads can be attached to the token transfer, enabling more complex chain interactions. This page outlines the core contract methods needed to integrate Token Bridge functionality into your smart contracts. To understand the theoretical workings of the Token Bridge, refer to the [Token Bridge](/docs/learn/transfers/token-bridge/){target=\_blank} page in the Learn section. ## Prerequisites To interact with the Wormhole Token Bridge, you'll need the following: - [The address of the Token Bridge contract](/docs/build/reference/contract-addresses/#token-bridge){target=\_blank} on the chains you're working with - [The Wormhole chain ID](/docs/build/reference/chain-ids/){target=\_blank} of the chains you're targeting for token transfers ## How to Interact with Token Bridge Contracts The primary functions of the Token Bridge contracts revolve around: - **Attesting a token** - registering a new token for cross-chain transfers - **Transferring tokens** - locking and minting tokens across chains - **Transferring tokens with a payload** - including additional data with transfers ### Attest a token Suppose a token has never been transferred to the target chain before transferring it cross-chain. In that case, its metadata must be registered so the Token Bridge can recognize it and create a wrapped version if necessary. The attestation process doesn't require you to manually input token details like name, symbol, or decimals. Instead, the Token Bridge contract retrieves these values from the token contract itself when you call the `attestToken()` method. ```solidity function attestToken( address tokenAddress, uint32 nonce ) external payable returns (uint64 sequence); ``` ??? interface "Parameters" `tokenAddress` ++"address"++ The contract address of the token to be attested. --- `nonce` ++"uint32"++ An arbitrary value provided by the caller to ensure uniqueness. ??? interface "Returns" `sequence` ++"uint64"++ A unique identifier for the attestation transaction. When `attestToken()` is called, the contract emits a Verifiable Action Approval (VAA) containing the token's metadata, which the Guardians sign and publish. You must ensure the token is ERC-20 compliant. If it does not implement the standard functions, the attestation may fail or produce incomplete metadata. ### Transfer Tokens Once a token is attested, a cross-chain token transfer can be initiated following the lock-and-mint mechanism. On the source chain, tokens are locked (or burned if they're already a wrapped asset), and a VAA is emitted. On the destination chain, that VAA is used to mint or release the corresponding amount of wrapped tokens. Call `transferTokens()` to lock/burn tokens and produce a VAA with transfer details. ```solidity function transferTokens( address token, uint256 amount, uint16 recipientChain, bytes32 recipient, uint256 arbiterFee, uint32 nonce ) external payable returns (uint64 sequence); ``` ??? interface "Parameters" `token` ++"address"++ The address of the token being transferred. --- `amount` ++"uint256"++ The amount of tokens to be transferred. --- `recipientChain` ++"uint16"++ The Wormhole chain ID of the destination chain. --- `recipient` ++"bytes32"++ The recipient's address on the destination chain. --- `arbiterFee` ++"uint256"++ Optional fee to be paid to an arbiter for relaying the transfer. --- `nonce` ++"uint32"++ A unique identifier for the transaction. ??? interface "Returns" `sequence` ++"uint64"++ A unique identifier for the transfer transaction. Once a transfer VAA is obtained from the Wormhole Guardian network, the final step is to redeem the tokens on the destination chain. Redemption verifies the VAA's authenticity and releases (or mints) tokens to the specified recipient. To redeem the tokens, call `completeTransfer()`. ```solidity function completeTransfer(bytes memory encodedVm) external; ``` ??? interface "Parameters" `encodedVm` ++"bytes memory"++ The signed VAA containing the transfer details. !!!note - The Token Bridge normalizes token amounts to 8 decimals when passing them between chains. Make sure your application accounts for potential decimal truncation - The VAA ensures the integrity of the message. Only after the Guardians sign the VAA can it be redeemed on the destination chain ### Transfer tokens with payload While a standard token transfer moves tokens between chains, a transfer with a payload allows you to embed arbitrary data in the VAA. This data can be used on the destination chain to execute additional logic—such as automatically depositing tokens into a DeFi protocol, initiating a swap on a DEX, or interacting with a custom smart contract. Call `transferTokensWithPayload()` instead of `transferTokens()` to include a custom payload (arbitrary bytes) with the token transfer. ```solidity function transferTokensWithPayload( address token, uint256 amount, uint16 recipientChain, bytes32 recipient, uint32 nonce, bytes memory payload ) external payable returns (uint64 sequence); ``` ??? interface "Parameters" `token` ++"address"++ The address of the token being transferred. --- `amount` ++"uint256"++ The amount of tokens to be transferred. --- `recipientChain` ++"uint16"++ The Wormhole chain ID of the destination chain. --- `recipient` ++"bytes32"++ The recipient's address on the destination chain. --- `nonce` ++"uint32"++ A unique identifier for the transaction. --- `payload` ++"bytes memory"++ Arbitrary data payload attached to the transaction. ??? interface "Returns" `sequence` ++"uint64"++ A unique identifier for the transfer transaction. After initiating a transfer on the source chain, the Wormhole Guardian network observes and signs the resulting message, creating a Verifiable Action Approval (VAA). You'll need to fetch this VAA and then call `completeTransferWithPayload()`. Only the designated recipient contract can redeem tokens. This ensures that the intended contract securely handles the attached payload. On successful redemption, the tokens are minted (if foreign) or released (if native) to the recipient address on the destination chain. For payload transfers, the designated contract can execute the payload's logic at this time. ```solidity function completeTransferWithPayload(bytes memory encodedVm) external returns (bytes memory); ``` ??? interface "Parameters" `encodedVm` ++"bytes memory"++ The signed VAA containing the transfer details. ??? interface "Returns" `bytes memory` The extracted payload data. ## Source Code References For a deeper understanding of the Token Bridge implementation and to review the actual source code, please refer to the following links: - [Token Bridge contract](https://github.com/wormhole-foundation/wormhole/blob/main/ethereum/contracts/bridge/Bridge.sol){target=\_blank} - [Token Bridge interface](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/interfaces/ITokenBridge.sol){target=\_blank} ## Portal Bridge A practical implementation of the Wormhole Token Bridge can be seen in [Portal Bridge](https://portalbridge.com/){target=\_blank}, which provides an easy-to-use interface for transferring tokens across multiple blockchain networks. It leverages the Wormhole infrastructure to handle cross-chain asset transfers seamlessly, offering users a convenient way to bridge their assets while ensuring security and maintaining token integrity. ## FAQs ### Can ownership of wrapped tokens be transferred from the Token Bridge? No, you cannot transfer ownership of wrapped token contracts from the [Token Bridge](/docs/learn/transfers/token-bridge/){target=\_blank} because the Token Bridge deploys and retains ownership of these contracts and tokens. - **On EVM chains** - when you attest a token, the Token Bridge deploys a new ERC-20 contract as a beacon proxy. The upgrade authority for these contracts is the Token Bridge contract itself - **On Solana** - the Token Bridge deploys a new SPL token, where the upgrade authority is a Program Derived Address (PDA) controlled by the Token Bridge The logic behind deploying these token contracts involves submitting an attestation VAA, which allows the Token Bridge 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? Because wrapped tokens are deployed and controlled by the Token Bridge program, which is under the authority of the Wormhole Guardians, there is no direct way for you to update their metadata. 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 Token Bridge, 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}. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/glossary/ --- BEGIN CONTENT --- --- title: Glossary description: Explore a comprehensive glossary of technical terms and key concepts used in the Wormhole network, covering Chain ID, Guardian, VAA, and more. categories: Basics --- # Glossary This glossary is an index of technical term definitions for words commonly used in Wormhole documentation. ## Chain ID Wormhole assigns a unique `u16` integer chain ID to each supported blockchain. These chain IDs are specific to Wormhole and may differ from those used by blockchains to identify their networks. You can find each chain ID documented on the [Wormhole Chain IDs](/docs/build/reference/chain-ids/){target=\_blank} page. ## Consistency Level The level of finality (consistency) a transaction should meet before being signed by a Guardian. See the [Wormhole Finality](/docs/build/reference/consistency-levels/){target=\_blank} reference page for details. ## Delivery Provider A Delivery Provider monitors for Wormhole Relayer delivery requests and delivers those requests to the intended target chain as instructed. ## Emitter The emitter contract makes the call to the Wormhole Core Contract. The published message includes the emitter contract address and, a sequence number for the message is tracked to provide a unique ID. ## Finality The finality of a transaction depends on its blockchain properties. Once a transaction is considered final, you can assume the resulting state changes it caused won't be reverted. ## Guardian A [Guardian](/docs/learn/infrastructure/guardians/){target=\_blank} is one of the 19 parties running validators in the Guardian Network contributing to the VAA multisig. ## Guardian Network Validators in their own P2P network who serve as Wormhole's oracle by observing activity on-chain and generating signed messages attesting to that activity. ## Guardian Set The Guardian Set is a set of guardians responsible for validating a message emitted from the core contracts. Occasionally, the members of the set will change through a governance action. ## Heartbeat Each Guardian will issue a `heartbeat` on a 15-second interval to signal that it is still running and convey details about its identity, uptime, version, and the status of the connected nodes. You can view the heartbeats on the [Wormhole dashboard](https://wormhole-foundation.github.io/wormhole-dashboard/#/?endpoint=Mainnet){target=\_blank}. ## Observation An Observation is a data structure describing a message emitted by the Core Contract and noticed by the Guardian node. ## Relayer A relayer is any process that delivers VAAs to a destination. ## Sequence A nonce, strictly increasing, which is tracked by the Wormhole Core Contract and unique to the emitter chain and address. ## Spy A Spy is a daemon that eavesdrops on the messages passed between Guardians, typically to track VAAs as they get signed. ## VAA [Verifiable Action Approvals](/docs/learn/infrastructure/vaas/){target=\_blank} (VAAs) are the base data structure in the Wormhole ecosystem. They contain emitted messages along with information such as what contract emitted the message. ## Validator A daemon configured to monitor a blockchain node and observe messages emitted by the Wormhole contracts. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/governance/architecture/ --- BEGIN CONTENT --- --- title: MultiGov Architecture description: Discover MultiGov's hub-and-spoke architecture, enabling secure cross-chain governance with Wormhole’s interoperability and decentralized coordination. categories: MultiGov --- # MultiGov Architecture MultiGov employs a hub-and-spoke model to enable cross-chain governance, utilizing Wormhole's interoperability infrastructure for secure cross-chain communication. This architecture allows coordinated decision-making across multiple blockchain networks while maintaining a central coordination point. ## Key Components ### Hub Chain Contracts The hub chain is the central point for managing proposals, tallying votes, executing decisions, and coordinating governance across connected chains. - **`HubGovernor`** - central governance contract managing proposals and vote tallying - **`HubVotePool`** - receives aggregated votes from spokes and submits them to `HubGovernor` - **`HubMessageDispatcher`** - relays approved proposal executions to spoke chains - **`HubProposalExtender`** - allows trusted actors to extend voting periods if needed - **`HubProposalMetadata`** - helper contract returning `proposalId` and vote start for `HubGovernor` proposals - **`HubEvmSpokeAggregateProposer`** - aggregates cross-chain voting weight for an address and proposes via the `HubGovernor` if eligible ### Spoke Chains Contracts Spoke chains handle local voting, forward votes to the hub, and execute approved proposals from the hub for decentralized governance. - **`SpokeVoteAggregator`** - collects votes on the spoke chain and forwards them to the hub - **`SpokeMessageExecutor`** - receives and executes approved proposals from the hub - **`SpokeMetadataCollector`** - fetches proposal metadata from the hub for spoke chain voters - **`SpokeAirlock`** - acts as governance's "admin" on the spoke, has permissions, and its treasury ### Spoke Solana Staking Program The Spoke Solana Staking Program handles local voting from users who have staked W tokens or are vested in the program, forwards votes to the hub, and executes approved proposals from the hub for decentralized governance. The program implements its functionality through instructions, using specialized PDA accounts where data is stored. Below are the key accounts in the program: - **`GlobalConfig`** - global program configuration - **`StakeAccountMetadata`** - stores user's staking information - **`CustodyAuthority`** - PDA account managing custody and overseeing token operations related to stake accounts - **`StakeAccountCustody`** - token account associated with a stake account for securely storing staked tokens - **`CheckpointData`** - tracks delegation history - **`SpokeMetadataCollector`** - collects and updates proposal metadata from the hub chain - **`GuardianSignatures`** - stores guardian signatures for message verification - **`ProposalData`** - stores data about a specific proposal, including votes and start time - **`ProposalVotersWeightCast`** - tracks individual voter's weight for a proposal - **`SpokeMessageExecutor`** - processes messages from a spoke chain via the Wormhole protocol - **`SpokeAirlock`** - manages PDA signing and seed validation for secure instruction execution - **`VestingBalance`** - stores total vesting balance and related staking information of a vester - **`VestingConfig`** - defines vesting configuration, including mint and admin details - **`Vesting`** - represents individual vesting allocations with maturation data - **`VoteWeightWindowLengths`** - tracks lengths of vote weight windows Each account is implemented as a Solana PDA (Program Derived Address) and utilizes Anchor's account framework for serialization and management. ## System Workflow The MultiGov system workflow details the step-by-step process for creating, voting on, and executing governance proposals across connected chains, from proposal creation to final cross-chain execution. ### EVM Governance Workflow The EVM-based MultiGov workflow follows these steps: 1. **Proposal creation**: 1. A user creates a proposal through the `HubEvmSpokeAggregateProposer`, which checks eligibility across chains, or directly on the `HubGovernor` via the `propose` method 2. The proposal is submitted to the `HubGovernor` if the user meets the proposal threshold 2. **Proposal metadata distribution**: 1. `HubProposalMetadata` creates a custom view method to be queried for use in the `SpokeMetadataCollector` 2. `SpokeMetadataCollector` on each spoke chain queries `HubProposalMetadata` for proposal details 3. **Voting process**: 1. Users on spoke chains vote through their respective `SpokeVoteAggregators` 2. `SpokeVoteAggregators` send aggregated votes to the `HubVotePool` via Wormhole 3. `HubVotePool` submits the aggregated votes to the `HubGovernor` 4. **Vote tallying and proposal execution**: 1. `HubGovernor` tallies votes from all chains 2. If a quorum is reached and there are more for votes than against votes, the vote passes and is queued for execution 3. After the timelock delay, the proposal can be executed on the hub chain 4. For cross-chain actions, a proposal should call the `dispatch` method in the `HubMessageDispatcher`, which sends execution messages to the relevant spoke chains 5. `SpokeMessageExecutors` on each spoke chain receive and execute the approved actions through their respective `SpokeAirlocks` ### Solana Governance Workflow The Solana-based MultiGov workflow follows these steps: 1. **Proposal creation**: 1. A user creates a proposal on `HubGovernor` by calling the `propose` method, provided they meet the proposal threshold 2. For the proposal to be executed on the Solana blockchain, a `SolanaPayload` must be generated and included in the `calldata` parameter of the `propose` function 3. The `SolanaPayload` contains encoded details specifying which instructions will be executed and which Solana program is responsible for execution 2. **Proposal metadata distribution**: 1. A user queries `getProposalMetadata` from `HubProposalMetadata` via the Wormhole query system to create a proposal on the **Spoke Solana Chain Staking Program** 2. The retrieved metadata is used in the `AddProposal` instruction in the Solana program 3. The proposal data is verified to ensure it matches the expected format 4. Guardian signatures are posted using the `PostSignatures` instruction 5. Once validated, the proposal is stored on-chain 3. **Voting process**: 1. Users vote on proposals stored in the `ProposalData` account on Solana 2. The `CastVote` instruction records their choice (`for_votes`, `against_votes`, or `abstain_votes`) 3. Eligibility and vote weight are verified using historical voter checkpoint data 4. A **Query Wormhole** request retrieves vote data from a Solana PDA 5. The signed response from Wormhole guardians is submitted to the `HubVotePool` on Ethereum for verification 6. The `crossChainVote` function in `HubVotePool` processes the validated response and forwards the aggregated vote data to the `HubGovernor` to finalize the decision 4. **Vote tallying and proposal execution**: 1. `HubGovernor` tallies votes from all chains 2. If a quorum is reached with more **for votes** than **against votes**, the proposal passes and is queued for execution 3. After the timelock delay, the proposal can be executed either on the hub chain or another chain 4. For cross-chain execution involving Solana, the proposal calls the `dispatch` method in `HubSolanaMessageDispatcher`, which sends execution messages to Solana 5. On Solana, the `ReceiveMessage` instruction processes the approved message, and the `SpokeAirlock` executes the corresponding instructions ## Cross-Chain Communication MultiGov relies on Wormhole's infrastructure for all cross-chain messaging, ensuring secure and reliable communication between chains. Wormhole's cross-chain state read system, known as Queries, is used for vote aggregation and proposal metadata. Additionally, cross-chain proposal execution messages are transmitted through Wormhole's custom relaying system, enabling seamless coordination across multiple blockchain networks. ## Security Measures - **Vote weight window** - implements a moving window for vote weight checkpoints to mitigate cross-chain double voting - **Proposal extension** - `HubProposalExtender` allows for extending voting periods by a trusted actor in the case of network issues or high-stakes decisions - **Timelock** - a timelock period between proposal approval and execution allows for additional security checks and community review - **Wormhole verification** - all cross-chain messages are verified using Wormhole's secure messaging protocol ## Detailed Architecture Diagram This architecture ensures that MultiGov can operate securely and efficiently across multiple chains, allowing for truly decentralized and cross-chain governance while maintaining a unified decision-making process. ![detailed multigov architecture diagram](/docs/images/learn/governance/multigov-detailed.webp) --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/governance/faq/ --- BEGIN CONTENT --- --- title: MultiGov Theoretical FAQs description: Find answers to common questions about MultiGov, covering cross-chain governance, technical setup, security, proposal creation, and more. categories: MultiGov --- # FAQs ## General Questions ### What is MultiGov? MultiGov is a cross-chain governance system that extends traditional DAO governance across multiple blockchain networks. It leverages Wormhole's interoperability infrastructure for seamless voting and proposal mechanisms across various chains. ### How does MultiGov differ from traditional DAO governance? Unlike traditional DAO governance, which typically operates on a single blockchain, MultiGov allows for coordinated decision-making and proposal execution across multiple chains. This enables more inclusive participation from token holders on different networks and more complex, cross-chain governance actions. ### What are the main components of MultiGov? The main components of MultiGov include: - **Hub chain** - central coordination point for governance activities - **Spoke chains** - additional chains where token holders can participate in governance - **Wormhole integration** - enables secure cross-chain message passing - **Governance token** - allows holders to participate in governance across all integrated chains --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/governance/ --- BEGIN CONTENT --- --- title: Learn about MultiGov description: Explore the MultiGov documentation for a comprehensive guide covering architecture, deployment, upgrading, integration, and FAQs. categories: MultiGov --- # MultiGov Discover everything you need to know about MultiGov, Wormhole's cross-chain governance solution. Get an introduction to the core concepts, explore how the system's components work together, and find answers to common questions to help you navigate the platform effectively. ## Get Started
- :octicons-book-16:{ .lg .middle } **Overview** --- Explore MultiGov, a cross-chain governance system using Wormhole for seamless voting and proposal execution across multiple blockchain networks. [:custom-arrow: Take a first glance at MultiGov](/docs/learn/governance/overview/) - :octicons-book-16:{ .lg .middle } **Architecture** --- Discover MultiGov's hub-and-spoke architecture, enabling EVM and Solana cross-chain governance through coordinated decision-making. [:custom-arrow: Learn about MultiGov architecture](/docs/learn/governance/architecture/) - :octicons-question-16:{ .lg .middle } **Theoretical FAQs** --- Find answers to common theoretical questions about MultiGov. [:custom-arrow: Find the answer to your theoretical questions](/docs/learn/governance/faq/)
## Next Steps
- :octicons-checklist-16:{ .lg .middle } **Begin the MultiGov Integration Process** --- Learn how to get started with MultiGov, from evaluating cross-chain governance needs to deploying with help from the Tally team. [:custom-arrow: Start the integration process now](/docs/build/multigov/) - :octicons-rocket-16:{ .lg .middle } **Deploy to EVM Chains** --- Set up and deploy MultiGov on EVM chains with step-by-step instructions for configuring, compiling, and deploying smart contracts across chains. [:custom-arrow: Discover how to deploy MultiGov](/docs/build/multigov/deploy-to-evm/) - :octicons-rocket-16:{ .lg .middle } **Deploy to Solana** --- Set up and deploy the MultiGov Staking Program on Solana with step-by-step instructions for configuring, funding, deploying, and initializing the program. [:custom-arrow: Discover how to deploy MultiGov on Solana](/docs/build/multigov/deploy-to-solana/) - :octicons-code-square-16:{ .lg .middle } **Tutorials** --- Access step-by-step guides for executing cross-chain governance actions, including treasury management proposals with MultiGov and Wormhole. [:custom-arrow: Create MultiGov solutions](/docs/tutorials/multigov/) - :octicons-question-16:{ .lg .middle } **Technical FAQs** --- Find answers to common technical questions about MultiGov, covering technical setup, security, proposal creation, and more. [:custom-arrow: Find the answer to your technical questions](/docs/build/multigov/faq/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/governance/overview/ --- BEGIN CONTENT --- --- title: MultiGov Overview description: Explore MultiGov, a cross-chain governance system using Wormhole for seamless voting and proposal execution across multiple blockchain networks. categories: MultiGov --- # MultiGov: Cross-Chain Governance with Wormhole ## Overview ### What Is MultiGov and Why Is It Important? MultiGov is a cross-chain governance system that extends traditional DAO governance across multiple blockchain networks. By leveraging Wormhole's interoperability infrastructure, MultiGov enables seamless voting and proposal mechanisms across various chains. MultiGov is important because it: - **Increases participation** by allowing token holders from multiple chains to engage in governance - **Enhances security** by leveraging Wormhole's robust cross-chain communication - **Improves scalability** by integrating any chain supported by Wormhole - **Enables unified governance** and coordinated decision-making across multiple networks ### Key Features - **Hub and spoke model** - central coordination on a hub chain with participation from multiple spoke chains. A hub chain is where the governance state lives, while the spoke chains can be considered extensions of governance that allow for participation by token holders on other chains - **Cross-chain voting** - token holders on any integrated chain can cast votes - **Vote aggregation** - votes from all chains are collected and tallied on the hub - **Cross-chain proposal execution** - approved proposals can be executed across multiple chains - **Wormhole integration** - secure and reliable cross-chain communication - **Flexible architecture** - can integrate with any Wormhole-supported blockchain ### High-Level Architecture Diagram The diagram below represents MultiGov's high-level architecture, focusing on its hub-and-spoke model for decentralized governance across multiple chains. The hub chain acts as the central governance controller, managing proposal creation, vote tallying, and execution, while the spoke chains handle local voting and proposal execution on individual chains. The hub and spoke chains communicate via Wormhole's cross-chain messaging infrastructure, ensuring secure and efficient governance across multiple blockchain networks. For a deeper understanding of the system's structure and how the components interact, refer to the [MultiGov Architecture](/docs/learn/governance/architecture/){target=\_blank} page. ![High-level architecture diagram illustrating the hub-and-spoke structure of the MultiGov system. The diagram shows three key components: Hub Chain and two Spoke Chains, interconnected via Wormhole for cross-chain governance.](/docs/images/learn/governance/multigov-high-level.webp) --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/ --- BEGIN CONTENT --- --- title: Learn about Wormhole description: Learn the basics of Wormhole, covering its architecture, messaging protocols, and how it enables multichain communication and asset transfers. template: root-index-page.html --- # Learn These informational sections will help you get to know Wormhole. Start with the fundamentals, then discover Wormhole's multichain transfer products and governance system. ## Fundamentals To understand Wormhole from the ground up, visit the following sections.
- :octicons-book-16:{ .lg .middle } **Introduction to Wormhole** --- Learn more about the problems Wormhole solves, explore use cases, and view a list of supported blockchains. [:custom-arrow: Introduction to Wormhole](/docs/learn/introduction/) - :octicons-book-16:{ .lg .middle } **Messaging Infrastructure** --- Investigate the messaging protocol architecture with an in-depth exploration of the individual components powering Wormhole's infrastructure. [:custom-arrow: Understand Wormhole's infrastructure](/docs/learn/infrastructure/)
## Multichain Transfers Wormhole transfer products offer multiple options to meet your project's needs. Which product is best for your use depends upon the asset transfer type you want to add to your dApp. ### Wrapped Tokens This method is likely familiar to you if you've previously built bridging projects.
- :octicons-book-16:{ .lg .middle } **Token Bridge** --- - Use Wormhole deployed contracts to simplify development - Multichain token transfers through lock-and-mint-mechanism - Option to include message payload for uses like social platforms [:custom-arrow: Learn about Token Bridge](/docs/learn/transfers/token-bridge/)
### Native Tokens Eliminate wrapped assets to preserve your token's native properties across chains.
- :octicons-book-16:{ .lg .middle } **Native Token Transfers** --- - Deploy custom smart contracts to retain token ownership and upgrade authority with complete customizability - No wrapped tokens or liquidity pools to avoid slippage and MEV risk - Custom attestation is available by adding external verifiers [:custom-arrow: Learn about Native Token Transfers](/docs/learn/transfers/native-token-transfers/)
### Intent-Based Transfers Institutional-scale digital asset settlement for individual users and insitutions.
- :octicons-book-16:{ .lg .middle } **Settlement** --- - Leverages three protocols, providing flexibility and redundancy - Chain-agnostic to liquidity on chains, enabling broader chain support - English auction determines the winning solver for settlement - USDC as the shuttle asset for seamless cross-chain settlement [:custom-arrow: Learn about Settlement](/docs/learn/transfers/settlement/)
## Governance Explore MultiGov, Wormhole's multichain governance solution.
- :octicons-book-16:{ .lg .middle } **Multichain Governance** --- - Multichain voting and proposal execution - Aggregates votes from all chains - Can integrate with any Wormhole-supported blockchain [:custom-arrow: Learn about MultiGov](/docs/learn/governance/)
## Additional Resources
- :octicons-book-16:{ .lg .middle } **Product Comparison** --- Compare Wormhole's multichain solutions for bridging, native transfers, data queries, and governance to enable seamless blockchain interoperability. [:custom-arrow: Compare Products](/docs/build/start-building/products/){target=\_blank} - :octicons-book-16:{ .lg .middle } **Use Cases** --- Explore Wormhole's use cases, from multichain swaps to DeFi, lending, gaming, and more. See how projects integrate Wormhole solutions. [:custom-arrow: Discover Use Cases](/docs/build/start-building/use-cases/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/infrastructure/architecture/ --- BEGIN CONTENT --- --- title: Architecture description: Overview of Wormhole's architecture, detailing key on-chain and off-chain components like the Core Contract, Guardian Network, and relayers. categories: Basics --- # Architecture ## Overview Wormhole has several noteworthy components. Before discussing each component in depth, this page will provide an overview of how the major pieces fit together. ![Wormhole architecture detailed diagram: source to target chain communication.](/docs/images/learn/infrastructure/architecture/architecture-1.webp) The preceding diagram outlines the end-to-end flow of multichain communication through Wormhole's architecture, which is described as follows: 1. **Source chain** - a source contract emits a message by interacting with the [Wormhole Core Contract](/docs/learn/infrastructure/core-contracts/){target=\_blank} on the source chain, which publishes the message in the blockchain's transaction logs 2. **Guardian Network** - [Guardians](/docs/learn/infrastructure/guardians/){target=\_blank} validate these messages and sign them to produce [Verifiable Action Approvals (VAAs)](/docs/learn/infrastructure/vaas/){target=\_blank} 3. **Relayers** - off-chain relayers or applications fetch the VAA and relay it to the target chain 4. **Target chain** - on the target chain, the message is consumed by the appropriate contract. This contract interacts with the Wormhole Core Contract to verify the VAA and execute the intended multichain operation. The flow from the relayer to the target chain involves an entry point contract, which could vary based on the use case: - In some applications, the target contract acts as the entry point and performs verification via the Core Contract - In products like the Token Bridge, the Token Bridge contract itself interacts with the Core Contract ## On-Chain Components - **Emitter** - a contract that calls the publish message method on the Core Contract. To identify the message, the Core Contract will write an event to the transaction logs with details about the emitter and sequence number. This may be your [xDapp](/docs/learn/glossary/#xdapp){target=\_blank} or an existing ecosystem protocol - **[Wormhole Core Contract](/docs/learn/infrastructure/core-contracts/){target=\_blank}** - primary contract, this is the contract which the Guardians observe and which fundamentally allows for multichain communication - **Transaction logs** - blockchain-specific logs that allow the Guardians to observe messages emitted by the Core Contract ## Off-Chain Components - **Guardian Network** - validators that exist in their own P2P network. Guardians observe and validate the messages emitted by the Core Contract on each supported chain to produce VAAs (signed messages) - **[Guardian](/docs/learn/infrastructure/guardians/){target=\_blank}** - one of 19 validators in the Guardian Network that contributes to the VAA multisig - **[Spy](/docs/learn/infrastructure/spy/){target=\_blank}** - a daemon that subscribes to messages published within the Guardian Network. A Spy can observe and forward network traffic, which helps scale up VAA distribution - **[API](https://docs.wormholescan.io/){target=\_blank}** - a REST server to retrieve details for a VAA or the Guardian Network - **[VAAs](/docs/learn/infrastructure/vaas/){target=\_blank}** - Verifiable Action Approvals (VAAs) are the signed attestation of an observed message from the Wormhole Core Contract - **[Relayer](/docs/learn/infrastructure/relayer/){target=\_blank}** - any off-chain process that relays a VAA to the target chain - **Wormhole relayers** - a decentralized relayer network that delivers messages that are requested on-chain via the Wormhole relayer contract - **Custom relayers** - relayers that only handle VAAs for a specific protocol or multichain application. They can execute custom logic off-chain, reducing gas costs and increasing multichain compatibility. Currently, multichain application developers are responsible for developing and hosting custom relayers ## Next Steps
- :octicons-book-16:{ .lg .middle } **Core Contracts** --- Discover Wormhole's Core Contracts, enabling multichain communication with message sending, receiving, and multicast features for efficient synchronization. [:custom-arrow: Explore Core Contracts](/docs/learn/infrastructure/core-contracts/) - :octicons-tools-16:{ .lg .middle } **Core Messaging** --- Follow the guides in this section to work directly with the building blocks of Wormhole messaging, Wormhole-deployed relayers and Core Contracts, to send, receive, validate, and track multichain messages. [:custom-arrow: Build with Core Messaging](/docs/build/core-messaging/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/infrastructure/core-contracts/ --- BEGIN CONTENT --- --- title: Core Contracts description: Discover Wormhole's Core Contracts, which enable multichain communication with message sending, receiving, and multicast features for efficient synchronization. categories: Basics --- # Core Contracts ## Introduction The Wormhole Core Contract is deployed across each supported blockchain network. This contract is a fundamental component of the Wormhole interoperability protocol and acts as the foundational layer enabling secure and efficient multichain messaging. All multichain applications either interact directly with the Core Contract or with another contract that does. This page summarizes the key functions of the Core Contract and outlines how the Core Contract works. ## Key Functions Key functions of the Wormhole Core Contract include the following: - **Multichain messaging** - standardizes and secures the format of messages to facilitate consistent communication for message transfer between Wormhole-connected blockchain networks, allowing developers to leverage the unique features of each network - **Verification and validation** - verifies and validates all VAAs received on the target chain by confirming the Guardian signature to ensure the message is legitimate and has not been manipulated or altered - **Guardian Network coordination** - coordinates with Wormhole's Guardian Network to facilitate secure, trustless communication across chains and ensure that only validated interactions are processed to enhance the protocol's overall security and reliability - **Event emission for monitoring** - emits events for every multichain message processed, allowing for network activity monitoring like tracking message statuses, debugging, and applications that can react to multichain events in real time ## How the Core Contract Works The Wormhole Core Contract is central in facilitating secure and efficient multichain transactions. It enables communication between different blockchain networks by packaging transaction data into standardized messages, verifying their authenticity, and ensuring they are executed correctly on the destination chain. The following describes the role of the Wormhole Core Contract in message transfers: 1. **Message submission** - when a user initiates a multichain transaction, the Wormhole Core Contract on the source chain packages the transaction data into a standardized message payload and submits it to the Guardian Network for verification 2. **Guardian verification** - the Guardians independently observe and sign the message. Once enough Guardians have signed the message, the collection of signatures is combined with the message and metadata to produce a VAA 3. **Message reception and execution** - on the target chain, the Wormhole Core Contract receives the verified message, checks the Guardians' signatures, and executes the corresponding actions like minting tokens, updating states, or calling specific smart contract functions For a closer look at how messages flow between chains and all of the components involved, you can refer to the [Architecture Overview](/docs/learn/infrastructure/architecture/) page. ### Message Submission You can send multichain messages by calling a function against the source chain Core Contract, which then publishes the message. Message publishing strategies can differ by chain; however, generally, the Core Contract posts the following items to the blockchain logs: - `emitterAddress` - the contract which made the call to publish the message - `sequenceNumber` - a unique number that increments for every message for a given emitter (and implicitly chain) - `consistencyLevel`- the level of finality to reach before the Guardians will observe and attest the emitted event. This is a defense against reorgs and rollbacks since a transaction, once considered "final," is guaranteed not to have the state changes it caused rolled back. Since different chains use different consensus mechanisms, each one has different finality assumptions, so this value is treated differently on a chain-by-chain basis. See the options for finality for each chain in the [Wormhole Finality](/docs/build/reference/consistency-levels/){target=\_blank} reference page There are no fees to publish a message except when publishing on Solana, but this is subject to change in the future. ### Message Reception When you receive a multichain message on the target chain Core Contract, you generally must parse and verify the [components of a VAA](/docs/learn/infrastructure/vaas#vaa-format){target=\_blank}. Receiving and verifying a VAA ensures that the Guardian Network properly attests to the message and maintains the integrity and authenticity of the data transmitted between chains. ## Multicast Multicast refers to simultaneously broadcasting a single message or transaction across different blockchains with no destination address or chain for the sending and receiving functions. VAAs attest that "this contract on this chain said this thing." Therefore, VAAs are multicast by default and will be verified as authentic on any chain where they are used. This multicast-by-default model makes it easy to synchronize state across the entire ecosystem. A blockchain can make its data available to every chain in a single action with low latency, which reduces the complexity of the n^2 problems encountered by routing data to many blockchains. This doesn't mean an application _cannot_ specify a destination address or chain. For example, the [Token Bridge](/docs/learn/transfers/token-bridge/){target=\_blank} and [Wormhole relayer](/docs/learn/infrastructure/relayer/){target=\_blank} contracts require that some destination details be passed and verified on the destination chain. Because the VAA creation is separate from relaying, the multicast model does not incur an additional cost when a single chain is targeted. If the data isn't needed on a certain blockchain, don't relay it there, and it won't cost anything. ## Next Steps
- :octicons-book-16:{ .lg .middle } **Verified Action Approvals (VAA)** --- Learn about Verified Action Approvals (VAAs) in Wormhole, their structure, validation, and their role in multichain communication. [:custom-arrow: Learn About VAAs](/docs/learn/infrastructure/vaas/) - :octicons-tools-16:{ .lg .middle } **Get Started with Core Contracts** --- This guide walks through the key methods of the Core Contracts, providing you with the knowledge needed to integrate them into your multichain contracts. [:custom-arrow: Build with Core Contracts](/docs/build/core-messaging/core-contracts/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/infrastructure/guardians/ --- BEGIN CONTENT --- --- title: Guardians description: Explore Wormhole's Guardian Network, a decentralized system for secure, scalable cross-chain communication across various blockchain ecosystems. categories: Basics --- ## Guardian Wormhole relies on a set of 19 distributed nodes that monitor the state on several blockchains. In Wormhole, these nodes are referred to as Guardians. The current Guardian set can be seen in the [Dashboard](https://wormhole-foundation.github.io/wormhole-dashboard/#/?endpoint=Mainnet){target=\_blank}. Guardians fulfill their role in the messaging protocol as follows: 1. Each Guardian observes messages and signs the corresponding payloads in isolation from the other Guardians 2. Guardians combine their indpendent signatures to form a multisig 3. This multisig represents proof that a majority of the Wormhole network has observed and agreed upon a state Wormhole refers to these multisigs as [Verifiable Action Approvals](/docs/learn/infrastructure/vaas/){target=\_blank} (VAAs). ## Guardian Network The Guardian Network functions as Wormhole's decentralized oracle, ensuring secure, cross-chain interoperability. Learning about this critical element of the Wormhole ecosystem will help you better understand the protocol. The Guardian Network is designed to help Wormhole deliver on five key principles: - **Decentralization** - control of the network is distributed across many parties - **Modularity** - independent components (e.g., oracle, relayer, applications) ensure flexibility and upgradeability - **Chain agnosticism** - supports EVM, Solana, and other blockchains without relying on a single network - **Scalability** - can handle large transaction volumes and high-value transfers - **Upgradeable** - can change the implementation of its existing modules without breaking integrators to adapt to changes in decentralized computing The following sections explore each principle in detail. ### Decentralization Decentralization remains the core concern for interoperability protocols. Earlier solutions were fully centralized, and even newer models often rely on a single entity or just one or two actors, creating low thresholds for collusion or failure. Two common approaches to decentralization have notable limitations: - **Proof-of-Stake (PoS)** - while PoS is often seen as a go-to model for decentralization, it's not well-suited for a network that verifies many blockchains and doesn't run its own smart contracts. Its security in this context is unproven, and it introduces complexities that make other design goals harder to achieve - **Zero-Knowledge Proofs (ZKPs)** - ZKPs offer a trustless and decentralized approach, but the technology is still early-stage. On-chain verification is often too computationally expensive—especially on less capable chains—so a multisig-based fallback is still required for practical deployment In the current De-Fi landscape, most major blockchains are secured by a small group of validator companies. Only a limited number of companies worldwide have the expertise and capital to run high-performance validators. If a protocol could unite many of these top validator companies into a purpose-built consensus mechanism designed for interoperability, it would likely offer better performance and security than a token-incentivized network. The key question is: how many of them could Wormhole realistically involve? To answer that, consider these key constraints and design decisions: - **Threshold signatures allow flexibility, but** - with threshold signatures, in theory, any number of validators could participate. However, threshold signatures are not yet widely supported across blockchains. Verifying them is expensive and complex, especially in a chain-agnostic system - **t-Schnorr multisig is more practical** - Wormhole uses [t-Schnorr multisig](https://en.wikipedia.org/wiki/Schnorr_signature){target=\_blank}, which is broadly supported and relatively inexpensive to verify. However, verification costs scale linearly with the number of signers, so the size of the validator set needs to be carefully chosen - **19 validators is the optimal tradeoff** - a set of 19 participants presents a practical compromise between decentralization and efficiency. With a two-thirds consensus threshold, only 13 signatures must be verified on-chain—keeping gas costs reasonable while ensuring strong security - **Security through reputation, not tokens** - Wormhole relies on a network of established validator companies instead of token-based incentives. These 19 Guardians are among the most trusted operators in the industry—real entities with a track record, not anonymous participants This forms the foundation for a purpose-built Proof-of-Authority (PoA) consensus model, where each Guardian has an equal stake. As threshold signatures gain broader support, the set can expand. Once ZKPs become widely viable, the network can evolve into a fully trustless system. ### Modularity Wormhole is designed with simple components that are very good at a single function. Separating security and consensus (Guardians) from message delivery ([relayers](/docs/learn/infrastructure/relayer/){target=\_blank}) allows for the flexibility to change or upgrade one component without disrupting the others. ### Chain Agnosticism Today, Wormhole supports a broader range of ecosystems than any other interoperability protocol because it uses simple tech (t-schnorr signatures), an adaptable, heterogeneous relayer model, and a robust validator network. Wormhole can expand to new ecosystems as quickly as a [Core Contract](/docs/learn/infrastructure/core-contracts/){target=\_blank} can be developed for the smart contract runtime. ### Scalability Wormhole scales well, as demonstrated by its ability to handle substantial total value locked (TVL) and transaction volume even during tumultuous events. Every Guardian must run a full node for every blockchain in the ecosystem. This requirement can be computationally heavy to set up; however, once all the full nodes are running, the Guardian Network's actual computation needs become lightweight. Performance is generally limited by the speed of the underlying blockchains, not the Guardian Network itself. ### Upgradeable Wormhole is designed to adapt and evolve in the following ways: - **Guardian Set expansion** – future updates may introduce threshold signatures to allow for more Guardians in the set - **ZKP integration** - as Zero-Knowledge Proofs become more widely supported, the network can transition to a fully trustless model These principles combine to create a clear pathway towards a fully trustless interoperability layer that spans decentralized computing. ## Next Steps
- :octicons-book-16:{ .lg .middle } **Relayers** --- Discover the role of relayers in the Wormhole network, including client-side, custom, and Wormhole-deployed types, for secure cross-chain communication. [:custom-arrow: Learn About Relayers](/docs/learn/infrastructure/relayer/) - :octicons-tools-16:{ .lg .middle } **Query Guardian Data** --- Learn how to use Wormhole Queries to add real-time access to Guardian-attested on-chain data via a REST endpoint to your dApp, enabling secure cross-chain interactions and verifications. [:custom-arrow: Build with Queries](/docs/build/queries/overview/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/infrastructure/ --- BEGIN CONTENT --- --- title: Infrastructure Components description: Explore Wormhole's infrastructure, including the key components that enable secure multichain communication and asset transfers across blockchain networks. categories: Basics --- # Infrastructure Components This section examines the core components that power Wormhole's infrastructure, including Guardians, relayers, VAAs, and the Spy. ## Get Started Start here for an overview of Wormhole architecture components and security mechanisms:
- :octicons-book-16:{ .lg .middle } **Architecture Overview** --- Overview of Wormhole's architecture, detailing key on-chain and off-chain components like the Core Contract, Guardian Network, and relayers. [:custom-arrow: Learn About Architecture](/docs/learn/infrastructure/architecture/) - :octicons-book-16:{ .lg .middle } **Security** --- Explore Wormhole's security features, including the Guardian network, governance, and monitoring. [:custom-arrow: Learn About Security](/docs/learn/security/)
## Explore Components The relationship between individual components can be demonstrated through the simplified flow of a multichain message from a source-chain contract to a target-chain contract. Select the title of each step to learn more about that component: [timeline left(wormhole-docs/.snippets/text/learn/infrastructure/infrastructure-index-timeline.json)] The [Spy](/docs/learn/infrastructure/spy/) continuously runs in the background to subscribe to gossiped messages across the Guardian Network and enable real-time network activity monitoring. ## Next Steps
- :octicons-book-16:{ .lg .middle } **Messaging Components** --- Learn more about individual messaging components such as Core Contracts, VAAs, Guardians, and relayers [:custom-arrow: Explore Core Contracts](/docs/learn/infrastructure/core-contracts/) - :octicons-people-16:{ .lg .middle } **Core Messaging Guides** --- Explore this section for guides to using Wormhole Relayer and Core Contracts in your project. [:custom-arrow: Build with Core Messaging](/docs/build/core-messaging/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/infrastructure/relayer/ --- BEGIN CONTENT --- --- title: Relayers description: Discover the role of relayers in the Wormhole network, including client-side, custom, and Wormhole-deployed types, for secure cross-chain communication. categories: Basics --- # Relayers This page provides a comprehensive guide to relayers within the Wormhole network, describing their role, types, and benefits in facilitating cross-chain processes. Relayers in the Wormhole context are processes that deliver [Verified Action Approvals (VAAs)](/docs/learn/infrastructure/vaas/){target=\_blank} to their destination, playing a crucial role in Wormhole's security model. They can't compromise security, only availability, and act as delivery mechanisms for VAAs without the capacity to tamper with the outcome. There are three primary types of relayers discussed: - **Client-side relaying** - a cost-efficient, no-backend-infrastructure approach relying on user-facing front ends. It provides a simple solution, although it can complicate the user experience due to the manual steps involved - **Custom relayers** - backend components that handle parts of the cross-chain process, offering a smoother user experience and allowing off-chain calculations to reduce gas costs. These relayers could operate through direct listening to the Guardian Network (Spy relaying) - **Wormhole-deployed relayers** - a decentralized relayer network that can deliver arbitrary VAAs, reducing the developer's need to develop, host, or maintain relayers. However, they require all calculations to be done on-chain and might be less gas-efficient ## Fundamentals This section highlights the crucial principles underpinning the operation and handling of relayers within the Wormhole network. Relayers are fundamentally trustless entities within the network, meaning while they don't require your trust to operate, you also shouldn't trust them implicitly. Relayers function as delivery mechanisms, transporting VAAs from their source to their destination. Key characteristics of VAAs include: - Public emission from the Guardian Network - Authentication through signatures from the Guardian Network - Verifiability by any entity or any Wormhole Core Contract These characteristics mean anyone can pick up a VAA and deliver it anywhere, but no one can alter the VAA content without invalidating the signatures. Keep in mind the following security considerations around relayers: - **Trusting information** - it is crucial not to trust information outside your contract or a VAA. Relying on information from a relayer could expose you to input attacks - **Gas optimization** - using relayers to perform trustless off-chain computation to pass into the destination contract can optimize gas costs but also risk creating attack vectors if not used correctly - **Deterministic by design** - the design of a relayer should ensure a single, deterministic way to process messages in your protocol. Relayers should have a "correct" implementation, mirroring "crank turner" processes used elsewhere in blockchain ## Client-Side Relaying Client-side relaying relies on user-facing front ends, such as a webpage or a wallet, to complete the cross-chain process. ### Key Features - **Cost-efficiency** - users only pay the transaction fee for the second transaction, eliminating any additional costs - **No backend infrastructure** - the process is wholly client-based, eliminating the need for a backend relaying infrastructure ### Implementation Users themselves carry out the three steps of the cross-chain process: 1. Perform an action on chain A 2. Retrieve the resulting VAA from the Guardian Network 3. Perform an action on chain B using the VAA ### Considerations Though simple, this type of relaying is generally not recommended if your aim is a highly polished user experience. It can, however, be useful for getting a Minimum Viable Product (MVP) up and running. - Users must sign all required transactions with their own wallet - Users must have funds to pay the transaction fees on every chain involved - The user experience may be cumbersome due to the manual steps involved ## Custom Relayers Custom relayers are purpose-built components within the Wormhole protocol, designed to relay messages for specific applications. They can perform off-chain computations and can be customized to suit a variety of use cases. The main method of setting up a custom relayer is by listening directly to the Guardian Network via a [Spy](/docs/learn/infrastructure/spy/). ### Key Features - **Optimization** - capable of performing trustless off-chain computations which can optimize gas costs - **Customizability** - allows for specific strategies like batching, conditional delivery, multi-chain deliveries, and more - **Incentive structure** - developers have the freedom to design an incentive structure suitable for their application - **Enhanced UX** - the ability to retrieve a VAA from the Guardian Network and perform an action on the target chain using the VAA on behalf of the user can simplify the user experience ### Implementation A plugin relayer to make the development of custom relayers easier is available in the [main Wormhole repository](https://github.com/wormhole-foundation/wormhole/tree/main/relayer){target=\_blank}. This plugin sets up the basic infrastructure for relaying, allowing developers to focus on implementing the specific logic for their application. ### Considerations Remember, despite their name, custom relayers still need to be considered trustless. VAAs are public and can be submitted by anyone, so developers shouldn't rely on off-chain relayers to perform any computation considered "trusted." - Development work and hosting of relayers are required - The fee-modeling can become complex, as relayers are responsible for paying target chain fees - Relayers are responsible for availability, and adding dependencies for the cross-chain application ## Wormhole Relayers Wormhole relayers are a component of a decentralized network in the Wormhole protocol. They facilitate the delivery of VAAs to recipient contracts compatible with the standard relayer API. ### Key Features - **Lower operational costs** - no need to develop, host, or maintain individual relayers - **Simplified integration** - because there is no need to run a relayer, integration is as simple as calling a function and implementing an interface ### Implementation The Wormhole relayer integration involves two key steps: - **Delivery request** - request delivery from the ecosystem Wormhole relayer contract - **Relay reception** - implement a [`receiveWormholeMessages`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/bacbe82e6ae3f7f5ec7cdcd7d480f1e528471bbb/src/interfaces/IWormholeReceiver.sol#L44-L50){target=\_blank} function within their contracts. This function is invoked upon successful relay of the VAA ### Considerations Developers should note that the choice of relayers depends on their project's specific requirements and constraints. Wormhole relayers offer simplicity and convenience but limit customization and optimization opportunities compared to custom relayers. - All computations are performed on-chain - Potentially less gas-efficient compared to custom relayers - Optimization features like conditional delivery, batching, and off-chain calculations might be restricted - Support may not be available for all chains ## Next Steps
- :octicons-book-16:{ .lg .middle } **Spy** --- Discover Wormhole's Spy daemon, which subscribes to gossiped messages in the Guardian Network, including VAAs and Observations, with setup instructions. [:custom-arrow: Learn More About the Spy](/docs/learn/infrastructure/spy/) - :octicons-book-16:{ .lg .middle } **Build with Wormhole Relayers** --- Learn how to use Wormhole-deployed relayer configurations for seamless cross-chain messaging between contracts on different EVM blockchains without off-chain deployments. [:custom-arrow: Get Started with Wormhole Relayers](/docs/build/core-messaging/wormhole-relayers/) - :octicons-book-16:{ .lg .middle } **Run a Custom Relayer** --- Learn how to build and configure your own off-chain custom relaying solution to relay Wormhole messages for your applications using the Relayer Engine. [:custom-arrow: Get Started with Custom Relayers](/docs/infrastructure/relayers/run-relayer/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/infrastructure/spy/ --- BEGIN CONTENT --- --- title: Spy description: Discover Wormhole's Spy daemon, which subscribes to gossiped messages in the Guardian Network, including VAAs and Observations, with setup instructions. categories: Basics --- # Spy In Wormhole's ecosystem, the _Spy_ is a daemon, a continuously running background process that monitors messages within the Guardian Network. Unlike Guardians, a Spy doesn't perform validation; instead, it serves as an interface for observing the network's message traffic, enabling applications and users to access live data transmitted over Wormhole. The primary purpose of a Spy is to subscribe to the gossiped messages across the Guardian Network, tracking key message types that allow integrators and applications to monitor real-time network activity without directly engaging in consensus operations. This page provides a comprehensive guide to where the Spy fits within the Wormhole network, describing the key features and role in facilitating multichain processes. ## Key Features - **Real-time monitoring of Wormhole messages** - the Spy allows users to observe Wormhole messages as they are published across supported chains in near real-time - **Filterable and observable message streams** - users can filter message streams by chain, emitter, and other criteria, making it easier to track specific contracts or categories of interest - **Integration-friendly event streaming** - the Spy exposes gRPC and WebSocket interfaces, making it easy to integrate message observation into custom tooling, dashboards, or indexing services - **Support for multiple message protocols** - it can observe messages from different Wormhole messaging protocols (Token Bridge, CCTP, NTT, etc.), providing broad coverage of cross-chain activity - **Lightweight and infrastructure-ready** - the Spy is designed to run as part of indexing or backend services, not requiring validator-level infrastructure ## Integrator Use Case The Spy provides a valuable mechanism for integrators to observe real-time network activity in the Guardian Network without directly engaging in validation or consensus. By running a Spy, integrators can track multichain events and message flows — such as VAAs, observations, and Guardian heartbeats — to monitor network activity essential to their applications. This monitoring capability is especially beneficial for applications that need immediate insights into multichain data events. Integrators can run a Spy to ensure their applications are promptly informed of message approvals, observations, or Guardian liveness signals, supporting timely and responsive app behavior without additional overhead on network resources. ## Observable Message Categories A Spy can access the following categories of messages shared over the gossip protocol: - [Verifiable Action Approvals (VAAs)](/docs/learn/infrastructure/vaas/){target=\_blank} - packets of multichain data - The Spy can detect whether a VAA has been approved by the Guardian Network, making it a valuable tool for applications needing real-time multichain verification - [Observations](/docs/learn/glossary/#observation){target=\_blank} - emitted by Wormhole's core contracts, observations are picked up by the Guardians and relayed across the network - A Spy allow users to monitor these messages, adding transparency and insight into blockchain events - [Guardian heartbeats](/docs/learn/glossary/#heartbeat){target=\_blank} - heartbeat messages represent Guardian node status - By monitoring heartbeats, a Spy can signal the liveness and connectivity of Guardians in the network ## Additional Resources
- :octicons-code-16:{ .lg .middle } **Spy Source Code** --- To see the source code for the Go implementation of the Spy, visit the `wormhole` repository on GitHub. [:custom-arrow: View the Source Code](https://github.com/wormhole-foundation/wormhole/blob/main/node/cmd/spy/spy.go){target=\_blank} - :octicons-code-16:{ .lg .middle } **Alternative Implementation** --- Visit the `beacon` repository on GitHub to learn more about Beacon, an alternative highly available, reduced-latency version of the Wormhole Spy. [:custom-arrow: Get Started with Pyth Beacon](https://github.com/pyth-network/beacon) - :octicons-book-16:{ .lg .middle } **Discover Wormhole Queries** --- For an alternative option to on-demand access to Guardian-attested multichain data, see the Wormhole Queries page. Queries provide a simple, REST endpoint style developer experience. [:custom-arrow: Explore Queries](/docs/build/queries/overview/)
## Next Steps
- :octicons-code-16:{ .lg .middle } **Run a Spy** --- Learn how to run the needed infrastructure to spin up a Spy daemon locally and subscribe to a stream of Verifiable Action Approvals (VAAs). [:custom-arrow: Spin Up a Spy](/docs/infrastructure/spy/run-spy/){target=\_blank} - :octicons-code-16:{ .lg .middle } **Use Queries** --- For access to real-time network data without infrastructure overhead, follow this guide and use Wormhole Query to construct a query, make a request, and verify the response. [:custom-arrow: Get Started with Queries](/docs/build/queries/use-queries/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/infrastructure/vaas/ --- BEGIN CONTENT --- --- title: VAAs description: Learn about Verified Action Approvals (VAAs) in Wormhole, their structure, validation, and role in cross-chain communication. categories: Basics --- # Verified Action Approvals Verified Action Approvals (VAAs) are Wormhole's core messaging primitive. They are packets of cross-chain data emitted whenever a cross-chain application contract interacts with the Core Contract. [Guardians](/docs/learn/infrastructure/guardians/){target=\_blank} validate messages emitted by contracts before sending them to the target chain. Once a majority of Guardians agree the message is valid, they sign a keccak256 hash of the message body. The message is wrapped up in a structure called a VAA, which combines the message with the Guardian signatures to form a proof. VAAs are uniquely indexed by the (`emitter_chain`, `emitter_address`, `sequence`) tuple. To obtain a VAA, one can query the [Wormholescan API](https://docs.wormholescan.io/){target=\_blank} with this information. The `sequence` field depends on the final ordering of blocks on the emitter chain. When a lower consistency level is chosen (i.e., not waiting for finality), there is a chance that chain reorganizations could lead to multiple, different VAAs appearing for what looks like the “same” message on the user side. The tuple (`emitter_chain`, `emitter_address`, `sequence`) can only be considered unique if the chain does not undergo a reorg and the block containing the message has effectively reached finality. However, there is always a small chance of an extended reorg that could invalidate or alter a previously emitted sequence number. ## VAA Format The basic VAA consists of header and body components described as follows: - **Header** - holds metadata about the current VAA, the Guardian set that is currently active, and the list of signatures gathered so far - `version` ++"byte"++ - the VAA Version - `guardian_set_index` ++"u32"++ - indicates which Guardian set is signing - `len_signatures` ++"u8"++ - the number of signatures stored - `signatures` ++"[]signature"++ - the collection of Guardian signatures Where each `signature` is: - `index` ++"u8"++ - the index of this Guardian in the Guardian set - `signature` ++"[65]byte"++ - the ECDSA signature - **Body** - _deterministically_ derived from an on-chain message. Any two Guardians processing the same message must derive the same resulting body to maintain a one-to-one relationship between VAAs and messages to avoid double-processing messages - `timestamp` ++"u32"++ - the timestamp of the block this message was published in - `nonce` ++"u32"++ - `emitter_chain` ++"u16"++ - the id of the chain that emitted the message - `emitter_address` ++"[32]byte"++ - the contract address (Wormhole formatted) that called the Core Contract - `sequence` ++"u64"++ - the auto-incrementing integer that represents the number of messages published by this emitter - `consistency_level` ++"u8"++ - the consistency level (finality) required by this emitter - `payload` ++"[]byte"++ - arbitrary bytes containing the data to be acted on The deterministic nature of the body is only strictly true once the chain's state is finalized. If a reorg occurs, and a transaction that previously appeared in block X is replaced by block Y, Guardians observing different forks may generate different VAAs for what the emitter contract believes is the same message. This scenario is less likely once a block is sufficiently buried, but it can still happen if you choose a faster (less finalized) consistency level The body contains relevant information for entities, such as contracts or other systems, that process or utilize VAAs. When a function like `parseAndVerifyVAA` is called, the body is returned, allowing verification of the `emitterAddress` to determine if the VAA originated from a trusted contract. Because VAAs have no destination, they are effectively multicast. Any Core Contract on any chain in the network will verify VAAs as authentic. If a VAA has a specific destination, relayers are responsible for appropriately completing that delivery. ## Consistency and Finality The consistency level determines whether Guardians wait for a chain's final commitment state or issue a VAA sooner under less-final conditions. This choice is especially relevant for blockchains without instant finality, where the risk of reorganization remains until a block is deeply confirmed. Guardian watchers are specialized processes that monitor each blockchain in real-time. They enforce the selected consistency level by deciding whether enough commitment has been reached before signing and emitting a VAA. Some chains allow only one commitment level (effectively final), while others let integrators pick between near-final or fully finalized states. Choosing a faster option speeds up VAA production but increases reorg risk. A more conservative option takes longer but reduces the likelihood of rollback. ## Signatures The body of the VAA is hashed twice with `keccak256` to produce the signed digest message. ```js // hash the bytes of the body twice digest = keccak256(keccak256(body)) // sign the result signature = ecdsa_sign(digest, key) ``` !!!tip "Hash vs. double hash" Different implementations of the ECDSA signature validation may apply a keccak256 hash to the message passed, so care must be taken to pass the correct arguments. For example, the [Solana secp256k1 program](https://docs.solanalabs.com/runtime/programs#secp256k1-program){target=\_blank} will hash the message passed. In this case, the argument for the message should be a single hash of the body, not the twice-hashed body. ## Payload Types Different applications built on Wormhole may specify a format for the payloads attached to a VAA. This payload provides information on the target chain and contract so it can take action (e.g., minting tokens to a receiver address). ### Token Transfer Many bridges use a lockup/mint and burn/unlock mechanism to transfer tokens between chains. Wormhole's generic message-passing protocol handles the routing of lock and burn events across chains to ensure Wormhole's Token Bridge is chain-agnostic and can be rapidly integrated into any network with a Wormhole contract. Transferring tokens from the sending chain to the destination chain requires the following steps: 1. Lock the token on the sending chain 2. The sending chain emits a message as proof the token lockup is complete 3. The destination chain receives the message confirming the lockup event on the sending chain 4. The token is minted on the destination chain The message the sending chain emits to verify the lockup is referred to as a transfer message and has the following structure: - `payload_id` ++"u8"++ - the ID of the payload. This should be set to `1` for a token transfer - `amount` ++"u256"++ - amount of tokens being transferred - `token_address` ++"u8[32]"++ - address on the source chain - `token_chain` ++"u16"++ - numeric ID for the source chain - `to` ++"u8[32]"++ - address on the destination chain - `to_chain` ++"u16"++ - numeric ID for the destination chain - `fee` ++"u256"++ - portion of amount paid to a relayer This structure contains everything the destination chain needs to learn about a lockup event. Once the destination chain receives this payload, it can mint the corresponding asset. Note that the destination chain is agnostic regarding how the tokens on the sending side were locked. They could have been burned by a mint or locked in a custody account. The protocol relays the event once enough Guardians have attested to its existence. ### Attestation While the destination chain can trust the message from the sending chain to inform it of token lockup events, it has no way of verifying the correct token is locked up. To solve this, the Token Bridge supports token attestation. To create a token attestation, the sending chain emits a message containing metadata about a token, which the destination chain may use to preserve the name, symbol, and decimal precision of a token address. The message format for token attestation is as follows: - `payload_id` ++"u8"++ - the ID of the payload. This should be set to `2` for an attestation - `token_address` ++"[32]byte"++ - address of the originating token contract - `token_chain` ++"u16"++ - chain ID of the originating token - `decimals` ++"u8"++ - number of decimals this token should have - `symbol` ++"[32]byte"++ - short name of asset - `name` ++"[32]byte"++ - full name of asset #### Attestation Tips Be aware of the following considerations when working with attestations: - Attestations use a fixed-length byte array to encode UTF8 token name and symbol data. Because the byte array is fixed length, the data contained may truncate multibyte Unicode characters - When sending an attestation VAA, it is recommended to send the longest UTF8 prefix that doesn't truncate a character and then right-pad it with zero bytes - When parsing an attestation VAA, it is recommended to trim all trailing zero bytes and convert the remainder to UTF-8 via any lossy algorithm - Be mindful that different on-chain systems may have different VAA parsers, resulting in different names/symbols on different chains if the string is long or contains invalid UTF8 - Without knowing a token's decimal precision, the destination chain cannot correctly mint the number of tokens when processing a transfer. For this reason, the Token Bridge requires an attestation for each token transfer ### Token Transfer with Message The Token Transfer with Message data structure is identical to the token-only data structure, except for the following: - **`fee` field** - replaced with the `from_address` field - **`payload` field** - is added containing arbitrary bytes. A dApp may include additional data in this arbitrary byte field to inform some application-specific behavior This VAA type was previously known as Contract Controlled Transfer and is also sometimes referred to as a `payload3` message. The Token Transfer with Message data sructure is as follows: - `payload_id` ++"u8"++ - the ID of the payload. This should be set to `3` for a token transfer with message - `amount` ++"u256"++ - amount of tokens being transferred - `token_address` ++"u8[32]"++ - address on the source chain - `token_chain` ++"u16"++ - numeric ID for the source chain - `to` ++"u8[32]"++ - address on the destination chain - `to_chain` ++"u16"++ - numeric ID for the destination chain - `from_address` ++"u8[32]"++ - address that called the Token Bridge on the source chain - `payload` ++"[]byte"++ - message, arbitrary bytes, app-specific ### Governance Governance VAAs don't have a `payload_id` field like the preceding formats. Instead, they trigger an action in the deployed contracts (for example, an upgrade). #### Action Structure Governance messages contain pre-defined actions, which can target the various Wormhole modules currently deployed on-chain. The structure includes the following fields: - `module` ++"u8[32]"++ - contains a right-aligned module identifier - `action` ++"u8"++ - predefined governance action to execute - `chain` ++"u16"++ - chain the action is targeting. This should be set to `0` for all chains - `args` ++"any"++ - arguments to the action Below is an example message containing a governance action triggering a code upgrade to the Solana Core Contract. The module field here is a right-aligned encoding of the ASCII Core, represented as a 32-byte hex string. ```js module: 0x0000000000000000000000000000000000000000000000000000436f7265 action: 1 chain: 1 new_contract: 0x348567293758957162374959376192374884562522281937446234828323 ``` #### Actions The meaning of each numeric action is pre-defined and documented in the Wormhole design documents. For each application, the relevant definitions can be found via these links: - [Core governance actions](https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0002_governance_messaging.md){target=\_blank} - [Token Bridge governance actions](https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0003_token_bridge.md){target=\_blank} ## Lifetime of a Message Anyone can submit a VAA to the target chain. Guardians typically don't perform this step to avoid transaction fees. Instead, applications built on top of Wormhole can acquire a VAA via the Guardian RPC and submit it in a separate flow. With the concepts now defined, it is possible to illustrate a full flow for message passing between two chains. The following stages demonstrate each step of processing that the Wormhole network performs to route a message. 1. **A message is emitted by a contract running on Chain A** - any contract can emit messages, and the Guardians are programmed to observe all chains for these events. Here, the Guardians are represented as a single entity to simplify the graphics, but the observation of the message must be performed individually by each of the 19 Guardians 2. **Signatures are aggregated** - Guardians independently observe and sign the message. Once enough Guardians have signed the message, the collection of signatures is combined with the message and metadata to produce a VAA 3. **VAA submitted to target chain** - the VAA acts as proof that the Guardians have collectively attested the existence of the message payload. The VAA is submitted (or relayed) to the target chain to be processed by a receiving contract and complete the final step ![Lifetime of a message diagram](/docs/images/learn/infrastructure/vaas/lifetime-vaa-diagram.webp) ## Next Steps
- :octicons-book-16:{ .lg .middle } **Guardians** --- Explore Wormhole's Guardian Network, a decentralized system for secure, scalable cross-chain communication across various blockchain ecosystems. [:custom-arrow: Learn About Guardians](/docs/learn/infrastructure/guardians/) - :octicons-tools-16:{ .lg .middle } **Wormhole Relayer** --- Explore this guide to using Wormhole-deployed relayers to send and receive messages using VAAs. [:custom-arrow: Build with Wormhole Relayer](/docs/build/core-messaging/wormhole-relayers/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/introduction/ --- BEGIN CONTENT --- --- title: Introduction to Wormhole description: Wormhole is a protocol for seamless communication between blockchains, enabling cross-chain applications and integrations. categories: Basics --- # Introduction to Wormhole In the rapidly evolving landscape of blockchain technology, interoperability between different blockchains remains a significant challenge. Developers often face hurdles in creating applications that can seamlessly operate across multiple blockchains, limiting innovation and the potential of decentralized ecosystems. Wormhole addresses this problem by providing a _generic message-passing_ protocol that enables secure and efficient communication between blockchains. By allowing data and asset transfers across various blockchain networks, Wormhole breaks down the walls that traditionally separate these ecosystems. Wormhole is distinguished by its focus on robust security, scalability, and transparency. The protocol is supported by a decentralized network of validators that ensure the integrity of every cross-chain transaction. This, combined with Wormhole’s proven performance in real-world applications, gives developers a dependable platform to create and scale multichain applications confidently. ![Message-passing process in the Wormhole protocol](/docs/images/learn/introduction/introduction-1.webp) !!! note The above is an oversimplified illustration of the protocol; details about the architecture and components are available on the [architecture page](/docs/learn/infrastructure/architecture/){target=\_blank}. Wormhole allows developers to leverage the strengths of multiple blockchain ecosystems without being confined to one. This means applications can benefit from the unique features of various networks—such as Solana's high throughput, Ethereum's security, and Cosmos's interoperability while maintaining a unified, efficient user experience. This page introduces the key concepts and components necessary to understand how Wormhole enables fast, secure, and scalable cross-chain communication. ## What Problems Does Wormhole Solve? Interoperability is a critical challenge in the rapidly evolving blockchain landscape. Individual blockchains are often isolated, limiting the potential for integrated applications operating across multiple ecosystems. Wormhole solves this problem by enabling seamless communication between blockchains, allowing developers to create multichain applications that can leverage the unique features of each network. Critical problems Wormhole addresses include: - **Blockchain isolation** - Wormhole connects disparate blockchains, enabling the transfer of assets, data, and governance actions across networks - **Cross-chain complexity** - by abstracting the complexities of cross-chain communication, Wormhole makes it easier for developers to build and deploy cross-chain applications - **Security and decentralization** - Wormhole prioritizes security through a decentralized Guardian network that validates and signs messages, ensuring the integrity of cross-chain interactions ## What Does Wormhole Offer? Wormhole provides a suite of tools and protocols that support a wide range of use cases: - **Cross-chain messaging** - securely transfer arbitrary data between blockchains, enabling the development of cross-chain decentralized applications (xDapps) - **Asset transfers** - facilitate the movement of tokens and NFTs across supported chains with ease, powered by protocols built on Wormhole like [Portal](https://portalbridge.com/){target=\_blank} - **Developer tools** - leverage [Wormhole’s SDKs](/docs/build/toolkit/typescript-sdk/){target=\_blank}, [APIs](/docs/build/toolkit/#wormhole-api-docs){target=\_blank}, [Wormhole Scan](https://wormholescan.io/){target=\_blank}, and documentation to build and deploy cross-chain applications quickly and efficiently ## What Isn't Wormhole? - **Wormhole is _not_ a blockchain** - it acts as a communication layer that connects different blockchains, enabling them to interact without being a blockchain itself - **Wormhole is _not_ a token bridge** - while it facilitates token transfers, Wormhole also supports a wide range of cross-chain applications, making it much more versatile than a typical bridge ## Use Cases of Wormhole Consider the following examples of potential applications enabled by Wormhole: - **Cross-chain exchange** - using [Wormhole Connect](/docs/build/transfers/connect/overview/){target=\_blank}, developers can build exchanges that allow deposits from any Wormhole-connected chain, significantly increasing liquidity access - **[Cross-chain governance](https://wormhole.com/blog/stake-for-governance-is-now-live-for-w-token-holders){target=\_blank}** - NFT collections on different networks can use Wormhole to communicate votes cast on their respective chains to a designated "voting" chain for combined proposals - **Cross-chain game** - games can be developed on a performant network like Solana, with rewards issued as NFTs on another network, such as Ethereum ## Explore Discover more about the Wormhole ecosystem, components, and protocols: - **[Architecture](/docs/learn/infrastructure/architecture/){target=\_blank}** - explore the components of the protocol - **[Protocol Specifications](https://github.com/wormhole-foundation/wormhole/tree/main/whitepapers){target=\_blank}** - learn about the protocols built on top of Wormhole ## Demos Demos offer more realistic implementations than tutorials: - **[Wormhole Scaffolding](https://github.com/wormhole-foundation/wormhole-scaffolding){target=\_blank}** - quickly set up a project with the Scaffolding repository - **[xDapp Book Projects](https://github.com/wormhole-foundation/xdapp-book/tree/main/projects){target=\_blank}** - run and learn from example programs !!! note Wormhole Integration Complete? Let us know so we can list your project in our ecosystem directory and introduce you to our global, multichain community! **[Reach out now!](https://forms.clickup.com/45049775/f/1aytxf-10244/JKYWRUQ70AUI99F32Q){target=\_blank}** ## Supported Blockchains Wormhole supports a growing number of blockchains.
### EVM | Ethereum | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Acala | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Arbitrum | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Avalanche | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Base | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Berachain | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Blast | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | BNB Smart Chain | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Celo | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Fantom | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Gnosis | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | HyperEVM | EVM | :x: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs | | Ink | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Kaia | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Karura | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs | | Linea | EVM | :x: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Mantle | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Mezo | EVM | :x: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Monad | EVM | :x: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Moonbeam | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Neon | EVM | :x: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Oasis | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Optimism | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Polygon | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Scroll | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Seievm | EVM | :x: | :white_check_mark: | | | SNAXchain | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Unichain | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | World Chain | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | X Layer | EVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | ### SVM | Solana | SVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Pythnet | SVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | ### AVM | Algorand | AVM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | ### CosmWasm | Injective | CosmWasm | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Neutron | CosmWasm | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Osmosis | CosmWasm | :x: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Sei | CosmWasm | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Terra | CosmWasm | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | Terra 2.0 | CosmWasm | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | | XPLA | CosmWasm | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | ### Move VM | Aptos | Move VM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | ### NEAR VM | NEAR | NEAR VM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer | ### Sui Move VM | Sui | Sui Move VM | :white_check_mark: | :white_check_mark: | :material-web:Website:material-file-document:Developer Docs:octicons-package-16:Block Explorer |
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/security/ --- BEGIN CONTENT --- --- title: Security description: Explore Wormhole's security features, including the Guardian network, governance, monitoring, open-source development, and bug bounty programs. categories: Basics --- # Security ## Core Security Assumptions At its core, Wormhole is secured by a network of [Guardian](/docs/learn/infrastructure/guardians/){target=\_blank} nodes that validate and sign messages. If a super majority (e.g., 13 out of 19) of Guardians sign the same message, it can be considered valid. A smart contract on the target chain will verify the signatures and format of the message before approving any transaction. - Wormhole's core security primitive is its signed messages (signed [VAAs](/docs/learn/glossary/#vaa){target=\_blank}) - The Guardian network is currently secured by a collection of 19 of the world's top [validator companies](https://wormhole-foundation.github.io/wormhole-dashboard/#/?endpoint=Mainnet){target=\_blank} - Guardians produce signed state attestations (signed VAAs) when requested by a Core Contract integrator - Every Guardian runs full nodes (rather than light nodes) of every blockchain in the Wormhole network, so if a blockchain suffers a consensus attack or hard fork, the blockchain will disconnect from the network rather than potentially produce invalid signed VAAs - Any Signed VAA can be verified as authentic by the Core Contract of any other chain - [Relayers](/docs/learn/glossary/#relayer){target=\_blank} are considered untrusted in the Wormhole ecosystem In summary: - **Core integrators aren't exposed to risk from chains and contracts they don't integrate with** - By default, you only trust Wormhole's signing process and the core contracts of the chains you're on - You can expand your contract and chain dependencies as you see fit Core assumptions aside, many other factors impact the real-world security of decentralized platforms. Here is more information on additional measures that have been put in place to ensure the security of Wormhole. ## Guardian Network Wormhole is an evolving platform. While the Guardian set currently comprises 19 validators, this is a limitation of current blockchain technology. ### Governance Governance is the process through which contract upgrades happen. Guardians manually vote on governance proposals that originate inside the Guardian Network and are then submitted to ecosystem contracts. This means that governance actions are held to the same security standard as the rest of the system. A two-thirds supermajority of the Guardians is required to pass any governance action. Governance messages can target any of the various wormhole modules, including the core contracts and all currently deployed token bridge contracts. When a Guardian signs such a message, its signature implies a vote on the action in question. Once more than two-thirds of the Guardians have signed, the message and governance action are considered valid. All governance actions and contract upgrades have been managed via Wormhole's on-chain governance system. Via governance, the Guardians can: - Change the current Guardian set - Expand the Guardian set - Upgrade ecosystem contract implementations The governance system is fully open source in the core repository. See the [Open Source section](#open-source){target=\_blank} for contract source. ## Monitoring A key element of Wormhole's defense-in-depth strategy is that each Guardian is a highly competent validator company with its own in-house processes for running, monitoring, and securing blockchain operations. This heterogeneous approach to monitoring increases the likelihood that fraudulent activity is detected and reduces the number of single failure points in the system. Guardians are not just running Wormhole validators; they're running validators for every blockchain inside of Wormhole as well, which allows them to perform monitoring holistically across decentralized computing rather than just at a few single points. Guardians monitor: - Block production and consensus of each blockchain - if a blockchain's consensus is violated, it will be disconnected from the network until the Guardians resolve the issue - Smart contract level data - via processes like the Governor, Guardians constantly monitor the circulating supply and token movements across all supported blockchains - Guardian level activity - the Guardian Network functions as an autonomous decentralized computing network, ensuring independent security measures across its validators ## Asset Layer Protections One key strength of the Wormhole ecosystem is the Guardians’ ability to validate and protect the integrity of assets across multiple blockchains. To enforce the Wormhole Asset Layer’s core protections, the Global Accountant tracks the total circulating supply of all Wormhole assets across all chains, preventing any blockchain from bridging assets that could violate the supply invariant. In addition to the Global Accountant, Guardians may only sign transfers that do not violate the requirements of the Governor. The [Governor](https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0007_governor.md){target=\_blank} tracks inflows and outflows of all blockchains and delays suspicious transfers that may indicate an exploit. ## Open Source Wormhole builds in the open and is always open source. - **[Wormhole core repository](https://github.com/wormhole-foundation/wormhole){target=\_blank}** - **[Wormhole Foundation GitHub organization](https://github.com/wormhole-foundation){target=\_blank}** - **[Wormhole contract deployments](/docs/learn/infrastructure/core-contracts/){target=\_blank}** ## Audits Wormhole has been heavily audited, with _29 third-party audits completed_ and more started. Audits have been performed by the following firms: - [Trail of Bits](https://www.trailofbits.com/){target=\_blank} - [Neodyme](https://neodyme.io/en/){target=\_blank} - [Kudelski](https://kudelskisecurity.com/){target=\_blank} - [OtterSec](https://osec.io/){target=\_blank} - [Certik](https://www.certik.com/){target=\_blank} - [Hacken](https://hacken.io/){target=\_blank} - [Zellic](https://www.zellic.io/){target=\_blank} - [Coinspect](https://www.coinspect.com/){target=\_blank} - [Halborn](https://www.halborn.com/){target=\_blank} - [Cantina](https://cantina.xyz/welcome){target=\_blank} All audits and final reports can be found in [security page of the GitHub Repo](https://github.com/wormhole-foundation/wormhole/blob/main/SECURITY.md#3rd-party-security-audits){target=\blank}. ## Bug Bounties Wormhole has one of the largest bug bounty programs in software development and has repeatedly shown commitment to engaging with the white hat community. Wormhole runs a bug bounty program through [Immunefi](https://immunefi.com/bug-bounty/wormhole/){target=\blank} program, with a top payout of **5 million dollars**. If you are interested in contributing to Wormhole security, please look at this section for [Getting Started as a White Hat](https://github.com/wormhole-foundation/wormhole/blob/main/SECURITY.md#white-hat-hacking){target=\blank}, and follow the [Wormhole Contributor Guidelines](https://github.com/wormhole-foundation/wormhole/blob/main/CONTRIBUTING.md){target=\blank}. For more information about submitting to the bug bounty programs, refer to the [Wormhole Immunefi page](https://immunefi.com/bug-bounty/wormhole/){target=\blank}. ## Learn More The [SECURITY.md](https://github.com/wormhole-foundation/wormhole/blob/main/SECURITY.md){target=\blank} from the official repository has the latest security policies and updates. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/transfers/cctp/ --- BEGIN CONTENT --- --- title: Circle's CCTP Bridge description: Unlock fast USDC transfers with Wormhole's integration of Circle's CCTP, featuring automatic relaying via the Wormhole relayer and native gas solutions. categories: Transfer --- # Circle's CCTP Bridge Wormhole Connect and the Wormhole TypeScript SDK support fast, cheap, native USDC bridging between all networks supported by Circle's [Cross-Chain Transfer Protocol](https://www.circle.com/en/cross-chain-transfer-protocol){target=\_blank}. CCTP is Circle's native USDC cross-chain transfer attestation service. While this protocol is wholly separate from Wormhole itself, Wormhole builds on top of CCTP and adds several valuable augmentations, making it more straightforward to use and more useful for end users. These features include: - **Automated relaying** - eliminates the need for users to redeem USDC transfers themselves - **Gas payment on the destination chain** - allows users to transfer USDC without needing to pay gas on the destination chain - **Gas drop off** - enables users to convert a portion of USDC into the destination chain's gas token upon a successful transfer !!! note Wormhole supports all CCTP-supported chains but at the moment only a [handful of chains](https://developers.circle.com/stablecoins/docs/supported-domains){target=\_blank} are supported by Circle. You can use Wormhole's CCTP-powered USDC bridging by embedding the [Connect Widget](/docs/build/transfers/connect/overview/){target=\_blank} or by integrating the [TypeScript SDK](/docs/build/toolkit/typescript-sdk/){target=\_blank} directly. ## Automatic Relaying To complete a CCTP transfer, the [Circle Attestation](https://developers.circle.com/api-reference/stablecoins/common/get-attestation){target=\_blank} must be delivered to the destination chain. This attestation delivery may be difficult or impossible in some contexts. For example, in a browser context, the user doesn't wish to wait for finality to deliver the attestation. To address this difficulty, the Wormhole CCTP relayer may be used either with the [Wormhole Connect Widget](/docs/build/transfers/connect/overview/){target=\_blank} or more directly with the [Wormhole TypeScript SDK](/docs/build/toolkit/typescript-sdk/){target=\_blank}. The Wormhole CCTP Relayer charges a fee to deliver the attestation and complete the transfer. | Chain | Fee | |:---------------:|:---------------:| | Ethereum | 1.0 USDC | | Everything else | 0.1 USDC | ## Native Gas Drop Off Another advantage of using the automatic relaying feature is the opportunity to transfer some native gas to the receiver on the destination chain. This feature is referred to as _native gas drop off_. The ability to perform native gas drop off addresses the common issue where a user may hold a balance of USDC but has no native gas to perform subsequent transactions. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/transfers/ --- BEGIN CONTENT --- --- title: Multichain Transfers description: This section introduces the core messaging protocols that power seamless multichain communication and asset transfer within the Wormhole ecosystem. categories: Transfer --- # Multichain Transfers These sections include information about Wormhole's transfer products to help you learn how they work and determine the best transfer product to fit your needs. Use the following links to jump directly to each Wormhole transfer product information page or continue for a product comparison: - [**Native Token Transfers (NTT)**](/docs/learn/transfers/native-token-transfers/) - a mechanism to transfer native tokens multichain seamlessly without converting to a wrapped asset - [**Settlement**](/docs/learn/transfers/settlement/) - intent-based protocols enabling fast multichain transfers, optimized liquidity flows, and interoperability without relying on traditional bridging methods - [**Token Bridge**](/docs/learn/transfers/token-bridge/) - a bridging solution that uses a lock and mint mechanism ## Compare Transfer Products A few key comparisons can help you readily differentiate between Wormhole transfer product offerings. Use the following sections to help compare and select products: ### NTT vs. Token Bridge Understand the key differences between Native Token Transfers (NTT) and Token Bridge to determine which solution best fits your needs. - Native Token Transfers (NTT) move tokens in their original form without wrapping them, ensuring compatibility with on-chain applications but requiring custom contracts on both the source and destination chains - Token Bridge locks tokens on the source chain and mints wrapped versions on the destination chain. This method does not require changes to existing token contracts and supports additional message payloads for more complex use cases
| Supports | NTT | Token Bridge | |---------------------------|--------------------|--------------------| | Message Payload | :white_check_mark: | :white_check_mark: | | Wrapped Assets | :x: | :white_check_mark: | | Native Assets | :white_check_mark: | :x: | | Contract-Free Development | :x: | :white_check_mark: | | User-Owned Contracts | :white_check_mark: | :x: |
In the following video, Wormhole Foundation DevRel Pauline Barnades walks you through the key differences between Wormhole’s Native Token Transfers (NTT) and Token Bridge and how to select the best option for your use case:
### Settlement Settlement enables fast and efficient multichain transfers by optimizing liquidity without relying on traditional bridging methods. Unlike NTT, which moves native assets directly between chains, and Token Bridge, which locks tokens and mints wrapped versions, Settlement uses intent-based execution. Users specify the desired transfer outcome, and solvers compete to fulfill it most efficiently. By leveraging a decentralized solver network, Settlement ensures efficient cross-chain liquidity without locking assets or requiring asset wrapping, providing a seamless and capital-efficient solution for multichain transactions. ## Additional Resources
- :octicons-tools-16:{ .lg .middle } **Product Comparison** --- Compare Wormhole's multichain solutions for bridging, native transfers, data queries, and governance to enable seamless blockchain interoperability. [:custom-arrow: Compare Products](/docs/build/start-building/products/#transfer-products) - :octicons-book-16:{ .lg .middle } **Use Cases** --- Explore Wormhole's use cases, from multichain swaps to DeFi, lending, gaming, and more. See how projects integrate Wormhole solutions. [:custom-arrow: Discover Use Cases](/docs/build/start-building/use-cases/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/transfers/native-token-transfers/architecture/ --- BEGIN CONTENT --- --- title: Native Token Transfers Architecture description: Explore Wormhole's Native Token Transfers architecture, which covers components, message flow, rate limiting, and custom transceivers. categories: NTT, Transfer --- ## Introduction The Native Token Transfers (NTT) architecture within the Wormhole ecosystem offers a robust framework for secure and efficient token transfers across multiple blockchains. This architecture relies on the manager and transceiver core components that work together to manage cross-chain communication and token operations complexities. For the technical implementations of the functions, refer to the [Managers and Transceivers](/docs/build/transfers/native-token-transfers/managers-transceivers/){target=\_blank} page. ## System Components The NTT framework is composed of managers, which oversee the transfer process, and transceivers, which handle cross-chain messaging, ensuring smooth and reliable token transfers. ### Managers _Managers_ are responsible for handling the flow of token transfers between different blockchains and ensuring that tokens are locked or burned on the source chain before being minted or unlocked on the destination chain. The main tasks of managers include rate-limiting transactions, verifying message authenticity (message attestation), and managing the interaction between multiple transceivers, who are responsible for cross-chain communications. Each manager is assigned to a specific token but can operate across multiple chains. Their key responsibility is to ensure that tokens are securely locked or burned on the source chain before being minted or unlocked on the destination chain. This provides the integrity of token transfers and prevents double-spending. A manager is responsible for: - **Handling token transfer flow** - upon a transfer request, `NttManager` either locks or burns tokens depending on the configuration, emits a `TransferSent` event, and ensures tokens can’t be accessed on the source chain before leasing them on the destination chain. This process safeguards against double-spending and maintains a secure transfer - **Rate-limiting** - the `NttManager` contract includes rate-limiting functionality to prevent overloading the network or flooding the target chain. The `NttManager` applies rate limits to manage transfer flow and prevent network congestion. Limits apply to both outgoing and incoming transfers - **Outbound** - transfers exceeding the outbound limit are queued (if `shouldQueue` is true) or reverted - **Inbound** - similar limits apply on the destination chain, delaying transfers if capacity is exceeded Rate limit duration and queuing are customizable per chain, and events notify users when transfers hit the limit - **Message authenticity verification** - the `NttManager` ensures transfer security by verifying message authenticity through multiple attestations from transceivers. For each transfer, a threshold number of attestation signatures must be gathered from transceivers. Once verified, `NttManager` releases tokens on the destination chain, ensuring only authenticated transfers are processed - **Interaction with transceivers** - `NttManager` collaborates with transceivers, forwarding transfer messages between chains and handling message verification. Transceivers route messages with transfer details to the destination chain, coordinating with `NttManager` to verify that tokens are locked or burned before releasing them on the other side. Transceivers can be customized to work with different security protocols, adding flexibility ### Transceivers _Transceivers_ facilitate cross-chain token transfers by ensuring the accurate transmission of messages between different blockchains. They work in conjunction with managers to route token transfers from the source chain to the recipient chain. Their primary function is to ensure that messages regarding the transfer process are delivered correctly, and that tokens are safely transferred across chains. While transceivers operate closely with Wormhole's ecosystem, they can also be configured independently of Wormhole's core system, allowing for flexibility. This adaptability allows them to be integrated with various verification backends to accommodate different security needs or platform-specific requirements. Transceivers are entrusted with several responsibilities: - **Message transmission** - transceivers handle the routing of transfer messages between chains. When a transfer is initiated, the transceiver sends the message (including transfer details like recipient and amount) to the destination chain’s manager for verification and processing - **Manager coordination** - transceivers work with managers to ensure tokens are locked or burned on the source chain before issuance on the destination chain, reinforcing the security of each transfer - **Custom verification support** - transceivers can integrate with custom verification backends, allowing flexibility to adapt to different security protocols or chain requirements. This customization enables protocols to use different attestation standards as needed How it works: 1. The transceiver receives instructions from the manager to send messages across chains 2. It quotes delivery fees, handles cross-chain message relaying, and verifies delivery to ensure tokens are safely transferred 3. For each message, the transceiver coordinates with managers, ensuring only authorized transfers are processed on the destination chain ![NTT architecture diagram](/docs/images/learn/transfers/native-token-transfers/architecture/architecture-1.webp) !!! note [Learn more](/docs/learn/transfers/native-token-transfers/architecture/#lifecycle-of-a-message){target=\_blank} about the architecture of Native Token Transfers message lifecycles. #### Custom Transceivers The NTT framework supports advanced features such as custom transceivers for specialized message verification, enhancing security and adaptability. The architecture includes detailed processes for initiating transfers, managing rate limits, and finalizing token operations, with specific instructions and events outlined for EVM-compatible chains and Solana. NTT has the flexibility to support custom message verification in addition to Wormhole Guardian message verification. Custom verifiers are implemented as transceiver contracts and can be protocol-specific or provided by other third-party attesters. Protocols can also configure the threshold of attestations required to mark a token transfer as valid — for example, 2/2, 2/3, 3/5. ![Custom Attestation with NTT diagram](/docs/images/learn/transfers/native-token-transfers/architecture/architecture-2.webp) The verifier performs checks based on predefined criteria and issues approval for transactions that meet these requirements. This approval is incorporated into the Wormhole message, ensuring that only transactions verified by both the Wormhole Guardian Network and the additional verifier are processed. The model includes an extra verifier in the bridging process, enhancing security and providing an added assurance of transaction integrity. For more details, to collaborate, or to see examples of custom transceivers, [contact](https://discord.com/invite/wormholecrypto){target=\_blank} Wormhole contributors. ## Lifecycle of a Message The lifecycle of a message in the Wormhole ecosystem for Native Token Transfers (NTT) involves multiple steps to ensure secure and accurate cross-chain token transfers. This lifecycle can vary depending on the blockchain being used, and the following explanations focus on the EVM and Solana implementations. The key stages include initiating the transfer, handling rate limits, sending and receiving messages, and finally, minting or unlocking tokens on the destination chain. ### Transfer The process begins when a client initiates a transfer. For EVM, this is done using the `transfer` function, whereas in Solana, the client uses either the `transfer_lock` or `transfer_burn` instruction, depending on whether the program is in locking or burning mode. The client specifies the transfer amount, recipient chain ID, recipient address, and a flag (`should_queue` on both EVM and Solana) to decide whether the transfer should be queued if it hits the rate limit. In both cases: - If the source chain is in locking mode, the tokens are locked on the source chain to be unlocked on the destination chain - If the source chain is in burning mode, the tokens are burned on the source chain, and new tokens are minted on the destination chain Once initiated, an event (such as `TransferSent` on EVM or a corresponding log on Solana) is emitted to signal that the transfer process has started. ### Rate Limit Both EVM and Solana implement rate-limiting for transfers to prevent abuse or network overload. Rate limits apply to both the source and destination chains. If transfers exceed the current capacity, depending on whether the `shouldQueue` flag is set to true, they can be queued. - On EVM, the transfer is added to an outbound queue if it hits the rate limit, with a delay corresponding to the configured rate limit duration. If `shouldQueue` is set to false, the transfer is reverted with an error - On Solana, the transfer is added to an **Outbox** via the `insert_into_outbox method`, and if the rate limit is hit, the transfer is queued with a `release_timestamp`. If `shouldQueue` is false, the transfer is reverted with a `TransferExceedsRateLimit` error Both chains emit events or logs when transfers are rate-limited or queued. ### Send After being forwarded to the Transceiver, the message is transmitted across the chain. Transceivers are responsible for delivering the message containing the token transfer details. Depending on the Transceiver's implementation, messages may be routed through different systems, such as Wormhole relayers or other custom relaying solutions. Once the message is transmitted, an event is emitted to signal successful transmission. - In EVM, the message is sent using the `sendMessage` function, which handles the transmission based on the Transceiver's implementation. The Transceiver may use Wormhole relayers or custom relaying solutions to forward the message - In Solana, the transfer message is placed in an Outbox and released via the `release_outbound` instruction. The Solana transceiver, such as the Wormhole Transceiver, may send the message using the `post_message` instruction, which Wormhole Guardians observe for verification In both cases, an event or log (e.g., `SendTransceiverMessage` on EVM or a similar log on Solana) is emitted to signal that the message has been transmitted. ### Receive Upon receiving the message on the destination chain, an off-chain relayer forwards the message to the destination Transceiver for verification. - In EVM, the message is received by the `NttManager` on the destination chain, which verifies the message's authenticity. Depending on the M of N threshold set for the attestation process, the message may require attestations from multiple transceivers - In Solana, the message is received via the `receive_message` instruction in the Wormhole Transceiver program. The message is verified and stored in a `VerifiedTransceiverMessage` account, after which it is placed in an Inbox for further processing In both chains, replay protection mechanisms ensure that a message cannot be executed more than once. Events or logs are emitted (e.g., `ReceivedMessage` on EVM or `ReceiveMessage` on Solana) to notify that the message has been successfully received. ### Mint or Unlock Finally, after the message is verified and attested to, the tokens can be either minted (if they were burned on the source chain) or unlocked (if they were locked). The tokens are then transferred to the recipient on the destination chain, completing the cross-chain token transfer process. - On EVM, tokens are either minted (if burned on the source chain) or unlocked (if locked on the source chain). The `TransferRedeemed` event signals that the tokens have been successfully transferred - On Solana, the tokens are unlocked or minted depending on whether the program is in locking or burning mode. The `release_inbound_unlock` or `release_inbound_mint` instruction is used to complete the transfer, and a corresponding log is produced In both cases, once the tokens have been released, the transfer process is complete, and the recipient receives the tokens. Events are emitted to indicate that the transfer has been fully redeemed. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/transfers/native-token-transfers/deployment/ --- BEGIN CONTENT --- --- title: Native Token Transfers - Deployment Models description: Explore Wormhole's Native Token Transfers deployment models——hub-and-spoke, burn-and-mint——for seamless cross-chain token transfers. categories: NTT, Transfer --- # Deployment Models The Wormhole framework offers two deployment models, each catering to different token management needs: the hub-and-spoke model and the burn-and-mint model. These models provide flexible solutions for both existing token deployments and new projects looking to enable secure and seamless multichain token transfers. ## Hub-and-Spoke The hub-and-spoke model involves locking tokens on a central hub chain and minting them on destination spoke chains. This model maintains the total supply on the hub chain and is backward-compatible with any existing token deployment. This model is ideal for existing token deployments that don't want to alter existing token contracts. It maintains the canonical balance on a hub chain while allowing for secure native deployment to new blockchains. - **Hub chain** - tokens are locked when initiating a transfer - **Spoke chains** - Equivalent tokens are minted on the destination chain When transferring tokens back to the original hub chain, the tokens on the source spoke chain are burned, and the previously locked tokens on the hub chain are unlocked. However, when transferring tokens directly between spoke chains, the tokens are burned on the source spoke chain and minted on the destination spoke chain. ## Burn-and-Mint The burn-and-mint model involves burning tokens on the source chain and minting them on the destination chain. This results in a simplified multichain transfer process that distributes the total supply across multiple chains and produces a native multichain token. This model best suits new token deployments or projects willing to upgrade existing contracts. - **Source chain** - tokens are burned when initiating a transfer - **Destination chain** - equivalent tokens are minted on the destination chain --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/transfers/native-token-transfers/ --- BEGIN CONTENT --- --- title: A Quick Look at Native Token Transfers description: This section covers Wormhole's Native Token Transfers (NTT), an open source, flexible, and composable framework for transferring tokens across blockchains. categories: NTT, Transfer --- # Native Token Transfers ## Get Started This section covers Wormhole's Native Token Transfers (NTT), an open source, flexible, and composable framework for transferring tokens across blockchains.
- :octicons-question-16:{ .lg .middle } **Overview** --- Dive into an introduction to NTT and discover what NTT is, what its key features are, and the available integration paths. [:custom-arrow: Learn more about NTT](/docs/learn/transfers/native-token-transfers/overview/) - :octicons-question-16:{ .lg .middle } **Architecture** --- Explore NTT's architecture to understand its core components and how they work together to manage cross-chain communication. [:custom-arrow: Discover how NTT works](/docs/learn/transfers/native-token-transfers/architecture/) - :octicons-book-16:{ .lg .middle } **Deployment Models** --- The NTT framework offers two deployment models for different token management needs: the hub-and-spoke and burn-and-mint models. [:custom-arrow: Check out the deployment models](/docs/learn/transfers/native-token-transfers/deployment/) - :octicons-shield-lock-16:{ .lg .middle } **Security** --- Explore NTT's security measures, including the Global Accountant and governance strategies for seamless token safety. [:custom-arrow: Review the security measures](/docs/learn/transfers/native-token-transfers/security/)
## Next Steps Ready to dive in and start building? Check out the following resources to begin the deployment process and make the most of your deployment.
- :octicons-rocket-16:{ .lg .middle } **Deploy NTT** --- Explore detailed guides that walk you through the entire deployment process, from installing the NTT CLI to deploying NTT across supported chains. [:custom-arrow: Deploy now using the NTT CLI](/docs/build/transfers/native-token-transfers/deployment-process/) - :octicons-checklist-16:{ .lg .middle } **Post Deployment Recommendations** --- Already deployed your NTT project? Check out these post deployment recommendations and integration demos to get the most out of your deployment. [:custom-arrow: Get the most of out your NTT deployment](/docs/build/transfers/native-token-transfers/deployment-process/post-deployment/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/transfers/native-token-transfers/overview/ --- BEGIN CONTENT --- --- title: Native Token Transfers Overview description: Explore Wormhole's Native Token Transfers for flexible cross-chain transfers with full control over token behavior, security, and integration features. categories: NTT, Transfer --- # Native Token Transfers !!!tip "Looking to deploy NTT?" If you're ready to deploy NTT or access the CLI, follow the detailed [NTT Deployment Section](/docs/build/transfers/native-token-transfers/deployment-process/){target=\_blank}. - For deployment steps on EVM, visit the [Deploy to EVM page](/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-evm/){target=\_blank} - For deployment steps on Solana, visit the [Deploy to Solana page](/docs/build/transfers/native-token-transfers/deployment-process/deploy-to-solana/){target=\_blank} ## Introduction Wormhole's Native Token Transfers (NTT) is an open source, flexible, and composable framework for transferring tokens across blockchains. By eliminating wrapped assets, NTT preserves each token’s native properties across chains, letting you maintain complete control over metadata, ownership, upgrade authority, and other custom features. The framework offers two modes of operation for existing token deployments. In locking mode, the original token supply is preserved on a single chain. In contrast, the burning mode enables the deployment of multichain tokens, distributing the supply across various chains. ## Key Features Wormhole's Native Token Transfers (NTT) framework offers a comprehensive and flexible solution for seamless token transfers across blockchains. Below are some of the key features that make this framework stand out: - **No wrapped tokens** – tokens remain native on every chain where NTT is deployed. All token properties and metadata remain consistent, avoiding any confusion or overhead introduced by wrapped tokens - **Unified user experience** - tokens retain their properties on each chain, remaining completely fungible and ensuring a consistent user experience - **No liquidity pools** - transfer tokens without the need for liquidity pools, avoiding fees, slippage, and MEV risk - **Integrator flexibility** - retained ownership, upgrade authority, and complete customizability over token contracts - **Advanced rate limiting** - inbound and outbound rate limits are configurable per chain and over arbitrary periods, preventing abuse while managing network congestion and allowing for controlled deployments to new chains - **Global Accountant** - ensures accounting integrity across chains by checking that the number of tokens burned and transferred out of a chain never exceeds the number of tokens minted - **Access control** - to prevent unauthorized calls to administrative functions, protocols can choose to assign specific functions, such as the Pauser role, to a separate address from the owner - **Maximum composability** - open source and extensible for widespread adoption and integration with other protocols - **Custom attestation** - optionally add external verifiers and configure custom message attestation thresholds ## Integration Paths Integrators looking to deploy their token to connected chains can use the NTT framework or the Token Bridge. Both options carry a distinct integration path and feature set depending on your requirements, as outlined in the following sections. ### Native Token Transfers Framework The Native Token Transfers Framework is highly customizable and ideal for applications such as a DeFi governance token deployed across multiple chains, which seeks to achieve fungible multichain liquidity and direct integration into governance processes. - **Mechanism** - can entirely utilize a burn-and-mint mechanism or can be paired for a hub-and-spoke model - **Security** - fully configurable rate limiting, pausing, access control, and threshold attestations. Integrated with the Global Accountant - **Contract ownership** - retain ownership and upgrade authority of token contracts on each chain - **Token contracts** - native contracts owned by your protocol governance - **Integration** - streamlined, customizable framework allows for more sophisticated and bespoke deployments The following example projects demonstrate the use of the Wormhole NTT framework through Wormhole Connect and the TypeScript SDK: - [NTT Connect](https://github.com/wormhole-foundation/demo-ntt-connect){target=\_blank} - [NTT TS SDK](https://github.com/wormhole-foundation/demo-ntt-ts-sdk){target=\_blank} ### Token Bridge The Token Bridge offers a secure, low-effort integration suitable for applications like a Web3 game that wants to make its token tradable across multiple chains. - **Mechanism** - solely utilizes a lock and mint model. Unlike NTT, the Token Bridge issues a wrapped asset on the destination chain, rather than preserving the original token contract - **Security** - preconfigured rate limiting and integrated Global Accountant - **Contract ownership** - Token Bridge contracts are upgradeable via [Wormhole Governance](/docs/learn/security/){target=\_blank} - **Token contracts** - wrapped asset contract owned by the Wormhole Token Bridge contract, upgradeable via a 13/19 Guardian governance process - **Integration** - straightforward and permissionless method to deploy on multiple chains !!! note [Learn more](/docs/learn/infrastructure/vaas/){target=\_blank} about the core messaging primitives in the Wormhole network. ## Supported Token Standards Native Token Transfers (NTT) in Wormhole primarily support **ERC-20 tokens**, the most widely used standard for fungible tokens on the Ethereum network and other EVM-compatible blockchains. The NttManager contract leverages the IERC20 interface and SafeERC20 utility from OpenZeppelin to ensure secure and efficient token transfers. Additionally, it supports ERC-20 Burnable tokens, allowing tokens to be burned on the source chain when needed for cross-chain transfers. At this time, NTT focuses on ERC-20 tokens, and other token standards, such as ERC-721 (non-fungible tokens) or ERC-1155 (multi-token standard), are not natively supported. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/transfers/native-token-transfers/security/ --- BEGIN CONTENT --- --- title: Native Token Transfers Security description: Explore the security measures of Native Token Transfers, including the Global Accountant and governance strategies for seamless token safety. categories: NTT, Transfer --- # Security ## Global Accountant The Global Accountant is a defense-in-depth security feature that checks the integrity of every token transfer. It ensures that chain balances remain isolated and more tokens cannot be burned and transferred out of a chain than were ever minted. This feature ensures native asset fungibility remains in 1:1 parity. At no time will assets coming from a spoke chain exceed the number of native assets sent to that spoke chain. The Guardians, with their role in enforcing accounting transparency, provide a reassuring layer of security, attesting to a Native Token Transfer (NTT) only if it passes integrity checks. [Contact](https://discord.com/invite/wormholecrypto){target=\_blank} Wormhole contributors if you are interested in configuring the Global Accountant for your multichain deployment. ## Governance and Upgradeability Integrators should implement governance mechanisms to manage the addition and removal of transceivers and to upgrade contracts using proxy patterns, as demonstrated in the upgrade functions in the `NttManager` contracts. These processes can also set thresholds and rules for attestation and message approval. The registry component of the NTT system is crucial for maintaining a trusted list of transceivers and managing their status. Governance processes for the following actions can be submitted directly to the corresponding contract on-chain, whether it is one or multiple of the bridging contracts or one of the token contracts: - Adding or removing a transceiver address from the registry - Setting the token contract address on a bridging contract - Setting the Wormhole Core Contract address on a bridging contract - Setting the registered bridging contract address on the token contract This governance model ensures that the system remains secure while being adaptable to new requirements in any environment where it is deployed. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/transfers/settlement/architecture/ --- BEGIN CONTENT --- --- title: Settlement Protocol Architecture description: Explore Wormhole Settlement's native swap protocols—Liquidity Layer, Mayan Swift, and MCTP—for scalable, efficient cross-chain asset transfers. categories: Settlement, Transfer --- # Settlement Protocol Architecture ## Introduction This page describes the high-level mechanics of the underlying native swap protocols in the Wormhole SDK. While built on Wormhole messaging, each protocol uses a novel architecture with unique price discovery, scalability, and latency tradeoffs. These designs enable redundancy to handle highly asymmetric flows and sharp volume changes. These sections will cover the following: - **Wormhole Liquidity Layer** - a cross-chain transfer protocol that utilizes Solana as the central orchestration layer for cross-chain intents, allowing solvers to deploy liquidity from a single Solana-based hub rather than distributing it across each supported chain - **Mayan Swift** - a flexible cross-chain intent protocol that embeds a competitive on-chain price auction to determine the best possible execution for the expressed user intent - **Mayan MCTP** - a cross-chain intents protocol that leverages Circle's CCTP (Cross-Chain Transfer Protocol) mechanism and Wormhole messaging to enable secure, fee-managed asset transfers across chains ## Wormhole Liquidity Layer Wormhole Liquidity Layer is a cross-chain transfer protocol that enables faster-than-finality transfers across the Wormhole ecosystem through a novel, Solana-based hub-and-spoke architecture. The hub-and-spoke model leverages interoperable token standards like Circle's CCTP (and Wormhole's NTT), allowing the solver to natively mint and burn assets between chains for intent fulfillment. This architecture allows solvers to facilitate cross-chain transfers by fronting assets on the destination chain and assuming the finality risk of the originating source chain transaction. Solvers concentrate their liquidity entirely on Solana, where they participate in permissionless on-chain English auctions (open ascending-price auctions where bidders publicly raise bids until only one bidder remains) to fulfill each cross-chain transfer. Upon the conclusion of each auction, the winning solver initiates a transfer from Solana to the specified destination chain. The solver rebalances inventory once the originating source chain transaction reaches finality and arrives to Solana. ![Wormhole Settlments Liquidity layer architecture diagram: source chain to hub to destination chain](/docs/images/learn/transfers/settlement/architecture/architecture-1.webp) The Wormhole Liquidity Layer serves as the underlying chain abstraction infrastructure layer for protocols across Wormhole-connected ecosystems by enabling protocols to bundle call data containing arbitrary protocol actions, which can be executed atomically alongside each transfer. This feature allows developers to create fully chain-abstracted user experiences, including constructing natively cross-chain decentralized exchanges (DEXs), borrow-lend protocols, payment protocols, and other applications atop this layer. ### Solvers and Liquidity Fragmentation Traditional intent-based protocols require solvers to distribute their capital across each supported chain in the network. This liquidity fragmentation leads to capital inefficiency and requires complex rebalancing to manage asymmetric flows between chains. As the number of chains increases, solvers face scalability challenges, which can result in market concentration, reducing competition and potentially impacting price discovery in intent execution. Using a hub-and-spoke model, the Wormhole Liquidity Layer solves these challenges by consolidating solver liquidity on a single chain, Solana. This model eliminates the need for complex cross-chain rebalancing and simplifies solvers' infrastructure requirements. Solvers only need to consider the finality risk of the originating source chain transaction and the payload size when bidding on transfers. By concentrating liquidity on Solana, the protocol can handle large transfer volumes with a smaller capital base, enhancing capital efficiency and lowering barriers to entry for solvers. This approach promotes competition, improves overall market efficiency, and ultimately benefits users with better prices while still preserving the speed of transactions. ### Enable Unified Liquidity The novel hub-and-spoke liquidity architecture relies on interoperable token standards that enable cross-chain token fungibility, such as Circle's Cross-Chain Transfer Protocol (CCTP) and Wormhole's Native Token Transfers (NTT). These protocols allow assets to move seamlessly between chains, making unified liquidity possible. On the liquidity hub (Solana), solvers concentrate their liquidity in NTT or CCTP-supported assets, such as USDC. These assets act as the shuttle between chains but may not necessarily be the user's original or final asset. After the Solana auction concludes, the appropriate instructions are called on the CCTP or NTT contract, initiating the transfer from Solana to the destination chain by burning/locking the asset on Solana and sequentially minting on the destination chain. Solvers rebalance their inventory on Solana using these interoperable token standards as well. Once the originating source chain transaction reaches finality and arrives to Solana, the solver can redeem the NTT or CCTP message, minting the inventory for use once again. By leveraging interoperable token standards like NTT, this model of liquidity facilitation for cross-chain intents can arbitrarily scale to any chain or ecosystem while preserving fully unified liquidity—eliminating the need for solver "buy-in" for new chain expansion. Additionally, this means new chains, even without proven traction, can access the same amount of liquidity for cross-chain intent fulfillment from day one of mainnet launch as they would if they were long-standing ecosystems with clear evidence of adoption — commonly overlooked by solvers who must aggressively prioritize high flow chains to due high opportunity costs. This includes new ecosystems without Centralized Exchange (CEX) enabled withdrawals. ### Protocol Flow: How It Works 1. **Initiation** - users or protocols initiate a transfer via an interface or directly on-chain. They choose between a standard transfer (waiting for finality on the sending chain) or a fast transfer (triggering the auction process). For fast transfers, users or the protocol specify a maximum fee and an auction start deadline !!! Note If an auction doesn't start within the set deadline, a standard transfer will proceed directly from the source to the destination chain. 2. **Auction** - solvers monitor the Wormhole network for these fast transfer requests and initiate an auction on Solana by offering to fulfill the transfer at or below the user's maximum fee. To start the auction, the solver must transfer the requested funds plus a small security deposit to the Matching Engine contract 3. **Competition** - once initiated, other solvers can participate by submitting lower bids in a simple English auction, aiming to provide users with the best rate. If a new solver submits a better offer, the previous solver's funds and security deposit are returned, with the latest offer taking precedence atomically. This competition ensures that users receive the best possible transfer rate 4. **Fulfillment** - after the auction concludes, the winning solver must complete the transfer within a predefined grace period to earn their fee and reclaim their security deposit. Failure to do so may result in the security deposit being slashed, with the slashed amount compensating the user for delays. This mechanism incentivizes prompt execution. Upon successful completion, the Fast Transfer hub sends the USDC to the user's destination wallet, and the solver receives their security deposit and transfer fee 5. **Settlement** - once the source chain transaction reaches finality, the winning solver can use the finalized Wormhole message to settle the auction with the matching engine and rebalance. This allows the solver to retrieve the original transfer amount into their wallet ## Mayan Swift Mayan Swift is a flexible cross-chain intent protocol that embeds a competitive on-chain price auction to determine the best possible execution for the expressed user intent. ### On-Chain Competitive Price Discovery Mechanism Traditional intent-based protocols essentially function as cross-chain limit orders. If the order is profitable, solvers will compete to fulfill it, leading to MEV-like competition focused on speed. While functional, this methodology presents two clear inefficiencies and drawbacks. First, they lack a competitive price discovery mechanism as limit order prices are typically determined through centralized off-chain systems. Second, in this MEV-like market structure, only a single solver can win, while the others lose out on transaction fees. This dynamic of deadweight loss results in solvers prioritizing high-margin orders, ultimately resulting in elevated fees for end-users without commensurate benefits. Mayan Swift addresses these limitations by implementing competitive on-chain English auctions on Solana as an embedded price discovery mechanism, fundamentally shifting solver competition from speed-based to price-based execution. Through this architecture, the solver offering the best possible price secures the right to fulfill the order within pre-specified deadline parameters. ![Mayan Swift - Intent-centric design](/docs/images/learn/transfers/settlement/architecture/architecture-2.webp) ### Protocol Flow: How It Works 1. **Initiation** - the user creates an order by signing a transaction that locks one of the primary assets (USDC or ETH) into the Mayan smart contract, specifying the desired outcome. !!!note If the input asset is not a primary asset, it is converted into a primary asset within the same transaction before the order is submitted. Each order includes properties such as destination chain, destination wallet address, output token address, minimum output amount, gas drop amount, deadline, and 32 bytes of random hex to prevent collisions. A Keccak-256 hash is then calculated to identify the order 2. **Auction** - solvers observe on-chain data or subscribe to the Mayan explorer web socket (solvers using the Mayan explorer verify the order's integrity by checking the data against the on-chain hash). Once the new order is verified, an on-chain auction on Solana is initiated by passing the order ID and the bid amount, which cannot be lower than the minimum amount. Other solvers can increase the bid by submitting a higher amount before the auction ends 3. **Fulfillment** - the auction ends three seconds after the initial bid. Once the auction ends, the winning solver can execute an instruction that passes their wallet address on the destination chain. This triggers a Wormhole message containing the order ID and the winner's wallet address. Wormhole Guardians then sign this message, allowing the winning solver to fulfill the order on the destination chain by submitting proof of their win and the promised amount to the Mayan contract before the deadline. The Mayan contract deducts a protocol fee (currently 3 basis points) and a referral fee (if applicable), transferring the remaining amount to the user's destination wallet. It also triggers a Wormhole message as proof of fulfillment 4. **Settlement** - after the Wormhole Guardians sign the fulfillment message, the winning solver can submit this message on the source chain to unlock the user's funds and transfer them to their own wallet. Upon fulfillment, the solver has the option to delay triggering a Wormhole message immediately. Instead, they can batch the proofs and, once the batch reaches a certain threshold, issue a batched proof to unlock all orders simultaneously, saving on gas fees ## Mayan MCTP Mayan MCTP is a cross-chain intents protocol that leverages Circle's CCTP (Cross-Chain Transfer Protocol) mechanism and Wormhole messaging to enable secure, fee-managed asset transfers across chains. ![Mayan MCTP diagram](/docs/images/learn/transfers/settlement/architecture/architecture-3.webp) ### Protocol Flow: How It Works 1. **Initiation** - the user creates an order by signing a transaction that locks one USDC into the Mayan smart contract, specifying the desired outcome. !!!note If the input asset is not USDC, it is converted into a primary asset within the same transaction before the order is submitted. The contract constructs a `BridgeWithFeeMsg` structure, which includes parameters such as the action type, payload type, nonce, destination address, gas drop, redeem fee, and an optional custom payload hash 2. **Intent submission** - the contract calls the CCTP messenger to deposit the tokens for bridging. A unique nonce is generated, and a corresponding fee-lock record is created in the contract's storage. This record includes the locked fee, gas drop parameters, and destination details. The constructed message is hashed and published through Wormhole. The protocol fee is deducted during this step, and the Wormhole message is broadcast with the specified [consistency (finality) level](/docs/build/reference/consistency-levels/){target=\_blank} 3. **Fulfillment** - on the destination chain, the protocol receives a CCTP message with corresponding signatures and verifies the payload using Wormhole's verification mechanism. Once validated, the redeemed tokens are transferred to the intended recipient, deducting the redeem fee as per protocol rules The protocol provides mechanisms for unlocking the fee once the bridging process is completed. This can occur immediately upon fulfillment or be batched for efficiency. In the fee unlock flow, the contract verifies the unlock message via Wormhole and then releases the locked fee to the designated unlocker address. ## Where to Go Next - To learn more about available EVM functions, see the [Build on the Wormhole Liquidity Layer](/docs/build/transfers/settlement/liquidity-layer/){target=\_blank} guide - To learn how to integrate settlement routes into your application, see the [Integrate Wormhole Settlement Routes Using the SDK](https://github.com/wormhole-foundation/demo-mayanswift){target=\_blank} tutorial --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/transfers/settlement/ --- BEGIN CONTENT --- --- title: Wormhole Settlement description: Learn about Wormhole Settlement, an intent-based solution enabling fast and efficient asset transfers across Ethereum, Solana, Sui, and more. categories: Settlement, Transfer --- # Wormhole Settlement ## Get Started This section covers Wormhole Settlement, an intent-based solution enabling fast and efficient asset transfers across Ethereum, Solana, Sui, and more.
- :octicons-question-16:{ .lg .middle } **Overview** --- Discover Wormhole Settlement, enabling fast, intent-based asset transfers across Ethereum, Solana, Sui, and more for institutions and builders. [:custom-arrow: Learn more about Wormhole Settlement](/docs/learn/transfers/settlement/overview/) - :octicons-question-16:{ .lg .middle } **Protocol Architectures** --- Explore Wormhole Settlement's native swap protocols—Liquidity Layer, Mayan Swift, and MCTP — for scalable, efficient cross-chain asset transfers. [:custom-arrow: Discover protocol architectures](/docs/learn/transfers/settlement/architecture/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/transfers/settlement/overview/ --- BEGIN CONTENT --- --- title: Wormhole Settlement Overview description: Discover Wormhole Settlement, enabling fast, intent-based asset transfers across Ethereum, Solana, Sui, and more for institutions and builders. categories: Settlement, Transfer --- # Wormhole Settlement Overview ## Introduction Wormhole Settlement is a fast, institutional-scale digital asset settlement — a new way to transfer assets across chains. With Wormhole Settlement, an intent-based asset transfer for individual users and institutions, you can swap, bridge, and build across multiple chains. You can implement cross-chain functionality within your dApps extremely simply and without compromising user experience, widening the horizons of your product offerings and the number and type of users you can cater to. The Settlement supports Ethereum, Ton, Optimism, Arbitrum, Base, Avalanche, Unichain, Polygon, Solana, and Sui, with many more on the horizon. It is powered by Wormhole Messaging, Wormhole Native Token Transfer (NTT), and Circle's CCTP and built in collaboration with the intent experts at Mayan Finance. Settlement represents Wormhole's first step towards optimizing the bridging experience and building a product that users and institutions use daily. Use it to send assets between chains, rebalance institutional inventories on-chain cheaply and quickly, or allow your application to be accessible by any user no matter what assets they hold or what chain they call home. ## Key Features - **Integrator flexibility** - apps leveraging the SDK can select any one of three potential routes surfaced, each with its tradeoffs concerning time vs cost; they may extend this to users as well - **Scalable liquidity** - taking into account the sometimes idiosyncratic yet sharp inflows into the Solana ecosystem, the hub-spoke model of the Wormhole Liquidity Layer and the flexible design of Swift are designed for capital efficiency - **Arbitrary payload support** - integrators can bundle `callData` containing arbitrary protocol actions to enable seamless one-click user experiences, such as swap plus stake ## Integrator Paths ### SDK Integrators Wormhole provides an SDK that enables apps to abstract away the complexity of cross-chain token swaps. The SDK handles route discovery, fee estimation, and transaction construction. Apps can embed this feature in their backend or create an interface for users to bridge into their respective ecosystems quickly. ### NTT Integrators NTT partners, current and future, can leverage Wormhole Settlement for near-instant NTT transfers from any chain, including Ethereum mainnet and its L2s. This eliminates waiting for slow source chain confirmation times (sometimes 15 minutes or more). If interested, please [fill out this interest form](https://wormhole.com/contact){target=\_blank}. ### Chain Integrators Due to the hub-spoke model of liquidity, new chains without proven traction can access the same level of liquidity for cross-chain intent fulfillment from day one of mainnet launch as established ecosystems with clear evidence of adoption. !!!tip Looking to integrate Wormhole Settlement? If you're ready, check out how to [integrate Wormhole Settlement Routes using the SDK](https://github.com/wormhole-foundation/demo-mayanswift){target=\_blank}. ## Related Resources - To learn more about the architecture of Wormhole-native swap protocols, see the [Settlement Protocol Architectures](/docs/learn/transfers/settlement/architecture/){target=\_blank} page --- END CONTENT --- Doc-Content: https://wormhole.com/docs/learn/transfers/token-bridge/ --- BEGIN CONTENT --- --- title: Token Bridge description: Learn about Wormhole's Token Bridge for cross-chain transfers using lock and mint mechanisms, ensuring secure and efficient asset movement. categories: Token-Bridge, Transfer --- # Token Bridge Transferring tokens across blockchain networks is challenging due to the lack of interoperability. Maintaining token properties such as value, name, and precision while ensuring security during transfers often requires complex and costly solutions like liquidity pools or native swaps, which can introduce inefficiencies and risks. Wormhole’s Token Bridge addresses these challenges by providing a decentralized protocol for seamless cross-chain token transfers through a lock-and-mint mechanism. Using Wormhole’s message-passing protocol, the Token Bridge allows standards-compliant tokens, like ERC-20 on Ethereum or SPL on Solana, to be transferred between different blockchains while preserving their original attributes. Offering a more efficient, scalable, and secure alternative to traditional solutions, the Token Bridge ensures that assets retain their properties across multiple blockchain ecosystems. Additionally, it supports flexible features like [Token Transfers with Messages](/docs/learn/infrastructure/vaas/#token-transfer-with-message){target=\_blank}, enabling custom interactions by allowing tokens to carry additional data for smart contract integration on the destination chain. This page introduces the core concepts and functions of Wormhole’s Token Bridge, explaining how it operates, its key features, and how it enables secure and efficient cross-chain token transfers. ### How Does It Work? At the core of the Token Bridge lies the lock-and-mint mechanism, which uses the [Core Contract](/docs/learn/infrastructure/core-contracts/){target=\_blank} with a specific [payload](/docs/learn/infrastructure/vaas/#token-transfer){target=\_blank} to pass information about the transfer. Tokens on the source chain are locked, and wrapped tokens are minted on the destination chain. This approach guarantees that token transfers are secure and consistent, ensuring that token properties such as name, symbol, and decimal precision are preserved across chains. Before a token can be transferred to a new chain, the token’s metadata must be [attested](/docs/learn/infrastructure/vaas/#attestation){target=\_blank}. This process registers the token details (such as decimals and symbol) on the destination chain, enabling the creation of wrapped assets. While the [Core Contract](/docs/learn/infrastructure/core-contracts/){target=\_blank} has no specific receiver by default, transfers sent through the Token Bridge do have a specific receiver chain and address to ensure the tokens are minted to the expected recipient. In addition to standard token transfers, the Token Bridge supports [Token Transfers with Messages](/docs/learn/infrastructure/vaas/#token-transfer-with-message){target=\_blank}. This functionality allows users to attach additional data to token transfers, enabling more complex interactions with smart contracts on the destination chain. For instance, a token transfer can include a payload that triggers specific actions, such as interacting with a decentralized exchange (DEX) or automated market maker (AMM). ### Token Transfer Flow The transfer process is simple yet secure, involving a few key steps: 1. **Attestation** - first, a token's metadata is attested on the source chain, ensuring that its properties are consistent across chains 2. **Locking** - on the source chain, the native token is locked in a custody account 3. **Message emission** - a message detailing the transfer is sent through Wormhole’s Guardian Network, which verifies the transfer and signs the message 4. **Verification and minting** - on the destination chain, the transfer message is verified, and wrapped tokens are minted, or native tokens are released from custody ![Token Bridge detailed flow](/docs/images/learn/transfers/token-bridge/token-bridge-diagram.webp) ### Key Features of the Token Bridge The Token Bridge creates wrapped versions when tokens are transferred to a different chain. These wrapped assets represent the locked tokens on the source chain and allow users to interact with them on the destination chain. This mechanism ensures seamless functionality without needing liquidity pools or native token swaps. The Token Bridge employs a universal token representation that is compatible with various virtual machine (VM) data types. This allows the tokens to interact with decentralized applications (dApps) across different chains without issues related to differing token standards. ### Message and Payload Structure To facilitate cross-chain communication, the Token Bridge uses specialized payloads that carry the necessary information for token transfers and attestations. These payloads ensure that the correct tokens are minted or unlocked on the destination chain. - `Transfer` - this payload initiates the transfer of tokens, either by minting wrapped tokens or releasing locked tokens - `TransferWithPayload` - in addition to transferring tokens, this payload carries additional data, allowing integration with smart contracts or dApps on the target chain - `AssetMeta` - before a token can be transferred for the first time, this payload is used to attest to the token’s metadata, including decimals, symbol, and name - `RegisterChain` - register the Token Bridge contract (emitter address) for a foreign chain - `UpgradeContract` - upgrade the contract Each payload type is designed to serve a specific function in the token transfer process, ensuring that the bridge operates efficiently and securely. One of the key challenges in cross-chain token transfers is maintaining the correct token precision. The Token Bridge addresses this using the `AssetMeta` payload to store token metadata. Before transferring a token to a new chain, metadata such as its decimal precision, name, and symbol must be attested. The bridge ensures token amounts are truncated to a maximum of 8 decimals, guaranteeing compatibility with chains that may not support higher decimal precision. For example, an 18-decimal token on Ethereum will be represented with only eight decimals on the destination chain, simplifying integration with various decentralized applications. ### Security and Authorization The Token Bridge uses an emitter chain and address authorization system to verify the validity of messages. Each Token Bridge endpoint is registered on its respective chain, ensuring only trusted contracts can send or receive transfer messages. The [Wormhole Guardian Network](/docs/learn/infrastructure/guardians/#guardian-network){target=\_blank} plays a critical role in verifying each transfer and ensuring that the message is signed and relayed securely between chains. ### Portal Bridge A real-world example of Wormhole's Token Bridge in action is the [Portal Bridge](https://portalbridge.com/){target=\_blank}, which provides users with a simple interface to transfer tokens across multiple blockchains. Using the Wormhole infrastructure, Portal Bridge guarantees secure and seamless cross-chain transfers, making it easier for users to move assets between different blockchain ecosystems. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/connect/ --- BEGIN CONTENT --- --- title: Wormhole Connect Tutorials description: Enable cross-chain connectivity with Wormhole Connect. Learn integration and simplify user experiences across multiple blockchains. categories: Connect, Transfer --- # Connect Wormhole Connect makes it simple to link your application to multiple blockchain ecosystems. These tutorials will teach you how to integrate Connect into your projects, streamline cross-chain interactions, simplify user onboarding, and deliver a smoother overall experience. ## Tutorials
- :octicons-repo-16:{ .lg .middle } **Integrate Connect into a React DApp** --- Learn how to incorporate Wormhole Connect into a React application. This step-by-step tutorial guides you through enabling cross-chain token transfers and interactions, bridging assets between networks, and enhancing the user experience with streamlined blockchain connectivity. [:custom-arrow: Start building](/docs/tutorials/connect/react-dapp/)
## Additional Resources
- :octicons-tools-16:{ .lg .middle } **Connect** --- Get deeper insights into setting up and customizing Wormhole Connect. Explore advanced guides, best practices, and configuration tips to streamline your cross-chain integrations. [:custom-arrow: Learn more](/docs/build/transfers/connect/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/connect/react-dapp/ --- BEGIN CONTENT --- --- title: Integrate Connect into a React DApp Tutorial description: Learn how to use Wormhole Connect to transfers tokens cross-chain seamlessly between Sui and Avalanche Fuji with this step-by-step guide. categories: Connect, Transfer --- # Integrate Connect into a React DApp :simple-github: [Source code on GitHub](https://github.com/wormhole-foundation/demo-basic-connect){target=\_blank} ## Introduction In this tutorial, we’ll explore how to integrate [Wormhole Connect](https://github.com/wormhole-foundation/wormhole-connect){target=\_blank} to enable cross-chain token transfers and interactions. Wormhole Connect offers a simplified interface for developers to facilitate seamless token transfers between blockchains. Using Wormhole Connect, you can easily bridge assets across multiple ecosystems without diving into the complex mechanics of cross-chain communication. While this tutorial will guide you through the process using a specific blockchain as an example, the principles and steps outlined here can be applied to any blockchain supported by Wormhole. In this example, we’ll work with Sui as our source blockchain and Avalanche Fuji as the destination blockchain. ## Prerequisites To get started with Wormhole Connect, we'll first need to set up a basic environment that allows for cross-chain token transfers. Before starting this tutorial, 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 - A [Sui wallet](https://suiwallet.com/){target=\_blank} set up and ready for use - A [compatible wallet](https://support.avax.network/en/articles/5520938-what-are-the-official-avalanche-wallets){target=\_blank} for Avalanche Fuji, such as [MetaMask](https://metamask.io/){target=\_blank} - Testnet tokens for [Sui](https://docs.sui.io/guides/developer/getting-started/get-coins){target=\_blank} and [Fuji](https://core.app/tools/testnet-faucet/?subnet=c&token=c){target=\_blank} to cover gas fees ## Set Up Connect for Sui Transfers ### Create a React Project Start by setting up your React app: 1. Open your terminal and run the following command to create a new React app: ```bash npx create-react-app connect-tutorial ``` 2. Navigate into the project directory: ```bash cd connect-tutorial ``` ### Install Wormhole Connect Next, install the Wormhole Connect package as a dependency by running the following command inside your project directory: ```bash npm install @wormhole-foundation/wormhole-connect ``` ### Integrate Connect into the Application Now, we need to modify the default `App.js` file to integrate Wormhole Connect. We are going to use [version V1.0](/docs/build/transfers/connect/upgrade/){target=\_blank}, make sure to check which version of connect you are using. Open `src/App.js` and replace the content with the following code: === "JavaScript" ```js import logo from './logo.svg'; import './App.css'; import WormholeConnect from '@wormhole-foundation/wormhole-connect'; const config = { network: 'Testnet', chains: ['Sui', 'Avalanche'], }; function App() { return ; } export default App; ``` === "TypeScript" ```ts import './App.css'; import WormholeConnect, { WormholeConnectConfig, WormholeConnectTheme, } from '@wormhole-foundation/wormhole-connect'; function App() { const config: WormholeConnectConfig = { network: 'Testnet', chains: ['Sui', 'Avalanche'], ui: { title: 'SUI Connect TS Demo', }, }; const theme: WormholeConnectTheme = { mode: 'dark', primary: '#78c4b6', }; return ; } export default App; ``` - Set `network` to `testnet` - this ensures that Wormhole Connect uses the testnet environment - Set `chains` to `['Sui', 'Avalanche']` - configures the app to allow transfers between Sui and Avalanche Fuji, the testnet for Avalanche ### Customize Wormhole Connect To further customize Wormhole Connect for your application, such as adjusting the UI, adding custom tokens, or configuring specific chain settings, you can refer to the [Wormhole Connect Configuration guide](/docs/build/transfers/connect/configuration/#introduction){target=\_blank}. ### Run the Application Make sure you’re in the root directory of your React app, and run the following command to start the application: ```bash npm start ``` Now your React app should be up and running, and Wormhole Connect should be visible on `http://localhost:3000/`. You should see the Wormhole Connect component, which will include a UI for selecting networks and tokens for cross-chain transfers. ## Transfer Tokens from Sui to Fuji Before transferring token ensure you have enough testnet SUI and Fuji tokens to cover the gas fees for the transfer. To transfer tokens from Sui to Fuji in the Wormhole Connect interface: 1. Select **Sui** as the source network, connect your Sui wallet, and choose **SUI** as the asset you wish to transfer 2. Choose **Fuji** as the destination network and connect your wallet with the Fuji network 3. Enter the amount of SUI tokens you wish to transfer ![](/docs/images/tutorials/connect/react-dapp/connect-1.webp) 4. Choose to view other routes ![](/docs/images/tutorials/connect/react-dapp/connect-2.webp) 5. Select the manual bridge option, which will require two transactions: one on the source chain (Sui) and one on the destination chain (Fuji) !!! note It is recommended to use the manual bridge option for this tutorial. The automatic bridge feature is currently undergoing improvements, while the manual bridge ensures that transfers complete successfully. ![](/docs/images/tutorials/connect/react-dapp/connect-3.webp) 6. Review and confirm the transfer on Sui. This will lock your tokens on the Sui chain ![](/docs/images/tutorials/connect/react-dapp/connect-4.webp) 7. Follow the on-screen prompts to approve the transaction. You will be asked to sign with your Sui wallet ![](/docs/images/tutorials/connect/react-dapp/connect-5.webp) Once the transaction has been submitted, Wormhole Connect will display the progress of the transfer. Monitor the status until you’re prompted to complete the transaction on the destination chain. You can also track your transactions on [Wormholescan](https://wormholescan.io/#/?network=Testnet){target=\_blank}. ## Claim Tokens on Fuji After the Sui transaction is complete, confirm the final transaction on Fuji by claiming the wrapped tokens. You will be asked to confirm the transaction with your Fuji wallet. ![](/docs/images/tutorials/connect/react-dapp/connect-6.webp) Once confirmed, check your Fuji wallet to verify that the wrapped SUI tokens have been successfully received. ![](/docs/images/tutorials/connect/react-dapp/connect-7.webp) ## Resources If you'd like to explore the complete project or need a reference while following this tutorial, you can find the entire codebase in the [Sui-Connect GitHub repository](https://github.com/wormhole-foundation/demo-basic-connect){target=\_blank}. The repository includes an integration of Wormhole Connect in a React app for bridging tokens between the Sui and Fuji (Avalanche Testnet) networks. ## Conclusion In this tutorial, you’ve gained hands-on experience with integrating Wormhole Connect to enable cross-chain token transfers. You’ve learned to configure a React app for seamless interactions between Sui and Avalanche Fuji, providing users with the ability to bridge assets across chains with ease. By following these steps, you've learned how to: - Set up a React project tailored for cross-chain transfers - Install and configure Wormhole Connect to support multiple blockchains - Implement a streamlined UI for selecting source and destination chains, connecting wallets, and initiating transfers - Execute a token transfer from Sui to Avalanche Fuji, monitoring each step and confirming the transaction on both networks With these tools and knowledge, you’re now equipped to build powerful cross-chain applications using Wormhole Connect, opening up possibilities for users to move assets across ecosystems securely and efficiently. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/ --- BEGIN CONTENT --- --- title: Tutorials description: Discover product-specific Wormhole tutorials. Learn setup, integration, and advanced features to develop cross-chain apps confidently. template: root-index-page.html --- # Tutorials In this section, you'll find tutorials focused on individual Wormhole products. Each product folder contains detailed guides to help you integrate and use specific Wormhole services, such as Token Bridge, Wormhole Connect, and more. Whether setting up your first product or diving deeper into advanced features, these tutorials will walk you through the entire process, from installation to implementation. ## Browse Tutorials by Product ## Get Started
- :octicons-arrow-switch-16:{ .lg .middle } **Connect** --- With Wormhole Connect, you can enable seamless connectivity between different blockchain ecosystems. These tutorials guide you through integrating Connect into your projects, allowing you to easily leverage cross-chain interactions, simplify onboarding, and improve user experience. [:custom-arrow: Start building](/docs/tutorials/connect/) - :octicons-sync-16:{ .lg .middle } **Multichain Assets** --- Learn how to create, register, and manage wrapped multichain assets across multiple chains. These tutorials will guide you through the process of enabling asset transfers between supported networks. [:custom-arrow: Start building](/docs/tutorials/multichain-assets/) - :octicons-people-16:{ .lg .middle } **MultiGov** --- Unleash the power of cross-chain governance with Multigov. These tutorials guide you through setting up and managing governance structures spanning multiple blockchains, enabling collective decision-making and coordinated upgrades across decentralized ecosystems. [:custom-arrow: Start building](/docs/tutorials/multigov/) - :octicons-file-code-16:{ .lg .middle } **Solidity SDK** --- Learn to build smart contracts that communicate across multiple blockchains. These tutorials show you how to create robust cross-chain contracts, allowing your dApps to move beyond a single network and tap into global liquidity, functionality, and user bases. [:custom-arrow: Start building](/docs/tutorials/solidity-sdk/) - :octicons-code-square-16:{ .lg .middle } **TypeScript SDK** --- Master the tools to build cross-chain applications with the Wormhole SDK. These tutorials cover installation to advanced functionality, helping you streamline development, reduce complexity, and quickly bring your ideas to life. [:custom-arrow: Start building](/docs/tutorials/typescript-sdk/) - :octicons-code-square-16:{ .lg .middle } **Wormholescan API** --- Explore hands-on tutorials for using the Wormholescan API to retrieve blockchain data, track transactions, validate VAAs, check redemption status, and more. [:custom-arrow: Start building](/docs/tutorials/wormholescan/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/multichain-assets/ --- BEGIN CONTENT --- --- title: Multichain Assets Tutorials description: Explore comprehensive, step-by-step tutorials on how to register, manage, and work with multichain assets within the Wormhole ecosystem. --- # Multichain Assets Multichain assets, often represented as wrapped tokens, enable seamless cross-chain interoperability. This section provides step-by-step tutorials for registering, managing, and working with these assets across different blockchains. ## Tutorials
- :octicons-repo-16:{ .lg .middle } **Create Multichain Tokens** --- Learn how to register your token on both a source and target chain, and create a wrapped version for seamless interoperability. [:custom-arrow: Register your assets now](/docs/tutorials/multichain-assets/multichain-token/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/multichain-assets/multichain-token/ --- BEGIN CONTENT --- --- title: Create Multichain Tokens description: Learn how to create a multichain token, bridge tokens across blockchains, and update metadata for seamless multichain interoperability. --- # Create Multichain Tokens ## Introduction Blockchain ecosystems are becoming increasingly interconnected, with assets often needing to exist across multiple networks to maximize their utility and reach. For example, tokens created on one chain may want to expand to others to tap into broader audiences and liquidity pools. This guide explains how to create a multichain token—a token that seamlessly bridges across blockchains using the Wormhole protocol. The process is designed to be user-friendly. With just a few steps, your token can become multichain, enabling it to be traded or used on various networks. By the end of this tutorial, you'll learn: - How to register your token for bridging - How to create a wrapped version of your token - How to ensure its visibility on blockchain explorers Let’s begin with a straightforward, step-by-step process for creating a multichain token and expanding its reach. ## Register the Token on the Source Chain The first step in creating a multichain token is registering your token on its source chain. This ensures the token is prepared for bridging across blockchains. Follow these steps: 1. Open the [Portal Bridge](https://portalbridge.com/advanced-tools/#/register){target=\_blank} 2. Select the blockchain where your token is currently deployed (source chain) 3. Connect your wallet by following the on-screen instructions 4. Locate the **Asset** field and paste the token contract address 5. Click **Next** to proceed ![Source Chain Registration Screen](/docs/images/tutorials/multichain-assets/multichain-tokens/multichain-token-1.webp) ## Register the Token on the Target Chain After registering your token on the source chain, the next step is to select the target chain—the blockchain where you want the wrapped version of your token to exist. This step connects your token to its destination network. 1. Choose the blockchain where you want the token to be bridged (target chain) 2. Connect your wallet to the target chain 3. Click **Next** to finalize the registration process ![Target Chain Registration Screen](/docs/images/tutorials/multichain-assets/multichain-tokens/multichain-token-2.webp) ## Send an Attestation Attestation is a key step in the process. It verifies your token’s metadata, ensuring it is correctly recognized on the target chain’s blockchain explorer (e.g., [Etherscan](https://etherscan.io/){target=\_blank}). 1. Click **Attest** to initiate the attestation process 2. Approve the transaction in your wallet when prompted ![Send Attestation Screen](/docs/images/tutorials/multichain-assets/multichain-tokens/multichain-token-3.webp) !!! note - Attestation is crucial for token metadata to appear correctly on blockchain explorers like Etherscan, allowing users to identify and trust your token - Ensure you have sufficient funds to cover transaction fees on the target chain ## Create the Wrapped Token The final step is to create the wrapped token on the target chain. This token represents the original asset and enables its use within the target blockchain. 1. Click **Create** to generate the wrapped token 2. Approve the transaction in your wallet when prompted ![Create Wrapped Token Screen](/docs/images/tutorials/multichain-assets/multichain-tokens/multichain-token-4.webp) Upon successful creation, you will see a confirmation screen displaying key details such as the source chain, target chain, and transaction status. This helps verify that the process was completed correctly. Refer to the image below as an example: ![Confirmation Screen](/docs/images/tutorials/multichain-assets/multichain-tokens/multichain-token-5.webp) ## Additional Steps and Recommendations After creating your multichain token, there are a few optional but highly recommended steps to ensure the best experience for users interacting with your token. ### Add Your Token to the Wormhole Metadata List (Legacy) For legacy compatibility in the [**Advanced Tools**](https://portalbridge.com/advanced-tools/){target=\_blank} section of Portal Bridge, you can request updates to your token metadata. Follow these steps: 1. Join the [Wormhole Discord server](https://discord.com/invite/wormholecrypto){target=\_blank} 2. Submit a request for metadata updates in the appropriate support channel !!! note These updates only apply to the **Advanced Tools** section of Portal Bridge and will not update how your token appears in other Wormhole-powered apps or on blockchain explorers like Etherscan. ### Update Metadata on Blockchain Explorers It is recommended that you update your token’s metadata on blockchain explorers such as Etherscan. This includes adding details like the token logo, price, and contract verification. 1. Create an account on the relevant scanner and go to the [token update section](https://etherscan.io/tokenupdate){target=\_blank} (or the relevant scanner that you would like to update metadata on) 2. Copy and paste the wrapped contract address in the **Token Update Application Form** 3. Before proceeding to the next step, you will need to verify as the contract address owner on [Etherscan’s address verification tool](https://etherscan.io/verifyAddress/){target=\_blank} 4. Follow the directions to verify contract address ownership via MetaMask by reviewing the [guide on verifying address ownership](https://info.etherscan.com/how-to-verify-address-ownership/){target=\_blank} - Given that Wormhole may be the contract owner, use the manual verification process by reaching out through the [Etherscan contact form](https://etherscan.io/contactus){target=\_blank}. The team will provide support as needed 5. Once the step above is completed, follow the [instructions to update token information](https://info.etherscan.com/how-to-update-token-information-on-token-page/){target=\_blank} --- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/multigov/ --- BEGIN CONTENT --- --- title: Step-by-Step MultiGov Tutorials description: Access step-by-step guides for executing cross-chain governance actions, including treasury management proposals with MultiGov and Wormhole. categories: MultiGov --- # MultiGov Welcome to the MultiGov tutorials section. In this section, you will find tutorials that walk you through the key steps of working with MultiGov, providing clear instructions to help you get started. As you explore, you'll gain a deeper understanding of MultiGov's features and functionality. ## Tutorials
- :octicons-repo-16:{ .lg .middle } **Cross-Chain Treasury Management Proposal** --- Learn how to propose governance actions on a hub chain, gather votes from spoke chains, aggregate the results, and carry out the final decision. Following these steps, you’ll master end-to-end governance workflows spanning multiple networks. [:custom-arrow: Start building](/docs/tutorials/multigov/treasury-proposal/)
## Additional Resources
- :octicons-book-16:{ .lg .middle } **Governance Fundamentals** --- Dive into Wormhole’s governance mechanisms. Understand how cross-chain governance works, proposal creation, voting, and execution. [:custom-arrow: Explore governance](/docs/learn/governance/) - :octicons-tools-16:{ .lg .middle } **Implement MultiGov** --- Integrate MultiGov into your smart contracts. Access reference code, best practices, and guidance for deploying cross-chain governance systems. [:custom-arrow: Build with MultiGov](/docs/build/multigov/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/multigov/treasury-proposal/ --- BEGIN CONTENT --- --- title: MultiGov Guides description: Learn how to initiate a proposal on a hub chain, vote from spoke chains, aggregate the votes, and finally execute the proposal using Wormhole's MultiGov. categories: MultiGov --- # Cross-Chain treasury management proposal This guide walks through the process of creating and executing a cross-chain governance proposal to mint W tokens to both the Optimism and Arbitrum treasuries. In this tutorial, we'll cover how to create a proposal on the hub chain (Ethereum Mainnet), cast votes from spoke chains (Optimism and Arbitrum), aggregate votes, and execute the proposal. ## Create a Proposal The first step is to create a proposal on the hub chain, which in this case is Ethereum Mainnet. The proposal will contain instructions to mint 10 W tokens to the Optimism treasury and 15 ETH to the Arbitrum treasury. In the following code snippet, we initialize the proposal with two transactions, each targeting the Hub's Message Dispatcher contract. These transactions will relay the governance actions to the respective spoke chains via Wormhole. Key actions: - Define the proposal targets (two transactions to the Message Dispatcher) - Set values for each transaction (in this case, both are 0 as we're not transferring any native ETH) - Encode the calldata for minting 10 W tokens on Optimism and sending 15 ETH to Arbitrum - Finally, we submit the proposal to the `HubGovernor` contract ```solidity HubGovernor governor = HubGovernor(GOVERNOR_ADDRESS); // Prepare proposal details address[] memory targets = new address[](2); targets[0] = HUB_MESSAGE_DISPATCHER_ADDRESS; targets[1] = HUB_MESSAGE_DISPATCHER_ADDRESS; uint256[] memory values = new uint256[](2); values[0] = 0; values[1] = 0; bytes[] memory calldatas = new bytes[](2); // Prepare message for Optimism to mint 10 W tokens // bytes created using abi.encodeWithSignature("mint(address,uint256)", 0xB0fFa8000886e57F86dd5264b9582b2Ad87b2b91, 10e18) calldatas[0] = abi.encodeWithSignature( "dispatch(bytes)", abi.encode( OPTIMISM_WORMHOLE_CHAIN_ID, [OPTIMISM_WORMHOLE_TREASURY_ADDRESS], [uint256(10 ether)], [hex"0x40c10f19000000000000000000000000b0ffa8000886e57f86dd5264b9582b2ad87b2b910000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000"] ) ); // Prepare message for Arbitrum to receive 15 ETH calldatas[1] = abi.encodeWithSignature( "dispatch(bytes)", abi.encode( ARBITRUM_WORMHOLE_CHAIN_ID, [ARBITRUM_WORMHOLE_TREASURY_ADDRESS], [uint256(15 ether)], [hex"0x40c10f19000000000000000000000000b0ffa8000886e57f86dd5264b9582b2ad87b2b910000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000"] ) ); string memory description = "Mint 10 W to Optimism treasury and 10 W to Arbitrum treasury via Wormhole"; // Create the proposal uint256 proposalId = governor.propose( targets, values, calldatas, description ) ``` ??? interface "Parameters" `GOVERNOR_ADDRESS` ++"address"++ The address of the `HubGovernor` contract on Ethereum Mainnet. --- `targets` ++"address[]"++ An array that specifies the addresses that will receive the proposal's actions. Here, both are set to the `HUB_MESSAGE_DISPATCHER_ADDRESS`. --- `values` ++"uint256[]"++ An array containing the value of each transaction (in Wei). In this case, both are set to zero because no ETH is being transferred. --- `calldatas` ++"bytes[]"++ The calldata for the proposal. These are encoded contract calls containing cross-chain dispatch instructions for minting tokens and sending ETH. The calldata specifies minting 10 W tokens to the Optimism treasury and sending 15 ETH to the Arbitrum treasury. --- `description` ++"string"++ A description of the proposal, outlining the intent to mint tokens to Optimism and send ETH to Arbitrum. ??? interface "Returns" `proposalId` ++"uint256"++ The ID of the newly created proposal on the hub chain. ## Vote on the Proposal via Spoke Once the proposal is created on the hub chain, stakeholders can cast their votes on the spoke chains. This snippet demonstrates how to connect to a spoke chain and cast a vote for the proposal. The voting power (weight) is calculated based on each stakeholder's token holdings on the spoke chain. Key actions: - Connect to the `SpokeVoteAggregator` contract on the spoke chain. This contract aggregates votes from the spoke chains and relays them to the hub chain - Cast a vote in support of the proposal ```solidity // Connect to the SpokeVoteAggregator contract of the desired chain SpokeVoteAggregator voteAggregator = SpokeVoteAggregator(VOTE_AGGREGATOR_ADDRESS); // Cast a vote uint8 support = 1; // 1 for supporting, 0 for opposing uint256 weight = voteAggregator.castVote(proposalId, support); ``` ??? interface "Parameters" `VOTE_AGGREGATOR_ADDRESS` ++"address"++ The address of the `SpokeVoteAggregator` contract on the spoke chain (Optimism or Arbitrum). --- `proposalId` ++"uint256"++ The ID of the proposal created on the hub chain, which is being voted on. --- `support` ++"uint8"++ The vote being cast (`1` for supporting the proposal, `0` for opposing). ??? interface "Returns" `weight` ++"uint256"++ The weight of the vote, determined by the voter’s token holdings on the spoke chain. ## Vote Aggregation In the background process, votes cast on the spoke chains are aggregated and sent back to the hub chain for final tallying. This is typically handled off-chain by a "crank turner" service, which periodically queries the vote status and updates the hub chain. Key actions: - Aggregate votes from different chains and submit them to the hub chain for tallying ```solidity // Aggregate votes sent to Hub (this would typically be done by a "crank turner" off-chain) hubVotePool.crossChainVote(queryResponseRaw, signatures); ``` ??? interface "Parameters" `queryResponseRaw` ++"bytes"++ The raw vote data from the spoke chains. --- `signatures` ++"bytes"++ Cryptographic signatures that verify the validity of the votes from the spoke chains. ## Execute Proposal and Dispatch Cross-Chain Messages After the proposal passes and the votes are tallied, the next step is to execute the proposal. The `HubGovernor` contract will dispatch the cross-chain messages to the spoke chains, where the respective treasuries will receive the tokens. Key actions: - Execute the proposal after the voting period ends and the proposal passes - The `execute` function finalizes the proposal execution by dispatching the cross-chain governance actions. The `descriptionHash` ensures that the executed proposal matches the one that was voted on. ```solidity HubGovernor governor = HubGovernor(GOVERNOR_ADDRESS); // Standard timelock execution governor.execute(targets, values, calldatas, descriptionHash); ``` ??? interface "Parameters" `governor` ++"HubGovernor"++ The `HubGovernor` contract instance. --- `targets` ++"address[]"++ An array containing the target addresses for the proposal’s transactions (in this case, the `HUB_MESSAGE_DISPATCHER_ADDRESS` for both). --- `values` ++"uint256[]"++ An array of values (in Wei) associated with each transaction (both are zero in this case). --- `calldatas` ++"bytes[]"++ The encoded transaction data to dispatch the governance actions (e.g., minting tokens and transferring ETH). --- `descriptionHash` ++"bytes32"++ A hash of the proposal’s description, used to verify the proposal before execution. ??? interface "Returns" No direct return, but executing this function finalizes the cross-chain governance actions by dispatching the encoded messages via Wormhole to the spoke chains. Once the proposal is executed, the encoded messages will be dispatched via Wormhole to the spoke chains, where the Optimism and Arbitrum treasuries will receive their respective funds. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/settlement/.index/ --- BEGIN CONTENT --- --- title: Wormhole Settlement description: Follow step-by-step tutorials to integrate Wormhole Settlement Routes using the SDK for seamless cross-chain swaps and efficient asset transfers. --- # Wormhole Settlement This section provides hands-on tutorials to help you integrate Wormhole Settlement Routes into your application. Learn how to use the SDK to manage cross-chain swaps, optimize fees, and streamline transaction execution. ## Tutorials
- :octicons-repo-16:{ .lg .middle } **Integrate Wormhole Settlement Routes using the SDK** --- Learn how to integrate Wormhole Settlement Routes using the SDK to simplify cross-chain swaps, manage fees, and execute seamless transactions. [:custom-arrow: Integrate Settlement Routes](/docs/tutorials/settlement/settlement-routes/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/settlement/.settlement-routes/ --- BEGIN CONTENT --- --- title: Wormhole Settlements description: Learn how to integrate Wormhole Settlement Routes using the SDK to simplify cross-chain swaps, manage fees, and execute seamless transactions. --- # Integrate Wormhole Settlement Routes Using the SDK ## Introduction This guide explains integrating Wormhole Settlement Routes from the Wormhole SDK into your application. These Routes abstract the complexity of cross-chain token swaps by handling route discovery, fee estimation, and transaction construction, all useful for dApps seeking to embed cross-chain swaps. By following this tutorial you will install the Wormhole SDK Route package, configure and execute a swap, and explore error handling and troubleshooting. ## Prerequisites Before beginning this project, make sure you have the following: - **Wormhole SDK Route package** - installed using your preferred package manager To install the package with npm, run the following command in your terminal: ```sh npm install @mayan-finance/wormhole-sdk-route ``` Alternatively, clone the repository and install dependencies: ```sh git clone https://github.com/mayan-finance/wormhole-sdk-route.git cd wormhole-sdk-route npm install ``` - **Data for parameters** - you will need: - [Chain IDs](/docs/build/reference/chain-ids/){target=\_blank} for the source and destination chains - An contract address for the token you want to swap and the token you want to receive on the destination chain ## Configure and Setup To initiate a swap, you must create a configuration object that specifies all required parameters. These typically include: - `sourceChain` - identifier for the chain where the swap begins - `destinationChain` - identifier for the target chain - `inputTokenAddress` - address of the token you want to swap - `outputTokenAddress` - identifier/address of the desired token on the destination chain - `amount` - the amount to swap (expressed in the smallest unit, e.g., wei for Ethereum) - `slippageTolerance` - acceptable percentage of slippage (e.g., 0.005 for 0.5%) ```ts import { SwapRoute, SwapRouteConfig } from '@mayan-finance/wormhole-sdk-route'; const config: SwapRouteConfig = { sourceChain: 'ethereum', destinationChain: 'solana', inputTokenAddress: '0xYourInputTokenAddress', outputTokenAddress: 'So11111111111111111111111111111111111111112', // Example token on Solana amount: '1000000000000000000', // For instance, 1 token in wei slippageTolerance: 0.005, // 0.5% slippage tolerance // Additional parameters may be included as needed }; const swapRoute = new SwapRoute(config); ``` ## Execute a Swap Once the configuration is complete, the next steps are to retrieve the optimal swap route and execute the transaction. ### Fetch the Swap Route Before sending a transaction, fetch the optimal swap route to review details such as fees and expected outputs: ```ts async function getSwapDetails() { try { const routeDetails = await swapRoute.getRoute(); console.log('Optimal Swap Route:', routeDetails); return routeDetails; } catch (error) { console.error('Error fetching swap route:', error); throw error; } } getSwapDetails(); ``` ### Execute the Swap Transaction Once the route is confirmed, execute the swap: ```ts async function executeSwap() { try { const txResponse = await swapRoute.executeSwap(); console.log('Swap executed successfully:', txResponse); // Further transaction handling (e.g., waiting for confirmation) can be added here. } catch (error) { console.error('Swap execution failed:', error); } } executeSwap(); ``` ## Complete Example Integration Below is a complete example that puts together configuration, route fetching, and swap execution: ```ts import { SwapRoute, SwapRouteConfig } from '@mayan-finance/wormhole-sdk-route'; async function performSwap() { // Configure the swap parameters const config: SwapRouteConfig = { sourceChain: 'ethereum', destinationChain: 'solana', inputTokenAddress: '0xYourInputTokenAddress', outputTokenAddress: 'So11111111111111111111111111111111111111112', amount: '1000000000000000000', slippageTolerance: 0.005, // Include additional settings as needed }; // Initialize the swap route const swapRoute = new SwapRoute(config); try { // Retrieve the optimal swap route details const routeDetails = await swapRoute.getRoute(); console.log('Optimal Swap Route:', routeDetails); // Execute the swap transaction const txResponse = await swapRoute.executeSwap(); console.log('Swap Transaction Response:', txResponse); } catch (error) { console.error('An error occurred during the swap process:', error); } } performSwap(); ``` ## Error Handling and Troubleshooting - **Route fetching errors** - ensure all configuration parameters (chain IDs, token addresses, amounts) are correct and that network endpoints are reachable - **Transaction execution errors** - verify that the connected wallet has sufficient funds and that transaction parameters meet the network’s requirements. Detailed logging can assist with troubleshooting - **Miscellaneous** - to pass a `ReferrerAddress` to the initiation functions, you can create a class that extends the `MayanRoute` class. Override the `referrerAddress` method to return addresses by platform, as shown in this example: ```ts class MayanRefRoute extends MayanRoute { override referrerAddress(): ReferrerAddresses | undefined { return { evm: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbe" }; } } ``` --- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/solidity-sdk/cross-chain-contracts/ --- BEGIN CONTENT --- --- title: Create Cross-Chain Contracts description: Learn how to create cross-chain contracts using Wormhole's Solidity SDK. Deploy contracts on Avalanche and Celo Testnets and send messages across chains. --- # Create Cross-Chain Messaging Contracts :simple-github: [Source code on GitHub](https://github.com/wormhole-foundation/demo-wormhole-messaging){target=\_blank} ## Introduction Wormhole's cross-chain messaging allows smart contracts to interact seamlessly across multiple blockchains. This enables developers to build decentralized applications that leverage the strengths of different networks, whether it's Avalanche, Celo, Ethereum, or beyond. In this tutorial, we'll explore using [Wormhole's Solidity SDK](https://github.com/wormhole-foundation/wormhole-solidity-sdk){target=\_blank} to create cross-chain contracts to send and receive messages across chains. Wormhole's messaging infrastructure simplifies data transmission, event triggering, and transaction initiation across blockchains. In this tutorial, we'll guide you through a simple yet powerful hands-on demonstration that showcases this practical capability. We'll deploy contracts on two Testnets—Avalanche Fuji and Celo Alfajores—and send messages from one chain to another. This tutorial is perfect for those new to cross-chain development and seeking hands-on experience with Wormhole's powerful toolkit. By the end of this tutorial, you will have not only built a fully functioning cross-chain message sender and receiver using Solidity but also developed a comprehensive understanding of how to interact with the Wormhole relayer, manage cross-chain costs, and ensure your smart contracts are configured correctly on both source and target chains. This tutorial assumes a basic understanding of Solidity and smart contract development. Before diving in, it may be helpful to review [the basics of Wormhole](/docs/learn/){target=\_blank} to familiarize yourself with the protocol. ## Wormhole Overview We'll interact with two key Wormhole components: the [Wormhole relayer](/docs/learn/infrastructure/relayer/){target=\_blank} and the [Wormhole Core Contracts](/docs/learn/infrastructure/core-contracts/){target=\_blank}. The relayer handles cross-chain message delivery and ensures the message is accurately received on the target chain. This allows smart contracts to communicate across blockchains without developers worrying about the underlying complexity. Additionally, we'll rely on the Wormhole relayer to automatically determine cross-chain transaction costs and facilitate payments. This feature simplifies cross-chain development by allowing you to specify only the target chain and the message. The relayer handles the rest, ensuring that the message is transmitted with the appropriate fee. ![Wormhole architecture detailed diagram: source to target chain communication.](/docs/images/learn/infrastructure/architecture/architecture-1.webp) ## Prerequisites Before starting this tutorial, 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 - [Foundry](https://book.getfoundry.sh/getting-started/installation){target=\_blank} for deploying contracts - Testnet tokens for [Avalanche-Fuji](https://core.app/tools/testnet-faucet/?token=C){target=\_blank} and [Celo-Alfajores](https://faucet.celo.org/alfajores){target=\_blank} to cover gas fees - Wallet private key ## Build Cross-Chain Messaging Contracts In this section, we'll deploy two smart contracts: one to send a message from Avalanche Fuji and another to receive it on Celo Alfajores. The contracts interact with the Wormhole relayer to transmit messages across chains. At a high level, our contracts will: 1. Send a message from Avalanche to Celo using the Wormhole relayer 2. Receive and process the message on Celo, logging the content of the message Before diving into the deployment steps, let's first break down key parts of the contracts. ### Sender Contract: MessageSender The `MessageSender` contract is responsible for quoting the cost of sending a message cross-chain and then sending that message. Key functions include: - **`quoteCrossChainCost`** - calculates the cost of delivering a message to the target chain using the Wormhole relayer - **`sendMessage`** - encodes the message and sends it to the target chain and contract address using the Wormhole relayer Here's the core of the contract: ```solidity uint16 targetChain, address targetAddress, string memory message ) external payable { uint256 cost = quoteCrossChainCost(targetChain); require( msg.value >= cost, "Insufficient funds for cross-chain delivery" ); wormholeRelayer.sendPayloadToEvm{value: cost}( targetChain, targetAddress, abi.encode(message, msg.sender), 0, GAS_LIMIT ); } ``` You can find the full code for the `MessageSender.sol` below. ??? code "MessageSender.sol" ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import "lib/wormhole-solidity-sdk/src/interfaces/IWormholeRelayer.sol"; contract MessageSender { IWormholeRelayer public wormholeRelayer; uint256 constant GAS_LIMIT = 50000; constructor(address _wormholeRelayer) { wormholeRelayer = IWormholeRelayer(_wormholeRelayer); } function quoteCrossChainCost( uint16 targetChain ) public view returns (uint256 cost) { (cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( targetChain, 0, GAS_LIMIT ); } function sendMessage( uint16 targetChain, address targetAddress, string memory message ) external payable { uint256 cost = quoteCrossChainCost(targetChain); require( msg.value >= cost, "Insufficient funds for cross-chain delivery" ); wormholeRelayer.sendPayloadToEvm{value: cost}( targetChain, targetAddress, abi.encode(message, msg.sender), 0, GAS_LIMIT ); } } ``` ### Receiver Contract: MessageReceiver The `MessageReceiver` contract handles incoming cross-chain messages. When a message arrives, it decodes the payload and logs the message content. It ensures that only authorized contracts can send and process messages, adding an extra layer of security in cross-chain communication. #### Emitter Validation and Registration In cross-chain messaging, validating the sender is essential to prevent unauthorized contracts from sending messages. The `isRegisteredSender` modifier ensures that messages can only be processed if they come from the registered contract on the source chain. This guards against malicious messages and enhances security. Key implementation details include: - **`registeredSender`** - stores the address of the registered sender contract - **`setRegisteredSender`** - registers the sender's contract address on the source chain. It ensures that only registered contracts can send messages, preventing unauthorized senders - **`isRegisteredSender`** - restricts the processing of messages to only those from registered senders, preventing unauthorized cross-chain communication ```solidity require( registeredSenders[sourceChain] == sourceAddress, "Not registered sender" ); _; } function setRegisteredSender( uint16 sourceChain, bytes32 sourceAddress ) public { require( msg.sender == registrationOwner, "Not allowed to set registered sender" ); registeredSenders[sourceChain] = sourceAddress; } ``` #### Message Processing The `receiveWormholeMessages` is the core function that processes the received message. It checks that the Wormhole relayer sent the message, decodes the payload, and emits an event with the message content. It is essential to verify the message sender to prevent unauthorized messages. ```solidity bytes memory payload, bytes[] memory, bytes32 sourceAddress, uint16 sourceChain, bytes32 ) public payable override isRegisteredSender(sourceChain, sourceAddress) { require( msg.sender == address(wormholeRelayer), "Only the Wormhole relayer can call this function" ); // Decode the payload to extract the message string memory message = abi.decode(payload, (string)); // Example use of sourceChain for logging if (sourceChain != 0) { emit SourceChainLogged(sourceChain); } // Emit an event with the received message emit MessageReceived(message); } ``` You can find the full code for the `MessageReceiver.sol` below. ??? code "MessageReceiver.sol" ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import "lib/wormhole-solidity-sdk/src/interfaces/IWormholeRelayer.sol"; import "lib/wormhole-solidity-sdk/src/interfaces/IWormholeReceiver.sol"; contract MessageReceiver is IWormholeReceiver { IWormholeRelayer public wormholeRelayer; address public registrationOwner; // Mapping to store registered senders for each chain mapping(uint16 => bytes32) public registeredSenders; event MessageReceived(string message); event SourceChainLogged(uint16 sourceChain); constructor(address _wormholeRelayer) { wormholeRelayer = IWormholeRelayer(_wormholeRelayer); registrationOwner = msg.sender; // Set contract deployer as the owner } modifier isRegisteredSender(uint16 sourceChain, bytes32 sourceAddress) { require( registeredSenders[sourceChain] == sourceAddress, "Not registered sender" ); _; } function setRegisteredSender( uint16 sourceChain, bytes32 sourceAddress ) public { require( msg.sender == registrationOwner, "Not allowed to set registered sender" ); registeredSenders[sourceChain] = sourceAddress; } // Update receiveWormholeMessages to include the source address check function receiveWormholeMessages( bytes memory payload, bytes[] memory, bytes32 sourceAddress, uint16 sourceChain, bytes32 ) public payable override isRegisteredSender(sourceChain, sourceAddress) { require( msg.sender == address(wormholeRelayer), "Only the Wormhole relayer can call this function" ); // Decode the payload to extract the message string memory message = abi.decode(payload, (string)); // Example use of sourceChain for logging if (sourceChain != 0) { emit SourceChainLogged(sourceChain); } // Emit an event with the received message emit MessageReceived(message); } } ``` ## Deploy Contracts This section will guide you through deploying the cross-chain messaging contracts on the Avalanche Fuji and Celo Alfajores Testnets. Follow these steps to get your contracts up and running. ### Deployment Tools We use _Foundry_ to deploy our smart contracts. However, you can use any tool you're comfortable with, such as: - [Remix](https://remix.ethereum.org/){target=\_blank} for a browser-based IDE - [Hardhat](https://hardhat.org/hardhat-runner/docs/getting-started#installation){target=\_blank} for a more extensive JavaScript/TypeScript workflow - [Foundry](https://book.getfoundry.sh/getting-started/installation){target=\_blank} for a CLI-focused experience with built-in scripting and testing features The contracts and deployment steps remain the same regardless of your preferred tool. The key is to ensure you have the necessary Testnet funds and are deploying to the right networks. ### Repository Setup To get started with cross-chain messaging using Wormhole, first clone the [GitHub repository](https://github.com/wormhole-foundation/demo-wormhole-messaging){target=\_blank}. This repository includes everything you need to deploy, interact, and test the message flow between chains. This demo focuses on using the scripts, so it's best to take a look at them, starting with `deploySender.ts`, `deployReceiver.ts`, and `sendMessage.ts`. To configure the dependencies properly, run the following command: ```bash npm install ``` The repository includes: - Two Solidity contracts: - **`MessageSender.sol`** - contract that sends the cross-chain message from Avalanche - **`MessageReceiver.sol`** - contract that receives the cross-chain message on Celo - Deployment scripts located in the `script` directory: - **`deploySender.ts`** - deploys the `MessageSender` contract to Avalanche - **`deployReceiver.ts`** - deploys the `MessageReceiver` contract to Celo - **`sendMessage.ts`** - sends a message from Avalanche to Celo - Configuration files and ABI JSON files for easy deployment and interaction: - **`chains.json`** - configuration file that stores key information for the supported Testnets, including the Wormhole relayer addresses, RPC URLs, and chain IDs. You likely won't need to modify this file unless you're working with different networks - A dedicated `interfaces` directory inside the `src` folder for TypeScript type definitions: - **`ChainsConfig.ts`** - defines the types for the `chains.json` configuration file - **`DeployedContracts.ts`** - contains types for deployed contract addresses and related information - **`MessageJsons.ts`** - includes types for ABI and bytecode JSONs used by the deployment scripts - **`index.ts`** - serves as an export aggregator for the interfaces, simplifying imports in other files ### Important Setup Steps 1. **Add your private key** - create a `.env` file in the root of the project and add your private key: ```env touch .env ``` Inside `.env`, add your private key in the following format: ```env PRIVATE_KEY=INSERT_PRIVATE_KEY ``` 2. **Compile the contracts** - ensure everything is set up correctly by compiling the contracts: ```bash forge build ``` The expected output should be similar to this:
forge build > [⠒] Compiling... > [⠰] Compiling 30 files with 0.8.23 [⠔] Solc 0.8.23 finished in 2.29s Compiler run successful!
### Deployment Process Both deployment scripts, `deploySender.ts` and `deployReceiver.ts`, perform the following key tasks: 1. **Load configuration and contract details** - each script begins by loading the necessary configuration details, such as the network's RPC URL and the contract's ABI and bytecode. This information is essential for deploying the contract to the correct blockchain network === "`chains.json`" ```json { "chains": [ { "description": "Avalanche testnet fuji", "chainId": 6, "rpc": "https://api.avax-test.network/ext/bc/C/rpc", "tokenBridge": "0x61E44E506Ca5659E6c0bba9b678586fA2d729756", "wormholeRelayer": "0xA3cF45939bD6260bcFe3D66bc73d60f19e49a8BB", "wormhole": "0x7bbcE28e64B3F8b84d876Ab298393c38ad7aac4C" }, { "description": "Celo Testnet", "chainId": 14, "rpc": "https://alfajores-forno.celo-testnet.org", "tokenBridge": "0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153", "wormholeRelayer": "0x306B68267Deb7c5DfCDa3619E22E9Ca39C374f84", "wormhole": "0x88505117CA88e7dd2eC6EA1E13f0948db2D50D56" } ] } ``` === "`deploySender.ts`" ```typescript const chains: ChainsConfig = JSON.parse( fs.readFileSync( path.resolve(__dirname, '../deploy-config/chains.json'), 'utf8' ) ); // Get the Avalanche Fuji configuration const avalancheChain = chains.chains.find((chain) => chain.description.includes('Avalanche testnet') ); ``` === "`deployReceiver.ts`" ```typescript const chains: ChainsConfig = JSON.parse( fs.readFileSync( path.resolve(__dirname, '../deploy-config/chains.json'), 'utf8' ) ); // Get the Celo Testnet configuration const celoChain = chains.chains.find((chain) => chain.description.includes('Celo Testnet') ); ``` !!! note The `chains.json` file contains the configuration details for the Avalanche Fuji and Celo Alfajores Testnets. You can modify this file to add more networks if needed. For a complete list of contract addresses, visit the [reference page](/docs/build/reference/){target=\_blank}. 2. **Set up provider and wallet** - the scripts establish a connection to the blockchain using a provider and create a wallet instance using a private key. This wallet is responsible for signing the deployment transaction === "`deploySender.ts`" ```typescript const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); ``` === "`deployReceiver.ts`" ```typescript const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); ``` 3. **Deploy the contract** - the contract is deployed to the network specified in the configuration. Upon successful deployment, the contract address is returned, which is crucial for interacting with the contract later on === "`deploySender.ts`" ```typescript avalancheChain.wormholeRelayer ); await senderContract.waitForDeployment(); ``` === "`deployReceiver.ts`" ```typescript celoChain.wormholeRelayer ); await receiverContract.waitForDeployment(); ``` 4. **Register the `MessageSender` on the target chain** - after you deploy the `MessageReceiver` contract on the Celo Alfajores network, the sender contract address from Avalanche Fuji needs to be registered. This ensures that only messages from the registered `MessageSender` contract are processed This additional step is essential to enforce emitter validation, preventing unauthorized senders from delivering messages to the `MessageReceiver` contract ```typescript const avalancheSenderAddress = deployedContracts.avalanche?.MessageSender; if (!avalancheSenderAddress) { throw new Error('Avalanche MessageSender address not found.'); } // Define the source chain ID for Avalanche Fuji const sourceChainId = 6; // Call setRegisteredSender on the MessageReceiver contract const tx = await (receiverContract as any).setRegisteredSender( sourceChainId, ethers.zeroPadValue(avalancheSenderAddress, 32) ); await tx.wait(); ``` You can find the full code for the `deploySender.ts` and `deployReceiver.ts` below. ??? code "deploySender.ts" ```typescript import { ethers } from 'ethers'; import fs from 'fs'; import path from 'path'; import dotenv from 'dotenv'; import { ChainsConfig, DeployedContracts, MessageSenderJson, } from './interfaces'; dotenv.config(); async function main(): Promise { // Load the chain configuration from JSON const chains: ChainsConfig = JSON.parse( fs.readFileSync( path.resolve(__dirname, '../deploy-config/chains.json'), 'utf8' ) ); // Get the Avalanche Fuji configuration const avalancheChain = chains.chains.find((chain) => chain.description.includes('Avalanche testnet') ); if (!avalancheChain) { throw new Error( 'Avalanche testnet configuration not found in chains.json.' ); } // Set up the provider and wallet const provider = new ethers.JsonRpcProvider(avalancheChain.rpc); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); // Load the ABI and bytecode of the MessageSender contract const messageSenderJson: MessageSenderJson = JSON.parse( fs.readFileSync( path.resolve(__dirname, '../out/MessageSender.sol/MessageSender.json'), 'utf8' ) ); const { abi, bytecode } = messageSenderJson; // Create a ContractFactory for MessageSender const MessageSender = new ethers.ContractFactory(abi, bytecode, wallet); // Deploy the contract using the Wormhole Relayer address for Avalanche Fuji const senderContract = await MessageSender.deploy( avalancheChain.wormholeRelayer ); await senderContract.waitForDeployment(); console.log('MessageSender deployed to:', senderContract.target); // `target` is the address in ethers.js v6 // Update the deployedContracts.json file const deployedContractsPath = path.resolve( __dirname, '../deploy-config/deployedContracts.json' ); const deployedContracts: DeployedContracts = JSON.parse( fs.readFileSync(deployedContractsPath, 'utf8') ); deployedContracts.avalanche = { MessageSender: senderContract.target as any, deployedAt: new Date().toISOString(), }; fs.writeFileSync( deployedContractsPath, JSON.stringify(deployedContracts, null, 2) ); } main().catch((error) => { console.error(error); process.exit(1); }); ``` ??? code "deployReceiver.ts" ```typescript import { ethers } from 'ethers'; import fs from 'fs'; import path from 'path'; import dotenv from 'dotenv'; import { ChainsConfig, DeployedContracts, MessageReceiverJson, } from './interfaces'; dotenv.config(); async function main(): Promise { // Load the chain configuration from the JSON file const chains: ChainsConfig = JSON.parse( fs.readFileSync( path.resolve(__dirname, '../deploy-config/chains.json'), 'utf8' ) ); // Get the Celo Testnet configuration const celoChain = chains.chains.find((chain) => chain.description.includes('Celo Testnet') ); if (!celoChain) { throw new Error('Celo Testnet configuration not found.'); } // Set up the provider and wallet const provider = new ethers.JsonRpcProvider(celoChain.rpc); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); // Load the ABI and bytecode of the MessageReceiver contract const messageReceiverJson: MessageReceiverJson = JSON.parse( fs.readFileSync( path.resolve( __dirname, '../out/MessageReceiver.sol/MessageReceiver.json' ), 'utf8' ) ); const { abi, bytecode } = messageReceiverJson; // Create a ContractFactory for MessageReceiver const MessageReceiver = new ethers.ContractFactory(abi, bytecode, wallet); // Deploy the contract using the Wormhole Relayer address for Celo Testnet const receiverContract = await MessageReceiver.deploy( celoChain.wormholeRelayer ); await receiverContract.waitForDeployment(); console.log('MessageReceiver deployed to:', receiverContract.target); // `target` is the contract address in ethers.js v6 // Update the deployedContracts.json file const deployedContractsPath = path.resolve( __dirname, '../deploy-config/deployedContracts.json' ); const deployedContracts: DeployedContracts = JSON.parse( fs.readFileSync(deployedContractsPath, 'utf8') ); // Retrieve the address of the MessageSender from the deployedContracts.json file const avalancheSenderAddress = deployedContracts.avalanche?.MessageSender; if (!avalancheSenderAddress) { throw new Error('Avalanche MessageSender address not found.'); } // Define the source chain ID for Avalanche Fuji const sourceChainId = 6; // Call setRegisteredSender on the MessageReceiver contract const tx = await (receiverContract as any).setRegisteredSender( sourceChainId, ethers.zeroPadValue(avalancheSenderAddress, 32) ); await tx.wait(); console.log( `Registered MessageSender (${avalancheSenderAddress}) for Avalanche chain (${sourceChainId})` ); deployedContracts.celo = { MessageReceiver: receiverContract.target as any, deployedAt: new Date().toISOString(), }; fs.writeFileSync( deployedContractsPath, JSON.stringify(deployedContracts, null, 2) ); } main().catch((error) => { console.error(error); process.exit(1); }); ``` ### Deploy the Sender Contract The sender contract will handle quoting and sending messages cross-chain. 1. Run the following command to deploy the sender contract: ```bash npm run deploy:sender ``` 2. Once deployed, the contract address will be displayed. You may check the contract on the [Avalanche Fuji Explorer](https://testnet.snowtrace.io/){target=\_blank}
npm run deploy:sender > wormhole-cross-chain@1.0.0 deploy:sender > node script/deploySender.ts MessageSender deployed to: 0xf5c474f335fFf617fA6FD04DCBb17E20ee0cEfb1
### Deploy the Receiver Contract The receiver contract listens for cross-chain messages and logs them when received. 1. Deploy the receiver contract with this command: ```bash npm run deploy:receiver ``` 2. After deployment, note down the contract address. You may check the contract on the [Celo Alfajores Explorer](https://alfajores.celoscan.io/){target=\_blank}. ## Send a Cross-Chain Message Now that both the sender and receiver contracts are deployed, let's move on to the next exciting step: sending a cross-chain message from Avalanche Fuji to Celo Alfajores. In this example, we will use the `sendMessage.ts` script to transmit a message from the sender contract on Avalanche to the receiver contract on Celo. The script uses [Ethers.js](https://docs.ethers.org/v6/){target=\_blank} to interact with the deployed contracts, calculate the cross-chain cost dynamically, and handle the transaction. Let's break down the script step by step. 1. **Load configuration files** 1. **`chains.json`** - contains details about the supported Testnet chains, such as RPC URLs and relayer addresses 2. **`deployedContracts.json`** - stores the addresses of the deployed sender and receiver contracts. This file is dynamically updated when contracts are deployed, but users can also manually add their own deployed contract addresses if needed ```typescript fs.readFileSync( path.resolve(__dirname, '../deploy-config/chains.json'), 'utf8' ) ); const deployedContracts: DeployedContracts = JSON.parse( fs.readFileSync( path.resolve(__dirname, '../deploy-config/deployedContracts.json'), 'utf8' ) ); ``` 2. **Configure the provider and signer** - the script first reads the chain configurations and extracts the contract addresses. One essential step in interacting with a blockchain is setting up a _provider_. A provider is your connection to the blockchain network. It allows your script to interact with the blockchain, retrieve data, and send transactions. In this case, we're using a JSON-RPC provider Next, we configure the wallet, which will be used to sign transactions. The wallet is created using the private key and the provider. This ensures that all transactions sent from this wallet are broadcast to the Avalanche Fuji network: ```typescript const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); ``` After setting up the wallet, the script loads the ABI for the `MessageSender.sol` contract and creates an instance of it: ```typescript fs.readFileSync( path.resolve(__dirname, '../out/MessageSender.sol/MessageSender.json'), 'utf8' ) ); ``` 3. **Set up the message details** - the next part of the script defines the target chain (Celo) and the target address (the receiver contract on Celo): ```typescript const targetAddress = deployedContracts.celo.MessageReceiver; ``` You can customize the message that will be sent across chains: ```typescript ``` 4. **Estimate cross-chain cost** - before sending the message, we dynamically calculate the cross-chain cost using the `quoteCrossChainCost` function: ```typescript ``` This ensures that the transaction includes enough funds to cover the gas fees for the cross-chain message. 5. **Send a message** - with everything set up, the message is sent using the `sendMessage` function: ```typescript targetChain, targetAddress, message, { value: txCost, } ); ``` After sending, the script waits for the transaction to be confirmed: ```typescript ``` 6. **Run the script** - to send the message, run the following command: ```bash npm run send:message ``` If everything is set up correctly, the message will be sent from the Avalanche Fuji Testnet to the Celo Alfajores Testnet. You can monitor the transaction and verify that the message was received on Celo using the [Wormhole Explorer](https://wormholescan.io/#/?network=TESTNET){target=\_blank}. The console should output something similar to this:
npm run send:message > wormhole-cross-chain@1.0.0 send:message > node script/sendMessage.ts Sender Contract Address: 0xD720BFF42a0960cfF1118454A907a44dB358f2b1 Receiver Contract Address: 0x692550997C252cC5044742D1A2BD91E4f4b46D39 ... Transaction sent, waiting for confirmation... ... Message sent! Transaction hash: 0x9d359a66ba42baced80062229c0b02b4f523fe304aff3473dcf53117aee13fb6 You may see the transaction status on the Wormhole Explorer: https://wormholescan.io/#/tx/0x9d359a66ba42baced80062229c0b02b4f523fe304aff3473dcf53117aee13fb6?network=TESTNET
You can find the full code for the `sendMessage.ts` below. ??? code "sendMessage.ts" ```solidity import { ethers } from 'ethers'; import fs from 'fs'; import path from 'path'; import dotenv from 'dotenv'; import { ChainsConfig, DeployedContracts } from './interfaces'; dotenv.config(); async function main(): Promise { // Load the chain configuration and deployed contract addresses const chains: ChainsConfig = JSON.parse( fs.readFileSync( path.resolve(__dirname, '../deploy-config/chains.json'), 'utf8' ) ); const deployedContracts: DeployedContracts = JSON.parse( fs.readFileSync( path.resolve(__dirname, '../deploy-config/deployedContracts.json'), 'utf8' ) ); console.log( 'Sender Contract Address: ', deployedContracts.avalanche.MessageSender ); console.log( 'Receiver Contract Address: ', deployedContracts.celo.MessageReceiver ); console.log('...'); // Get the Avalanche Fuji configuration const avalancheChain = chains.chains.find((chain) => chain.description.includes('Avalanche testnet') ); if (!avalancheChain) { throw new Error( 'Avalanche testnet configuration not found in chains.json.' ); } // Set up the provider and wallet const provider = new ethers.JsonRpcProvider(avalancheChain.rpc); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); // Load the ABI of the MessageSender contract const messageSenderJson = JSON.parse( fs.readFileSync( path.resolve(__dirname, '../out/MessageSender.sol/MessageSender.json'), 'utf8' ) ); const abi = messageSenderJson.abi; // Create a contract instance for MessageSender const MessageSender = new ethers.Contract( deployedContracts.avalanche.MessageSender, // Automatically use the deployed address abi, wallet ); // Define the target chain and target address (the Celo receiver contract) const targetChain = 14; // Wormhole chain ID for Celo Alfajores const targetAddress = deployedContracts.celo.MessageReceiver; // The message you want to send const message = 'Hello from Avalanche to Celo!'; // Dynamically quote the cross-chain cost const txCost = await MessageSender.quoteCrossChainCost(targetChain); // Send the message (make sure to send enough gas in the transaction) const tx = await MessageSender.sendMessage( targetChain, targetAddress, message, { value: txCost, } ); console.log('Transaction sent, waiting for confirmation...'); await tx.wait(); console.log('...'); console.log('Message sent! Transaction hash:', tx.hash); console.log( `You may see the transaction status on the Wormhole Explorer: https://wormholescan.io/#/tx/${tx.hash}?network=TESTNET` ); } main().catch((error) => { console.error(error); process.exit(1); }); ``` ## Conclusion You're now fully equipped to build cross-chain contracts using the Wormhole protocol! With this tutorial, you've learned how to: - Deploy sender and receiver contracts on different Testnets - Send a cross-chain message from one blockchain to another - Monitor the status of your cross-chain transactions using the Wormhole Explorer and Wormhole-Solidity-SDK --- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/solidity-sdk/cross-chain-token-contracts/ --- BEGIN CONTENT --- --- title: Cross-Chain Token Transfers description: Learn how to create cross-chain token transfers using Wormhole's Solidity SDK. Build and deploy smart contracts to send tokens from one blockchain to another. --- # Create Cross-Chain Token Transfer Contracts :simple-github: [Source code on GitHub](https://github.com/wormhole-foundation/demo-cross-chain-token-transfer){target=\_blank} ## Introduction In this tutorial, you'll learn how to create a simple cross-chain token transfer system using the Wormhole protocol via the [Wormhole Solidity SDK](https://github.com/wormhole-foundation/wormhole-solidity-sdk){target=\_blank}. We'll guide you through building and deploying smart contracts that enable seamless token transfers of IERC-20 tokens between blockchains. Whether you're a developer looking to explore cross-chain applications or just interested in the Wormhole protocol, this guide will help you understand the fundamentals. By the end of this tutorial, you'll have a working cross-chain token transfer system built with the powerful tools provided by the Wormhole Solidity SDK, which you can further customize and integrate into your projects. ## 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 - [Foundry](https://book.getfoundry.sh/getting-started/installation){target=\_blank} for deploying contracts - Testnet tokens for [Avalanche-Fuji](https://core.app/tools/testnet-faucet/?token=C){target=\_blank} and [Celo-Alfajores](https://faucet.celo.org/alfajores){target=\_blank} to cover gas fees - [USDC Testnet](https://faucet.circle.com/){target=\_blank} tokens on Avalanche-Fuji or/and Celo-Alfajores for cross-chain transfer - Wallet private key ## Valid Tokens for Transfer It's important to note that this tutorial leverages [Wormhole's TokenBridge](https://github.com/wormhole-foundation/wormhole/blob/6130bbb6f456b42b789a71f7ea2fd049d632d2fb/ethereum/contracts/bridge/TokenBridge.sol){target=\_blank} to transfer tokens between chains. So, the tokens you'd like to transfer must have an attestation on the `TokenBridge` contract of the target blockchain. To simplify this process, we've included a tool for verifying if a token has an attestation on the target chain. This tool uses the [`wrappedAsset`](https://github.com/wormhole-foundation/wormhole/blob/6130bbb6f456b42b789a71f7ea2fd049d632d2fb/ethereum/contracts/bridge/BridgeGetters.sol#L50-L52){target=\_blank} function from the `TokenBridge` contract. If the token has an attestation, the `wrappedAsset` function returns the address of the wrapped token on the target chain; otherwise, it returns the zero address. ???- tip "Check Token Attestation" 1. Clone the [repository](https://github.com/wormhole-foundation/demo-cross-chain-token-transfer){target=\_blank} and navigate to the project directory: ```bash git clone https://github.com/wormhole-foundation/demo-cross-chain-token-transfer.git cd cross-chain-token-transfers ``` 2. Install the dependencies: ```bash npm install ``` 3. Run the script to check token attestation: ```bash npm run verify ``` 4. Follow the prompts: 1. Enter the RPC URL of the target chain 2. Enter the `TokenBridge` contract address on the target chain 3. Enter the token contract address on the source chain 4. Enter the source chain ID 5. The expected output when the token has an attestation:
npm run verify > cross-chain-token-transfer@1.0.0 verify > npx ts-node script/check-attestation.ts Enter the TARGET chain RPC URL: https://alfajores-forno.celo-testnet.org Enter the Token Bridge contract address on the TARGET chain: 0x05...E153 Enter the token contract address on the SOURCE chain: 0x54...bc65 Enter the SOURCE chain ID: 6 The token is attested on the target chain. Wrapped token address: 0xDDB349c976cA2C873644F21f594767Eb5390C831
Using this tool ensures that you only attempt to transfer tokens with verified attestations, avoiding any potential issues during the cross-chain transfer process. ## Project Setup Let's start by initializing a new Foundry project. This will set up a basic structure for our smart contracts. 1. Open your terminal and run the following command to initialize a new Foundry project: ```bash forge init cross-chain-token-transfers ``` This will create a new directory named `cross-chain-token-transfers` with a basic project structure. This also initializes a new `git` repository. 2. Navigate into the newly created project directory: ```bash cd cross-chain-token-transfers ``` 3. Install the Wormhole Solidity SDK: ```bash forge install wormhole-foundation/wormhole-solidity-sdk ``` To ease development, we'll use the Wormhole Solidity SDK, which provides useful helpers for cross-chain development. This SDK includes the `TokenSender` and `TokenReceiver` abstract classes, which simplify sending and receiving tokens across chains. ## Build Cross-Chain Contracts In this section, we'll build two smart contracts to send tokens from a source chain and receive them on a target chain. These contracts will interact with the Wormhole protocol to facilitate secure and seamless cross-chain token transfers. At a high level, our contracts will: 1. Send tokens from one blockchain to another using the Wormhole protocol 2. Receive and process the tokens on the target chain, ensuring they are correctly transferred to the intended recipient Before diving into the contract implementation steps, let’s first break down the key parts of the contracts. ### Sender Contract: CrossChainSender The `CrossChainSender` contract calculates the cost of sending tokens across chains and then facilitates the actual token transfer. Let's start writing the `CrossChainSender` contract: 1. Create a new file named `CrossChainSender.sol` in the `/src` directory: ```bash touch src/CrossChainSender.sol ``` 2. Open the file. First, we'll start with the imports and the contract setup: ```solidity pragma solidity ^0.8.13; import "lib/wormhole-solidity-sdk/src/WormholeRelayerSDK.sol"; import "lib/wormhole-solidity-sdk/src/interfaces/IERC20.sol"; contract CrossChainSender is TokenSender { uint256 constant GAS_LIMIT = 250_000; constructor( address _wormholeRelayer, address _tokenBridge, address _wormhole ) TokenBase(_wormholeRelayer, _tokenBridge, _wormhole) {} ``` This sets up the basic structure of the contract, including the necessary imports and the constructor that initializes the contract with the Wormhole-related addresses. With the contract structure in place, define the following functions within its body to enable multichain token transfers. 3. Next, let's add a function that estimates the cost of sending tokens across chains: ```solidity uint16 targetChain ) public view returns (uint256 cost) { uint256 deliveryCost; (deliveryCost, ) = wormholeRelayer.quoteEVMDeliveryPrice( targetChain, 0, GAS_LIMIT ); cost = deliveryCost + wormhole.messageFee(); } ``` This function, `quoteCrossChainDeposit`, helps calculate the cost of transferring tokens to a different chain. It factors in the delivery cost and the cost of publishing a message via the Wormhole protocol. 4. Finally, we'll add the function that sends the tokens across chains: ```solidity uint16 targetChain, address targetReceiver, address recipient, uint256 amount, address token ) public payable { uint256 cost = quoteCrossChainDeposit(targetChain); require( msg.value == cost, "msg.value must equal quoteCrossChainDeposit(targetChain)" ); IERC20(token).transferFrom(msg.sender, address(this), amount); bytes memory payload = abi.encode(recipient); sendTokenWithPayloadToEvm( targetChain, targetReceiver, payload, 0, GAS_LIMIT, token, amount ); } ``` This `sendCrossChainDeposit` function is where the actual token transfer happens. It sends the tokens to the recipient on the target chain using the Wormhole protocol. Here’s a breakdown of what happens in each step of the `sendCrossChainDeposit` function: 1. **Cost calculation** - the function starts by calculating the cost of the cross-chain transfer using `quoteCrossChainDeposit`(`targetChain`). This cost includes both the delivery fee and the Wormhole message fee. The `sendCrossChainDeposit` function then checks that the user has sent the correct amount of Ether to cover this cost (`msg.value`) 2. **Token transfer to contract** - the next step is to transfer the specified amount of tokens from the user to the contract itself using `IERC-20(token).transferFrom(msg.sender, address(this), amount)`. This ensures that the contract has custody of the tokens before initiating the cross-chain transfer 3. **Payload encoding** - The recipient's address on the target chain is encoded into a payload using `abi.encode(recipient)`. This payload will be sent along with the token transfer, so the target contract knows who should receive the tokens on the destination chain 4. **Cross-chain transfer** - the `sendTokenWithPayloadToEvm` function is called to initiate the cross-chain token transfer. This function: - Specifies the `targetChain` (the Wormhole chain ID of the destination blockchain). - Sends the `targetReceiver` contract address on the target chain that will receive the tokens. - Attaches the payload containing the recipient's address. - Sets the `GAS_LIMIT` for the transaction. - Passes the token `address` and `amount` to transfer. This triggers the Wormhole protocol to handle the cross-chain messaging and token transfer, ensuring the tokens and payload reach the correct destination on the target chain. You can find the complete code for the `CrossChainSender.sol` below. ??? code "MessageSender.sol" ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import "lib/wormhole-solidity-sdk/src/WormholeRelayerSDK.sol"; import "lib/wormhole-solidity-sdk/src/interfaces/IERC20.sol"; contract CrossChainSender is TokenSender { uint256 constant GAS_LIMIT = 250_000; constructor( address _wormholeRelayer, address _tokenBridge, address _wormhole ) TokenBase(_wormholeRelayer, _tokenBridge, _wormhole) {} // Function to get the estimated cost for cross-chain deposit function quoteCrossChainDeposit( uint16 targetChain ) public view returns (uint256 cost) { uint256 deliveryCost; (deliveryCost, ) = wormholeRelayer.quoteEVMDeliveryPrice( targetChain, 0, GAS_LIMIT ); cost = deliveryCost + wormhole.messageFee(); } // Function to send tokens and payload across chains function sendCrossChainDeposit( uint16 targetChain, address targetReceiver, address recipient, uint256 amount, address token ) public payable { uint256 cost = quoteCrossChainDeposit(targetChain); require( msg.value == cost, "msg.value must equal quoteCrossChainDeposit(targetChain)" ); IERC20(token).transferFrom(msg.sender, address(this), amount); bytes memory payload = abi.encode(recipient); sendTokenWithPayloadToEvm( targetChain, targetReceiver, payload, 0, GAS_LIMIT, token, amount ); } } ``` ### Receiver Contract: CrossChainReceiver The `CrossChainReceiver` contract is designed to handle the receipt of tokens and payloads from another blockchain. It ensures that the tokens are correctly transferred to the designated recipient on the receiving chain. Let's start writing the `CrossChainReceiver` contract: 1. Create a new file named `CrossChainReceiver.sol` in the `/src` directory: ```bash touch src/CrossChainReceiver.sol ``` 2. Open the file. First, we'll start with the imports and the contract setup: ```solidity pragma solidity ^0.8.13; import "lib/wormhole-solidity-sdk/src/WormholeRelayerSDK.sol"; import "lib/wormhole-solidity-sdk/src/interfaces/IERC20.sol"; contract CrossChainReceiver is TokenReceiver { // The Wormhole relayer and registeredSenders are inherited from the Base.sol contract constructor( address _wormholeRelayer, address _tokenBridge, address _wormhole ) TokenBase(_wormholeRelayer, _tokenBridge, _wormhole) {} ``` Similar to the `CrossChainSender` contract, this sets up the basic structure of the contract, including the necessary imports and the constructor that initializes the contract with the Wormhole-related addresses. 3. Next, let's add a function inside the contract to handle receiving the payload and tokens: ```solidity bytes memory payload, TokenReceived[] memory receivedTokens, bytes32 sourceAddress, uint16 sourceChain, bytes32 // deliveryHash ) internal override onlyWormholeRelayer isRegisteredSender(sourceChain, sourceAddress) { require(receivedTokens.length == 1, "Expected 1 token transfer"); // Decode the recipient address from the payload address recipient = abi.decode(payload, (address)); // Transfer the received tokens to the intended recipient IERC20(receivedTokens[0].tokenAddress).transfer( recipient, receivedTokens[0].amount ); } ``` This `receivePayloadAndTokens` function processes the tokens and payload sent from another chain, decodes the recipient address, and transfers the tokens to them using the Wormhole protocol. This function also validates the emitter (`sourceAddress`) to ensure the message comes from a trusted sender. This function ensures that: - It only processes one token transfer at a time - The `sourceAddress` is checked against a list of registered senders using the `isRegisteredSender` modifier, which verifies if the emitter is allowed to send tokens to this contract - The recipient address is decoded from the payload, and the received tokens are transferred to them using the ERC-20 interface After we call `sendTokenWithPayloadToEvm` on the source chain, the message goes through the standard Wormhole message lifecycle. Once a [VAA (Verifiable Action Approval)](/docs/learn/infrastructure/vaas/){target=\_blank} is available, the delivery provider will call `receivePayloadAndTokens` on the target chain and target address specified, with the appropriate inputs. ??? tip "Understanding the `TokenReceived` Struct" Let’s delve into the fields provided to us in the `TokenReceived` struct: ```solidity struct TokenReceived { bytes32 tokenHomeAddress; uint16 tokenHomeChain; address tokenAddress; uint256 amount; uint256 amountNormalized; } ``` - **`tokenHomeAddress`** - the original address of the token on its native chain. This is the same as the token field in the call to `sendTokenWithPayloadToEvm` unless the original token sent is a Wormhole-wrapped token. In that case, this will be the address of the original version of the token (on its native chain) in Wormhole address format (left-padded with 12 zeros) - **`tokenHomeChain`** - the Wormhole chain ID corresponding to the home address above. This will typically be the source chain unless the original token sent is a Wormhole-wrapped asset, which will be the chain of the unwrapped version of the token - **`tokenAddress`** - the address of the IERC-20 token on the target chain that has been transferred to this contract. If `tokenHomeChain` equals the target chain, this will be the same as `tokenHomeAddress`; otherwise, it will be the Wormhole-wrapped version of the token sent - **`amount`** - the token amount sent to you with the same units as the original token. Since `TokenBridge` only sends with eight decimals of precision, if your token has 18 decimals, this will be the "amount" you sent, rounded down to the nearest multiple of 10^10 - **`amountNormalized`** - the amount of token divided by (1 if decimals ≤ 8, else 10^(decimals - 8)) You can find the complete code for the `CrossChainReceiver.sol` contract below: ??? code "CrossChainReceiver.sol" ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import "lib/wormhole-solidity-sdk/src/WormholeRelayerSDK.sol"; import "lib/wormhole-solidity-sdk/src/interfaces/IERC20.sol"; contract CrossChainReceiver is TokenReceiver { // The Wormhole relayer and registeredSenders are inherited from the Base.sol contract constructor( address _wormholeRelayer, address _tokenBridge, address _wormhole ) TokenBase(_wormholeRelayer, _tokenBridge, _wormhole) {} // Function to receive the cross-chain payload and tokens with emitter validation function receivePayloadAndTokens( bytes memory payload, TokenReceived[] memory receivedTokens, bytes32 sourceAddress, uint16 sourceChain, bytes32 // deliveryHash ) internal override onlyWormholeRelayer isRegisteredSender(sourceChain, sourceAddress) { require(receivedTokens.length == 1, "Expected 1 token transfer"); // Decode the recipient address from the payload address recipient = abi.decode(payload, (address)); // Transfer the received tokens to the intended recipient IERC20(receivedTokens[0].tokenAddress).transfer( recipient, receivedTokens[0].amount ); } } ``` ## Deploy the Contracts Now that you've written the `CrossChainSender` and `CrossChainReceiver` contracts, it's time to deploy them to your chosen networks. 1. **Set up deployment configuration** - before deploying, you must configure the networks and the deployment environment. This information is stored in a configuration file 1. Create a directory named deploy-config in the root of your project: ```bash mkdir deploy-config ``` 2. Create a `config.json` file in the `deploy-config` directory: ```bash touch deploy-config/config.json ``` 3. Open the `config.json` file and add the following configuration: ```json { "chains": [ { "description": "Avalanche testnet fuji", "chainId": 6, "rpc": "https://api.avax-test.network/ext/bc/C/rpc", "tokenBridge": "0x61E44E506Ca5659E6c0bba9b678586fA2d729756", "wormholeRelayer": "0xA3cF45939bD6260bcFe3D66bc73d60f19e49a8BB", "wormhole": "0x7bbcE28e64B3F8b84d876Ab298393c38ad7aac4C" }, { "description": "Celo Testnet", "chainId": 14, "rpc": "https://alfajores-forno.celo-testnet.org", "tokenBridge": "0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153", "wormholeRelayer": "0x306B68267Deb7c5DfCDa3619E22E9Ca39C374f84", "wormhole": "0x88505117CA88e7dd2eC6EA1E13f0948db2D50D56" } ] } ``` This file specifies the details for each chain where you plan to deploy your contracts, including the RPC URL, the `TokenBridge` address, the Wormhole relayer, and the Wormhole Core Contract. For a complete list of Wormhole contract addresses on various blockchains, refer to the [Wormhole Contract Addresses](/docs/build/reference/contract-addresses/){target=_blank}. !!! note You can add your desired chains to this file by specifying the required fields for each chain. In this example, we use the Avalanche Fuji and Celo Alfajores Testnets. 4. Create a `contracts.json` file in the `deploy-config` directory: ```bash echo '{}' > deploy-config/contracts.json ``` This file can be left blank initially. It will be automatically updated with the deployed contract addresses after a successful deployment 2. **Set up your Node.js environment** - you'll need to set up your Node.js environment to run the deployment script 1. Initialize a Node.js project: ```bash npm init -y ``` 2. Install the necessary dependencies: ```bash npm install ethers dotenv readline-sync @types/readline-sync ``` These dependencies are required for the deployment script to work properly. 3. **Compile your smart contracts** - compile your smart contracts using Foundry. This ensures that your contracts are up-to-date and ready for deployment - Run the following command to compile your contracts: ```bash forge build ``` This will generate the necessary ABI and bytecode files in a directory named `/out`. The expected output should be similar to this:
forge build > [⠒] Compiling... > [⠰] Compiling 30 files with 0.8.23 [⠔] Solc 0.8.23 finished in 2.29s Compiler run successful!
4. **Write the deployment script** - you’ll need a script to automate the deployment of your contracts. Let’s create the deployment script 1. Create a new file named `deploy.ts` in the `/script` directory: ```bash touch script/deploy.ts ``` 2. Open the file and load imports and configuration: ```typescript import * as fs from 'fs'; import * as path from 'path'; import * as dotenv from 'dotenv'; import readlineSync from 'readline-sync'; dotenv.config(); ``` Import the required libraries and modules to interact with Ethereum, handle file paths, load environment variables, and enable user interaction via the terminal. 3. Define interfaces to use for chain configuration and contract deployment: ```typescript description: string; chainId: number; rpc: string; tokenBridge: string; wormholeRelayer: string; wormhole: string; } interface DeployedContracts { [chainId: number]: { networkName: string; CrossChainSender?: string; CrossChainReceiver?: string; deployedAt: string; }; } ``` These interfaces define the structure of the chain configuration and the contract deployment details. 4. Load and select the chains for deployment: ```typescript const configPath = path.resolve(__dirname, '../deploy-config/config.json'); return JSON.parse(fs.readFileSync(configPath, 'utf8')).chains; } function selectChain( chains: ChainConfig[], role: 'source' | 'target' ): ChainConfig { console.log(`\nSelect the ${role.toUpperCase()} chain:`); chains.forEach((chain, index) => { console.log(`${index + 1}: ${chain.description}`); }); const chainIndex = readlineSync.questionInt( `\nEnter the number for the ${role.toUpperCase()} chain: ` ) - 1; return chains[chainIndex]; } ``` The `loadConfig` function reads the chain configuration from the `config.json` file, and the `selectChain` function allows the user to choose the source and target chains for deployment interactively. The user is prompted in the terminal to select which chains to use, making the process interactive and user-friendly. 5. Define the main function for deployment and load the chain configuration: ```typescript const chains = loadConfig(); const sourceChain = selectChain(chains, 'source'); const targetChain = selectChain(chains, 'target'); ``` - The `main` function is the entry point for the deployment script - We then call the `loadConfig` function we previously defined to load the chain configuration from the `config.json` file 6. Set up provider and wallet: ```typescript const targetProvider = new ethers.JsonRpcProvider(targetChain.rpc); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, sourceProvider); ``` The scripts establish a connection to the blockchain using a provider and create a wallet instance using a private key. This wallet is responsible for signing the deployment transaction on the source chain. 7. Read the compiled contracts: ```typescript fs.readFileSync( path.resolve( __dirname, '../out/CrossChainSender.sol/CrossChainSender.json' ), 'utf8' ) ); ``` - This code reads the `CrossChainSender.json` file, the compiled output of the `CrossChainSender.sol` contract - The file is in the `../out/` directory, which contains the ABI (Application Binary Interface) and bytecode generated during contract compilation - It uses the `fs.readFileSync` function to read the file and `JSON.parse` to convert the file contents (in JSON format) into a JavaScript object 8. Extract the contract ABI and bytecode: ```typescript const bytecode = senderJson.bytecode; ``` - **ABI (Application Binary Interface)** - defines the structure of the contract’s functions, events, and data types, allowing the front end to interact with the contract on the blockchain - **Bytecode** - this is the compiled machine code that will be deployed to the blockchain to create the contract 9. Create the Contract Factory: ```typescript abi, bytecode, wallet ); ``` - **`ethers.ContractFactory`** - creates a new contract factory using the ABI, bytecode, and a wallet (representing the signer). The contract factory is responsible for deploying instances of the contract to the blockchain - This is a crucial step for deploying the contract since the factory will create and deploy the `CrossChainSender` contract 10. Deploy the `CrossChainSender` and `CrossChainReceiver` contracts: === "`CrossChainSender`" ```typescript const senderContract = await CrossChainSenderFactory.deploy( sourceChain.wormholeRelayer, sourceChain.tokenBridge, sourceChain.wormhole ); await senderContract.waitForDeployment(); ``` === "`CrossChainReceiver`" ```typescript process.env.PRIVATE_KEY!, targetProvider ); const receiverJson = JSON.parse( fs.readFileSync( path.resolve( __dirname, '../out/CrossChainReceiver.sol/CrossChainReceiver.json' ), 'utf8' ) ); const CrossChainReceiverFactory = new ethers.ContractFactory( receiverJson.abi, receiverJson.bytecode, targetWallet ); const receiverContract = await CrossChainReceiverFactory.deploy( targetChain.wormholeRelayer, targetChain.tokenBridge, targetChain.wormhole ); await receiverContract.waitForDeployment(); ``` Both functions deploy the respective contracts to the selected chains. For the `CrossChainReceiver` contract: - It defines the wallet related to the target chain - The logic reads the compiled ABI and bytecode from the JSON file generated during compilation - It creates a new contract factory using the ABI, bytecode, and wallet - It deploys the contract to the selected chain passing in the Wormhole Relayer, `TokenBridge`, and Wormhole addresses 11. Save the deployed contract addresses: === "`senderAddress`" ```typescript console.log( `CrossChainSender on ${sourceChain.description}: ${senderAddress}` ); ``` === "`receiverAddress`" ```typescript console.log( `CrossChainReceiver on ${targetChain.description}: ${receiverAddress}` ); ``` You may display the deployed contract addresses in the terminal or save them to a JSON file for future reference. 12. Register the `CrossChainSender` address on the target chain: ```typescript receiverAddress, receiverJson.abi, targetWallet ); const tx = await CrossChainReceiverContract.setRegisteredSender( sourceChain.chainId, ethers.zeroPadValue(senderAddress as BytesLike, 32) ); await tx.wait(); ``` After you deploy the `CrossChainReceiver` contract on the target network, the sender contract address from the source chain needs to be registered. This ensures that only messages from the registered `CrossChainSender` contract are processed. This additional step is essential to enforce emitter validation, preventing unauthorized senders from delivering messages to the `CrossChainReceiver` contract. 13. Save the deployment details: ???- example "Save Deployment Details Example" ```typescript __dirname, '../deploy-config/contracts.json' ); let deployedContracts: DeployedContracts = {}; if (fs.existsSync(deployedContractsPath)) { deployedContracts = JSON.parse( fs.readFileSync(deployedContractsPath, 'utf8') ); } // Update the contracts.json file: // If a contract already exists on a chain, update its address; otherwise, add a new entry. if (!deployedContracts[sourceChain.chainId]) { deployedContracts[sourceChain.chainId] = { networkName: sourceChain.description, deployedAt: new Date().toISOString(), }; } deployedContracts[sourceChain.chainId].CrossChainSender = senderAddress.toString(); deployedContracts[sourceChain.chainId].deployedAt = new Date().toISOString(); if (!deployedContracts[targetChain.chainId]) { deployedContracts[targetChain.chainId] = { networkName: targetChain.description, deployedAt: new Date().toISOString(), }; } deployedContracts[targetChain.chainId].CrossChainReceiver = receiverAddress.toString(); deployedContracts[targetChain.chainId].deployedAt = new Date().toISOString(); // Save the updated contracts.json file fs.writeFileSync( deployedContractsPath, JSON.stringify(deployedContracts, null, 2) ); ``` Add your desired logic to save the deployed contract addresses in a JSON file (or another format). This will be important later when transferring tokens, as you'll need these addresses to interact with the deployed contracts. 14. Handle errors and finalize the script: ```typescript if (error.code === 'INSUFFICIENT_FUNDS') { console.error( 'Error: Insufficient funds for deployment. Please make sure your wallet has enough funds to cover the gas fees.' ); } else { console.error('An unexpected error occurred:', error.message); } process.exit(1); } } main().catch((error) => { console.error(error); process.exit(1); }); ``` The try-catch block wraps the deployment logic to catch any errors that may occur. - If the error is due to insufficient funds, it logs a clear message about needing more gas fees - For any other errors, it logs the specific error message to help with debugging The `process.exit(1)` ensures that the script exits with a failure status code if any error occurs. You can find the full code for the `deploy.ts` file below: ??? code "deploy.ts" ```solidity import { BytesLike, ethers } from 'ethers'; import * as fs from 'fs'; import * as path from 'path'; import * as dotenv from 'dotenv'; import readlineSync from 'readline-sync'; dotenv.config(); interface ChainConfig { description: string; chainId: number; rpc: string; tokenBridge: string; wormholeRelayer: string; wormhole: string; } interface DeployedContracts { [chainId: number]: { networkName: string; CrossChainSender?: string; CrossChainReceiver?: string; deployedAt: string; }; } function loadConfig(): ChainConfig[] { const configPath = path.resolve(__dirname, '../deploy-config/config.json'); return JSON.parse(fs.readFileSync(configPath, 'utf8')).chains; } function selectChain( chains: ChainConfig[], role: 'source' | 'target' ): ChainConfig { console.log(`\nSelect the ${role.toUpperCase()} chain:`); chains.forEach((chain, index) => { console.log(`${index + 1}: ${chain.description}`); }); const chainIndex = readlineSync.questionInt( `\nEnter the number for the ${role.toUpperCase()} chain: ` ) - 1; return chains[chainIndex]; } async function main() { const chains = loadConfig(); const sourceChain = selectChain(chains, 'source'); const targetChain = selectChain(chains, 'target'); const sourceProvider = new ethers.JsonRpcProvider(sourceChain.rpc); const targetProvider = new ethers.JsonRpcProvider(targetChain.rpc); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, sourceProvider); const senderJson = JSON.parse( fs.readFileSync( path.resolve( __dirname, '../out/CrossChainSender.sol/CrossChainSender.json' ), 'utf8' ) ); const abi = senderJson.abi; const bytecode = senderJson.bytecode; const CrossChainSenderFactory = new ethers.ContractFactory( abi, bytecode, wallet ); try { const senderContract = await CrossChainSenderFactory.deploy( sourceChain.wormholeRelayer, sourceChain.tokenBridge, sourceChain.wormhole ); await senderContract.waitForDeployment(); // Safely access the deployed contract's address const senderAddress = (senderContract as ethers.Contract).target; console.log( `CrossChainSender on ${sourceChain.description}: ${senderAddress}` ); const targetWallet = new ethers.Wallet( process.env.PRIVATE_KEY!, targetProvider ); const receiverJson = JSON.parse( fs.readFileSync( path.resolve( __dirname, '../out/CrossChainReceiver.sol/CrossChainReceiver.json' ), 'utf8' ) ); const CrossChainReceiverFactory = new ethers.ContractFactory( receiverJson.abi, receiverJson.bytecode, targetWallet ); const receiverContract = await CrossChainReceiverFactory.deploy( targetChain.wormholeRelayer, targetChain.tokenBridge, targetChain.wormhole ); await receiverContract.waitForDeployment(); // Safely access the deployed contract's address const receiverAddress = (receiverContract as ethers.Contract).target; console.log( `CrossChainReceiver on ${targetChain.description}: ${receiverAddress}` ); // Register the sender contract in the receiver contract console.log( `Registering CrossChainSender (${senderAddress}) as a valid sender in CrossChainReceiver (${receiverAddress})...` ); const CrossChainReceiverContract = new ethers.Contract( receiverAddress, receiverJson.abi, targetWallet ); const tx = await CrossChainReceiverContract.setRegisteredSender( sourceChain.chainId, ethers.zeroPadValue(senderAddress as BytesLike, 32) ); await tx.wait(); console.log( `CrossChainSender registered as a valid sender on ${targetChain.description}` ); // Load existing deployed contract addresses from contracts.json const deployedContractsPath = path.resolve( __dirname, '../deploy-config/contracts.json' ); let deployedContracts: DeployedContracts = {}; if (fs.existsSync(deployedContractsPath)) { deployedContracts = JSON.parse( fs.readFileSync(deployedContractsPath, 'utf8') ); } // Update the contracts.json file: // If a contract already exists on a chain, update its address; otherwise, add a new entry. if (!deployedContracts[sourceChain.chainId]) { deployedContracts[sourceChain.chainId] = { networkName: sourceChain.description, deployedAt: new Date().toISOString(), }; } deployedContracts[sourceChain.chainId].CrossChainSender = senderAddress.toString(); deployedContracts[sourceChain.chainId].deployedAt = new Date().toISOString(); if (!deployedContracts[targetChain.chainId]) { deployedContracts[targetChain.chainId] = { networkName: targetChain.description, deployedAt: new Date().toISOString(), }; } deployedContracts[targetChain.chainId].CrossChainReceiver = receiverAddress.toString(); deployedContracts[targetChain.chainId].deployedAt = new Date().toISOString(); // Save the updated contracts.json file fs.writeFileSync( deployedContractsPath, JSON.stringify(deployedContracts, null, 2) ); } catch (error: any) { if (error.code === 'INSUFFICIENT_FUNDS') { console.error( 'Error: Insufficient funds for deployment. Please make sure your wallet has enough funds to cover the gas fees.' ); } else { console.error('An unexpected error occurred:', error.message); } process.exit(1); } } main().catch((error) => { console.error(error); process.exit(1); }); ``` 5. **Add your private key** - you'll need to provide your private key. It allows your deployment script to sign the transactions that deploy the smart contracts to the blockchain. Without it, the script won't be able to interact with the blockchain on your behalf Create a `.env` file in the root of the project and add your private key: ```bash touch .env ``` Inside `.env`, add your private key in the following format: ```env PRIVATE_KEY=INSERT_PRIVATE_KEY ``` 6. **Run the deployment script** 1. Open a terminal and run the following command: ```bash npx ts-node script/deploy.ts ``` This will execute the deployment script, deploying both contracts to the selected chains. 2. Check the deployment output: - You will see the deployed contract addresses printed in the terminal if successful. The `contracts.json` file will be updated with these addresses - If you encounter an error, the script will provide feedback, such as insufficient funds for gas If you followed the logic provided in the full code above, your terminal output should look something like this:
npx ts-node deploy.ts > cross-chain-token-transfer@1.0.0 deploy > npx ts-node script/deploy.ts Select the SOURCE chain: 1: Avalanche testnet fuji 2: Celo Testnet Enter the number for the SOURCE chain: 1 Select the TARGET chain: 1: Avalanche testnet fuji 2: Celo Testnet Enter the number for the TARGET chain: 2 CrossChainSender Avalanche testnet fuji: 0x1Cac52a183D02F9002fdb37b13eC2fAB950d44E3 CrossChainReceiver Celo Testnet: 0xD720BFF42a0960cfF1118454A907a44dB358f2b1 Registering CrossChainSender (0x1Cac52a183D02F9002fdb37b13eC2fAB950d44E3) as a valid sender in CrossChainReceiver (0xD720BFF42a0960cfF1118454A907a44dB358f2b1)... CrossChainSender registered as a valid sender on Celo Testnet
## Transfer Tokens Across Chains ### Quick Recap Up to this point, you've set up a new Solidity project using Foundry, developed two key contracts (`CrossChainSender` and `CrossChainReceiver`), and created a deployment script to deploy these contracts to different blockchain networks. The deployment script also saves the new contract addresses for easy reference. With everything in place, it's time to transfer tokens using the deployed contracts. In this step, you'll write a script to transfer tokens across chains using the `CrossChainSender` and `CrossChainReceiver` contracts you deployed earlier. This script will interact with the contracts and facilitate the cross-chain token transfer. ### Transfer Script 1. **Set up the transfer script** 1. Create a new file named `transfer.ts` in the `/script` directory: ```bash touch script/transfer.ts ``` 2. Open the file. Start with the necessary imports, interfaces and configurations: ```typescript import * as fs from 'fs'; import * as path from 'path'; import * as dotenv from 'dotenv'; import readlineSync from 'readline-sync'; dotenv.config(); interface ChainConfig { description: string; chainId: number; rpc: string; tokenBridge: string; wormholeRelayer: string; wormhole: string; } interface DeployedContracts { [chainId: number]: { networkName: string; CrossChainSender?: string; CrossChainReceiver?: string; deployedAt: string; }; } ``` These imports include the essential libraries for interacting with Ethereum, handling file paths, loading environment variables, and managing user input. 3. Load configuration and contracts: ```typescript const configPath = path.resolve(__dirname, '../deploy-config/config.json'); return JSON.parse(fs.readFileSync(configPath, 'utf8')).chains; } function loadDeployedContracts(): DeployedContracts { const contractsPath = path.resolve( __dirname, '../deploy-config/contracts.json' ); if ( !fs.existsSync(contractsPath) || fs.readFileSync(contractsPath, 'utf8').trim() === '' ) { console.error( 'No contracts found. Please deploy contracts first before transferring tokens.' ); process.exit(1); } return JSON.parse(fs.readFileSync(contractsPath, 'utf8')); } ``` These functions load the network and contract details that were saved during deployment. 4. Allow users to select source and target chains: Refer to the deployed contracts and create logic as desired. In our example, we made this process interactive, allowing users to select the source and target chains from all the historically deployed contracts. This interactive approach helps ensure the correct chains are selected for the token transfer. ```typescript chainId: number; networkName: string; } { const sourceOptions = Object.entries(deployedContracts).filter( ([, contracts]) => contracts.CrossChainSender ); if (sourceOptions.length === 0) { console.error('No source chains available with CrossChainSender deployed.'); process.exit(1); } console.log('\nSelect the source chain:'); sourceOptions.forEach(([chainId, contracts], index) => { console.log(`${index + 1}: ${contracts.networkName}`); }); const selectedIndex = readlineSync.questionInt(`\nEnter the number for the source chain: `) - 1; return { chainId: Number(sourceOptions[selectedIndex][0]), networkName: sourceOptions[selectedIndex][1].networkName, }; } function selectTargetChain(deployedContracts: DeployedContracts): { chainId: number; networkName: string; } { const targetOptions = Object.entries(deployedContracts).filter( ([, contracts]) => contracts.CrossChainReceiver ); if (targetOptions.length === 0) { console.error( 'No target chains available with CrossChainReceiver deployed.' ); process.exit(1); } console.log('\nSelect the target chain:'); targetOptions.forEach(([chainId, contracts], index) => { console.log(`${index + 1}: ${contracts.networkName}`); }); const selectedIndex = readlineSync.questionInt(`\nEnter the number for the target chain: `) - 1; return { chainId: Number(targetOptions[selectedIndex][0]), networkName: targetOptions[selectedIndex][1].networkName, }; } ``` 2. **Implement the token transfer logic** 1. Start the `main` function: ```typescript const chains = loadConfig(); const deployedContracts = loadDeployedContracts(); // Select the source chain (only show chains with CrossChainSender deployed) const { chainId: sourceChainId, networkName: sourceNetworkName } = selectSourceChain(deployedContracts); const sourceChain = chains.find((chain) => chain.chainId === sourceChainId)!; // Select the target chain (only show chains with CrossChainReceiver deployed) const { chainId: targetChainId, networkName: targetNetworkName } = selectTargetChain(deployedContracts); const targetChain = chains.find((chain) => chain.chainId === targetChainId)!; // Set up providers and wallets const sourceProvider = new ethers.JsonRpcProvider(sourceChain.rpc); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, sourceProvider); // Load the ABI from the JSON file (use the compiled ABI from Forge or Hardhat) const CrossChainSenderArtifact = JSON.parse( fs.readFileSync( path.resolve( __dirname, '../out/CrossChainSender.sol/CrossChainSender.json' ), 'utf8' ) ); const abi = CrossChainSenderArtifact.abi; // Create the contract instance using the full ABI const CrossChainSender = new ethers.Contract( deployedContracts[sourceChainId].CrossChainSender!, abi, wallet ); ``` The `main` function is where the token transfer logic will reside. It loads the chain and contract details, sets up the wallet and provider, and loads the `CrossChainSender` contract. 2. Ask the user for token transfer details: You'll now ask the user for the token contract address, the recipient address on the target chain, and the amount of tokens to transfer. ```typescript 'Enter the token contract address: ' ); const recipientAddress = readlineSync.question( 'Enter the recipient address on the target chain: ' ); // Get the token contract const tokenContractDecimals = new ethers.Contract( tokenAddress, [ 'function decimals() view returns (uint8)', 'function approve(address spender, uint256 amount) public returns (bool)', ], wallet ); // Fetch the token decimals const decimals = await tokenContractDecimals.decimals(); // Get the amount from the user, then parse it according to the token's decimals const amount = ethers.parseUnits( readlineSync.question('Enter the amount of tokens to transfer: '), decimals ); ``` This section of the script prompts the user for the token contract address and the recipient's address, fetches the token's decimal value, and parses the amount accordingly. 3. Initiate the transfer: Finally, initiate the cross-chain transfer and log the details. ```typescript // Approve the CrossChainSender contract to transfer tokens on behalf of the user const tokenContract = new ethers.Contract( tokenAddress, ['function approve(address spender, uint256 amount) public returns (bool)'], wallet ); const approveTx = await tokenContract.approve( deployedContracts[sourceChainId].CrossChainSender!, amount ); await approveTx.wait(); console.log(`Approved tokens for cross-chain transfer.`); // Initiate the cross-chain transfer const transferTx = await CrossChainSender.sendCrossChainDeposit( targetChainId, deployedContracts[targetChainId].CrossChainReceiver!, recipientAddress, amount, tokenAddress, { value: cost } // Attach the necessary fee for cross-chain transfer ); await transferTx.wait(); console.log( `Transfer initiated from ${sourceNetworkName} to ${targetNetworkName}. Transaction Hash: ${transferTx.hash}` ); } ``` This part of the script first approves the token transfer, then initiates the cross-chain transfer using the `CrossChainSender` contract, and finally logs the transaction hash for the user to track. 4. Finalize the script: ```typescript console.error(error); process.exit(1); }); ``` This section finalizes the script by calling the `main` function and handling any errors that may occur during the token transfer process. You can find the full code for the `transfer.ts` file below: ??? code "transfer.ts" ```solidity import { ethers } from 'ethers'; import * as fs from 'fs'; import * as path from 'path'; import * as dotenv from 'dotenv'; import readlineSync from 'readline-sync'; dotenv.config(); interface ChainConfig { description: string; chainId: number; rpc: string; tokenBridge: string; wormholeRelayer: string; wormhole: string; } interface DeployedContracts { [chainId: number]: { networkName: string; CrossChainSender?: string; CrossChainReceiver?: string; deployedAt: string; }; } function loadConfig(): ChainConfig[] { const configPath = path.resolve(__dirname, '../deploy-config/config.json'); return JSON.parse(fs.readFileSync(configPath, 'utf8')).chains; } function loadDeployedContracts(): DeployedContracts { const contractsPath = path.resolve( __dirname, '../deploy-config/contracts.json' ); if ( !fs.existsSync(contractsPath) || fs.readFileSync(contractsPath, 'utf8').trim() === '' ) { console.error( 'No contracts found. Please deploy contracts first before transferring tokens.' ); process.exit(1); } return JSON.parse(fs.readFileSync(contractsPath, 'utf8')); } function selectSourceChain(deployedContracts: DeployedContracts): { chainId: number; networkName: string; } { const sourceOptions = Object.entries(deployedContracts).filter( ([, contracts]) => contracts.CrossChainSender ); if (sourceOptions.length === 0) { console.error('No source chains available with CrossChainSender deployed.'); process.exit(1); } console.log('\nSelect the source chain:'); sourceOptions.forEach(([chainId, contracts], index) => { console.log(`${index + 1}: ${contracts.networkName}`); }); const selectedIndex = readlineSync.questionInt(`\nEnter the number for the source chain: `) - 1; return { chainId: Number(sourceOptions[selectedIndex][0]), networkName: sourceOptions[selectedIndex][1].networkName, }; } function selectTargetChain(deployedContracts: DeployedContracts): { chainId: number; networkName: string; } { const targetOptions = Object.entries(deployedContracts).filter( ([, contracts]) => contracts.CrossChainReceiver ); if (targetOptions.length === 0) { console.error( 'No target chains available with CrossChainReceiver deployed.' ); process.exit(1); } console.log('\nSelect the target chain:'); targetOptions.forEach(([chainId, contracts], index) => { console.log(`${index + 1}: ${contracts.networkName}`); }); const selectedIndex = readlineSync.questionInt(`\nEnter the number for the target chain: `) - 1; return { chainId: Number(targetOptions[selectedIndex][0]), networkName: targetOptions[selectedIndex][1].networkName, }; } async function main() { const chains = loadConfig(); const deployedContracts = loadDeployedContracts(); // Select the source chain (only show chains with CrossChainSender deployed) const { chainId: sourceChainId, networkName: sourceNetworkName } = selectSourceChain(deployedContracts); const sourceChain = chains.find((chain) => chain.chainId === sourceChainId)!; // Select the target chain (only show chains with CrossChainReceiver deployed) const { chainId: targetChainId, networkName: targetNetworkName } = selectTargetChain(deployedContracts); const targetChain = chains.find((chain) => chain.chainId === targetChainId)!; // Set up providers and wallets const sourceProvider = new ethers.JsonRpcProvider(sourceChain.rpc); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, sourceProvider); // Load the ABI from the JSON file (use the compiled ABI from Forge or Hardhat) const CrossChainSenderArtifact = JSON.parse( fs.readFileSync( path.resolve( __dirname, '../out/CrossChainSender.sol/CrossChainSender.json' ), 'utf8' ) ); const abi = CrossChainSenderArtifact.abi; // Create the contract instance using the full ABI const CrossChainSender = new ethers.Contract( deployedContracts[sourceChainId].CrossChainSender!, abi, wallet ); // Display the selected chains console.log( `\nInitiating transfer from ${sourceNetworkName} to ${targetNetworkName}.` ); // Ask the user for token transfer details const tokenAddress = readlineSync.question( 'Enter the token contract address: ' ); const recipientAddress = readlineSync.question( 'Enter the recipient address on the target chain: ' ); // Get the token contract const tokenContractDecimals = new ethers.Contract( tokenAddress, [ 'function decimals() view returns (uint8)', 'function approve(address spender, uint256 amount) public returns (bool)', ], wallet ); // Fetch the token decimals const decimals = await tokenContractDecimals.decimals(); // Get the amount from the user, then parse it according to the token's decimals const amount = ethers.parseUnits( readlineSync.question('Enter the amount of tokens to transfer: '), decimals ); // Calculate the cross-chain transfer cost const cost = await CrossChainSender.quoteCrossChainDeposit(targetChainId); // Approve the CrossChainSender contract to transfer tokens on behalf of the user const tokenContract = new ethers.Contract( tokenAddress, ['function approve(address spender, uint256 amount) public returns (bool)'], wallet ); const approveTx = await tokenContract.approve( deployedContracts[sourceChainId].CrossChainSender!, amount ); await approveTx.wait(); console.log(`Approved tokens for cross-chain transfer.`); // Initiate the cross-chain transfer const transferTx = await CrossChainSender.sendCrossChainDeposit( targetChainId, deployedContracts[targetChainId].CrossChainReceiver!, recipientAddress, amount, tokenAddress, { value: cost } // Attach the necessary fee for cross-chain transfer ); await transferTx.wait(); console.log( `Transfer initiated from ${sourceNetworkName} to ${targetNetworkName}. Transaction Hash: ${transferTx.hash}` ); } main().catch((error) => { console.error(error); process.exit(1); }); ``` ### Transfer Tokens Now that your transfer script is ready, it’s time to execute it and perform a cross-chain token transfer. 1. **Run the transfer script** Open your terminal and run the transfer script: ```bash npx ts-node script/transfer.ts ``` This command will start the script, prompting you to select the source and target chains, input the token address, recipient address, and the amount of tokens to transfer. 2. **Follow the prompts** - the script will guide you through selecting the source and target chains and entering the necessary details for the token transfer. Once you provide all the required information, the script will initiate the token transfer 3. **Verify the transaction** - after running the script, you should see a confirmation message with the transaction hash. You can use this transaction hash to check the transfer status on the respective blockchain explorers You can verify the transaction on the [Wormhole Explorer](https://wormholescan.io/){target=\_balnk} using the link provided in the terminal output. This explorer also offers the option to add the transferred token to your MetaMask wallet automatically. If you followed the logic provided in the `transfer.ts` file above, your terminal output should look something like this:
npx ts-node transfer.ts > cross-chain-token-transfer@1.0.0 transfer > npx ts-node script/transfer.ts Select the source chain: 1: Avalanche testnet fuji 2: Celo Testnet Enter the number for the SOURCE chain: 1 Select the target chain: 1: Avalanche testnet fuji 2: Celo Testnet Enter the number for the TARGET chain: 2 Initiating transfer from Avalanche testnet fuji to Celo Testnet Enter the token contract address: 0x5425890298aed601595a70ab815c96711a31bc65 Enter the recipient address on the target chain: INSERT_YOUR_WALLET_ADDRESS Enter the amount of tokens to transfer: 2 Approved tokens for cross-chain transfer. Transfer initiated from Avalanche testnet fuji to Celo Testnet. Transaction Hash: 0x4a923975d955c1f226a1c2f61a1a0fa1ab1a9e229dc29ceaeadf8ef40acd071f
!!! note In this example, we demonstrated a token transfer from the Avalanche Fuji Testnet to the Celo Alfajores Testnet. We sent two units of USDC Testnet tokens using the token contract address `0x5425890298aed601595a70ab815c96711a31bc65`. You can replace these details with those relevant to your project or use the same for testing purposes. ## Resources If you'd like to explore the complete project or need a reference while following this tutorial, you can find the complete codebase in the [Cross-Chain Token Transfers GitHub repository](https://github.com/wormhole-foundation/demo-cross-chain-token-transfer){target=\_blank}. The repository includes all the scripts, contracts, and configurations needed to deploy and transfer tokens across chains using the Wormhole protocol. ## Conclusion Congratulations! You've successfully built and deployed a cross-chain token transfer system using Solidity and the Wormhole protocol. You've learned how to: - Set up a new Solidity project using Foundry - Develop smart contracts to send and receive tokens across chains - Write deployment scripts to manage and deploy contracts on different networks --- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/solidity-sdk/ --- BEGIN CONTENT --- --- title: Solidity SDK Tutorials description: Master cross-chain smart contracts with Wormhole's Solidity SDK. Learn messaging, token transfers, and secure, scalable dApp deployments across blockchains. --- # Solidity SDK Expand the reach of your decentralized applications by building smart contracts that can communicate across multiple blockchains. Through these tutorials, you’ll learn to create robust cross-chain contracts, enabling your dApps to tap into global liquidity, diverse functionalities, and a broader user base. ## Tutorials
- :octicons-repo-16:{ .lg .middle } **Create Cross-Chain Messaging Contracts** --- Learn how to build and deploy smart contracts that communicate seamlessly across multiple blockchains. This tutorial walks you through using Wormhole's Solidity SDK to send and receive messages between Avalanche Fuji and Celo Alfajores, helping you master emitter validation, relayer usage, and cross-chain cost management. [:custom-arrow: Start building](/docs/tutorials/solidity-sdk/cross-chain-contracts/) - :octicons-repo-16:{ .lg .middle } **Create Cross-Chain Token Transfer Contracts** --- Discover how to create a fully functional cross-chain token transfer system using Wormhole's Solidity SDK. This tutorial guides you through token attestation, contract deployment, and secure transfers of ERC-20 tokens between test networks. By the end, you’ll know how to tap into global liquidity and enrich your dApps with seamless multi-chain asset movement. [:custom-arrow: Start building](/docs/tutorials/solidity-sdk/cross-chain-token-contracts/)
## Additional Resources
- :octicons-book-16:{ .lg .middle } **Core Contracts** --- Dive deeper into Wormhole’s foundational concepts for cross-chain contracts. Discover how messaging, guardians, and VAAs work together to enable secure, scalable dApps. [:custom-arrow: Explore Core Contracts](/docs/learn/infrastructure/core-contracts/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/typescript-sdk/ --- BEGIN CONTENT --- --- title: Wormhole SDK Tutorials description: Master the Wormhole SDK. Build robust cross-chain dApps with messaging, token bridging, and governance across multiple networks. --- # Wormhole SDK The Wormhole SDK provides the essential building blocks for creating robust cross-chain applications. With its developer-friendly libraries, tools, and interfaces, you can quickly integrate Wormhole’s messaging, token transfer, and governance functionalities into your projects. Whether you’re building a simple cross-chain dApp or architecting a complex multi-chain ecosystem, these tutorials will guide you through mastering the SDK and unlocking the full potential of Wormhole’s infrastructure. ## Tutorials
- :octicons-repo-16:{ .lg .middle } **Transfer USDC via CCTP** --- Learn how to move native USDC across chains using Circle’s CCTP and Wormhole’s TypeScript SDK. This tutorial shows you how to streamline transfers with automated attestation and finalization or take complete control with manual steps. Gain the skills to handle seamless USDC bridging, optimize user experience, and improve the reliability of your cross-chain applications. [:custom-arrow: Start building](/docs/tutorials/typescript-sdk/usdc-via-cctp/) - :octicons-repo-16:{ .lg .middle } **Transfer Tokens via the Token Bridge** --- Learn how to build a versatile cross-chain token transfer application using Wormhole’s TypeScript SDK. This tutorial walks you through leveraging the Token Bridge to securely and efficiently move assets between EVM and non-EVM chains. By the end, you’ll understand how to transfer tokens between networks like Ethereum, Solana, Sui, and beyond, opening the door to an interconnected blockchain ecosystem. [:custom-arrow: Start building](/docs/tutorials/typescript-sdk/tokens-via-token-bridge/)
## Additional Resources
- :octicons-tools-16:{ .lg .middle } **Wormhole SDK Documentation** --- Learn to build cross-chain dApps using the Wormhole SDK. Access detailed guides, reference code, and best practices for robust application development. [:custom-arrow: Learn more](/docs/build/toolkit/typescript-sdk/)
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/typescript-sdk/tokens-via-token-bridge/ --- BEGIN CONTENT --- --- title: Transfer Tokens via Token Bridge Tutorial description: Learn to build a cross-chain native token transfer app using Wormhole’s TypeScript SDK, supporting native token transfers across EVM and non-EVM chains --- # Transfer Tokens via the Token Bridge :simple-github: [Source code on GitHub](https://github.com/wormhole-foundation/demo-basic-ts-sdk/){target=\_blank} ## Introduction This tutorial guides you through building a cross-chain token transfer application using the [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank} and its [Token Bridge](/docs/learn/transfers/token-bridge/){target=\_blank} method. The Token Bridge method enables secure and efficient cross-chain asset transfers across different blockchain networks, allowing users to move tokens seamlessly. By leveraging Wormhole’s Token Bridge, this guide shows you how to build an application that supports multiple transfer types: - EVM to EVM (e.g., Ethereum to Avalanche) - EVM to non-EVM chains (e.g., Ethereum to Solana) - Non-EVM to EVM chains (e.g., Sui to Avalanche) - Non-EVM to non-EVM chains (e.g., Solana to Sui) Existing solutions for cross-chain transfers can be complex and inefficient, requiring multiple steps and transaction fees. However, the Token Bridge method from Wormhole simplifies the process by handling the underlying attestation, transaction validation, and message passing across blockchains. At the end of this guide, you’ll have a fully functional setup for transferring assets across chains using Wormhole’s Token Bridge method. ## 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 - Native tokens (testnet or mainnet) in Solana and Sui wallets - A wallet with a private key, funded with native tokens (testnet or mainnet) for gas fees ## Supported Chains The Wormhole SDK supports a wide range of EVM and non-EVM chains, allowing you to facilitate cross-chain transfers efficiently. You can find a complete list of supported chains in the Wormhole SDK [GitHub repository](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/5810ebbd3635aaf1b5ab675da3f99f62aec2210f/core/base/src/constants/circle.ts#L14-L30){target=\_blank}, which covers both testnet and mainnet environments. ## Project Setup In this section, we’ll guide you through initializing the project, installing dependencies, and preparing your environment for cross-chain transfers. 1. **Initialize the project** - start by creating a new directory for your project and initializing it with `npm`, which will create the `package.json` file for your project ```bash mkdir native-transfers cd native-transfers npm init -y ``` 2. **Install dependencies** - install the required dependencies, including the Wormhole SDK and helper libraries ```bash npm install @wormhole-foundation/sdk dotenv tsx ``` 3. **Set up environment variables** - to securely store your private key, create a `.env` file in the root of your project ```bash touch .env ``` Inside the `.env` file, add your private keys. ```env ETH_PRIVATE_KEY="INSERT_YOUR_PRIVATE_KEY" SOL_PRIVATE_KEY="INSERT_YOUR_PRIVATE_KEY" SUI_PRIVATE_KEY="INSERT_SUI_MNEMONIC" ``` !!! note Ensure your private key contains native tokens for gas on both the source and destination chains. For Sui, you must provide a mnemonic instead of a private key. 4. **Create a `helpers.ts` file** - to simplify the interaction between chains, create a file to store utility functions for fetching your private key, set up signers for different chains, and manage transaction relays 1. Create the helpers file ```bash mkdir -p src/helpers touch src/helpers/helpers.ts ``` 2. Open the `helpers.ts` file and add the following code ```typescript import { ChainAddress, ChainContext, Network, Signer, Wormhole, Chain, TokenId, isTokenId, } 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'; import aptos from '@wormhole-foundation/sdk/aptos'; import { config } from 'dotenv'; config(); export interface SignerStuff { chain: ChainContext; signer: Signer; address: ChainAddress; } // Function to fetch environment variables (like your private key) function getEnv(key: string): string { const val = process.env[key]; if (!val) throw new Error(`Missing environment variable: ${key}`); return val; } // Signer setup function for different blockchain platforms export async function getSigner( chain: ChainContext, gasLimit?: bigint ): Promise<{ chain: ChainContext; signer: Signer; address: ChainAddress; }> { let signer: Signer; const platform = chain.platform.utils()._platform; switch (platform) { case 'Solana': signer = await ( await solana() ).getSigner(await chain.getRpc(), getEnv('SOL_PRIVATE_KEY')); break; case 'Evm': const evmSignerOptions = gasLimit ? { gasLimit } : {}; signer = await ( await evm() ).getSigner( await chain.getRpc(), getEnv('ETH_PRIVATE_KEY'), evmSignerOptions ); break; case 'Sui': signer = await ( await sui() ).getSigner(await chain.getRpc(), getEnv('SUI_MNEMONIC')); break; case 'Aptos': signer = await ( await aptos() ).getSigner(await chain.getRpc(), getEnv('APTOS_PRIVATE_KEY')); break; default: throw new Error('Unsupported platform: ' + platform); } return { chain, signer: signer as Signer, address: Wormhole.chainAddress(chain.chain, signer.address()), }; } export async function getTokenDecimals< N extends 'Mainnet' | 'Testnet' | 'Devnet' >( wh: Wormhole, token: TokenId, sendChain: ChainContext ): Promise { return isTokenId(token) ? Number(await wh.getDecimals(token.chain, token.address)) : sendChain.config.nativeTokenDecimals; } ``` - **`getEnv`** - this function fetches environment variables like your private key from the `.env` file - **`getSigner`** - based on the chain you're working with (EVM, Solana, Sui, etc.), this function retrieves a signer for that specific platform. The signer is responsible for signing transactions and interacting with the blockchain. It securely uses the private key stored in your `.env` file - **`getTokenDecimals`** - this function fetches the number of decimals for a token on a specific chain. It helps handle token amounts accurately during transfers ## Check and Create Wrapped Tokens Before tokens are transferred across chains, it should be checked whether a wrapped version exists on the destination chain. If not, an attestation must be generated to wrap it so it can be sent and received on that chain. In this section, you'll create a script that automates this process by checking whether Arbitrum Sepolia has a wrapped version on Base Sepolia and registering it if needed. ### Configure the Wrapped Token Script 1. **Create the `create-wrapped.ts` file** - set up the script file that will handle checking and wrapping tokens in the `src` directory ```bash mkdir -p src/scripts touch src/scripts/create-wrapped.ts ``` 2. **Open `create-wrapped.ts` and import the required modules** - import the necessary SDK modules to interact with Wormhole, EVM, Solana, and Sui chains, as well as helper functions for signing and sending transactions ```typescript import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import sui from '@wormhole-foundation/sdk/sui'; import { inspect } from 'util'; import { getSigner } from '../helpers/helpers'; ``` 3. **Initialize the Wormhole SDK** - initialize the `wormhole` function for the `Testnet` environment and specify the platforms (EVM, Solana, and Sui) to support ```typescript const wh = await wormhole('Testnet', [evm, solana, sui]); ``` !!! note You can replace `'Testnet'` with `'Mainnet'` if you want to perform transfers on Mainnet. 4. **Configure transfer parameters** - specify Arbitrum Sepolia as the source chain and Base Sepolia as the destination, retrieve the token ID from the source chain for transfer, and set the gas limit (optional) ```typescript const destChain = wh.getChain('BaseSepolia'); const token = await srcChain.getNativeWrappedTokenId(); const gasLimit = BigInt(2_500_000); ``` 5. **Set up the destination chain signer** - the signer authorizes transactions, such as submitting the attestation ```typescript ``` 6. **Check if the token is wrapped on the destination chain** - verify if the token already exists as a wrapped asset before creating an attestation ```typescript try { const wrapped = await tbDest.getWrappedAsset(token); console.log( `Token already wrapped on ${destChain.chain}. Skipping attestation.` ); return { chain: destChain.chain, address: wrapped }; } catch (e) { console.log( `No wrapped token found on ${destChain.chain}. Proceeding with attestation.` ); } ``` If the token is already wrapped, the script exits, and you may proceed to the [next section](/docs/tutorials/typescript-sdk/tokens-via-token-bridge/#token-transfers). Otherwise, an attestation must be generated. 7. **Set up the source chain signer** - the signer creates and submits the attestation transaction ```typescript ``` 8. **Create an attestation transaction** - generate and send an attestation for the token on the source chain to register it on the destination chain, then save the transaction ID to verify the attestation in the next step ```typescript const attestTxns = tbOrig.createAttestation( token.address, Wormhole.parseAddress(origSigner.chain(), origSigner.address()) ); const txids = await signSendWait(srcChain, attestTxns, origSigner); console.log('txids: ', inspect(txids, { depth: null })); const txid = txids[0]!.txid; console.log('Created attestation (save this): ', txid); ``` 9. **Retrieve the signed VAA** - once the attestation transaction is confirmed, use `parseTransaction(txid)` to extract Wormhole messages, then retrieve the signed VAA from the messages. The timeout defines how long to wait for the VAA before failure ```typescript console.log('Parsed Messages:', msgs); const timeout = 25 * 60 * 1000; const vaa = await wh.getVaa(msgs[0]!, 'TokenBridge:AttestMeta', timeout); if (!vaa) { throw new Error( 'VAA not found after retries exhausted. Try extending the timeout.' ); } ``` 10. **Submit the attestation on the destination chain** - submit the signed VAA using `submitAttestation(vaa, recipient)` to create the wrapped token on the destination chain, then send the transaction and await confirmation ```typescript vaa, Wormhole.parseAddress(destSigner.chain(), destSigner.address()) ); const tsx = await signSendWait(destChain, subAttestation, destSigner); ``` 11. **Wait for the wrapped asset to be available** - poll until the wrapped token is available on the destination chain ```typescript do { try { const wrapped = await tbDest.getWrappedAsset(token); return { chain: destChain.chain, address: wrapped }; } catch (e) { console.error('Wrapped asset not found yet. Retrying...'); } console.log('Waiting before checking again...'); await new Promise((r) => setTimeout(r, 2000)); } while (true); } console.log('Wrapped Asset: ', await waitForIt()); })().catch((e) => console.error(e)); ``` If the token is not found, it logs a message and retries after a short delay. Once the wrapped asset is detected, its address is returned. ??? code "Complete script" ```typescript import { Wormhole, signSendWait, wormhole } 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'; import { inspect } from 'util'; import { getSigner } from '../helpers/helpers'; (async function () { const wh = await wormhole('Testnet', [evm, solana, sui]); // Define the source and destination chains const srcChain = wh.getChain('ArbitrumSepolia'); const destChain = wh.getChain('BaseSepolia'); const token = await srcChain.getNativeWrappedTokenId(); const gasLimit = BigInt(2_500_000); // Destination chain signer setup const { signer: destSigner } = await getSigner(destChain, gasLimit); const tbDest = await destChain.getTokenBridge(); try { const wrapped = await tbDest.getWrappedAsset(token); console.log( `Token already wrapped on ${destChain.chain}. Skipping attestation.` ); return { chain: destChain.chain, address: wrapped }; } catch (e) { console.log( `No wrapped token found on ${destChain.chain}. Proceeding with attestation.` ); } // Source chain signer setup const { signer: origSigner } = await getSigner(srcChain); // Create an attestation transaction on the source chain const tbOrig = await srcChain.getTokenBridge(); const attestTxns = tbOrig.createAttestation( token.address, Wormhole.parseAddress(origSigner.chain(), origSigner.address()) ); const txids = await signSendWait(srcChain, attestTxns, origSigner); console.log('txids: ', inspect(txids, { depth: null })); const txid = txids[0]!.txid; console.log('Created attestation (save this): ', txid); // Retrieve the Wormhole message ID from the attestation transaction const msgs = await srcChain.parseTransaction(txid); console.log('Parsed Messages:', msgs); const timeout = 25 * 60 * 1000; const vaa = await wh.getVaa(msgs[0]!, 'TokenBridge:AttestMeta', timeout); if (!vaa) { throw new Error( 'VAA not found after retries exhausted. Try extending the timeout.' ); } console.log('Token Address: ', vaa.payload.token.address); // Submit the attestation on the destination chain console.log('Attesting asset on destination chain...'); const subAttestation = tbDest.submitAttestation( vaa, Wormhole.parseAddress(destSigner.chain(), destSigner.address()) ); const tsx = await signSendWait(destChain, subAttestation, destSigner); console.log('Transaction hash: ', tsx); // Poll for the wrapped asset until it's available async function waitForIt() { do { try { const wrapped = await tbDest.getWrappedAsset(token); return { chain: destChain.chain, address: wrapped }; } catch (e) { console.error('Wrapped asset not found yet. Retrying...'); } console.log('Waiting before checking again...'); await new Promise((r) => setTimeout(r, 2000)); } while (true); } console.log('Wrapped Asset: ', await waitForIt()); })().catch((e) => console.error(e)); ``` ### Run the Wrapped Token Creation Once the script is ready, execute it with: ```bash npx tsx src/scripts/create-wrapped.ts ``` If the token is already wrapped, the script exits. Otherwise, it generates an attestation and submits it. Once complete, you’re ready to transfer tokens across chains. ## Token Transfers In this section, you'll create a script to transfer native tokens across chains using Wormhole's Token Bridge method. The script will handle the transfer of Sui native tokens to Solana, demonstrating the seamless cross-chain transfer capabilities of the Wormhole SDK. Since both chains are non-EVM compatible, you'll need to manually handle the attestation and finalization steps. ### Configure Transfer Details Before initiating a cross-chain transfer, you must set up the chain context and signers for both the source and destination chains. 1. Create the `native-transfer.ts` file in the `src` directory to hold your script for transferring native tokens across chains ```bash touch src/scripts/native-transfer.ts ``` 2. Open the `native-transfer.ts` file and begin by importing the necessary modules from the SDK and helper files ```typescript Chain, Network, Wormhole, amount, wormhole, TokenId, TokenTransfer, } 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'; import { SignerStuff, getSigner, getTokenDecimals } from '../helpers/helpers'; ``` 3. **Initialize the Wormhole SDK** - initialize the `wormhole` function for the `Testnet` environment and specify the platforms (EVM, Solana, and Sui) to support ```typescript const wh = await wormhole('Testnet', [evm, solana, sui]); ``` 4. **Set up source and destination chains** - specify the source chain (Sui) and the destination chain (Solana) using the `getChain` method. This allows us to define where to send the native tokens and where to receive them ```typescript const rcvChain = wh.getChain('Solana'); ``` 5. **Configure the signers** - use the `getSigner` function to retrieve the signers responsible for signing transactions on the respective chains. This ensures that transactions are correctly authorized on both the source and destination chains ```typescript const destination = await getSigner(rcvChain); ``` 6. **Define the token to transfer** - specify the native token on the source chain (Sui in this example) by creating a `TokenId` object ```typescript ``` 7. **Define the transfer amount** - the amount of native tokens to transfer is specified. In this case, we're transferring 1 unit ```typescript ``` 8. **Set transfer mode** - specify that the transfer should be manual by setting `automatic = false`. This means you will need to handle the attestation and finalization steps yourself ```typescript ``` !!! note Automatic transfers are only supported for EVM chains. For non-EVM chains like Solana and Sui, you must manually handle the attestation and finalization steps. 9. **Define decimals** - fetch the number of decimals for the token on the source chain (Sui) using the `getTokenDecimals` function ```typescript ``` 10. **Perform the token transfer and exit the process** - initiate the transfer by calling the `tokenTransfer` function, which we’ll define in the next step. This function takes an object containing all required details for executing the transfer, including the `source` and `destination` chains, `token`, `mode`, and transfer `amount` ```typescript token, amount: amount.units(amount.parse(amt, decimals)), source, destination, automatic, }); ``` Finally, we use `process.exit(0);` to close the script once the transfer completes ```typescript })(); ``` ### Token Transfer Logic This section defines the `tokenTransfer` function, which manages the core steps for cross-chain transfer execution. This function will handle initiating the transfer on the source chain, retrieving the attestation, and completing the transfer on the destination chain. #### Defining the Token Transfer Function The `tokenTransfer` function initiates and manages the transfer process, handling all necessary steps to move tokens across chains with the Wormhole SDK. This function uses types from the SDK and our `helpers.ts` file to ensure chain compatibility. ```typescript wh: Wormhole, route: { token: TokenId; amount: bigint; source: SignerStuff; destination: SignerStuff; automatic: boolean; payload?: Uint8Array; } ) { // Token Transfer Logic import { Chain, Network, Wormhole, amount, wormhole, TokenId, TokenTransfer, } 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'; import { SignerStuff, getSigner, getTokenDecimals } from '../helpers/helpers'; (async function () { const wh = await wormhole('Testnet', [evm, solana, sui]); // Set up source and destination chains const sendChain = wh.getChain('Sui'); const rcvChain = wh.getChain('Solana'); // Get signer from local key but anything that implements const source = await getSigner(sendChain); const destination = await getSigner(rcvChain); // Shortcut to allow transferring native gas token const token = Wormhole.tokenId(sendChain.chain, 'native'); // Define the amount of tokens to transfer const amt = '1'; // Set automatic transfer to false for manual transfer const automatic = false; // Used to normalize the amount to account for the tokens decimals const decimals = await getTokenDecimals(wh, token, sendChain); // Perform the token transfer if no recovery transaction ID is provided const xfer = await tokenTransfer(wh, { token, amount: amount.units(amount.parse(amt, decimals)), source, destination, automatic, }); process.exit(0); })(); async function tokenTransfer( wh: Wormhole, route: { token: TokenId; amount: bigint; source: SignerStuff; destination: SignerStuff; automatic: boolean; payload?: Uint8Array; } ) { // Token Transfer Logic // Create a TokenTransfer object to track the state of the transfer over time const xfer = await wh.tokenTransfer( route.token, route.amount, route.source.address, route.destination.address, route.automatic ?? false, route.payload ); const quote = await TokenTransfer.quoteTransfer( wh, route.source.chain, route.destination.chain, xfer.transfer ); if (xfer.transfer.automatic && quote.destinationToken.amount < 0) throw 'The amount requested is too low to cover the fee and any native gas requested.'; // Submit the transactions to the source chain, passing a signer to sign any txns console.log('Starting transfer'); const srcTxids = await xfer.initiateTransfer(route.source.signer); console.log(`Source Trasaction ID: ${srcTxids[0]}`); console.log(`Wormhole Trasaction ID: ${srcTxids[1] ?? srcTxids[0]}`); // Wait for the VAA to be signed and ready (not required for auto transfer) console.log('Getting Attestation'); await xfer.fetchAttestation(60_000); // Redeem the VAA on the dest chain console.log('Completing Transfer'); const destTxids = await xfer.completeTransfer(route.destination.signer); console.log(`Completed Transfer: `, destTxids); } ``` #### Steps to Transfer Tokens The `tokenTransfer` function consists of several key steps to facilitate the cross-chain transfer. Let’s break down each step: 1. **Initialize the transfer object** - the `tokenTransfer` function begins by creating a `TokenTransfer` object, `xfer`, which tracks the state of the transfer process and provides access to relevant methods for each transfer step ```typescript route.token, route.amount, route.source.address, route.destination.address, route.automatic ?? false, route.payload ); ``` 2. **Estimate transfer fees and validate amount** - we obtain a fee quote for the transfer before proceeding. This step is significant in automatic mode (`automatic = true`), where the quote will include additional fees for relaying ```typescript wh, route.source.chain, route.destination.chain, xfer.transfer ); if (xfer.transfer.automatic && quote.destinationToken.amount < 0) throw 'The amount requested is too low to cover the fee and any native gas requested.'; ``` 3. **Submit the transaction to the source chain** - initiate the transfer on the source chain by submitting the transaction using `route.source.signer`, starting the token transfer process ```typescript console.log(`Source Trasaction ID: ${srcTxids[0]}`); ``` - **`srcTxids`** - the resulting transaction IDs are printed to the console. These IDs can be used to track the transfer’s progress on the source chain and [Wormhole network](https://wormholescan.io/#/?network=Testnet){target=\_blank} ???- note "How Cross-Chain Transfers Work in the Background" When `xfer.initiateTransfer(route.source.signer)` is called, it initiates the transfer on the source chain. Here’s what happens in the background: - **Token lock or burn** - tokens are either locked in a smart contract or burned on the source chain, representing the transfer amount - **VAA creation** - Wormhole’s network of Guardians generates a Verifiable Action Approval (VAA)—a signed proof of the transaction, which ensures it’s recognized across chains - **Tracking the transfer** - the returned transaction IDs allow you to track the transfer's progress both on the source chain and within Wormhole’s network - **Redemption on destination** - once detected, the VAA is used to release or mint the corresponding token amount on the destination chain, completing the transfer This process ensures a secure and verifiable transfer across chains, from locking tokens on the source chain to redeeming them on the destination chain. 4. **Wait for the attestation** - retrieve the Wormhole attestation (VAA), which serves as cryptographic proof of the transfer. In manual mode, you must wait for the VAA before redeeming the transfer on the destination chain ```typescript ``` 5. **Complete the transfer on the destination chain** - redeem the VAA on the destination chain to finalize the transfer ```typescript console.log(`Completed Transfer: `, destTxids); ``` ??? code "Complete script" ```typescript import { Chain, Network, Wormhole, amount, wormhole, TokenId, TokenTransfer, } 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'; import { SignerStuff, getSigner, getTokenDecimals } from '../helpers/helpers'; (async function () { const wh = await wormhole('Testnet', [evm, solana, sui]); // Set up source and destination chains const sendChain = wh.getChain('Sui'); const rcvChain = wh.getChain('Solana'); // Get signer from local key but anything that implements const source = await getSigner(sendChain); const destination = await getSigner(rcvChain); // Shortcut to allow transferring native gas token const token = Wormhole.tokenId(sendChain.chain, 'native'); // Define the amount of tokens to transfer const amt = '1'; // Set automatic transfer to false for manual transfer const automatic = false; // Used to normalize the amount to account for the tokens decimals const decimals = await getTokenDecimals(wh, token, sendChain); // Perform the token transfer if no recovery transaction ID is provided const xfer = await tokenTransfer(wh, { token, amount: amount.units(amount.parse(amt, decimals)), source, destination, automatic, }); process.exit(0); })(); async function tokenTransfer( wh: Wormhole, route: { token: TokenId; amount: bigint; source: SignerStuff; destination: SignerStuff; automatic: boolean; payload?: Uint8Array; } ) { // Token Transfer Logic // Create a TokenTransfer object to track the state of the transfer over time const xfer = await wh.tokenTransfer( route.token, route.amount, route.source.address, route.destination.address, route.automatic ?? false, route.payload ); const quote = await TokenTransfer.quoteTransfer( wh, route.source.chain, route.destination.chain, xfer.transfer ); if (xfer.transfer.automatic && quote.destinationToken.amount < 0) throw 'The amount requested is too low to cover the fee and any native gas requested.'; // Submit the transactions to the source chain, passing a signer to sign any txns console.log('Starting transfer'); const srcTxids = await xfer.initiateTransfer(route.source.signer); console.log(`Source Trasaction ID: ${srcTxids[0]}`); console.log(`Wormhole Trasaction ID: ${srcTxids[1] ?? srcTxids[0]}`); // Wait for the VAA to be signed and ready (not required for auto transfer) console.log('Getting Attestation'); await xfer.fetchAttestation(60_000); // Redeem the VAA on the dest chain console.log('Completing Transfer'); const destTxids = await xfer.completeTransfer(route.destination.signer); console.log(`Completed Transfer: `, destTxids); } ``` ### Run the Native Token Transfer Now that you’ve set up the project and defined the transfer logic, you can execute the script to transfer native tokens from the Sui chain to Solana. You can use `tsx` to run the TypeScript file directly: ```bash npx tsx src/scripts/native-transfer.ts ``` This initiates the native token transfer from the source chain (Sui) and completes it on the destination chain (Solana). You can monitor the status of the transaction on the [Wormhole explorer](https://wormholescan.io/#/?network=Testnet){target=\_blank}. ## Resources If you'd like to explore the complete project or need a reference while following this tutorial, you can find the complete codebase in [Wormhole's demo GitHub repository](https://github.com/wormhole-foundation/demo-basic-ts-sdk/){target=\_blank}. The repository includes all the example scripts and configurations needed to perform native token cross-chain transfers, including manual, automatic, and partial transfers using the Wormhole SDK. ## Conclusion You've successfully built a cross-chain token transfer application using Wormhole's TypeScript SDK and the Token Bridge method. This guide took you through the setup, configuration, and transfer logic required to move native tokens across non-EVM chains like Sui and Solana. The same transfer logic will apply if you’d like to extend this application to different chain combinations, including EVM-compatible chains. --- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/typescript-sdk/usdc-via-cctp/ --- BEGIN CONTENT --- --- title: Transfer USDC via CCTP and Wormhole SDK description: Learn how to perform USDC cross-chain transfers using Wormhole SDK and Circle's CCTP. Supports manual, automatic, and partial transfer recovery. --- # Transfer USDC via CCTP and Wormhole SDK :simple-github: [Source code on GitHub](https://github.com/wormhole-foundation/demo-cctp-transfer){target=\_blank} ## Introduction In this guide, we will show you how to bridge native USDC across different blockchain networks using [Circle's Cross-Chain Transfer Protocol](/docs/learn/transfers/cctp/){target=\_blank} (CCTP) and [Wormhole’s TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main){target=\_blank}. Traditionally, cross-chain transfers using CCTP involve multiple manual steps, such as initiating the transfer on the source chain, relaying messages between chains, and covering gas fees on both the source and destination chains. Without the TypeScript SDK, developers must handle these operations independently, adding complexity and increasing the chance for errors, mainly when dealing with gas payments on the destination chain and native gas token management. Wormhole’s TypeScript SDK simplifies this process by offering automated transfer relaying and handling gas payments on the destination chain. It also offers an option to include native gas tokens for seamless execution. This reduces developer overhead, makes transfers faster and more reliable, and enhances the user experience. In this guide, we’ll first explore the theory behind CCTP and then provide a step-by-step tutorial for integrating Wormhole’s TypeScript SDK into your application to streamline USDC transfers across multiple chains. ## Core Concepts When bridging assets across chains, there are two primary approaches to handling the transfer process: manual and automated. Below, you may find the differences between these approaches and how they impact the user experience: - **Manual transfers** - manual transfers involve three key steps: initiating the transfer on the source chain, fetching the Circle attestation to verify the transfer, and completing the transfer on the destination chain - **Automated transfers** - automatic transfers simplify the process by handling Circle attestations and finalization for you. With Wormhole's automated relaying, you only need to initiate the transfer, and the rest is managed for you ## 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 - [USDC tokens](https://faucet.circle.com/){target=\_blank} on supported chains. This tutorial uses Avalanche and Sepolia as examples - A wallet with a private key, funded with native tokens (Testnet or Mainnet) for gas fees ## Supported Chains The Wormhole SDK supports a wide range of EVM and non-EVM chains, allowing you to facilitate cross-chain transfers efficiently. You can find a complete list of supported chains in the Wormhole SDK [GitHub repository](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/5810ebbd3635aaf1b5ab675da3f99f62aec2210f/core/base/src/constants/circle.ts#L14-L30){target=_blank}, which covers both Testnet and Mainnet environments. ## Project Setup In this section, you'll set up your project for transferring USDC across chains using Wormhole's SDK and Circle's CCTP. We’ll guide you through initializing the project, installing dependencies, and preparing your environment for cross-chain transfers. 1. **Initialize the project** - start by creating a new directory for your project and initializing it with `npm`, which will create the `package.json` file for your project ```bash mkdir cctp-circle cd cctp-circle npm init -y ``` 2. **Install dependencies** - install the required dependencies, including the Wormhole SDK and helper libraries ```bash npm install @wormhole-foundation/sdk dotenv ``` 3. **Set up environment variables** - to securely store your private key, create a `.env` file in the root of your project ```bash touch .env ``` Inside the `.env` file, add your private key ```env ETH_PRIVATE_KEY="INSERT_YOUR_PRIVATE_KEY" SOL_PRIVATE_KEY="INSERT_YOUR_PRIVATE_KEY" ``` !!! note Ensure your private key contains USDC funds and native tokens for gas on both the source and destination chains. 4. **Create a `helpers.ts` file** - to simplify the interaction between chains, create a file to store utility functions for fetching your private key, setting up signers for different chains, and managing transaction relays 1. Create the helpers file ```bash mkdir helpers touch helpers/helpers.ts ``` 2. Open the `helpers.ts` file and add the following code ```typescript import { ChainAddress, ChainContext, Network, Signer, Wormhole, Chain, } from '@wormhole-foundation/sdk'; import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import { config } from 'dotenv'; config(); export interface SignerStuff { chain: ChainContext; signer: Signer; address: ChainAddress; } // Function to fetch environment variables (like your private key) function getEnv(key: string): string { const val = process.env[key]; if (!val) throw new Error(`Missing environment variable: ${key}`); return val; } // Signer setup function for different blockchain platforms export async function getSigner( chain: ChainContext ): Promise<{ chain: ChainContext; signer: Signer; address: ChainAddress; }> { let signer: Signer; const platform = chain.platform.utils()._platform; switch (platform) { case 'Solana': signer = await ( await solana() ).getSigner(await chain.getRpc(), getEnv('SOL_PRIVATE_KEY')); break; case 'Evm': signer = await ( await evm() ).getSigner(await chain.getRpc(), getEnv('ETH_PRIVATE_KEY')); break; default: throw new Error('Unsupported platform: ' + platform); } return { chain, signer: signer as Signer, address: Wormhole.chainAddress(chain.chain, signer.address()), }; } ``` - **`getEnv`** - this function fetches environment variables like your private key from the `.env` file - **`getSigner`** - based on the chain you're working with (EVM, Solana, etc.), this function retrieves a signer for that specific platform. The signer is responsible for signing transactions and interacting with the blockchain. It securely uses the private key stored in your `.env` file 5. **Create the main script** - create a new file named `manual-transfer.ts` to hold your script for transferring USDC across chains 1. Create the `manual-transfer.ts` file in the `src` directory ```bash touch src/manual-transfer.ts ``` 2. Open the `manual-transfer.ts` file and begin by importing the necessary modules from the SDK and helper files ```typescript import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import { getSigner } from './helpers/helpers'; ``` - **`evm`** - this import is for working with EVM-compatible chains, like Avalanche, Ethereum, Base Sepolia, and more - **`solana`** - this adds support for Solana, a non-EVM chain - **`getSigner`** - utility function from the helper file that retrieves the signer to sign transactions ## Manual Transfers In a manual USDC transfer, you perform each step of the cross-chain transfer process individually. This approach allows for greater control and flexibility over how the transfer is executed, which can be helpful in scenarios where you need to customize certain aspects of the transfer, such as gas management, specific chain selection, or signing transactions manually. This section will guide you through performing a manual USDC transfer across chains using the Wormhole SDK and Circle’s CCTP. ### Set Up the Transfer Environment #### Configure Transfer Details Before initiating a cross-chain transfer, you must set up the chain context and signers for both the source and destination chains. 1. **Initialize the Wormhole SDK** - initialize the `wormhole` function for the `Testnet` environment and specify the platforms (EVM and Solana) to support. This allows us to interact with both EVM-compatible chains like Avalanche and non-EVM chains like Solana if needed ```typescript const wh = await wormhole('Testnet', [evm, solana]); ``` !!! note You can replace `'Testnet'` with `'Mainnet'` if you want to perform transfers on Mainnet. 2. **Set up source and destination chains** - specify the source chain (Avalanche) and the destination chain (Sepolia) using the `getChain` method. This allows us to define where to send the USDC and where to receive them ```typescript const rcvChain = wh.getChain('Sepolia'); ``` 3. **Configure the signers** - use the `getSigner` function to retrieve the signers responsible for signing transactions on the respective chains. This ensures that transactions are correctly authorized on both the source and destination chains ```typescript const destination = await getSigner(rcvChain); ``` 4. **Define the transfer amount** - the amount of USDC to transfer is specified. In this case, we're transferring 0.1 USDC, which is parsed and converted into the base units expected by the Wormhole SDK ```typescript ``` 5. **Set transfer mode** - we specify that the transfer should be manual by setting `automatic = false`. This means you will need to handle the attestation and finalization steps yourself ```typescript ``` #### Initiate the Transfer To begin the manual transfer process, you first need to create the transfer object and then manually initiate the transfer on the source chain. 1. **Create the Circle transfer object** - the `wh.circleTransfer()` function creates an object with the transfer details, such as the amount of USDC, the source and destination addresses, and the mode. However, this does not initiate the transfer itself ```typescript amt, source.address, destination.address, automatic ); ``` 2. **Start the transfer** - the `initiateTransfer` function sends the transaction on the source chain. It involves signing and sending the transaction using the source signer. This will return a list of transaction IDs (`srcTxIds`) that you can use to track the transfer ```typescript console.log(`Started Transfer: `, srcTxids); ``` #### Fetch the Circle Attestation (VAA) Once you initialize the transfer on the source chain, you must fetch the VAA from Circle. The VAA serves as cryptographic proof that CCTP has successfully recognized the transfer. The transfer cannot be completed on the destination chain until this attestation is fetched. 1. **Set a timeout** - fetching the attestation can take some time, so setting a timeout is common. In this example, we set the timeout to 60 seconds ```typescript ``` 2. **Fetch the attestation** - after initiating the transfer, you can use the `fetchAttestation()` function to retrieve the VAA. This function will wait until the attestation is available or you reach the specified timeout ```typescript console.log(`Got Attestation: `, attestIds); ``` The `attestIds` will contain the details of the fetched attestation, which Wormhole uses to complete the transfer on the destination chain #### Complete the Transfer on the Destination Chain Once you fetch the VAA correctly, the final step is to complete the transfer on the destination chain (Sepolia in this example). This involves redeeming the VAA, which moves the USDC from Circle's custody onto the destination chain. Use the `completeTransfer()` function to finalize the transfer on the destination chain. This requires the destination signer to sign and submit the transaction to the destination chain ```typescript console.log(`Completed Transfer: `, dstTxids); console.log('Circle Transfer status: ', xfer); process.exit(0); ``` The `dstTxIds` will hold the transaction IDs for the transfer on the destination chain, confirming that the transfer has been completed You can find the full code for the manual USDC transfer script below: ???- code "`manual-transfer.ts`" ```typescript import { wormhole } from '@wormhole-foundation/sdk'; import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import { getSigner } from './helpers/helpers'; (async function () { const wh = await wormhole('Testnet', [evm, solana]); // Set up source and destination chains const sendChain = wh.getChain('Avalanche'); const rcvChain = wh.getChain('Sepolia'); // Configure the signers const source = await getSigner(sendChain); const destination = await getSigner(rcvChain); // Define the transfer amount (in the smallest unit, so 0.1 USDC = 100,000 units assuming 6 decimals) const amt = 100_000n; const automatic = false; // Create the Circle transfer object const xfer = await wh.circleTransfer( amt, source.address, destination.address, automatic ); console.log('Circle Transfer object created:', xfer); // Initiate the transfer on the source chain (Avalanche) console.log('Starting Transfer'); const srcTxids = await xfer.initiateTransfer(source.signer); console.log(`Started Transfer: `, srcTxids); // Wait for Circle Attestation (VAA) const timeout = 60 * 1000; // Timeout in milliseconds (60 seconds) console.log('Waiting for Attestation'); const attestIds = await xfer.fetchAttestation(timeout); console.log(`Got Attestation: `, attestIds); // Complete the transfer on the destination chain (Sepolia) console.log('Completing Transfer'); const dstTxids = await xfer.completeTransfer(destination.signer); console.log(`Completed Transfer: `, dstTxids); console.log('Circle Transfer status: ', xfer); process.exit(0); })(); ``` ### Run Manual Transfer To execute the manual transfer script, you can use `ts-node` to run the TypeScript file directly ```bash npx ts-node src/manual-transfer.ts ``` This will initiate the USDC transfer from the source chain (Avalanche) and complete it on the destination chain (Sepolia). You can monitor the status of the transaction on the [Wormhole explorer](https://wormholescan.io/){target=\_blank}. ### Complete Partial Transfer In some cases, a manual transfer might start but not finish—perhaps the user terminates their session, or there's an issue before the transfer can be completed. The Wormhole SDK allows you to reconstitute the transfer object from the transaction hash on the source chain. This feature is handy for recovering an incomplete transfer or when debugging. Here’s how you can complete a partial transfer using just the source chain and transaction hash: ```typescript wh, { chain: 'Avalanche', txid: '0x6b6d5f101a32aa6d2f7bf0bf14d72bfbf76a640e1b2fdbbeeac5b82069cda4dd', }, timeout ); const dstTxIds = await xfer.completeTransfer(destination.signer); console.log('Completed transfer: ', dstTxIds); ``` You will need to provide the below requirements to complete the partial transfer: - **Transaction ID (`txId`)** - the transaction hash from the source chain where the transfer was initiated - **Signer for the destination chain (`destination.signer`)** - the wallet or private key that can authorize and complete the transfer on the destination chain. This signer is the same as the `destination.signer` defined in the manual transfer setup This allows you to resume the transfer process by rebuilding the transfer object and completing it on the destination chain. It's especially convenient when debugging or handling interrupted transfers. You can find the full code for the manual USDC transfer script below: ??? code "`partial-transfer.ts`" ```typescript import { CircleTransfer, wormhole } from '@wormhole-foundation/sdk'; import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import { getSigner } from '../helpers/helpers'; (async function () { // Initialize the Wormhole object for the Testnet environment and add supported chains (evm and solana) const wh = await wormhole('Testnet', [evm, solana]); // Grab chain Contexts -- these hold a reference to a cached rpc client const rcvChain = wh.getChain('Sepolia'); // Get signer from local key const destination = await getSigner(rcvChain); const timeout = 60 * 1000; // Timeout in milliseconds (60 seconds) // Rebuild the transfer from the source txid const xfer = await CircleTransfer.from( wh, { chain: 'Avalanche', txid: '0x6b6d5f101a32aa6d2f7bf0bf14d72bfbf76a640e1b2fdbbeeac5b82069cda4dd', }, timeout ); const dstTxIds = await xfer.completeTransfer(destination.signer); console.log('Completed transfer: ', dstTxIds); console.log('Circle Transfer status: ', xfer); process.exit(0); })(); ``` ## Automatic Transfers The automatic transfer process simplifies the steps by automating the attestation fetching and transfer completion. All you need to do is initiate the transfer. ### Set Up the Transfer Environment #### Configure Transfer Details The setup for automatic transfers is similar to manual transfers, with the key difference being that the `automatic` flag is `true`. This indicates that Wormhole will handle the attestation and finalization steps for you ```typescript ``` #### Initiate the Transfer The transfer process is the same as that for manual transfers. You create the transfer object and then start the transfer on the source chain ```typescript amt, source.address, destination.address, automatic ); ``` #### Log Transfer Details After initiating the transfer, you can log the transaction IDs for both the source and destination chains. This will help you track the progress of the transfer ```typescript console.log(`Started Transfer: `, srcTxids); process.exit(0); ``` You can find the full code for the automatic USDC transfer script below: ??? code "`automatic-transfer.ts`" ```typescript import { wormhole } from '@wormhole-foundation/sdk'; import evm from '@wormhole-foundation/sdk/evm'; import solana from '@wormhole-foundation/sdk/solana'; import { getSigner } from '../helpers/helpers'; (async function () { // Initialize the Wormhole object for the Testnet environment and add supported chains (evm and solana) const wh = await wormhole('Testnet', [evm, solana]); // Set up source and destination chains const sendChain = wh.getChain('Avalanche'); const rcvChain = wh.getChain('Sepolia'); // Configure the signers const source = await getSigner(sendChain); const destination = await getSigner(rcvChain); // Define the transfer amount (in the smallest unit, so 0.1 USDC = 100,000 units assuming 6 decimals) const amt = 100_000_001n; const automatic = true; // Create the Circle transfer object (USDC-only) const xfer = await wh.circleTransfer( amt, source.address, destination.address, automatic ); console.log('Circle Transfer object created:', xfer); // Initiate the transfer on the source chain (Avalanche) console.log('Starting Transfer'); const srcTxids = await xfer.initiateTransfer(source.signer); console.log(`Started Transfer: `, srcTxids); process.exit(0); })(); ``` ### Run Automatic Transfer Assuming you have created a new `automatic-transfer.ts` file for automatic transfers under the `src` directory, use the following command to run it with `ts-node`: ```bash npx ts-node src/automatic-transfer.ts ``` The automatic relayer will take care of fetching the attestation and completing the transfer for you. ## Resources If you'd like to explore the complete project or need a reference while following this tutorial, you can find the complete codebase in [Wormhole's demo GitHub repository](https://github.com/wormhole-foundation/demo-cctp-transfer){target=\_blank}. The repository includes all the example scripts and configurations needed to perform USDC cross-chain transfers, including manual, automatic, and partial transfers using the Wormhole SDK and Circle's CCTP. ## Conclusion In this tutorial, you’ve gained hands-on experience with Circle’s CCTP and the Wormhole SDK. You’ve learned to perform manual and automatic USDC transfers across multiple chains and recover partial transfers if needed. By following these steps, you've learned how to: - Set up cross-chain transfers for native USDC between supported chains - Handle both manual and automatic relaying of transactions - Recover and complete incomplete transfers using the transaction hash and the destination chain’s signer --- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/wormholescan/ --- BEGIN CONTENT --- --- title: Wormholescan API Tutorials description: Explore step-by-step guides on using the Wormholescan API to fetch VAAs, validate signatures, check redemption status, and process cross-chain transactions. --- # Wormholescan API Explore hands-on tutorials for using the Wormholescan API to retrieve blockchain data, track transactions, validate VAAs, check redemption status, and more. ## Tutorials
- :octicons-repo-16:{ .lg .middle } **Replace Outdated Signatures in VAAs** --- Learn how to fetch VAAs, verify their validity, and replace outdated signatures using the Wormholescan API and Wormhole SDK. [:custom-arrow: Start tutorial](/docs/tutorials/wormholescan/replace-signatures/)
## Additional Resources
- :octicons-book-16:{ .lg .middle } **Wormholescan** --- Wormholescan is a blockchain explorer for tracking transactions, VAAs, and cross-chain activity. Its API provides programmatic access to transaction data, network analytics, and more. [:custom-arrow: Visit Wormholescan](https://wormholescan.io/){target=\_blank}
--- END CONTENT --- Doc-Content: https://wormhole.com/docs/tutorials/wormholescan/replace-signatures/ --- BEGIN CONTENT --- --- title: Replace Outdated Signatures in VAAs description: Learn how to fetch, validate, and replace outdated signatures in Wormhole VAAs using Wormholescan and the Wormhole SDK to ensure seamless processing. --- # Replace Outdated Signatures in VAAs :simple-github: [Source code on GitHub](https://github.com/wormhole-foundation/demo-vaa-signature-replacement){target=\_blank} ## Introduction Cross-chain transactions in Wormhole rely on [Verifiable Action Approvals (VAAs)](/docs/learn/infrastructure/vaas/){target=\_blank}, which contain signatures from a trusted set of validators called [Guardians](/docs/learn/infrastructure/guardians/){target=\_blank}. These signatures prove that the network approved an action, such as a token transfer. However, the set of Guardians changes over time. If a user generates a transaction and waits too long before redeeming it, the Guardian set may have already changed. This means the VAA will contain outdated signatures from Guardians, who are no longer part of the network, causing the transaction to fail. Instead of discarding these VAAs, we can fetch updated signatures and replace the outdated ones to ensure smooth processing. In this tutorial, you'll build a script from scratch to: - Fetch a VAA from [Wormholescan](https://wormholescan.io/#/developers/api-doc){target=\_blank} - Validate its signatures against the latest Guardian set - Replace outdated signatures using the [Wormhole SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank} - Output a valid VAA ready for submission By the end, you'll have a script that ensures VAAs remain valid and processable, avoiding transaction failures. ## 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 ## Project Setup In this section, you will create the directory, initialize a Node.js project, install dependencies, and configure TypeScript. 1. **Create the project** - set up the directory and navigate into it ```bash mkdir wormhole-scan-api-demo cd wormhole-scan-api-demo ``` 2. **Initialize a Node.js project** - generate a `package.json` file ```bash npm init -y ``` 3. **Set up TypeScript** - create a `tsconfig.json` file ```bash touch tsconfig.json ``` Then, add the following configuration: ```json title="tsconfig.json" { "compilerOptions": { "target": "es2016", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true } } ``` 4. **Install dependencies** - add the required packages ```bash npm install @wormhole-foundation/sdk axios web3 tsx @types/node ``` - `@wormhole-foundation/sdk` - handles VAAs and cross-chain interactions - `axios` - makes HTTP requests to the Wormholescan API - `web3` - interacts with Ethereum transactions and contracts - `tsx` - executes TypeScript files without compilation - `@types/node` - provides Node.js type definitions 5. **Create the project structure** - set up the required directories and files ```bash mkdir -p src/config && touch src/config/constants.ts src/config/layouts.ts mkdir -p src/helpers && touch src/helpers/vaaHelper.ts mkdir -p src/scripts && touch scripts/replaceSignatures.ts ``` - **`src/config/*`** - stores public configuration variables and layouts for serializing and deserializing data structures - **`src/helpers/*`** - contains utility functions - **`src/scripts/*`** - contains scripts for fetching and replacing signatures 6. **Set variables** - define key constants in `src/config/constants.ts` ```bash title="src/config/constants.ts" export const RPC = 'https://ethereum-rpc.publicnode.com'; export const ETH_CORE = '0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B'.toLowerCase(); export const WORMHOLESCAN_API = 'https://api.wormholescan.io/v1'; export const LOG_MESSAGE_PUBLISHED_TOPIC = '0x6eb224fb001ed210e379b335e35efe88672a8ce935d981a6896b27ffdf52a3b2'; export const TXS = [ '0x3ad91ec530187bb2ce3b394d587878cd1e9e037a97e51fbc34af89b2e0719367', '0x3c989a6bb40dcd4719453fbe7bbac420f23962c900ae75793124fc9cc614368c', ]; ``` - **`RPC`** - endpoint for interacting with an Ethereum RPC node - **`ETH_CORE`** - [Wormhole's Core Contract address on Ethereum](/docs/build/reference/contract-addresses/#core-contracts){target=\_blank} responsible for verifying VAAs - **`WORMHOLESCAN_API`** - base URL for querying the Wormholescan API to fetch VAA data and Guardian sets - **`LOG_MESSAGE_PUBLISHED_TOPIC`** - the event signature hash for `LogMessagePublished`, a Wormhole contract event that signals when a VAA has been emitted. This is used to identify relevant logs in transaction receipts - **`TXS`** - list of example transaction hashes that will be used for testing 7. **Define data structure for working with VAAs** - specify the ABI for the Wormhole Core Contract's `parseAndVerifyVM` function, which parses and verifies VAAs. Defining the data structure, also referred to as a [layout](/docs/build/toolkit/typescript-sdk/sdk-layout/){target=\_blank}, for this function ensures accurate decoding and validation of VAAs ```typescript title="src/config/layouts.ts" export const PARSE_AND_VERIFY_VM_ABI = { inputs: [{ internalType: 'bytes', name: 'encodedVM', type: 'bytes' }], name: 'parseAndVerifyVM', outputs: [ { components: [ { internalType: 'uint8', name: 'version', type: 'uint8' }, { internalType: 'uint32', name: 'timestamp', type: 'uint32' }, { internalType: 'uint32', name: 'nonce', type: 'uint32' }, { internalType: 'uint16', name: 'emitterChainId', type: 'uint16' }, { internalType: 'bytes32', name: 'emitterAddress', type: 'bytes32' }, { internalType: 'uint64', name: 'sequence', type: 'uint64' }, { internalType: 'uint8', name: 'consistencyLevel', type: 'uint8' }, { internalType: 'bytes', name: 'payload', type: 'bytes' }, { internalType: 'uint32', name: 'guardianSetIndex', type: 'uint32' }, { components: [ { internalType: 'bytes32', name: 'r', type: 'bytes32' }, { internalType: 'bytes32', name: 's', type: 'bytes32' }, { internalType: 'uint8', name: 'v', type: 'uint8' }, { internalType: 'uint8', name: 'guardianIndex', type: 'uint8' }, ], internalType: 'struct Structs.Signature[]', name: 'signatures', type: 'tuple[]', }, { internalType: 'bytes32', name: 'hash', type: 'bytes32' }, ], internalType: 'struct Structs.VM', name: 'vm', type: 'tuple', }, { internalType: 'bool', name: 'valid', type: 'bool' }, { internalType: 'string', name: 'reason', type: 'string' }, ], stateMutability: 'view', type: 'function', }; ``` ## Create VAA Handling Functions In this section, we'll create a series of helper functions in the `src/helpers/vaaHelper.ts` file that will retrieve and verify VAAs and fetch and replace outdated Guardian signatures to generate a correctly signed VAA. To get started, import the necessary dependencies: ```typescript title="src/helpers/vaaHelper.ts" import { eth } from 'web3'; import { deserialize, serialize, VAA, Signature, } from '@wormhole-foundation/sdk'; import { RPC, ETH_CORE, LOG_MESSAGE_PUBLISHED_TOPIC, WORMHOLESCAN_API, } from '../config/constants'; import { PARSE_AND_VERIFY_VM_ABI } from '../config/layouts'; ``` ### Fetch a VAA ID from a Transaction To retrieve a VAA, we first need to get its VAA ID from a transaction hash. This ID allows us to fetch the full VAA later. The VAA ID is structured as follows: ```bash chain/emitter/sequence ``` - `chain` - the [Wormhole chain ID](/docs/build/reference/chain-ids/){target=\_blank} (Ethereum is 2) - `emitter` - the contract address that emitted the VAA - `sequence` - a unique identifier for the event We must assemble the ID correctly since this is the format the Wormholescan API expects when querying VAAs. Follow the below steps to process the transaction logs and construct the VAA ID: 1. **Get the transaction receipt** - iterate over the array of transaction hashes and fetch the receipt to access its logs 2. **Find the Wormhole event** - iterate over the transaction logs and check for events emitted by the Wormhole Core contract. Look specifically for `LogMessagePublished` events, which indicate a VAA was created 3. **Extract the emitter and sequence number** - if a matching event is found, extract the emitter address from `log.topics[1]` and remove the `0x` prefix. Then, the sequence number from `log.data` is extracted, converting it from hex to an integer 4. **Construct the VAA ID** - format the extracted data in `chain/emitter/sequence` format ```typescript title="src/helpers/vaaHelper.ts" const vaaIds: string[] = []; for (const tx of txHashes) { try { const result = ( await axios.post(RPC, { jsonrpc: '2.0', id: 1, method: 'eth_getTransactionReceipt', params: [tx], }) ).data.result; if (!result) throw new Error(`Unable to fetch transaction receipt for ${tx}`); for (const log of result.logs) { if ( log.address === ETH_CORE && log.topics?.[0] === LOG_MESSAGE_PUBLISHED_TOPIC ) { const emitter = log.topics[1].substring(2); const seq = BigInt(log.data.substring(0, 66)).toString(); vaaIds.push(`2/${emitter}/${seq}`); } } } catch (error) { console.error(`Error processing ${tx}:`, error); } } return vaaIds; } ``` ???- code "Try it out: VAA ID retrieval" If you want to try out the function before moving forward, create a test file inside the `test` directory: 1. **Create the directory and file** - add a script to call `fetchVaaId` and print the result ```bash mkdir -p test touch test/fetchVaaId.run.ts ``` 2. **Add the function call** ```typescript title="test/fetchVaaId.run.ts" import { fetchVaaId } from '../src/helpers/vaaHelper'; import { TXS } from '../src/config/constants'; const testFetchVaaId = async () => { for (const tx of TXS) { const vaaIds = await fetchVaaId([tx]); if (vaaIds.length > 0) { console.log(`Transaction: ${tx}`); vaaIds.forEach((vaaId) => console.log(`VAA ID: ${vaaId}`)); } else { console.log(`No VAA ID found for transaction: ${tx}`); } } }; testFetchVaaId(); ``` 3. **Run the script** ```bash npx tsx test/fetchVaaId.run.ts ``` If successful, the output will be:
npx tsx test/fetchVaaId.run.ts Transaction: 0x3ad91ec530187bb2ce3b394d587878cd1e9e037a97e51fbc34af89b2e0719367 VAA ID: 2/0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa585/164170
If no VAA ID is found, the script will log an error message. ### Fetch the Full VAA Now that you have the VAA ID, we can use it to fetch the full VAA payload from the Wormholescan API. This payload contains the VAA bytes, which will later be used for signature validation. Open `src/helpers/vaaHelper.ts` and create the `fetchVaa()` function to iterate through VAA IDs and extract the `vaaBytes` payload. ```typescript title="src/helpers/vaaHelper.ts" vaaIds: string[] ): Promise<{ id: string; vaaBytes: string }[]> { const results: { id: string; vaaBytes: string }[] = []; for (const id of vaaIds) { try { const response = await axios.get(`${WORMHOLESCAN_API}/signed_vaa/${id}`); const vaaBytes = response.data.vaaBytes; results.push({ id, vaaBytes }); } catch (error) { console.error(`Error fetching VAA for ${id}:`, error); } } return results; } ``` ???- code "Try it out: VAA retrieval" If you want to try the function before moving forward, create a script inside the `test` directory 1. **Create the script file** ```bash touch test/fetchVaa.run.ts ``` 2. **Add the function call** ```typescript title="test/fetchVaa.run.ts" import { fetchVaaId, fetchVaa } from '../src/helpers/vaaHelper'; import { TXS } from '../src/config/constants'; const testFetchVaa = async () => { for (const tx of TXS) { const vaaIds = await fetchVaaId([tx]); if (vaaIds.length === 0) { console.log(`No VAA ID found for transaction: ${tx}`); continue; } for (const vaaId of vaaIds) { const vaaBytes = await fetchVaa([vaaId]); console.log( `Transaction: ${tx}\nVAA ID: ${vaaId}\nVAA Bytes: ${ vaaBytes.length > 0 ? vaaBytes[0].vaaBytes : 'Not found' }` ); } } }; testFetchVaa(); ``` 3. **Run the script** ```bash npx tsx test/fetchVaa.run.ts ``` If successful, the output will be:
npx tsx test/fetchVaa.run.ts Transaction: 0x3ad91ec530187bb2ce3b394d587878cd1e9e037a97e51fbc34af89b2e0719367 VAA Bytes: AQAAAAMNANQSwD/HRPcKp7Yxypl1ON8dZeMBzgYJrd2KYz6l9Tq9K9fj72fYJgkMeMaB9h...
If no VAA is found, the script will log an error message. ### Validate VAA Signatures Now, we need to verify its validity. A VAA is only considered valid if it contains signatures from currently active Guardians and is correctly verified by the Wormhole Core contract. Open `src/helpers/vaaHelper.ts` and add the `checkVaaValidity()` function. This function verifies whether a VAA is valid by submitting it to an Ethereum RPC node and checking for outdated signatures. Follow these steps to implement the function: 1. **Prepare the VAA for verification** - construct the VAA payload in a format that can be sent to the Wormhole Core contract 2. **Send an `eth_call` request** - submit the VAA to an Ethereum RPC node, calling the `parseAndVerifyVM` function on the Wormhole Core contract 3. **Decode the response** - check whether the VAA is valid. If it contains outdated signatures, further action will be required to replace them ```typescript title="src/helpers/vaaHelper.ts" try { const vaa = Buffer.from(vaaBytes, 'base64'); vaa[4] = 4; // Set guardian set index to 4 const result = ( await axios.post(RPC, { jsonrpc: '2.0', id: 1, method: 'eth_call', params: [ { from: null, to: ETH_CORE, data: eth.abi.encodeFunctionCall(PARSE_AND_VERIFY_VM_ABI, [ `0x${vaa.toString('hex')}`, ]), }, 'latest', ], }) ).data.result; const decoded = eth.abi.decodeParameters( PARSE_AND_VERIFY_VM_ABI.outputs, result ); console.log( `${decoded.valid ? '✅' : '❌'} VAA Valid: ${decoded.valid}${ decoded.valid ? '' : `, Reason: ${decoded.reason}` }` ); return { valid: decoded.valid, reason: decoded.reason }; } catch (error) { console.error(`Error checking VAA validity:`, error); return { valid: false, reason: 'RPC error' }; } } ``` ???- code "Try it out: VAA Validity" If you want to try the function before moving forward, create a script inside the `test` directory 1. **Create the script file** ```bash touch test/checkVaaValidity.run.ts ``` 2. **Add the function call** ```typescript title="test/checkVaaValidity.run.ts" import { fetchVaaId, fetchVaa, checkVaaValidity, } from '../src/helpers/vaaHelper'; import { TXS } from '../src/config/constants'; const testCheckVaaValidity = async () => { for (const tx of TXS) { const vaaIds = await fetchVaaId([tx]); if (vaaIds.length === 0) { console.log(`No VAA ID found for transaction: ${tx}`); continue; } for (const vaaId of vaaIds) { const vaaData = await fetchVaa([vaaId]); if (vaaData.length === 0 || !vaaData[0].vaaBytes) { console.log(`VAA not found for ID: ${vaaId}`); continue; } const result = await checkVaaValidity(vaaData[0].vaaBytes); console.log( `Transaction: ${tx}\nVAA ID: ${vaaId}\nVAA Validity:`, result ); } } }; testCheckVaaValidity(); ``` 3. **Run the script** ```bash npx tsx test/checkVaaValidity.run.ts ``` If the VAA is valid, the output will be:
npx tsx test/checkVaaValidity.run.ts ✅ VAA Valid: true
If invalid, the output will include the reason:
npx tsx test/checkVaaValidity.run.ts ❌ VAA Valid: false, Reason: VM signature invalid Transaction: 0x3ad91ec530187bb2ce3b394d587878cd1e9e037a97e51fbc34af89b2e0719367
### Fetch Observations (VAA Signatures) Before replacing outdated signatures, we need to fetch the original VAA signatures from Wormholescan. This allows us to compare them with the latest Guardian set and determine which ones need updating. Inside `src/helpers/vaaHelper.ts`, create the `fetchObservations()` function to query the Wormholescan API for observations related to a given VAA. Format the response by converting Guardian addresses to lowercase for consistency, and return an empty array if an error occurs. ```typescript title="src/helpers/vaaHelper.ts" try { console.log(`Fetching observations`); const response = await axios.get( `https://api.wormholescan.io/api/v1/observations/${vaaId}` ); return response.data.map((obs: any) => ({ guardianAddr: obs.guardianAddr.toLowerCase(), signature: obs.signature, })); } catch (error) { console.error(`Error fetching observations:`, error); return []; } } ``` ???- code "Try it out: Fetch Observations" If you want to try the function before moving forward, create a script inside the `test` directory 1. **Create the script file** ```bash touch test/fetchObservations.run.ts ``` 2. **Add the function call** ```typescript title="test/fetchObservations.run.ts" import { fetchVaaId, fetchObservations } from '../src/helpers/vaaHelper'; import { TXS } from '../src/config/constants'; const testFetchObservations = async () => { for (const tx of TXS) { const vaaIds = await fetchVaaId([tx]); if (vaaIds.length === 0) { console.log(`No VAA ID found for transaction: ${tx}`); continue; } for (const vaaId of vaaIds) { const observations = await fetchObservations(vaaId); if (observations.length === 0) { console.log(`No observations found for VAA ID: ${vaaId}`); continue; } console.log( `Transaction: ${tx}\nVAA ID: ${vaaId}\nObservations:`, observations ); } } }; testFetchObservations(); ``` 3. **Run the script** ```bash npx tsx test/fetchObservations.run.ts ``` If successful, the output will be:
npx tsx test/fetchObservations.run.ts Fetching observations Transaction: 0x3ad91ec530187bb2ce3b394d587878cd1e9e037a97e51fbc34af89b2e0719367 Observations: [ { guardianAddr: '0xda798f6896a3331f64b48c12d1d57fd9cbe70811', signature: 'ZGFlMDYyOGNjZjFjMmE0ZTk5YzE2OThhZjAzMDM4NzZlYTM1OWMxMzczNDA3YzdlMDMxZTkyNzk0ODkwYjRiYjRiOWFmNzM3NjRiMzIyOTE0ZTQwYzNlMjllMWEzNmM2NTc3ZDc5ZTdhNTM2MzA5YjA4YjExZjE3YzE3MDViNWIwMQ==' }, { guardianAddr: '0x74a3bf913953d695260d88bc1aa25a4eee363ef0', signature: 'MzAyOTU4OGU4MWU0ODc0OTAwNDU3N2EzMGZlM2UxMDJjOWYwMjM0NWVhY2VmZWQ0ZGJlNTFkNmI3YzRhZmQ5ZTNiODFjNTg3MDNmYzUzNmJiYWFiZjNlODc1YTY3OTQwMGE4MmE3ZjZhNGYzOGY3YmRmNDNhM2VhNGQyNWNlNGMwMA==' }, ...]
If no observations are found, the script will log an error message. ### Fetch the Latest Guardian Set Now that we have the original VAA signatures, we must fetch the latest Guardian set from Wormholescan. This will allow us to compare the stored signatures with the current Guardians and determine which signatures need replacing. Create the `fetchGuardianSet()` function inside `src/helpers/vaaHelper.ts` to fetch the latest Guardian set. ```typescript title="src/helpers/vaaHelper.ts" export async function fetchGuardianSet() { try { console.log('Fetching current guardian set'); const response = await axios.get(`${WORMHOLESCAN_API}/guardianset/current`); const guardians = response.data.guardianSet.addresses.map((addr: string) => addr.toLowerCase() ); const guardianSet = response.data.guardianSet.index; return [guardians, guardianSet]; } catch (error) { console.error('Error fetching guardian set:', error); return []; } } ``` ???- code "Try it out: Fetch Guardian Set" If you want to try the function before moving forward, create a script inside the `test` directory 1. **Create the script file** ```bash touch test/fetchGuardianSet.run.ts ``` 2. **Add the function call** ```typescript title="test/fetchGuardianSet.run.ts" import { fetchGuardianSet } from '../src/helpers/vaaHelper'; const testFetchGuardianSet = async () => { const [guardians, guardianSetIndex] = await fetchGuardianSet(); console.log('Current Guardian Set Index:', guardianSetIndex); console.log('Guardian Addresses:', guardians); }; testFetchGuardianSet(); ``` 3. **Run the script** ```bash npx tsx test/fetchGuardianSet.run.ts ``` If successful, the output will be:
npx tsx test/fetchGuardianSet.run.ts Fetching current guardian set Current Guardian Set Index: 4 Guardian Addresses: [ '0x5893b5a76c3f739645648885bdccc06cd70a3cd3', '0xff6cb952589bde862c25ef4392132fb9d4a42157', '0x114de8460193bdf3a2fcf81f86a09765f4762fd1', '0x107a0086b32d7a0977926a205131d8731d39cbeb', ...]
If an error occurs while fetching the Guardian set, a `500` status error will be logged. ### Replace Outdated Signatures With the full VAA, Guardian signatures, and the latest Guardian set, we can now update outdated signatures while maintaining the required signature count. 1. **Create the `replaceSignatures()` function** - open `src/helpers/vaaHelper.ts` and add the function header. To catch and handle errors properly, all logic will be wrapped inside a `try` block ```typescript title="src/helpers/vaaHelper.ts" vaa: string | Uint8Array, observations: { guardianAddr: string; signature: string }[], currentGuardians: string[], guardianSetIndex: number ) { console.log('Replacing Signatures...'); try { // Add logic in the following steps here console.error('Unexpected error in replaceSignatures:', error); } } ``` - **`vaa`** - original VAA bytes - **`observations`** - observed signatures from the network - **`currentGuardians`** - latest Guardian set - **`guardianSetIndex`** - current Guardian set index 2. **Validate input data** - ensure all required parameters are present before proceeding. If any required input is missing, the function throws an error to prevent execution with incomplete data. The Guardian set should never be empty; if it is, this likely indicates an error in fetching the Guardian set in a previous step ```typescript if (currentGuardians.length === 0) throw new Error('Guardian set is empty.'); if (observations.length === 0) throw new Error('No observations provided.'); ``` 3. **Filter valid signatures** - remove signatures from inactive Guardians, keeping only valid ones. If there aren't enough valid signatures to replace the outdated ones, execution is halted to prevent an incomplete or invalid VAA ```typescript currentGuardians.includes(sig.guardianAddr) ); if (validSigs.length === 0) throw new Error('No valid signatures found. Cannot proceed.'); ``` 4. **Convert valid signatures** - ensure signatures are correctly formatted for verification. Convert hex-encoded signatures if necessary and extract their components ```typescript .map((sig) => { try { const sigBuffer = Buffer.from(sig.signature, 'base64'); // If it's 130 bytes, it's hex-encoded and needs conversion const sigBuffer1 = sigBuffer.length === 130 ? Buffer.from(sigBuffer.toString(), 'hex') : sigBuffer; const r = BigInt('0x' + sigBuffer1.subarray(0, 32).toString('hex')); const s = BigInt('0x' + sigBuffer1.subarray(32, 64).toString('hex')); const vRaw = sigBuffer1[64]; const v = vRaw < 27 ? vRaw : vRaw - 27; return { guardianIndex: currentGuardians.indexOf(sig.guardianAddr), signature: new Signature(r, s, v), }; } catch (error) { console.error( `Failed to process signature for guardian: ${sig.guardianAddr}`, error ); return null; } }) .filter( (sig): sig is { guardianIndex: number; signature: Signature } => sig !== null ); // Remove null values ``` 5. **Deserialize the VAA** - convert the raw VAA data into a structured format for further processing ```typescript try { parsedVaa = deserialize('Uint8Array', vaa); } catch (error) { throw new Error(`Error deserializing VAA: ${error}`); } ``` 6. **Identify outdated signatures** - compare the current VAA signatures with the newly formatted ones to detect which signatures belong to outdated Guardians. Remove these outdated signatures to ensure only valid ones remain ```typescript .filter( (vaaSig) => !formattedSigs.some( (sig) => sig.guardianIndex === vaaSig.guardianIndex ) ) .map((sig) => sig.guardianIndex); console.log('Outdated Guardian Indexes:', outdatedGuardianIndexes); let updatedSignatures = parsedVaa.signatures.filter( (sig) => !outdatedGuardianIndexes.includes(sig.guardianIndex) ); ``` 7. **Replace outdated signatures** - substitute outdated signatures with valid ones while maintaining the correct number of signatures. If there aren’t enough valid replacements, execution stops ```typescript (sig) => !updatedSignatures.some((s) => s.guardianIndex === sig.guardianIndex) ); // Check if we have enough valid signatures to replace outdated ones** if (outdatedGuardianIndexes.length > validReplacements.length) { console.warn( `Not enough valid replacement signatures! Need ${outdatedGuardianIndexes.length}, but only ${validReplacements.length} available.` ); return; } updatedSignatures = [ ...updatedSignatures, ...validReplacements.slice(0, outdatedGuardianIndexes.length), ]; updatedSignatures.sort((a, b) => a.guardianIndex - b.guardianIndex); ``` 8. **Serialize the updated VAA** - reconstruct the VAA with the updated signatures and convert it into a format suitable for submission ```typescript ...parsedVaa, guardianSet: guardianSetIndex, signatures: updatedSignatures, }; let patchedVaa: Uint8Array; try { patchedVaa = serialize(updatedVaa); } catch (error) { throw new Error(`Error serializing updated VAA: ${error}`); } ``` 9. **Send the updated VAA for verification and handle errors** - submit the updated VAA to an Ethereum RPC node for validation, ensuring it can be proposed for Guardian approval. If an error occurs during submission or signature replacement, log the issue and prevent further execution ```typescript if (!(patchedVaa instanceof Uint8Array)) throw new Error('Patched VAA is not a Uint8Array!'); const vaaHex = `0x${Buffer.from(patchedVaa).toString('hex')}`; console.log('Sending updated VAA to RPC...'); const result = await axios.post(RPC, { jsonrpc: '2.0', id: 1, method: 'eth_call', params: [ { from: null, to: ETH_CORE, data: eth.abi.encodeFunctionCall(PARSE_AND_VERIFY_VM_ABI, [vaaHex]), }, 'latest', ], }); const verificationResult = result.data.result; console.log('Updated VAA (hex):', vaaHex); return verificationResult; } catch (error) { throw new Error(`Error sending updated VAA to RPC: ${error}`); } ``` ???- code "Complete Function" ```typescript vaa: string | Uint8Array, observations: { guardianAddr: string; signature: string }[], currentGuardians: string[], guardianSetIndex: number ) { console.log('Replacing Signatures...'); try { if (!vaa) throw new Error('VAA is undefined or empty.'); if (currentGuardians.length === 0) throw new Error('Guardian set is empty.'); if (observations.length === 0) throw new Error('No observations provided.'); const validSigs = observations.filter((sig) => currentGuardians.includes(sig.guardianAddr) ); if (validSigs.length === 0) throw new Error('No valid signatures found. Cannot proceed.'); const formattedSigs = validSigs .map((sig) => { try { const sigBuffer = Buffer.from(sig.signature, 'base64'); // If it's 130 bytes, it's hex-encoded and needs conversion const sigBuffer1 = sigBuffer.length === 130 ? Buffer.from(sigBuffer.toString(), 'hex') : sigBuffer; const r = BigInt('0x' + sigBuffer1.subarray(0, 32).toString('hex')); const s = BigInt('0x' + sigBuffer1.subarray(32, 64).toString('hex')); const vRaw = sigBuffer1[64]; const v = vRaw < 27 ? vRaw : vRaw - 27; return { guardianIndex: currentGuardians.indexOf(sig.guardianAddr), signature: new Signature(r, s, v), }; } catch (error) { console.error( `Failed to process signature for guardian: ${sig.guardianAddr}`, error ); return null; } }) .filter( (sig): sig is { guardianIndex: number; signature: Signature } => sig !== null ); // Remove null values let parsedVaa: VAA<'Uint8Array'>; try { parsedVaa = deserialize('Uint8Array', vaa); } catch (error) { throw new Error(`Error deserializing VAA: ${error}`); } const outdatedGuardianIndexes = parsedVaa.signatures .filter( (vaaSig) => !formattedSigs.some( (sig) => sig.guardianIndex === vaaSig.guardianIndex ) ) .map((sig) => sig.guardianIndex); console.log('Outdated Guardian Indexes:', outdatedGuardianIndexes); let updatedSignatures = parsedVaa.signatures.filter( (sig) => !outdatedGuardianIndexes.includes(sig.guardianIndex) ); const validReplacements = formattedSigs.filter( (sig) => !updatedSignatures.some((s) => s.guardianIndex === sig.guardianIndex) ); // Check if we have enough valid signatures to replace outdated ones** if (outdatedGuardianIndexes.length > validReplacements.length) { console.warn( `Not enough valid replacement signatures! Need ${outdatedGuardianIndexes.length}, but only ${validReplacements.length} available.` ); return; } updatedSignatures = [ ...updatedSignatures, ...validReplacements.slice(0, outdatedGuardianIndexes.length), ]; updatedSignatures.sort((a, b) => a.guardianIndex - b.guardianIndex); const updatedVaa: VAA<'Uint8Array'> = { ...parsedVaa, guardianSet: guardianSetIndex, signatures: updatedSignatures, }; let patchedVaa: Uint8Array; try { patchedVaa = serialize(updatedVaa); } catch (error) { throw new Error(`Error serializing updated VAA: ${error}`); } try { if (!(patchedVaa instanceof Uint8Array)) throw new Error('Patched VAA is not a Uint8Array!'); const vaaHex = `0x${Buffer.from(patchedVaa).toString('hex')}`; console.log('Sending updated VAA to RPC...'); const result = await axios.post(RPC, { jsonrpc: '2.0', id: 1, method: 'eth_call', params: [ { from: null, to: ETH_CORE, data: eth.abi.encodeFunctionCall(PARSE_AND_VERIFY_VM_ABI, [vaaHex]), }, 'latest', ], }); const verificationResult = result.data.result; console.log('Updated VAA (hex):', vaaHex); return verificationResult; } catch (error) { throw new Error(`Error sending updated VAA to RPC: ${error}`); } } catch (error) { console.error('Unexpected error in replaceSignatures:', error); } } ``` ## Create Script to Replace Outdated VAA Signatures Now that we have all the necessary helper functions, we will create a script to automate replacing outdated VAA signatures. This script will retrieve a transaction’s VAA sequentially, check its validity, fetch the latest Guardian set, and update its signatures. By the end, it will output a correctly signed VAA that can be proposed for Guardian approval. 1. **Open the file** - inside `src/scripts/replaceSignatures.ts`, import the required helper functions needed to process the VAAs ```typescript title="src/scripts/replaceSignatures.ts" fetchVaaId, fetchVaa, checkVaaValidity, fetchObservations, fetchGuardianSet, replaceSignatures, } from '../helpers/vaaHelper'; import { TXS } from '../config/constants'; ``` 2. **Define the main execution function** - add the following function inside `src/scripts/replaceSignatures.ts` to process each transaction in `TXS`, going step by step through the signature replacement process ```typescript try { for (const tx of TXS) { console.log(`\nProcessing TX: ${tx}\n`); // 1. Fetch Transaction VAA IDs: const vaaIds = await fetchVaaId([tx]); if (!vaaIds.length) continue; // 2. Fetch VAA Data: const vaaData = await fetchVaa(vaaIds); if (!vaaData.length) continue; const vaaBytes = vaaData[0].vaaBytes; if (!vaaBytes) continue; // 3. Check VAA Validity: const { valid } = await checkVaaValidity(vaaBytes); if (valid) continue; // 4. Fetch Observations (VAA signatures): const observations = await fetchObservations(vaaIds[0]); // 5. Fetch Current Guardian Set: const [currentGuardians, guardianSetIndex] = await fetchGuardianSet(); // 6. Replace Signatures: const response = await replaceSignatures( Buffer.from(vaaBytes, 'base64'), observations, currentGuardians, guardianSetIndex ); if (!response) continue; } } catch (error) { console.error('❌ Error in execution:', error); process.exit(1); } } ``` 3. **Make the script executable** - ensure it runs when executed ```typescript ``` To run the script, use the following command: ```bash npx tsx src/scripts/replaceSignatures.ts ```
npx tsx src/scripts/replaceSignatures.ts Processing TX: 0x3ad91ec530187bb2ce3b394d587878cd1e9e037a97e51fbc34af89b2e0719367 ❌ VAA Valid: false, Reason: VM signature invalid Fetching observations Fetching current guardian set Replacing Signatures... Outdated Guardian Indexes: [ 0 ] Sending updated VAA to RPC... Updated VAA (hex): 0x01000000040d010019447b72d51e33923a3d6b28496ccd3722d5f1e33e2...
The script logs each step, skipping valid VAAs, replacing outdated signatures for invalid VAAs, and logging any errors. It then completes with a valid VAA ready for submission. ## Resources You can explore the complete project and find all necessary scripts and configurations in Wormhole's [demo GitHub repository](https://github.com/wormhole-foundation/demo-vaa-signature-replacement){target=\_blank}. The demo repository includes a bonus script to check the VAA redemption status on Ethereum and Solana, allowing you to verify whether a transaction has already been redeemed on the destination chain. ## Conclusion You've successfully built a script to fetch, validate, and replace outdated signatures in VAAs using Wormholescan and the Wormhole SDK. It's important to note that this tutorial does not update VAAs in the Wormhole network. Before redeeming the VAA, you must propose it for Guardian approval to finalize the process. --- END CONTENT ---