{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-pages/developers/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["admonition"]},"type":"markdown"},"seo":{"title":"Transfer Tokens with Wormhole","siteUrl":"https://docs.xrplevm.org/","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"transfer-tokens-with-wormhole","__idx":0},"children":["Transfer Tokens with Wormhole"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Wormhole's ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Wrapped Token Transfers (WTT)"]},", formerly known as the Token Bridge, moves ERC-20 tokens between the XRPL EVM and the 30+ chains where WTT is deployed, using a lock-and-mint model:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Token registration (one-time)"]},": The token's metadata (symbol, name, decimals) is attested and registered on the destination chain."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Lock"]},": Tokens are locked in custody by the WTT contract on the source chain."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Observe and sign"]},": The Guardian network emits a signed VAA for the transfer."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Mint / Release"]},": The VAA is verified on the destination chain, minting wrapped tokens (or releasing native ones on the way back). Wrapped tokens are backed 1:1."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["WTT is available on XRPL EVM ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Mainnet and Testnet"]},". Contract addresses are listed in ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/pages/bridge/wormhole/deployed-contracts"},"children":["Deployed Contracts"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"option-1-portal-bridge-ui","__idx":1},"children":["Option 1: Portal Bridge (UI)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For manual transfers, use ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://portalbridge.com"},"children":["Portal Bridge"]},", the official user-facing app built on Wormhole. Connect your wallet, pick the source and destination chains, and follow the prompts."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"option-2-typescript-sdk-programmatic","__idx":2},"children":["Option 2: TypeScript SDK (Programmatic)"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"install","__idx":3},"children":["Install"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"npm install @wormhole-foundation/sdk\nnpm install -D tsx typescript\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"set-up-a-signer-helper","__idx":4},"children":["Set Up a Signer Helper"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"// helper.ts\nimport {\n  ChainAddress,\n  ChainContext,\n  Network,\n  Signer,\n  Wormhole,\n  Chain,\n  isTokenId,\n  TokenId,\n} from \"@wormhole-foundation/sdk\";\nimport evm from \"@wormhole-foundation/sdk/evm\";\n\nexport async function getSigner<N extends Network, C extends Chain>(\n  chain: ChainContext<N, C>,\n): Promise<{\n  chain: ChainContext<N, C>;\n  signer: Signer<N, C>;\n  address: ChainAddress<C>;\n}> {\n  const signer = await (\n    await evm()\n  ).getSigner(await chain.getRpc(), process.env.EVM_PRIVATE_KEY!);\n\n  return {\n    chain,\n    signer: signer as Signer<N, C>,\n    address: Wormhole.chainAddress(chain.chain, signer.address()),\n  };\n}\n\nexport async function getTokenDecimals<N extends Network>(\n  wh: Wormhole<N>,\n  token: TokenId,\n  chain: ChainContext<N, any>,\n): Promise<number> {\n  return isTokenId(token)\n    ? Number(await wh.getDecimals(token.chain, token.address))\n    : chain.config.nativeTokenDecimals;\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"transfer-from-xrpl-evm","__idx":5},"children":["Transfer from XRPL EVM"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The XRPL EVM's chain name in the Wormhole SDK is ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["XRPLEVM"]}]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"// transfer.ts\nimport { wormhole, amount, Wormhole } from \"@wormhole-foundation/sdk\";\nimport evm from \"@wormhole-foundation/sdk/evm\";\nimport { getSigner, getTokenDecimals } from \"./helper\";\n\n(async function () {\n  const wh = await wormhole(\"Mainnet\", [evm]);\n\n  const sendChain = wh.getChain(\"XRPLEVM\");\n  const rcvChain = wh.getChain(\"Ethereum\");\n\n  const source = await getSigner(sendChain);\n  const destination = await getSigner(rcvChain);\n\n  // Pass the address of the ERC20 token you want to bridge.\n  // To bridge XRP itself, use its ERC20 representation at the\n  // sentinel address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE.\n  const tokenId = Wormhole.tokenId(\n    \"XRPLEVM\",\n    \"0xINSERT_YOUR_ERC20_TOKEN_ADDRESS\",\n  );\n  const amt = \"1\";\n\n  const decimals = await getTokenDecimals(wh, tokenId, sendChain);\n  const transferAmount = amount.units(amount.parse(amt, decimals));\n\n  const xfer = await wh.tokenTransfer(\n    tokenId,\n    transferAmount,\n    source.address,\n    destination.address,\n    \"TokenBridge\", // WTT route (the SDK still uses the legacy route name)\n    undefined,\n  );\n\n  console.log(\"Starting Transfer\");\n  const srcTxids = await xfer.initiateTransfer(source.signer);\n  console.log(\"Started Transfer:\", srcTxids);\n\n  console.log(\"Fetching Attestation\");\n  const timeout = 5 * 60 * 1000;\n  await xfer.fetchAttestation(timeout);\n\n  console.log(\"Completing Transfer\");\n  const destTxids = await xfer.completeTransfer(destination.signer);\n  console.log(\"Completed Transfer:\", destTxids);\n\n  process.exit(0);\n})();\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run it with:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"npx tsx transfer.ts\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This is a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["manual"]}," transfer: your script initiates the transfer on XRPL EVM, waits for the Guardian attestation, and completes it on the destination chain with the destination signer. For development, switch ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"Mainnet\""]}," to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"Testnet\""]}," and fund your account from the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/pages/users/faucet"},"children":["faucet"]},"."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"Do not use the native token ID for XRP"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The WTT contract on XRPL EVM has no native token wrapping configured (its ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["WETH()"]}," slot is unset), so ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Wormhole.tokenId(\"XRPLEVM\", \"native\")"]}," transfers revert on-chain. Always pass an ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["ERC20 token address"]},". XRP itself is bridgeable this way: it is natively exposed as an ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/pages/developers/interacting-with-evm/advanced-guides/using-xrp-as-wrapped-erc20"},"children":["ERC20 at the sentinel address"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]},", which is also how the Connect widget and Portal Bridge route XRP. Mind the sentinel address usage limits documented in that guide when batching transfers."]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"First transfer of a token"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A token must be ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["attested"]}," on the destination chain once before it can be transferred there. If your token has never been bridged to the target chain, follow the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://wormhole.com/docs/products/token-transfers/wrapped-token-transfers/guides/attest-tokens/"},"children":["attestation guide"]}," first."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"next-steps","__idx":6},"children":["Next Steps"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/pages/bridge/wormhole/supported-tokens"},"children":["Supported Tokens on XRPL EVM"]},": the tokens currently live on each network."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://wormhole.com/docs/products/token-transfers/wrapped-token-transfers/overview/"},"children":["WTT overview"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://wormhole.com/docs/products/token-transfers/wrapped-token-transfers/get-started/"},"children":["WTT get-started guide"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/pages/developers/interacting-with-evm/advanced-guides/cross-chain-transactions/wormhole/native-token-transfers"},"children":["Native Token Transfers"]},": issue a multichain-native token instead of a wrapped one."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://wormholescan.io"},"children":["Track transfers on WormholeScan"]}]}]}]},"headings":[{"value":"Transfer Tokens with Wormhole","id":"transfer-tokens-with-wormhole","depth":1},{"value":"Option 1: Portal Bridge (UI)","id":"option-1-portal-bridge-ui","depth":2},{"value":"Option 2: TypeScript SDK (Programmatic)","id":"option-2-typescript-sdk-programmatic","depth":2},{"value":"Install","id":"install","depth":3},{"value":"Set Up a Signer Helper","id":"set-up-a-signer-helper","depth":3},{"value":"Transfer from XRPL EVM","id":"transfer-from-xrpl-evm","depth":3},{"value":"Next Steps","id":"next-steps","depth":2}],"frontmatter":{"seo":{"title":"Transfer Tokens with Wormhole"}},"editPage":{"to":"https://github.com/ripple/docs.xrplevm.org/tree/main/pages/developers/interacting-with-evm/advanced-guides/cross-chain-transactions/wormhole/transfer-tokens.md"},"lastModified":"2026-08-07T10:03:44.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/pages/developers/interacting-with-evm/advanced-guides/cross-chain-transactions/wormhole/transfer-tokens","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}