From bd986a1c818a8b19e659f43a064ac17d45a93da4 Mon Sep 17 00:00:00 2001 From: DAWN KELLY Date: Tue, 17 Jun 2025 15:17:14 -0400 Subject: [PATCH 1/3] starter for content --- products/token-bridge/guides/.pages | 2 +- products/token-bridge/guides/attest-tokens.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 products/token-bridge/guides/attest-tokens.md diff --git a/products/token-bridge/guides/.pages b/products/token-bridge/guides/.pages index 52178f6c6..daf168a97 100644 --- a/products/token-bridge/guides/.pages +++ b/products/token-bridge/guides/.pages @@ -1,3 +1,3 @@ title: Guides nav: -- 'Interact with Contracts': token-bridge-contracts.md +- 'Token Attestation': attest-tokens.md diff --git a/products/token-bridge/guides/attest-tokens.md b/products/token-bridge/guides/attest-tokens.md new file mode 100644 index 000000000..2a91bfd07 --- /dev/null +++ b/products/token-bridge/guides/attest-tokens.md @@ -0,0 +1,15 @@ +--- +title: Token Attestation +description: TODO +categories: Token-Bridge, Transfer +--- + +# Token Attestation + +## Introduction + +This guide demonstrates token attestation in order to register a token for transfer using the Token Bridge protocol. The example will register an arbitrary ERC-20 token deployed to Moonbase Alpha for transfer to Ethereum Sepolia but can be adapted for any supported chains. + +Completing this guide will help you to accomplish the following: + +- \ No newline at end of file From 48a42a7eebea2c8805057684204ae57fb4eefdb7 Mon Sep 17 00:00:00 2001 From: DAWN KELLY Date: Wed, 18 Jun 2025 15:23:58 -0400 Subject: [PATCH 2/3] adds content, llms --- .../guides/attest-tokens/attest.ts | 126 +++++ .../guides/attest-tokens/helper.ts | 59 +++ .../guides/attest-tokens/terminal01.html | 10 + .../guides/attest-tokens/terminal02.html | 5 + .../guides/attest-tokens/terminal03.html | 24 + llms-files/llms-token-bridge.txt | 498 ++++++++++++++++++ llms-files/llms-transfer.txt | 498 ++++++++++++++++++ llms-full.txt | 498 ++++++++++++++++++ llms.txt | 1 + products/token-bridge/guides/attest-tokens.md | 148 +++++- 10 files changed, 1864 insertions(+), 3 deletions(-) create mode 100644 .snippets/code/products/token-bridge/guides/attest-tokens/attest.ts create mode 100644 .snippets/code/products/token-bridge/guides/attest-tokens/helper.ts create mode 100644 .snippets/code/products/token-bridge/guides/attest-tokens/terminal01.html create mode 100644 .snippets/code/products/token-bridge/guides/attest-tokens/terminal02.html create mode 100644 .snippets/code/products/token-bridge/guides/attest-tokens/terminal03.html diff --git a/.snippets/code/products/token-bridge/guides/attest-tokens/attest.ts b/.snippets/code/products/token-bridge/guides/attest-tokens/attest.ts new file mode 100644 index 000000000..9fcd3c3bd --- /dev/null +++ b/.snippets/code/products/token-bridge/guides/attest-tokens/attest.ts @@ -0,0 +1,126 @@ +import { + wormhole, + Wormhole, + TokenId, + TokenAddress, +} from '@wormhole-foundation/sdk'; +import { signSendWait, toNative } from '@wormhole-foundation/sdk-connect'; +import evm from '@wormhole-foundation/sdk/evm'; +import solana from '@wormhole-foundation/sdk/solana'; +import { getSigner } from './helper'; + +async function attestToken() { + // Initialize wormhole instance, define the network, platforms, and chains + const wh = await wormhole('Testnet', [evm, solana]); + const sourceChain = wh.getChain('Moonbeam'); + const destinationChain = wh.getChain('Solana'); + + // Define the token to check for a wrapped version + const tokenId: TokenId = Wormhole.tokenId( + sourceChain.chain, + 'INSERT_TOKEN_CONTRACT_ADDRESS' + ); + // Check if the token is registered with destinationChain token bridge contract + // Registered = returns the wrapped token ID + // Not registered = runs the attestation flow to register the token + let wrappedToken: TokenId; + try { + wrappedToken = await wh.getWrappedAsset(destinationChain.chain, tokenId); + console.log( + '✅ Token already registered on destination:', + wrappedToken.address + ); + } catch (e) { + console.log( + '⚠️ Token is NOT registered on destination. Running attestation flow...' + ); + // Attestation flow code + // Retrieve the token bridge context for the source chain + // This is where you will send the transaction to attest the token + const tb = await sourceChain.getTokenBridge(); + // Get the signer for the source chain + const sourceSigner = await getSigner(sourceChain); + // Define the token to attest and a payer address + const token: TokenAddress = toNative( + sourceChain.chain, + tokenId.address.toString() + ); + const payer = toNative(sourceChain.chain, sourceSigner.signer.address()); + // Call the `createAttestation` method to create a new attestation + // and sign and send the transaction + for await (const tx of tb.createAttestation(token, payer)) { + const txids = await signSendWait( + sourceChain, + tb.createAttestation(token), + sourceSigner.signer + ); + console.log('✅ Attestation transaction sent:', txids); + // Parse the transaction to get Wormhole message ID + const messages = await sourceChain.parseTransaction(txids[0].txid); + console.log('✅ Attestation messages:', messages); + // Set a timeout for fetching the VAA, this can take several minutes + // depending on the source chain network and finality + const timeout = 25 * 60 * 1000; + // Fetch the VAA for the attestation message + const vaa = await wh.getVaa( + messages[0]!, + 'TokenBridge:AttestMeta', + timeout + ); + if (!vaa) throw new Error('❌ VAA not found before timeout.'); + // Get the token bridge context for the destination chain + // and submit the attestation VAA + const destTb = await destinationChain.getTokenBridge(); + // Get the signer for the destination chain + const destinationSigner = await getSigner(destinationChain); + const payer = toNative( + destinationChain.chain, + destinationSigner.signer.address() + ); + const destTxids = await signSendWait( + destinationChain, + destTb.submitAttestation(vaa, payer), + destinationSigner.signer + ); + console.log('✅ Attestation submitted on destination:', destTxids); + } + // Poll for the wrapped token to appear on the destination chain + const maxAttempts = 50; // ~5 minutes with 6s interval + const interval = 6000; + let attempt = 0; + let registered = false; + + while (attempt < maxAttempts && !registered) { + attempt++; + try { + const wrapped = await wh.getWrappedAsset( + destinationChain.chain, + tokenId + ); + console.log( + `✅ Wrapped token is now available on ${destinationChain.chain}:`, + wrapped.address + ); + registered = true; + } catch { + console.log( + `⏳ Waiting for wrapped token to register on ${destinationChain.chain}...` + ); + await new Promise((res) => setTimeout(res, interval)); + } + } + if (!registered) { + throw new Error( + `❌ Token attestation did not complete in time on ${destinationChain.chain}` + ); + } + console.log( + `🚀 Token attestation complete! Token registered with ${destinationChain.chain}.` + ); + } +} + +attestToken().catch((e) => { + console.error('❌ Error in attestToken', e); + process.exit(1); +}); \ No newline at end of file diff --git a/.snippets/code/products/token-bridge/guides/attest-tokens/helper.ts b/.snippets/code/products/token-bridge/guides/attest-tokens/helper.ts new file mode 100644 index 000000000..b377050ce --- /dev/null +++ b/.snippets/code/products/token-bridge/guides/attest-tokens/helper.ts @@ -0,0 +1,59 @@ +import { + Chain, + ChainAddress, + ChainContext, + Wormhole, + Network, + Signer, +} from '@wormhole-foundation/sdk'; +import type { SignAndSendSigner } from '@wormhole-foundation/sdk'; +import evm from '@wormhole-foundation/sdk/evm'; +import solana from '@wormhole-foundation/sdk/solana'; +import sui from '@wormhole-foundation/sdk/sui'; + +/** + * Returns a signer for the given chain using locally scoped credentials. + * The required values (EVM_PRIVATE_KEY, SOL_PRIVATE_KEY, SUI_MNEMONIC) must + * be loaded securely beforehand, for example via a keystore, secrets + * manager, or environment variables (not recommended). + */ +export async function getSigner( + chain: ChainContext +): Promise<{ + chain: ChainContext; + signer: SignAndSendSigner; + address: ChainAddress; +}> { + let signer: Signer; + const platform = chain.platform.utils()._platform; + + // Customize the signer by adding or removing platforms as needed + // Be sure to import the necessary packages for the platforms you want to support + switch (platform) { + case 'Evm': + signer = await ( + await evm() + ).getSigner(await chain.getRpc(), EVM_PRIVATE_KEY!); + break; + case 'Solana': + signer = await ( + await solana() + ).getSigner(await chain.getRpc(), SOL_PRIVATE_KEY!); + break; + case 'Sui': + signer = await ( + await sui() + ).getSigner(await chain.getRpc(), SUI_MNEMONIC!); + break; + default: + throw new Error(`Unsupported platform: ${platform}`); + } + + const typedSigner = signer as SignAndSendSigner; + + return { + chain, + signer: typedSigner, + address: Wormhole.chainAddress(chain.chain, signer.address()), + }; +} diff --git a/.snippets/code/products/token-bridge/guides/attest-tokens/terminal01.html b/.snippets/code/products/token-bridge/guides/attest-tokens/terminal01.html new file mode 100644 index 000000000..259aa8f8f --- /dev/null +++ b/.snippets/code/products/token-bridge/guides/attest-tokens/terminal01.html @@ -0,0 +1,10 @@ +
+ npx tsx attest.ts + ✅ Token already registered on destination: SolanaAddress { + type: 'Native', + address: PublicKey [PublicKey(2qjSAGrpT2eTb673KuGAR5s6AJfQ1X5Sg177Qzuqt7yB)] { + _bn: BN: 1b578bb9b7a04a1aab3b5b64b550d8fc4f73ab343c9cf8532d2976b77ec4a8ca + } + } + +
\ No newline at end of file diff --git a/.snippets/code/products/token-bridge/guides/attest-tokens/terminal02.html b/.snippets/code/products/token-bridge/guides/attest-tokens/terminal02.html new file mode 100644 index 000000000..ab81d4920 --- /dev/null +++ b/.snippets/code/products/token-bridge/guides/attest-tokens/terminal02.html @@ -0,0 +1,5 @@ +
+ npx tsx attest.ts + ⚠️ Token is NOT registered on destination. Running attestation flow... + +
\ No newline at end of file diff --git a/.snippets/code/products/token-bridge/guides/attest-tokens/terminal03.html b/.snippets/code/products/token-bridge/guides/attest-tokens/terminal03.html new file mode 100644 index 000000000..7edb4604b --- /dev/null +++ b/.snippets/code/products/token-bridge/guides/attest-tokens/terminal03.html @@ -0,0 +1,24 @@ +
+ npx tsx attest.ts + ⚠️ Token is NOT registered on destination. Running attestation + flow... + ✅ Attestation transaction sent: [ { chain: 'Moonbeam', txid: + '0xbaf7429e1099cac6f39ef7e3c30e38776cfb5b6be837dcd8793374c8ee491799' } + ] + ✅ Attestation messages: [ { chain: 'Moonbeam', emitter: UniversalAddress { + address: [Uint8Array] }, sequence: 1507n } ] + Retrying Wormholescan:GetVaaBytes, attempt 0/750 + Retrying Wormholescan:GetVaaBytes, attempt 1/750 + ..... + Retrying Wormholescan:GetVaaBytes, attempt 10/750 + 📨 Submitting attestation VAA to Solana... + ✅ Attestation submitted on destination: [ { chain: 'Solana', txid: + '3R4oF5P85jK3wKgkRs5jmE8BBLoM4wo2hWSgXXL6kA8efbj2Vj9vfuFSb53xALqYZuv3FnXDwJNuJfiKKDwpDH1r' + } ] + ✅ Wrapped token is now available on Solana: SolanaAddress { type: + 'Native', address: PublicKey + [PublicKey(2qjSAGrpT2eTb673KuGAR5s6AJfQ1X5Sg177Qzuqt7yB)] { _bn: BN: + 1b578bb9b7a04a1aab3b5b64b550d8fc4f73ab343c9cf8532d2976b77ec4a8ca } } + 🚀 Token attestation complete! + +
\ No newline at end of file diff --git a/llms-files/llms-token-bridge.txt b/llms-files/llms-token-bridge.txt index 70ba96db7..b6511f5b4 100644 --- a/llms-files/llms-token-bridge.txt +++ b/llms-files/llms-token-bridge.txt @@ -17,6 +17,7 @@ Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/re Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/concepts/transfer-flow.md [type: other] Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/faqs.md [type: other] Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/get-started.md [type: other] +Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/guides/attest-tokens.md [type: other] Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/guides/token-bridge-contracts.md [type: other] Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/overview.md [type: other] @@ -837,6 +838,503 @@ Now that you've completed a manual multichain token transfer, explore these guid - [Create Multichain Tokens](/docs/products/token-bridge/tutorials/multichain-token){target=\_blank}: Learn how to issue tokens that work across chains. --- END CONTENT --- +Doc-Content: https://wormhole.com/docs/products/token-bridge/guides/attest-tokens/ +--- BEGIN CONTENT --- +--- +title: Token Attestation +description: Create and submit a token attestation to register a token for transfer with Token Bridge using the TypeScript SDK. Required before first-time transfers. +categories: Token-Bridge, Transfer +--- + +# Token Attestation + +## Introduction + +This guide demonstrates token attestation for registering a token for transfer using the Token Bridge protocol. An attestation of the token's metadata (e.g., symbol, name, decimals) ensures consistent handling by the destination chain for ease of multichain interoperability. These steps are only required the first time a token is sent to a particular destination chain. + +Completing this guide will help you to accomplish the following: + +- Verify if a wrapped version of a token exists on a destination chain +- Create and submit token attestation to register a wrapped version of a token on a destination chain +- Check for the wrapped version to become available on the destination chain and return the wrapped token address + +The example will register an arbitrary ERC-20 token deployed to Moonbase Alpha for transfer to Solana but can be adapted for any supported chains. + +## Prerequisites + +Before you begin, ensure you have the following: + +- [Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm){target=\_blank} installed on your machine +- [TypeScript](https://www.typescriptlang.org/download/){target=\_blank} installed globally +- The contract address for the token you wish to register +- A wallet setup with the following: + - Private keys for your source and destination chains + - A small amount of gas tokens on your source and destination chains + +## Set Up Your Developer Environment + +Follow these steps to initialize your project, install dependencies, and prepare your developer environment for token attestation. + +1. Create a new directory and initialize a Node.js project using the following commands: + ```bash + mkdir attest-token + cd attest-token + npm init -y + ``` + +2. Install dependencies, including the Wormhole TypeScript SDK: + ```bash + npm install @wormhole-foundation/sdk -D tsx typescript + ``` + +3. Set up secure access to your wallets. This guide assumes you are loading your private key values from a secure keystore of your choice, such as a secrets manager or a CLI-based tool like [`cast wallet`](https://book.getfoundry.sh/reference/cast/cast-wallet){target=\_blank}. + + !!! warning + If you use a `.env` file during development, add it to your `.gitignore` to exclude it from version control. Never commit private keys or mnemonics to your repository. + +4. Create a new file named `helper.ts` to hold signer functions: + ```bash + touch helper.ts + ``` + +5. Open `helper.ts` and add the following code: + ```typescript title="helper.ts" + import { + Chain, + ChainAddress, + ChainContext, + Wormhole, + Network, + Signer, +} from '@wormhole-foundation/sdk'; +import type { SignAndSendSigner } from '@wormhole-foundation/sdk'; +import evm from '@wormhole-foundation/sdk/evm'; +import solana from '@wormhole-foundation/sdk/solana'; +import sui from '@wormhole-foundation/sdk/sui'; + +/** + * Returns a signer for the given chain using locally scoped credentials. + * The required values (EVM_PRIVATE_KEY, SOL_PRIVATE_KEY, SUI_MNEMONIC) must + * be loaded securely beforehand, for example via a keystore, secrets + * manager, or environment variables (not recommended). + */ +export async function getSigner( + chain: ChainContext +): Promise<{ + chain: ChainContext; + signer: SignAndSendSigner; + address: ChainAddress; +}> { + let signer: Signer; + const platform = chain.platform.utils()._platform; + + // Customize the signer by adding or removing platforms as needed + // Be sure to import the necessary packages for the platforms you want to support + switch (platform) { + case 'Evm': + signer = await ( + await evm() + ).getSigner(await chain.getRpc(), EVM_PRIVATE_KEY!); + break; + case 'Solana': + signer = await ( + await solana() + ).getSigner(await chain.getRpc(), SOL_PRIVATE_KEY!); + break; + case 'Sui': + signer = await ( + await sui() + ).getSigner(await chain.getRpc(), SUI_MNEMONIC!); + break; + default: + throw new Error(`Unsupported platform: ${platform}`); + } + + const typedSigner = signer as SignAndSendSigner; + + return { + chain, + signer: typedSigner, + address: Wormhole.chainAddress(chain.chain, signer.address()), + }; +} + + ``` + + You can view the [constants for platform names](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/3eae2e91fc3a6fec859eb87cfa85a4c92c65466f/core/base/src/constants/platforms.ts#L6){target=\_blank} in the GitHub repo for a list of supported platforms + +## Check for Wrapped Version + +If you are working with a newly created token that you know has never been transferred to the destination chain, you can continue to the [Create Attestation on the Source Chain](#create-attestation-on-the-source-chain) section. + +Since attestation is a one-time process, it is good practice when working with existing tokens to incorporate a check for wrapped versions into your Token Bridge transfer flow. Follow these steps to check for a wrapped version of a token: + +1. Create a new file called `attest.ts` to hold the wrapped version check and attestation logic: + ```bash + touch attest.ts + ``` + +2. Open `attest.ts` and add the following code: + ```typescript title="attest.ts" + wormhole, + Wormhole, + TokenId, + TokenAddress, +} from '@wormhole-foundation/sdk'; +import { signSendWait, toNative } from '@wormhole-foundation/sdk-connect'; +import evm from '@wormhole-foundation/sdk/evm'; +import solana from '@wormhole-foundation/sdk/solana'; +import { getSigner } from './helper'; + +async function attestToken() { + // Initialize wormhole instance, define the network, platforms, and chains + const wh = await wormhole('Testnet', [evm, solana]); + const sourceChain = wh.getChain('Moonbeam'); + const destinationChain = wh.getChain('Solana'); + + // Define the token to check for a wrapped version + const tokenId: TokenId = Wormhole.tokenId( + sourceChain.chain, + 'INSERT_TOKEN_CONTRACT_ADDRESS' + ); + // Check if the token is registered with destinationChain token bridge contract + // Registered = returns the wrapped token ID + // Not registered = runs the attestation flow to register the token + let wrappedToken: TokenId; + try { + wrappedToken = await wh.getWrappedAsset(destinationChain.chain, tokenId); + console.log( + '✅ Token already registered on destination:', + wrappedToken.address + ); + } catch (e) { + console.log( + '⚠️ Token is NOT registered on destination. Running attestation flow...' + ); + // Attestation flow code + console.error('❌ Error in attestToken', e); + process.exit(1); +});
+ ``` + + After initializing a Wormhole instance and defining the source and destination chains, this code does the following: + + - **Defines the token to check**: use the contract address on the source chain for this value. + - **Calls `getWrappedAsset`**: part of the [`Wormhole` class](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/connect/src/wormhole.ts#L47){target=\_blank}, the [`getWrappedAsset`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/connect/src/wormhole.ts#L205){target=\_blank} method: + - Accepts a [`TokenId`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/platforms/aptos/protocols/tokenBridge/src/types.ts#L12){target=\_blank} representing a token on the source chain. + - Checks for a corresponding wrapped version of the destination chain's Token Bridge contract. + - Returns the `TokenId` for the wrapped token on the destination chain if a wrapped version exists. + +3. Run the script using the following command: + ```bash + npx tsx attest.ts + ``` + +4. If the token has a wrapped version registered with the destination chain Token Bridge contract, you will see terminal output similar to the following: + +
+npx tsx attest.ts +✅ Token already registered on destination: SolanaAddress { + type: 'Native', + address: PublicKey [PublicKey(2qjSAGrpT2eTb673KuGAR5s6AJfQ1X5Sg177Qzuqt7yB)] { + _bn: BN: 1b578bb9b7a04a1aab3b5b64b550d8fc4f73ab343c9cf8532d2976b77ec4a8ca + } + } + +
+ + You can safely use Token Bridge to transfer this token to the destination chain. + + If a wrapped version isn't found on the destination chain, your terminal output will be similar to the following and you must attest the token before transfer: + +
+npx tsx attest.ts +⚠️ Token is NOT registered on destination. Running attestation flow... + +
+ +## Create Attestation on the Source Chain + +To create the attestation transaction on the source chain, open `attest.ts` and replace the "// Attestation flow code" comment with the following code: +```typescript title="attest.ts" +const tb = await sourceChain.getTokenBridge(); + // Get the signer for the source chain + const sourceSigner = await getSigner(sourceChain); + // Define the token to attest and a payer address + const token: TokenAddress = toNative( + sourceChain.chain, + tokenId.address.toString() + ); + const payer = toNative(sourceChain.chain, sourceSigner.signer.address()); + // Call the `createAttestation` method to create a new attestation + // and sign and send the transaction + for await (const tx of tb.createAttestation(token, payer)) { + const txids = await signSendWait( + sourceChain, + tb.createAttestation(token), + sourceSigner.signer + ); + console.log('✅ Attestation transaction sent:', txids); +``` + +This code does the following: + +- **Gets the source chain Token Bridge context**: this is where the transaction is sent to create the attestation. +- Defines the token to attest and the payer. +- **Calls `createAttestation`**: defined in the [`TokenBridge` interface](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L123){target=\_blank}, the [`createAttestation`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L188){target=\_blank} method does the following: + - Accepts a `TokenAddress` representing the token on its native chain. + - Accepts an optional `payer` address to cover the transaction fees for the attestation transaction. + - Prepares an attestation for the token including metadata such as address, symbol, and decimals. + - Returns an `AsyncGenerator` that yields unsigned transactions, which are then signed and sent to initiate the attestation process on the source chain. + +## Submit Attestation on Destination Chain + +The attestation flow finishes with the following: + +- Using the transaction ID returned from the `createAttestation` transaction on the source chain to retrieve the associated signed `TokenBridge:AttestMeta` VAA. +- Submitting the signed VAA to the destination chain to provide Guardian-backed verification of the attestation transaction on the source chain. +- The destination chain uses the attested metadata to create the wrapped version of the token and register it with its Token Bridge contract. + +Follow these steps to complete your attestation flow logic: + +1. Add the following code to `attest.ts`: + ```typescript title="attest.ts" + const messages = await sourceChain.parseTransaction(txids[0].txid); + console.log('✅ Attestation messages:', messages); + // Set a timeout for fetching the VAA, this can take several minutes + // depending on the source chain network and finality + const timeout = 25 * 60 * 1000; + // Fetch the VAA for the attestation message + const vaa = await wh.getVaa( + messages[0]!, + 'TokenBridge:AttestMeta', + timeout + ); + if (!vaa) throw new Error('❌ VAA not found before timeout.'); + // Get the token bridge context for the destination chain + // and submit the attestation VAA + const destTb = await destinationChain.getTokenBridge(); + // Get the signer for the destination chain + const destinationSigner = await getSigner(destinationChain); + const payer = toNative( + destinationChain.chain, + destinationSigner.signer.address() + ); + const destTxids = await signSendWait( + destinationChain, + destTb.submitAttestation(vaa, payer), + destinationSigner.signer + ); + console.log('✅ Attestation submitted on destination:', destTxids); + } + // Poll for the wrapped token to appear on the destination chain + const maxAttempts = 50; // ~5 minutes with 6s interval + const interval = 6000; + let attempt = 0; + let registered = false; + + while (attempt < maxAttempts && !registered) { + attempt++; + try { + const wrapped = await wh.getWrappedAsset( + destinationChain.chain, + tokenId + ); + console.log( + `✅ Wrapped token is now available on ${destinationChain.chain}:`, + wrapped.address + ); + registered = true; + } catch { + console.log( + `⏳ Waiting for wrapped token to register on ${destinationChain.chain}...` + ); + await new Promise((res) => setTimeout(res, interval)); + } + } + if (!registered) { + throw new Error( + `❌ Token attestation did not complete in time on ${destinationChain.chain}` + ); + } + console.log( + `🚀 Token attestation complete! Token registered with ${destinationChain.chain}.` + ); + } +} + ``` + +2. Run the script using the following command: + ```bash + npx tsx attest.ts + ``` + +3. You will see terminal output similar to the following: + +
+npx tsx attest.ts +⚠️ Token is NOT registered on destination. Running attestation + flow... +✅ Attestation transaction sent: [ { chain: 'Moonbeam', txid: + '0xbaf7429e1099cac6f39ef7e3c30e38776cfb5b6be837dcd8793374c8ee491799' } + ] +✅ Attestation messages: [ { chain: 'Moonbeam', emitter: UniversalAddress { + address: [Uint8Array] }, sequence: 1507n } ] +Retrying Wormholescan:GetVaaBytes, attempt 0/750 +Retrying Wormholescan:GetVaaBytes, attempt 1/750 +..... +Retrying Wormholescan:GetVaaBytes, attempt 10/750 +📨 Submitting attestation VAA to Solana... +✅ Attestation submitted on destination: [ { chain: 'Solana', txid: + '3R4oF5P85jK3wKgkRs5jmE8BBLoM4wo2hWSgXXL6kA8efbj2Vj9vfuFSb53xALqYZuv3FnXDwJNuJfiKKDwpDH1r' + } ] +✅ Wrapped token is now available on Solana: SolanaAddress { type: + 'Native', address: PublicKey + [PublicKey(2qjSAGrpT2eTb673KuGAR5s6AJfQ1X5Sg177Qzuqt7yB)] { _bn: BN: + 1b578bb9b7a04a1aab3b5b64b550d8fc4f73ab343c9cf8532d2976b77ec4a8ca } } +🚀 Token attestation complete! + +
+ + ??? example "View complete script" + ```typescript title="attest.ts" + import { + wormhole, + Wormhole, + TokenId, + TokenAddress, +} from '@wormhole-foundation/sdk'; +import { signSendWait, toNative } from '@wormhole-foundation/sdk-connect'; +import evm from '@wormhole-foundation/sdk/evm'; +import solana from '@wormhole-foundation/sdk/solana'; +import { getSigner } from './helper'; + +async function attestToken() { + // Initialize wormhole instance, define the network, platforms, and chains + const wh = await wormhole('Testnet', [evm, solana]); + const sourceChain = wh.getChain('Moonbeam'); + const destinationChain = wh.getChain('Solana'); + + // Define the token to check for a wrapped version + const tokenId: TokenId = Wormhole.tokenId( + sourceChain.chain, + 'INSERT_TOKEN_CONTRACT_ADDRESS' + ); + // Check if the token is registered with destinationChain token bridge contract + // Registered = returns the wrapped token ID + // Not registered = runs the attestation flow to register the token + let wrappedToken: TokenId; + try { + wrappedToken = await wh.getWrappedAsset(destinationChain.chain, tokenId); + console.log( + '✅ Token already registered on destination:', + wrappedToken.address + ); + } catch (e) { + console.log( + '⚠️ Token is NOT registered on destination. Running attestation flow...' + ); + // Attestation flow code + // Retrieve the token bridge context for the source chain + // This is where you will send the transaction to attest the token + const tb = await sourceChain.getTokenBridge(); + // Get the signer for the source chain + const sourceSigner = await getSigner(sourceChain); + // Define the token to attest and a payer address + const token: TokenAddress = toNative( + sourceChain.chain, + tokenId.address.toString() + ); + const payer = toNative(sourceChain.chain, sourceSigner.signer.address()); + // Call the `createAttestation` method to create a new attestation + // and sign and send the transaction + for await (const tx of tb.createAttestation(token, payer)) { + const txids = await signSendWait( + sourceChain, + tb.createAttestation(token), + sourceSigner.signer + ); + console.log('✅ Attestation transaction sent:', txids); + // Parse the transaction to get Wormhole message ID + const messages = await sourceChain.parseTransaction(txids[0].txid); + console.log('✅ Attestation messages:', messages); + // Set a timeout for fetching the VAA, this can take several minutes + // depending on the source chain network and finality + const timeout = 25 * 60 * 1000; + // Fetch the VAA for the attestation message + const vaa = await wh.getVaa( + messages[0]!, + 'TokenBridge:AttestMeta', + timeout + ); + if (!vaa) throw new Error('❌ VAA not found before timeout.'); + // Get the token bridge context for the destination chain + // and submit the attestation VAA + const destTb = await destinationChain.getTokenBridge(); + // Get the signer for the destination chain + const destinationSigner = await getSigner(destinationChain); + const payer = toNative( + destinationChain.chain, + destinationSigner.signer.address() + ); + const destTxids = await signSendWait( + destinationChain, + destTb.submitAttestation(vaa, payer), + destinationSigner.signer + ); + console.log('✅ Attestation submitted on destination:', destTxids); + } + // Poll for the wrapped token to appear on the destination chain + const maxAttempts = 50; // ~5 minutes with 6s interval + const interval = 6000; + let attempt = 0; + let registered = false; + + while (attempt < maxAttempts && !registered) { + attempt++; + try { + const wrapped = await wh.getWrappedAsset( + destinationChain.chain, + tokenId + ); + console.log( + `✅ Wrapped token is now available on ${destinationChain.chain}:`, + wrapped.address + ); + registered = true; + } catch { + console.log( + `⏳ Waiting for wrapped token to register on ${destinationChain.chain}...` + ); + await new Promise((res) => setTimeout(res, interval)); + } + } + if (!registered) { + throw new Error( + `❌ Token attestation did not complete in time on ${destinationChain.chain}` + ); + } + console.log( + `🚀 Token attestation complete! Token registered with ${destinationChain.chain}.` + ); + } +} + +attestToken().catch((e) => { + console.error('❌ Error in attestToken', e); + process.exit(1); +}); + ``` + +Congratulations! You've successfully created and submitted an attestation to register a token for transfer via Token Bridge. Consider the following options to build upon what you've achieved. + +## Next Steps + +- [**Transfer Wrapped Assets**](/docs/products/token-bridge/guides/attest-tokens): follow this guide to incorporate token attestation and registration into an end-to-end Token Bridge transfer flow. + +TODO: What else should be here? +--- END CONTENT --- + Doc-Content: https://wormhole.com/docs/products/token-bridge/guides/token-bridge-contracts/ --- BEGIN CONTENT --- --- diff --git a/llms-files/llms-transfer.txt b/llms-files/llms-transfer.txt index 8886537fe..faa2d6634 100644 --- a/llms-files/llms-transfer.txt +++ b/llms-files/llms-transfer.txt @@ -47,6 +47,7 @@ Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/re Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/settlement/overview.md [type: other] Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/concepts/transfer-flow.md [type: other] Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/faqs.md [type: other] +Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/guides/attest-tokens.md [type: other] Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/guides/token-bridge-contracts.md [type: other] Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/overview.md [type: other] @@ -7529,6 +7530,503 @@ Updating the metadata (such as the token image, name, or symbol) of a wrapped to 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/products/token-bridge/guides/attest-tokens/ +--- BEGIN CONTENT --- +--- +title: Token Attestation +description: Create and submit a token attestation to register a token for transfer with Token Bridge using the TypeScript SDK. Required before first-time transfers. +categories: Token-Bridge, Transfer +--- + +# Token Attestation + +## Introduction + +This guide demonstrates token attestation for registering a token for transfer using the Token Bridge protocol. An attestation of the token's metadata (e.g., symbol, name, decimals) ensures consistent handling by the destination chain for ease of multichain interoperability. These steps are only required the first time a token is sent to a particular destination chain. + +Completing this guide will help you to accomplish the following: + +- Verify if a wrapped version of a token exists on a destination chain +- Create and submit token attestation to register a wrapped version of a token on a destination chain +- Check for the wrapped version to become available on the destination chain and return the wrapped token address + +The example will register an arbitrary ERC-20 token deployed to Moonbase Alpha for transfer to Solana but can be adapted for any supported chains. + +## Prerequisites + +Before you begin, ensure you have the following: + +- [Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm){target=\_blank} installed on your machine +- [TypeScript](https://www.typescriptlang.org/download/){target=\_blank} installed globally +- The contract address for the token you wish to register +- A wallet setup with the following: + - Private keys for your source and destination chains + - A small amount of gas tokens on your source and destination chains + +## Set Up Your Developer Environment + +Follow these steps to initialize your project, install dependencies, and prepare your developer environment for token attestation. + +1. Create a new directory and initialize a Node.js project using the following commands: + ```bash + mkdir attest-token + cd attest-token + npm init -y + ``` + +2. Install dependencies, including the Wormhole TypeScript SDK: + ```bash + npm install @wormhole-foundation/sdk -D tsx typescript + ``` + +3. Set up secure access to your wallets. This guide assumes you are loading your private key values from a secure keystore of your choice, such as a secrets manager or a CLI-based tool like [`cast wallet`](https://book.getfoundry.sh/reference/cast/cast-wallet){target=\_blank}. + + !!! warning + If you use a `.env` file during development, add it to your `.gitignore` to exclude it from version control. Never commit private keys or mnemonics to your repository. + +4. Create a new file named `helper.ts` to hold signer functions: + ```bash + touch helper.ts + ``` + +5. Open `helper.ts` and add the following code: + ```typescript title="helper.ts" + import { + Chain, + ChainAddress, + ChainContext, + Wormhole, + Network, + Signer, +} from '@wormhole-foundation/sdk'; +import type { SignAndSendSigner } from '@wormhole-foundation/sdk'; +import evm from '@wormhole-foundation/sdk/evm'; +import solana from '@wormhole-foundation/sdk/solana'; +import sui from '@wormhole-foundation/sdk/sui'; + +/** + * Returns a signer for the given chain using locally scoped credentials. + * The required values (EVM_PRIVATE_KEY, SOL_PRIVATE_KEY, SUI_MNEMONIC) must + * be loaded securely beforehand, for example via a keystore, secrets + * manager, or environment variables (not recommended). + */ +export async function getSigner( + chain: ChainContext +): Promise<{ + chain: ChainContext; + signer: SignAndSendSigner; + address: ChainAddress; +}> { + let signer: Signer; + const platform = chain.platform.utils()._platform; + + // Customize the signer by adding or removing platforms as needed + // Be sure to import the necessary packages for the platforms you want to support + switch (platform) { + case 'Evm': + signer = await ( + await evm() + ).getSigner(await chain.getRpc(), EVM_PRIVATE_KEY!); + break; + case 'Solana': + signer = await ( + await solana() + ).getSigner(await chain.getRpc(), SOL_PRIVATE_KEY!); + break; + case 'Sui': + signer = await ( + await sui() + ).getSigner(await chain.getRpc(), SUI_MNEMONIC!); + break; + default: + throw new Error(`Unsupported platform: ${platform}`); + } + + const typedSigner = signer as SignAndSendSigner; + + return { + chain, + signer: typedSigner, + address: Wormhole.chainAddress(chain.chain, signer.address()), + }; +} + + ``` + + You can view the [constants for platform names](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/3eae2e91fc3a6fec859eb87cfa85a4c92c65466f/core/base/src/constants/platforms.ts#L6){target=\_blank} in the GitHub repo for a list of supported platforms + +## Check for Wrapped Version + +If you are working with a newly created token that you know has never been transferred to the destination chain, you can continue to the [Create Attestation on the Source Chain](#create-attestation-on-the-source-chain) section. + +Since attestation is a one-time process, it is good practice when working with existing tokens to incorporate a check for wrapped versions into your Token Bridge transfer flow. Follow these steps to check for a wrapped version of a token: + +1. Create a new file called `attest.ts` to hold the wrapped version check and attestation logic: + ```bash + touch attest.ts + ``` + +2. Open `attest.ts` and add the following code: + ```typescript title="attest.ts" + wormhole, + Wormhole, + TokenId, + TokenAddress, +} from '@wormhole-foundation/sdk'; +import { signSendWait, toNative } from '@wormhole-foundation/sdk-connect'; +import evm from '@wormhole-foundation/sdk/evm'; +import solana from '@wormhole-foundation/sdk/solana'; +import { getSigner } from './helper'; + +async function attestToken() { + // Initialize wormhole instance, define the network, platforms, and chains + const wh = await wormhole('Testnet', [evm, solana]); + const sourceChain = wh.getChain('Moonbeam'); + const destinationChain = wh.getChain('Solana'); + + // Define the token to check for a wrapped version + const tokenId: TokenId = Wormhole.tokenId( + sourceChain.chain, + 'INSERT_TOKEN_CONTRACT_ADDRESS' + ); + // Check if the token is registered with destinationChain token bridge contract + // Registered = returns the wrapped token ID + // Not registered = runs the attestation flow to register the token + let wrappedToken: TokenId; + try { + wrappedToken = await wh.getWrappedAsset(destinationChain.chain, tokenId); + console.log( + '✅ Token already registered on destination:', + wrappedToken.address + ); + } catch (e) { + console.log( + '⚠️ Token is NOT registered on destination. Running attestation flow...' + ); + // Attestation flow code + console.error('❌ Error in attestToken', e); + process.exit(1); +});
+ ``` + + After initializing a Wormhole instance and defining the source and destination chains, this code does the following: + + - **Defines the token to check**: use the contract address on the source chain for this value. + - **Calls `getWrappedAsset`**: part of the [`Wormhole` class](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/connect/src/wormhole.ts#L47){target=\_blank}, the [`getWrappedAsset`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/connect/src/wormhole.ts#L205){target=\_blank} method: + - Accepts a [`TokenId`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/platforms/aptos/protocols/tokenBridge/src/types.ts#L12){target=\_blank} representing a token on the source chain. + - Checks for a corresponding wrapped version of the destination chain's Token Bridge contract. + - Returns the `TokenId` for the wrapped token on the destination chain if a wrapped version exists. + +3. Run the script using the following command: + ```bash + npx tsx attest.ts + ``` + +4. If the token has a wrapped version registered with the destination chain Token Bridge contract, you will see terminal output similar to the following: + +
+npx tsx attest.ts +✅ Token already registered on destination: SolanaAddress { + type: 'Native', + address: PublicKey [PublicKey(2qjSAGrpT2eTb673KuGAR5s6AJfQ1X5Sg177Qzuqt7yB)] { + _bn: BN: 1b578bb9b7a04a1aab3b5b64b550d8fc4f73ab343c9cf8532d2976b77ec4a8ca + } + } + +
+ + You can safely use Token Bridge to transfer this token to the destination chain. + + If a wrapped version isn't found on the destination chain, your terminal output will be similar to the following and you must attest the token before transfer: + +
+npx tsx attest.ts +⚠️ Token is NOT registered on destination. Running attestation flow... + +
+ +## Create Attestation on the Source Chain + +To create the attestation transaction on the source chain, open `attest.ts` and replace the "// Attestation flow code" comment with the following code: +```typescript title="attest.ts" +const tb = await sourceChain.getTokenBridge(); + // Get the signer for the source chain + const sourceSigner = await getSigner(sourceChain); + // Define the token to attest and a payer address + const token: TokenAddress = toNative( + sourceChain.chain, + tokenId.address.toString() + ); + const payer = toNative(sourceChain.chain, sourceSigner.signer.address()); + // Call the `createAttestation` method to create a new attestation + // and sign and send the transaction + for await (const tx of tb.createAttestation(token, payer)) { + const txids = await signSendWait( + sourceChain, + tb.createAttestation(token), + sourceSigner.signer + ); + console.log('✅ Attestation transaction sent:', txids); +``` + +This code does the following: + +- **Gets the source chain Token Bridge context**: this is where the transaction is sent to create the attestation. +- Defines the token to attest and the payer. +- **Calls `createAttestation`**: defined in the [`TokenBridge` interface](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L123){target=\_blank}, the [`createAttestation`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L188){target=\_blank} method does the following: + - Accepts a `TokenAddress` representing the token on its native chain. + - Accepts an optional `payer` address to cover the transaction fees for the attestation transaction. + - Prepares an attestation for the token including metadata such as address, symbol, and decimals. + - Returns an `AsyncGenerator` that yields unsigned transactions, which are then signed and sent to initiate the attestation process on the source chain. + +## Submit Attestation on Destination Chain + +The attestation flow finishes with the following: + +- Using the transaction ID returned from the `createAttestation` transaction on the source chain to retrieve the associated signed `TokenBridge:AttestMeta` VAA. +- Submitting the signed VAA to the destination chain to provide Guardian-backed verification of the attestation transaction on the source chain. +- The destination chain uses the attested metadata to create the wrapped version of the token and register it with its Token Bridge contract. + +Follow these steps to complete your attestation flow logic: + +1. Add the following code to `attest.ts`: + ```typescript title="attest.ts" + const messages = await sourceChain.parseTransaction(txids[0].txid); + console.log('✅ Attestation messages:', messages); + // Set a timeout for fetching the VAA, this can take several minutes + // depending on the source chain network and finality + const timeout = 25 * 60 * 1000; + // Fetch the VAA for the attestation message + const vaa = await wh.getVaa( + messages[0]!, + 'TokenBridge:AttestMeta', + timeout + ); + if (!vaa) throw new Error('❌ VAA not found before timeout.'); + // Get the token bridge context for the destination chain + // and submit the attestation VAA + const destTb = await destinationChain.getTokenBridge(); + // Get the signer for the destination chain + const destinationSigner = await getSigner(destinationChain); + const payer = toNative( + destinationChain.chain, + destinationSigner.signer.address() + ); + const destTxids = await signSendWait( + destinationChain, + destTb.submitAttestation(vaa, payer), + destinationSigner.signer + ); + console.log('✅ Attestation submitted on destination:', destTxids); + } + // Poll for the wrapped token to appear on the destination chain + const maxAttempts = 50; // ~5 minutes with 6s interval + const interval = 6000; + let attempt = 0; + let registered = false; + + while (attempt < maxAttempts && !registered) { + attempt++; + try { + const wrapped = await wh.getWrappedAsset( + destinationChain.chain, + tokenId + ); + console.log( + `✅ Wrapped token is now available on ${destinationChain.chain}:`, + wrapped.address + ); + registered = true; + } catch { + console.log( + `⏳ Waiting for wrapped token to register on ${destinationChain.chain}...` + ); + await new Promise((res) => setTimeout(res, interval)); + } + } + if (!registered) { + throw new Error( + `❌ Token attestation did not complete in time on ${destinationChain.chain}` + ); + } + console.log( + `🚀 Token attestation complete! Token registered with ${destinationChain.chain}.` + ); + } +} + ``` + +2. Run the script using the following command: + ```bash + npx tsx attest.ts + ``` + +3. You will see terminal output similar to the following: + +
+npx tsx attest.ts +⚠️ Token is NOT registered on destination. Running attestation + flow... +✅ Attestation transaction sent: [ { chain: 'Moonbeam', txid: + '0xbaf7429e1099cac6f39ef7e3c30e38776cfb5b6be837dcd8793374c8ee491799' } + ] +✅ Attestation messages: [ { chain: 'Moonbeam', emitter: UniversalAddress { + address: [Uint8Array] }, sequence: 1507n } ] +Retrying Wormholescan:GetVaaBytes, attempt 0/750 +Retrying Wormholescan:GetVaaBytes, attempt 1/750 +..... +Retrying Wormholescan:GetVaaBytes, attempt 10/750 +📨 Submitting attestation VAA to Solana... +✅ Attestation submitted on destination: [ { chain: 'Solana', txid: + '3R4oF5P85jK3wKgkRs5jmE8BBLoM4wo2hWSgXXL6kA8efbj2Vj9vfuFSb53xALqYZuv3FnXDwJNuJfiKKDwpDH1r' + } ] +✅ Wrapped token is now available on Solana: SolanaAddress { type: + 'Native', address: PublicKey + [PublicKey(2qjSAGrpT2eTb673KuGAR5s6AJfQ1X5Sg177Qzuqt7yB)] { _bn: BN: + 1b578bb9b7a04a1aab3b5b64b550d8fc4f73ab343c9cf8532d2976b77ec4a8ca } } +🚀 Token attestation complete! + +
+ + ??? example "View complete script" + ```typescript title="attest.ts" + import { + wormhole, + Wormhole, + TokenId, + TokenAddress, +} from '@wormhole-foundation/sdk'; +import { signSendWait, toNative } from '@wormhole-foundation/sdk-connect'; +import evm from '@wormhole-foundation/sdk/evm'; +import solana from '@wormhole-foundation/sdk/solana'; +import { getSigner } from './helper'; + +async function attestToken() { + // Initialize wormhole instance, define the network, platforms, and chains + const wh = await wormhole('Testnet', [evm, solana]); + const sourceChain = wh.getChain('Moonbeam'); + const destinationChain = wh.getChain('Solana'); + + // Define the token to check for a wrapped version + const tokenId: TokenId = Wormhole.tokenId( + sourceChain.chain, + 'INSERT_TOKEN_CONTRACT_ADDRESS' + ); + // Check if the token is registered with destinationChain token bridge contract + // Registered = returns the wrapped token ID + // Not registered = runs the attestation flow to register the token + let wrappedToken: TokenId; + try { + wrappedToken = await wh.getWrappedAsset(destinationChain.chain, tokenId); + console.log( + '✅ Token already registered on destination:', + wrappedToken.address + ); + } catch (e) { + console.log( + '⚠️ Token is NOT registered on destination. Running attestation flow...' + ); + // Attestation flow code + // Retrieve the token bridge context for the source chain + // This is where you will send the transaction to attest the token + const tb = await sourceChain.getTokenBridge(); + // Get the signer for the source chain + const sourceSigner = await getSigner(sourceChain); + // Define the token to attest and a payer address + const token: TokenAddress = toNative( + sourceChain.chain, + tokenId.address.toString() + ); + const payer = toNative(sourceChain.chain, sourceSigner.signer.address()); + // Call the `createAttestation` method to create a new attestation + // and sign and send the transaction + for await (const tx of tb.createAttestation(token, payer)) { + const txids = await signSendWait( + sourceChain, + tb.createAttestation(token), + sourceSigner.signer + ); + console.log('✅ Attestation transaction sent:', txids); + // Parse the transaction to get Wormhole message ID + const messages = await sourceChain.parseTransaction(txids[0].txid); + console.log('✅ Attestation messages:', messages); + // Set a timeout for fetching the VAA, this can take several minutes + // depending on the source chain network and finality + const timeout = 25 * 60 * 1000; + // Fetch the VAA for the attestation message + const vaa = await wh.getVaa( + messages[0]!, + 'TokenBridge:AttestMeta', + timeout + ); + if (!vaa) throw new Error('❌ VAA not found before timeout.'); + // Get the token bridge context for the destination chain + // and submit the attestation VAA + const destTb = await destinationChain.getTokenBridge(); + // Get the signer for the destination chain + const destinationSigner = await getSigner(destinationChain); + const payer = toNative( + destinationChain.chain, + destinationSigner.signer.address() + ); + const destTxids = await signSendWait( + destinationChain, + destTb.submitAttestation(vaa, payer), + destinationSigner.signer + ); + console.log('✅ Attestation submitted on destination:', destTxids); + } + // Poll for the wrapped token to appear on the destination chain + const maxAttempts = 50; // ~5 minutes with 6s interval + const interval = 6000; + let attempt = 0; + let registered = false; + + while (attempt < maxAttempts && !registered) { + attempt++; + try { + const wrapped = await wh.getWrappedAsset( + destinationChain.chain, + tokenId + ); + console.log( + `✅ Wrapped token is now available on ${destinationChain.chain}:`, + wrapped.address + ); + registered = true; + } catch { + console.log( + `⏳ Waiting for wrapped token to register on ${destinationChain.chain}...` + ); + await new Promise((res) => setTimeout(res, interval)); + } + } + if (!registered) { + throw new Error( + `❌ Token attestation did not complete in time on ${destinationChain.chain}` + ); + } + console.log( + `🚀 Token attestation complete! Token registered with ${destinationChain.chain}.` + ); + } +} + +attestToken().catch((e) => { + console.error('❌ Error in attestToken', e); + process.exit(1); +}); + ``` + +Congratulations! You've successfully created and submitted an attestation to register a token for transfer via Token Bridge. Consider the following options to build upon what you've achieved. + +## Next Steps + +- [**Transfer Wrapped Assets**](/docs/products/token-bridge/guides/attest-tokens): follow this guide to incorporate token attestation and registration into an end-to-end Token Bridge transfer flow. + +TODO: What else should be here? +--- END CONTENT --- + Doc-Content: https://wormhole.com/docs/products/token-bridge/guides/token-bridge-contracts/ --- BEGIN CONTENT --- --- diff --git a/llms-full.txt b/llms-full.txt index ed511506f..baa7a4dc7 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -74,6 +74,7 @@ Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/re Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/concepts/transfer-flow.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/faqs.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/get-started.md +Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/guides/attest-tokens.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/guides/token-bridge-contracts.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/overview.md Doc-Page: https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/tutorials/multichain-token.md @@ -16801,6 +16802,503 @@ Now that you've completed a manual multichain token transfer, explore these guid - [Create Multichain Tokens](/docs/products/token-bridge/tutorials/multichain-token){target=\_blank}: Learn how to issue tokens that work across chains. --- END CONTENT --- +Doc-Content: https://wormhole.com/docs/products/token-bridge/guides/attest-tokens/ +--- BEGIN CONTENT --- +--- +title: Token Attestation +description: Create and submit a token attestation to register a token for transfer with Token Bridge using the TypeScript SDK. Required before first-time transfers. +categories: Token-Bridge, Transfer +--- + +# Token Attestation + +## Introduction + +This guide demonstrates token attestation for registering a token for transfer using the Token Bridge protocol. An attestation of the token's metadata (e.g., symbol, name, decimals) ensures consistent handling by the destination chain for ease of multichain interoperability. These steps are only required the first time a token is sent to a particular destination chain. + +Completing this guide will help you to accomplish the following: + +- Verify if a wrapped version of a token exists on a destination chain +- Create and submit token attestation to register a wrapped version of a token on a destination chain +- Check for the wrapped version to become available on the destination chain and return the wrapped token address + +The example will register an arbitrary ERC-20 token deployed to Moonbase Alpha for transfer to Solana but can be adapted for any supported chains. + +## Prerequisites + +Before you begin, ensure you have the following: + +- [Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm){target=\_blank} installed on your machine +- [TypeScript](https://www.typescriptlang.org/download/){target=\_blank} installed globally +- The contract address for the token you wish to register +- A wallet setup with the following: + - Private keys for your source and destination chains + - A small amount of gas tokens on your source and destination chains + +## Set Up Your Developer Environment + +Follow these steps to initialize your project, install dependencies, and prepare your developer environment for token attestation. + +1. Create a new directory and initialize a Node.js project using the following commands: + ```bash + mkdir attest-token + cd attest-token + npm init -y + ``` + +2. Install dependencies, including the Wormhole TypeScript SDK: + ```bash + npm install @wormhole-foundation/sdk -D tsx typescript + ``` + +3. Set up secure access to your wallets. This guide assumes you are loading your private key values from a secure keystore of your choice, such as a secrets manager or a CLI-based tool like [`cast wallet`](https://book.getfoundry.sh/reference/cast/cast-wallet){target=\_blank}. + + !!! warning + If you use a `.env` file during development, add it to your `.gitignore` to exclude it from version control. Never commit private keys or mnemonics to your repository. + +4. Create a new file named `helper.ts` to hold signer functions: + ```bash + touch helper.ts + ``` + +5. Open `helper.ts` and add the following code: + ```typescript title="helper.ts" + import { + Chain, + ChainAddress, + ChainContext, + Wormhole, + Network, + Signer, +} from '@wormhole-foundation/sdk'; +import type { SignAndSendSigner } from '@wormhole-foundation/sdk'; +import evm from '@wormhole-foundation/sdk/evm'; +import solana from '@wormhole-foundation/sdk/solana'; +import sui from '@wormhole-foundation/sdk/sui'; + +/** + * Returns a signer for the given chain using locally scoped credentials. + * The required values (EVM_PRIVATE_KEY, SOL_PRIVATE_KEY, SUI_MNEMONIC) must + * be loaded securely beforehand, for example via a keystore, secrets + * manager, or environment variables (not recommended). + */ +export async function getSigner( + chain: ChainContext +): Promise<{ + chain: ChainContext; + signer: SignAndSendSigner; + address: ChainAddress; +}> { + let signer: Signer; + const platform = chain.platform.utils()._platform; + + // Customize the signer by adding or removing platforms as needed + // Be sure to import the necessary packages for the platforms you want to support + switch (platform) { + case 'Evm': + signer = await ( + await evm() + ).getSigner(await chain.getRpc(), EVM_PRIVATE_KEY!); + break; + case 'Solana': + signer = await ( + await solana() + ).getSigner(await chain.getRpc(), SOL_PRIVATE_KEY!); + break; + case 'Sui': + signer = await ( + await sui() + ).getSigner(await chain.getRpc(), SUI_MNEMONIC!); + break; + default: + throw new Error(`Unsupported platform: ${platform}`); + } + + const typedSigner = signer as SignAndSendSigner; + + return { + chain, + signer: typedSigner, + address: Wormhole.chainAddress(chain.chain, signer.address()), + }; +} + + ``` + + You can view the [constants for platform names](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/3eae2e91fc3a6fec859eb87cfa85a4c92c65466f/core/base/src/constants/platforms.ts#L6){target=\_blank} in the GitHub repo for a list of supported platforms + +## Check for Wrapped Version + +If you are working with a newly created token that you know has never been transferred to the destination chain, you can continue to the [Create Attestation on the Source Chain](#create-attestation-on-the-source-chain) section. + +Since attestation is a one-time process, it is good practice when working with existing tokens to incorporate a check for wrapped versions into your Token Bridge transfer flow. Follow these steps to check for a wrapped version of a token: + +1. Create a new file called `attest.ts` to hold the wrapped version check and attestation logic: + ```bash + touch attest.ts + ``` + +2. Open `attest.ts` and add the following code: + ```typescript title="attest.ts" + wormhole, + Wormhole, + TokenId, + TokenAddress, +} from '@wormhole-foundation/sdk'; +import { signSendWait, toNative } from '@wormhole-foundation/sdk-connect'; +import evm from '@wormhole-foundation/sdk/evm'; +import solana from '@wormhole-foundation/sdk/solana'; +import { getSigner } from './helper'; + +async function attestToken() { + // Initialize wormhole instance, define the network, platforms, and chains + const wh = await wormhole('Testnet', [evm, solana]); + const sourceChain = wh.getChain('Moonbeam'); + const destinationChain = wh.getChain('Solana'); + + // Define the token to check for a wrapped version + const tokenId: TokenId = Wormhole.tokenId( + sourceChain.chain, + 'INSERT_TOKEN_CONTRACT_ADDRESS' + ); + // Check if the token is registered with destinationChain token bridge contract + // Registered = returns the wrapped token ID + // Not registered = runs the attestation flow to register the token + let wrappedToken: TokenId; + try { + wrappedToken = await wh.getWrappedAsset(destinationChain.chain, tokenId); + console.log( + '✅ Token already registered on destination:', + wrappedToken.address + ); + } catch (e) { + console.log( + '⚠️ Token is NOT registered on destination. Running attestation flow...' + ); + // Attestation flow code + console.error('❌ Error in attestToken', e); + process.exit(1); +});
+ ``` + + After initializing a Wormhole instance and defining the source and destination chains, this code does the following: + + - **Defines the token to check**: use the contract address on the source chain for this value. + - **Calls `getWrappedAsset`**: part of the [`Wormhole` class](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/connect/src/wormhole.ts#L47){target=\_blank}, the [`getWrappedAsset`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/connect/src/wormhole.ts#L205){target=\_blank} method: + - Accepts a [`TokenId`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/platforms/aptos/protocols/tokenBridge/src/types.ts#L12){target=\_blank} representing a token on the source chain. + - Checks for a corresponding wrapped version of the destination chain's Token Bridge contract. + - Returns the `TokenId` for the wrapped token on the destination chain if a wrapped version exists. + +3. Run the script using the following command: + ```bash + npx tsx attest.ts + ``` + +4. If the token has a wrapped version registered with the destination chain Token Bridge contract, you will see terminal output similar to the following: + +
+npx tsx attest.ts +✅ Token already registered on destination: SolanaAddress { + type: 'Native', + address: PublicKey [PublicKey(2qjSAGrpT2eTb673KuGAR5s6AJfQ1X5Sg177Qzuqt7yB)] { + _bn: BN: 1b578bb9b7a04a1aab3b5b64b550d8fc4f73ab343c9cf8532d2976b77ec4a8ca + } + } + +
+ + You can safely use Token Bridge to transfer this token to the destination chain. + + If a wrapped version isn't found on the destination chain, your terminal output will be similar to the following and you must attest the token before transfer: + +
+npx tsx attest.ts +⚠️ Token is NOT registered on destination. Running attestation flow... + +
+ +## Create Attestation on the Source Chain + +To create the attestation transaction on the source chain, open `attest.ts` and replace the "// Attestation flow code" comment with the following code: +```typescript title="attest.ts" +const tb = await sourceChain.getTokenBridge(); + // Get the signer for the source chain + const sourceSigner = await getSigner(sourceChain); + // Define the token to attest and a payer address + const token: TokenAddress = toNative( + sourceChain.chain, + tokenId.address.toString() + ); + const payer = toNative(sourceChain.chain, sourceSigner.signer.address()); + // Call the `createAttestation` method to create a new attestation + // and sign and send the transaction + for await (const tx of tb.createAttestation(token, payer)) { + const txids = await signSendWait( + sourceChain, + tb.createAttestation(token), + sourceSigner.signer + ); + console.log('✅ Attestation transaction sent:', txids); +``` + +This code does the following: + +- **Gets the source chain Token Bridge context**: this is where the transaction is sent to create the attestation. +- Defines the token to attest and the payer. +- **Calls `createAttestation`**: defined in the [`TokenBridge` interface](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L123){target=\_blank}, the [`createAttestation`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L188){target=\_blank} method does the following: + - Accepts a `TokenAddress` representing the token on its native chain. + - Accepts an optional `payer` address to cover the transaction fees for the attestation transaction. + - Prepares an attestation for the token including metadata such as address, symbol, and decimals. + - Returns an `AsyncGenerator` that yields unsigned transactions, which are then signed and sent to initiate the attestation process on the source chain. + +## Submit Attestation on Destination Chain + +The attestation flow finishes with the following: + +- Using the transaction ID returned from the `createAttestation` transaction on the source chain to retrieve the associated signed `TokenBridge:AttestMeta` VAA. +- Submitting the signed VAA to the destination chain to provide Guardian-backed verification of the attestation transaction on the source chain. +- The destination chain uses the attested metadata to create the wrapped version of the token and register it with its Token Bridge contract. + +Follow these steps to complete your attestation flow logic: + +1. Add the following code to `attest.ts`: + ```typescript title="attest.ts" + const messages = await sourceChain.parseTransaction(txids[0].txid); + console.log('✅ Attestation messages:', messages); + // Set a timeout for fetching the VAA, this can take several minutes + // depending on the source chain network and finality + const timeout = 25 * 60 * 1000; + // Fetch the VAA for the attestation message + const vaa = await wh.getVaa( + messages[0]!, + 'TokenBridge:AttestMeta', + timeout + ); + if (!vaa) throw new Error('❌ VAA not found before timeout.'); + // Get the token bridge context for the destination chain + // and submit the attestation VAA + const destTb = await destinationChain.getTokenBridge(); + // Get the signer for the destination chain + const destinationSigner = await getSigner(destinationChain); + const payer = toNative( + destinationChain.chain, + destinationSigner.signer.address() + ); + const destTxids = await signSendWait( + destinationChain, + destTb.submitAttestation(vaa, payer), + destinationSigner.signer + ); + console.log('✅ Attestation submitted on destination:', destTxids); + } + // Poll for the wrapped token to appear on the destination chain + const maxAttempts = 50; // ~5 minutes with 6s interval + const interval = 6000; + let attempt = 0; + let registered = false; + + while (attempt < maxAttempts && !registered) { + attempt++; + try { + const wrapped = await wh.getWrappedAsset( + destinationChain.chain, + tokenId + ); + console.log( + `✅ Wrapped token is now available on ${destinationChain.chain}:`, + wrapped.address + ); + registered = true; + } catch { + console.log( + `⏳ Waiting for wrapped token to register on ${destinationChain.chain}...` + ); + await new Promise((res) => setTimeout(res, interval)); + } + } + if (!registered) { + throw new Error( + `❌ Token attestation did not complete in time on ${destinationChain.chain}` + ); + } + console.log( + `🚀 Token attestation complete! Token registered with ${destinationChain.chain}.` + ); + } +} + ``` + +2. Run the script using the following command: + ```bash + npx tsx attest.ts + ``` + +3. You will see terminal output similar to the following: + +
+npx tsx attest.ts +⚠️ Token is NOT registered on destination. Running attestation + flow... +✅ Attestation transaction sent: [ { chain: 'Moonbeam', txid: + '0xbaf7429e1099cac6f39ef7e3c30e38776cfb5b6be837dcd8793374c8ee491799' } + ] +✅ Attestation messages: [ { chain: 'Moonbeam', emitter: UniversalAddress { + address: [Uint8Array] }, sequence: 1507n } ] +Retrying Wormholescan:GetVaaBytes, attempt 0/750 +Retrying Wormholescan:GetVaaBytes, attempt 1/750 +..... +Retrying Wormholescan:GetVaaBytes, attempt 10/750 +📨 Submitting attestation VAA to Solana... +✅ Attestation submitted on destination: [ { chain: 'Solana', txid: + '3R4oF5P85jK3wKgkRs5jmE8BBLoM4wo2hWSgXXL6kA8efbj2Vj9vfuFSb53xALqYZuv3FnXDwJNuJfiKKDwpDH1r' + } ] +✅ Wrapped token is now available on Solana: SolanaAddress { type: + 'Native', address: PublicKey + [PublicKey(2qjSAGrpT2eTb673KuGAR5s6AJfQ1X5Sg177Qzuqt7yB)] { _bn: BN: + 1b578bb9b7a04a1aab3b5b64b550d8fc4f73ab343c9cf8532d2976b77ec4a8ca } } +🚀 Token attestation complete! + +
+ + ??? example "View complete script" + ```typescript title="attest.ts" + import { + wormhole, + Wormhole, + TokenId, + TokenAddress, +} from '@wormhole-foundation/sdk'; +import { signSendWait, toNative } from '@wormhole-foundation/sdk-connect'; +import evm from '@wormhole-foundation/sdk/evm'; +import solana from '@wormhole-foundation/sdk/solana'; +import { getSigner } from './helper'; + +async function attestToken() { + // Initialize wormhole instance, define the network, platforms, and chains + const wh = await wormhole('Testnet', [evm, solana]); + const sourceChain = wh.getChain('Moonbeam'); + const destinationChain = wh.getChain('Solana'); + + // Define the token to check for a wrapped version + const tokenId: TokenId = Wormhole.tokenId( + sourceChain.chain, + 'INSERT_TOKEN_CONTRACT_ADDRESS' + ); + // Check if the token is registered with destinationChain token bridge contract + // Registered = returns the wrapped token ID + // Not registered = runs the attestation flow to register the token + let wrappedToken: TokenId; + try { + wrappedToken = await wh.getWrappedAsset(destinationChain.chain, tokenId); + console.log( + '✅ Token already registered on destination:', + wrappedToken.address + ); + } catch (e) { + console.log( + '⚠️ Token is NOT registered on destination. Running attestation flow...' + ); + // Attestation flow code + // Retrieve the token bridge context for the source chain + // This is where you will send the transaction to attest the token + const tb = await sourceChain.getTokenBridge(); + // Get the signer for the source chain + const sourceSigner = await getSigner(sourceChain); + // Define the token to attest and a payer address + const token: TokenAddress = toNative( + sourceChain.chain, + tokenId.address.toString() + ); + const payer = toNative(sourceChain.chain, sourceSigner.signer.address()); + // Call the `createAttestation` method to create a new attestation + // and sign and send the transaction + for await (const tx of tb.createAttestation(token, payer)) { + const txids = await signSendWait( + sourceChain, + tb.createAttestation(token), + sourceSigner.signer + ); + console.log('✅ Attestation transaction sent:', txids); + // Parse the transaction to get Wormhole message ID + const messages = await sourceChain.parseTransaction(txids[0].txid); + console.log('✅ Attestation messages:', messages); + // Set a timeout for fetching the VAA, this can take several minutes + // depending on the source chain network and finality + const timeout = 25 * 60 * 1000; + // Fetch the VAA for the attestation message + const vaa = await wh.getVaa( + messages[0]!, + 'TokenBridge:AttestMeta', + timeout + ); + if (!vaa) throw new Error('❌ VAA not found before timeout.'); + // Get the token bridge context for the destination chain + // and submit the attestation VAA + const destTb = await destinationChain.getTokenBridge(); + // Get the signer for the destination chain + const destinationSigner = await getSigner(destinationChain); + const payer = toNative( + destinationChain.chain, + destinationSigner.signer.address() + ); + const destTxids = await signSendWait( + destinationChain, + destTb.submitAttestation(vaa, payer), + destinationSigner.signer + ); + console.log('✅ Attestation submitted on destination:', destTxids); + } + // Poll for the wrapped token to appear on the destination chain + const maxAttempts = 50; // ~5 minutes with 6s interval + const interval = 6000; + let attempt = 0; + let registered = false; + + while (attempt < maxAttempts && !registered) { + attempt++; + try { + const wrapped = await wh.getWrappedAsset( + destinationChain.chain, + tokenId + ); + console.log( + `✅ Wrapped token is now available on ${destinationChain.chain}:`, + wrapped.address + ); + registered = true; + } catch { + console.log( + `⏳ Waiting for wrapped token to register on ${destinationChain.chain}...` + ); + await new Promise((res) => setTimeout(res, interval)); + } + } + if (!registered) { + throw new Error( + `❌ Token attestation did not complete in time on ${destinationChain.chain}` + ); + } + console.log( + `🚀 Token attestation complete! Token registered with ${destinationChain.chain}.` + ); + } +} + +attestToken().catch((e) => { + console.error('❌ Error in attestToken', e); + process.exit(1); +}); + ``` + +Congratulations! You've successfully created and submitted an attestation to register a token for transfer via Token Bridge. Consider the following options to build upon what you've achieved. + +## Next Steps + +- [**Transfer Wrapped Assets**](/docs/products/token-bridge/guides/attest-tokens): follow this guide to incorporate token attestation and registration into an end-to-end Token Bridge transfer flow. + +TODO: What else should be here? +--- END CONTENT --- + Doc-Content: https://wormhole.com/docs/products/token-bridge/guides/token-bridge-contracts/ --- BEGIN CONTENT --- --- diff --git a/llms.txt b/llms.txt index b96b1956f..b86c1da99 100644 --- a/llms.txt +++ b/llms.txt @@ -72,6 +72,7 @@ - [Flow of a Token Bridge Transfer](https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/concepts/transfer-flow.md): Learn how the Wormhole Token Bridge enables secure, cross-chain token transfers by combining token-specific logic with Wormhole's core message-passing layer. - [Token Bridge FAQs](https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/faqs.md): Find answers to common questions about the Wormhole Token Bridge, including managing wrapped assets and understanding gas fees. - [Get Started with Token Bridge](https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/get-started.md): Perform token transfers using Wormhole’s Token Bridge with the TypeScript SDK, including manual (Solana–Sepolia) and automatic (Fuji–Alfajores). +- [Token Attestation](https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/guides/attest-tokens.md): Create and submit a token attestation to register a token for transfer with Token Bridge using the TypeScript SDK. Required before first-time transfers. - [Get Started with Token Bridge](https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/guides/token-bridge-contracts.md): Learn how to integrate Wormhole's Token Bridge for seamless multichain token transfers with a lock-and-mint mechanism and cross-chain asset management. - [Token Bridge Overview](https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/overview.md): With Wormhole Token Bridge, you can enable secure, multichain communication, build multichain apps, sync data, and coordinate actions across blockchains. - [Create Multichain Tokens](https://raw.githubusercontent.com/wormhole-foundation/wormhole-docs/refs/heads/main/products/token-bridge/tutorials/multichain-token.md): Learn how to create a multichain token, bridge tokens across blockchains, and update metadata for seamless multichain interoperability. diff --git a/products/token-bridge/guides/attest-tokens.md b/products/token-bridge/guides/attest-tokens.md index 2a91bfd07..aa7427ac1 100644 --- a/products/token-bridge/guides/attest-tokens.md +++ b/products/token-bridge/guides/attest-tokens.md @@ -1,6 +1,6 @@ --- title: Token Attestation -description: TODO +description: Create and submit a token attestation to register a token for transfer with Token Bridge using the TypeScript SDK. Required before first-time transfers. categories: Token-Bridge, Transfer --- @@ -8,8 +8,150 @@ categories: Token-Bridge, Transfer ## Introduction -This guide demonstrates token attestation in order to register a token for transfer using the Token Bridge protocol. The example will register an arbitrary ERC-20 token deployed to Moonbase Alpha for transfer to Ethereum Sepolia but can be adapted for any supported chains. +This guide demonstrates token attestation for registering a token for transfer using the Token Bridge protocol. An attestation of the token's metadata (e.g., symbol, name, decimals) ensures consistent handling by the destination chain for ease of multichain interoperability. These steps are only required the first time a token is sent to a particular destination chain. Completing this guide will help you to accomplish the following: -- \ No newline at end of file +- Verify if a wrapped version of a token exists on a destination chain +- Create and submit token attestation to register a wrapped version of a token on a destination chain +- Check for the wrapped version to become available on the destination chain and return the wrapped token address + +The example will register an arbitrary ERC-20 token deployed to Moonbase Alpha for transfer to Solana but can be adapted for any supported chains. + +## Prerequisites + +Before you begin, ensure you have the following: + +- [Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm){target=\_blank} installed on your machine +- [TypeScript](https://www.typescriptlang.org/download/){target=\_blank} installed globally +- The contract address for the token you wish to register +- A wallet setup with the following: + - Private keys for your source and destination chains + - A small amount of gas tokens on your source and destination chains + +## Set Up Your Developer Environment + +Follow these steps to initialize your project, install dependencies, and prepare your developer environment for token attestation. + +1. Create a new directory and initialize a Node.js project using the following commands: + ```bash + mkdir attest-token + cd attest-token + npm init -y + ``` + +2. Install dependencies, including the Wormhole TypeScript SDK: + ```bash + npm install @wormhole-foundation/sdk -D tsx typescript + ``` + +3. Set up secure access to your wallets. This guide assumes you are loading your private key values from a secure keystore of your choice, such as a secrets manager or a CLI-based tool like [`cast wallet`](https://book.getfoundry.sh/reference/cast/cast-wallet){target=\_blank}. + + !!! warning + If you use a `.env` file during development, add it to your `.gitignore` to exclude it from version control. Never commit private keys or mnemonics to your repository. + +4. Create a new file named `helper.ts` to hold signer functions: + ```bash + touch helper.ts + ``` + +5. Open `helper.ts` and add the following code: + ```typescript title="helper.ts" + --8<-- 'code/products/token-bridge/guides/attest-tokens/helper.ts' + ``` + + You can view the [constants for platform names](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/3eae2e91fc3a6fec859eb87cfa85a4c92c65466f/core/base/src/constants/platforms.ts#L6){target=\_blank} in the GitHub repo for a list of supported platforms + +## Check for Wrapped Version + +If you are working with a newly created token that you know has never been transferred to the destination chain, you can continue to the [Create Attestation on the Source Chain](#create-attestation-on-the-source-chain) section. + +Since attestation is a one-time process, it is good practice when working with existing tokens to incorporate a check for wrapped versions into your Token Bridge transfer flow. Follow these steps to check for a wrapped version of a token: + +1. Create a new file called `attest.ts` to hold the wrapped version check and attestation logic: + ```bash + touch attest.ts + ``` + +2. Open `attest.ts` and add the following code: + ```typescript title="attest.ts" + --8<-- 'code/products/token-bridge/guides/attest-tokens/attest.ts:1:37' + --8<-- 'code/products/token-bridge/guides/attest-tokens/attest.ts:123:126' + ``` + + After initializing a Wormhole instance and defining the source and destination chains, this code does the following: + + - **Defines the token to check**: use the contract address on the source chain for this value. + - **Calls `getWrappedAsset`**: part of the [`Wormhole` class](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/connect/src/wormhole.ts#L47){target=\_blank}, the [`getWrappedAsset`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/connect/src/wormhole.ts#L205){target=\_blank} method: + - Accepts a [`TokenId`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/platforms/aptos/protocols/tokenBridge/src/types.ts#L12){target=\_blank} representing a token on the source chain. + - Checks for a corresponding wrapped version of the destination chain's Token Bridge contract. + - Returns the `TokenId` for the wrapped token on the destination chain if a wrapped version exists. + +3. Run the script using the following command: + ```bash + npx tsx attest.ts + ``` + +4. If the token has a wrapped version registered with the destination chain Token Bridge contract, you will see terminal output similar to the following: + + --8<-- 'code/products/token-bridge/guides/attest-tokens/terminal01.html' + + You can safely use Token Bridge to transfer this token to the destination chain. + + If a wrapped version isn't found on the destination chain, your terminal output will be similar to the following and you must attest the token before transfer: + + --8<-- 'code/products/token-bridge/guides/attest-tokens/terminal02.html' + +## Create Attestation on the Source Chain + +To create the attestation transaction on the source chain, open `attest.ts` and replace the "// Attestation flow code" comment with the following code: +```typescript title="attest.ts" +--8<-- 'code/products/token-bridge/guides/attest-tokens/attest.ts:39:57' +``` + +This code does the following: + +- **Gets the source chain Token Bridge context**: this is where the transaction is sent to create the attestation. +- Defines the token to attest and the payer. +- **Calls `createAttestation`**: defined in the [`TokenBridge` interface](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L123){target=\_blank}, the [`createAttestation`](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/a48c9132015279ca6a2d3e9c238a54502b16fc7e/core/definitions/src/protocols/tokenBridge/tokenBridge.ts#L188){target=\_blank} method does the following: + - Accepts a `TokenAddress` representing the token on its native chain. + - Accepts an optional `payer` address to cover the transaction fees for the attestation transaction. + - Prepares an attestation for the token including metadata such as address, symbol, and decimals. + - Returns an `AsyncGenerator` that yields unsigned transactions, which are then signed and sent to initiate the attestation process on the source chain. + +## Submit Attestation on Destination Chain + +The attestation flow finishes with the following: + +- Using the transaction ID returned from the `createAttestation` transaction on the source chain to retrieve the associated signed `TokenBridge:AttestMeta` VAA. +- Submitting the signed VAA to the destination chain to provide Guardian-backed verification of the attestation transaction on the source chain. +- The destination chain uses the attested metadata to create the wrapped version of the token and register it with its Token Bridge contract. + +Follow these steps to complete your attestation flow logic: + +1. Add the following code to `attest.ts`: + ```typescript title="attest.ts" + --8<-- 'code/products/token-bridge/guides/attest-tokens/attest.ts:58:122' + ``` + +2. Run the script using the following command: + ```bash + npx tsx attest.ts + ``` + +3. You will see terminal output similar to the following: + + --8<-- 'code/products/token-bridge/guides/attest-tokens/terminal03.html' + + ??? example "View complete script" + ```typescript title="attest.ts" + --8<-- 'code/products/token-bridge/guides/attest-tokens/attest.ts' + ``` + +Congratulations! You've successfully created and submitted an attestation to register a token for transfer via Token Bridge. Consider the following options to build upon what you've achieved. + +## Next Steps + +- [**Transfer Wrapped Assets**](/docs/products/token-bridge/guides/attest-tokens): follow this guide to incorporate token attestation and registration into an end-to-end Token Bridge transfer flow. + +TODO: What else should be here? \ No newline at end of file From 632ac754dd0a5bbabd09dfd1664082ec4bb01035 Mon Sep 17 00:00:00 2001 From: DAWN KELLY Date: Fri, 20 Jun 2025 11:45:56 -0400 Subject: [PATCH 3/3] adds links, llms --- llms-files/llms-token-bridge.txt | 9 ++++++--- llms-files/llms-transfer.txt | 9 ++++++--- llms-full.txt | 9 ++++++--- products/token-bridge/guides/attest-tokens.md | 6 +++--- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/llms-files/llms-token-bridge.txt b/llms-files/llms-token-bridge.txt index 13e8d3ce9..6b7c5eb9b 100644 --- a/llms-files/llms-token-bridge.txt +++ b/llms-files/llms-token-bridge.txt @@ -850,7 +850,7 @@ categories: Token-Bridge, Transfer ## Introduction -This guide demonstrates token attestation for registering a token for transfer using the Token Bridge protocol. An attestation of the token's metadata (e.g., symbol, name, decimals) ensures consistent handling by the destination chain for ease of multichain interoperability. These steps are only required the first time a token is sent to a particular destination chain. +This guide demonstrates token attestation for registering a token for transfer using the [Token Bridge](/docs/products/token-bridge/overview) protocol. An attestation of the token's metadata (e.g., symbol, name, decimals) ensures consistent handling by the destination chain for ease of multichain interoperability. These steps are only required the first time a token is sent to a particular destination chain. Completing this guide will help you to accomplish the following: @@ -882,7 +882,7 @@ Follow these steps to initialize your project, install dependencies, and prepare npm init -y ``` -2. Install dependencies, including the Wormhole TypeScript SDK: +2. Install dependencies, including the [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank}: ```bash npm install @wormhole-foundation/sdk -D tsx typescript ``` @@ -1012,7 +1012,10 @@ async function attestToken() { '⚠️ Token is NOT registered on destination. Running attestation flow...' ); // Attestation flow code - console.error('❌ Error in attestToken', e); + } + +attestToken().catch((e) => { + console.error('❌ Error in attestToken', e); process.exit(1); });
``` diff --git a/llms-files/llms-transfer.txt b/llms-files/llms-transfer.txt index 2014c392d..5ee93d0cd 100644 --- a/llms-files/llms-transfer.txt +++ b/llms-files/llms-transfer.txt @@ -7542,7 +7542,7 @@ categories: Token-Bridge, Transfer ## Introduction -This guide demonstrates token attestation for registering a token for transfer using the Token Bridge protocol. An attestation of the token's metadata (e.g., symbol, name, decimals) ensures consistent handling by the destination chain for ease of multichain interoperability. These steps are only required the first time a token is sent to a particular destination chain. +This guide demonstrates token attestation for registering a token for transfer using the [Token Bridge](/docs/products/token-bridge/overview) protocol. An attestation of the token's metadata (e.g., symbol, name, decimals) ensures consistent handling by the destination chain for ease of multichain interoperability. These steps are only required the first time a token is sent to a particular destination chain. Completing this guide will help you to accomplish the following: @@ -7574,7 +7574,7 @@ Follow these steps to initialize your project, install dependencies, and prepare npm init -y ``` -2. Install dependencies, including the Wormhole TypeScript SDK: +2. Install dependencies, including the [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank}: ```bash npm install @wormhole-foundation/sdk -D tsx typescript ``` @@ -7704,7 +7704,10 @@ async function attestToken() { '⚠️ Token is NOT registered on destination. Running attestation flow...' ); // Attestation flow code - console.error('❌ Error in attestToken', e); + } + +attestToken().catch((e) => { + console.error('❌ Error in attestToken', e); process.exit(1); }); ``` diff --git a/llms-full.txt b/llms-full.txt index 875217956..792c449b2 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -16818,7 +16818,7 @@ categories: Token-Bridge, Transfer ## Introduction -This guide demonstrates token attestation for registering a token for transfer using the Token Bridge protocol. An attestation of the token's metadata (e.g., symbol, name, decimals) ensures consistent handling by the destination chain for ease of multichain interoperability. These steps are only required the first time a token is sent to a particular destination chain. +This guide demonstrates token attestation for registering a token for transfer using the [Token Bridge](/docs/products/token-bridge/overview) protocol. An attestation of the token's metadata (e.g., symbol, name, decimals) ensures consistent handling by the destination chain for ease of multichain interoperability. These steps are only required the first time a token is sent to a particular destination chain. Completing this guide will help you to accomplish the following: @@ -16850,7 +16850,7 @@ Follow these steps to initialize your project, install dependencies, and prepare npm init -y ``` -2. Install dependencies, including the Wormhole TypeScript SDK: +2. Install dependencies, including the [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank}: ```bash npm install @wormhole-foundation/sdk -D tsx typescript ``` @@ -16980,7 +16980,10 @@ async function attestToken() { '⚠️ Token is NOT registered on destination. Running attestation flow...' ); // Attestation flow code - console.error('❌ Error in attestToken', e); + } + +attestToken().catch((e) => { + console.error('❌ Error in attestToken', e); process.exit(1); }); ``` diff --git a/products/token-bridge/guides/attest-tokens.md b/products/token-bridge/guides/attest-tokens.md index aa7427ac1..e0dc8672f 100644 --- a/products/token-bridge/guides/attest-tokens.md +++ b/products/token-bridge/guides/attest-tokens.md @@ -8,7 +8,7 @@ categories: Token-Bridge, Transfer ## Introduction -This guide demonstrates token attestation for registering a token for transfer using the Token Bridge protocol. An attestation of the token's metadata (e.g., symbol, name, decimals) ensures consistent handling by the destination chain for ease of multichain interoperability. These steps are only required the first time a token is sent to a particular destination chain. +This guide demonstrates token attestation for registering a token for transfer using the [Token Bridge](/docs/products/token-bridge/overview) protocol. An attestation of the token's metadata (e.g., symbol, name, decimals) ensures consistent handling by the destination chain for ease of multichain interoperability. These steps are only required the first time a token is sent to a particular destination chain. Completing this guide will help you to accomplish the following: @@ -40,7 +40,7 @@ Follow these steps to initialize your project, install dependencies, and prepare npm init -y ``` -2. Install dependencies, including the Wormhole TypeScript SDK: +2. Install dependencies, including the [Wormhole TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts){target=\_blank}: ```bash npm install @wormhole-foundation/sdk -D tsx typescript ``` @@ -76,7 +76,7 @@ Since attestation is a one-time process, it is good practice when working with e 2. Open `attest.ts` and add the following code: ```typescript title="attest.ts" --8<-- 'code/products/token-bridge/guides/attest-tokens/attest.ts:1:37' - --8<-- 'code/products/token-bridge/guides/attest-tokens/attest.ts:123:126' + --8<-- 'code/products/token-bridge/guides/attest-tokens/attest.ts:120:126' ``` After initializing a Wormhole instance and defining the source and destination chains, this code does the following: