Transfer Assets from Ethereum into Polkadot¶
Introduction¶
This guide walks you through transferring tokens from Ethereum to Polkadot using the ParaSpell XCM SDK and Snowbridge. Snowbridge is a trustless, decentralized bridge integrated into the Polkadot protocol that enables secure asset transfers between the Ethereum and Polkadot ecosystems.
How the Bridge Works¶
The following diagram shows the complete flow when bridging WETH from Ethereum to Polkadot:
sequenceDiagram
participant User
participant Ethereum
participant Relayers
participant Polkadot
User->>Ethereum: 1. Approve & send tokens
Ethereum->>Ethereum: Lock tokens
Note over Relayers: ~30 min relay
Relayers->>Polkadot: 2. Relay message
Polkadot->>Polkadot: Verify & mint tokens
Polkadot-->>User: 3. Tokens available In this guide, you will:
- Set up an Ethereum development environment with the ParaSpell SDK
- Approve tokens for the Snowbridge Gateway contract
- Build and execute a bridge transfer from Ethereum to Polkadot Hub
- Monitor the transfer status
Prerequisites¶
Before you begin, ensure you have the following:
- A basic understanding of XCM.
- Familiarity with JavaScript/TypeScript and Ethereum development.
- Node.js v18 or higher and npm installed.
- An Ethereum wallet with WETH tokens. See Prepare Tokens for Bridging for instructions to wrap ETH if needed.
- A Polkadot account to receive the bridged assets.
Prepare Tokens for Bridging¶
To bridge ETH, you need to wrap it into WETH first. The WETH contract on Ethereum mainnet is:
Why WETH instead of ETH?
Snowbridge only supports ERC-20 tokensβit cannot bridge native ETH directly. The bridge uses a standardized token interface to lock assets on Ethereum and mint corresponding representations on Polkadot. Since native ETH doesn't conform to the ERC-20 standard, you must first wrap it into WETH (Wrapped ETH), an ERC-20-compliant token pegged 1:1 to ETH.
You can wrap ETH by:
- Visiting the WETH contract on Etherscan
- Connecting your wallet
- Calling the
depositfunction with the amount of ETH you want to wrap
Initialize Your Project¶
Follow these steps to setup and initialize your project: 1. Create the project folder using the following commands:
- Initialize the JavaScript project:
Install dev dependencies:
Sending assets from Ethereum to Polkadot requires the PJS version of the ParaSpell SDK. Install the necessary dependencies using the following command:
Now add the following setup code to index.ts:
import { EvmBuilder, getTokenBalance, approveToken, getBridgeStatus } from '@paraspell/sdk-pjs';
import { ethers } from 'ethers';
// Ethereum mainnet RPC endpoint (use your own API key)
const ETH_RPC = 'https://eth-mainnet.g.alchemy.com/v2/INSERT_API_KEY';
// WETH contract address on Ethereum mainnet
const WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
// WETH has 18 decimals
const WETH_UNITS = 1_000_000_000_000_000_000n;
// Amount to bridge: 0.001 WETH
const AMOUNT = (WETH_UNITS / 1000n).toString();
// Your Polkadot address to receive the bridged tokens (SS58 format)
const RECIPIENT_ADDRESS = 'INSERT_YOUR_POLKADOT_ADDRESS';
// Connect to Ethereum mainnet
async function getProviderAndSigner() {
// For browser wallet (MetaMask)
// const provider = new ethers.BrowserProvider(window.ethereum);
// For script-based execution with private key
const provider = new ethers.JsonRpcProvider(ETH_RPC);
const signer = new ethers.Wallet('INSERT_PRIVATE_KEY', provider);
return { provider, signer };
}
Replace INSERT_YOUR_POLKADOT_ADDRESS with your Polkadot account address (SS58 format) that will receive the bridged tokens on Polkadot Hub.
Security Warning
Never commit private keys or seed phrases to production code. Use environment variables or secure key management systems.
Approve Tokens for Bridging¶
Before bridging ERC-20 tokens, you must approve the Snowbridge Gateway contract to spend your tokens. The ParaSpell SDK provides the following helper functions for approving spending:
async function approveTokens() {
const { signer } = await getProviderAndSigner();
// Check current WETH balance
const balance = await getTokenBalance(signer, 'WETH');
console.log(`Current WETH balance: ${balance}`);
if (BigInt(balance) === 0n) {
console.log('No WETH balance. Please wrap some ETH to WETH first.');
console.log(`WETH contract: ${WETH_ADDRESS}`);
process.exit(1);
}
// Approve the Snowbridge Gateway contract to spend WETH
const { result: approveTx } = await approveToken(signer, BigInt(AMOUNT), 'WETH');
console.log(`Approval transaction hash: ${approveTx.hash}`);
// Wait for confirmation
await approveTx.wait();
console.log('Token approval confirmed!');
}
Run the approval script:
You will see output confirming the token balance and approval similar to the following:
Build and Execute the Bridge Transfer¶
Now build and execute the bridge transfer from Ethereum to Polkadot Hub:
async function bridgeToPolkadot() {
const { provider, signer } = await getProviderAndSigner();
console.log('Building bridge transaction...');
console.log(`Bridging ${AMOUNT} WETH to Polkadot Hub`);
console.log(`Recipient: ${RECIPIENT_ADDRESS}`);
// Build and execute the bridge transfer
// Note: 'AssetHubPolkadot' is the SDK identifier for Polkadot Hub
const result = await EvmBuilder(provider)
.to('AssetHubPolkadot')
.currency({
symbol: 'WETH',
amount: AMOUNT,
})
.address(RECIPIENT_ADDRESS)
.signer(signer)
.build();
console.log('Bridge transaction submitted!');
console.log(`Transaction hash: ${result.response.hash}`);
console.log('Transfer will arrive in approximately 30 minutes.');
// Wait for Ethereum confirmation
await result.response.wait();
console.log('Ethereum transaction confirmed! Waiting for bridge relay...');
}
Comment out the approveTokens() function and run the transfer:
After submitting the transaction, you will see the Ethereum transaction hash included in the terminal output similar to the following:
Monitor the Transfer¶
Bridge transfers from Ethereum to Polkadot take approximately 30 minutes. You can monitor the transfer status in several ways:
- Snowbridge App - Visit app.snowbridge.network to check the transfer history
- Etherscan - Track your Ethereum transaction on etherscan.io
- Polkadot.js Apps - Monitor incoming transfers on Polkadot Hub
You can also check the bridge status programmatically:
async function checkBridgeStatus() {
// Check the current status of the Snowbridge
const status = await getBridgeStatus();
console.log('Bridge Status:', status);
}
Supported Assets and Destinations¶
Snowbridge can bridge any ERC-20 token from Ethereum, but the destination chain must have that asset registered as a foreign asset to receive it.
Verify Foreign Assets
Foreign asset creation on Polkadot Hub is permissionlessβanyone can register a foreign asset. Before bridging, verify that the foreign asset registered on Polkadot Hub corresponds to the legitimate token on Ethereum by checking the asset's multilocation and confirming the Ethereum contract address matches the official token contract.
You can query which assets are supported for a specific route using the ParaSpell SDK as follows:
import { getSupportedAssets } from '@paraspell/sdk-pjs';
// Get assets supported for Ethereum β Polkadot Hub transfers
const supportedAssets = getSupportedAssets('Ethereum', 'AssetHubPolkadot');
console.log('Supported assets:', supportedAssets.map(a => a.symbol));
Running this script will return a list of supported assets in the terminal similar to the following:
Common supported tokens on Polkadot Hub include:
| Token | Symbol | Description |
|---|---|---|
| Wrapped Ether | WETH | Ethereum's native token wrapped as ERC-20 |
| Wrapped Bitcoin | WBTC | Bitcoin wrapped as an ERC-20 token |
| Shiba Inu | SHIB | Popular ERC-20 meme token |
To transfer to a different destination, change the .to() parameter and verify the asset is supported using the following code:
// Check if WETH is supported on Hydration
const hydrationAssets = getSupportedAssets('Ethereum', 'Hydration');
const wethSupported = hydrationAssets.some(a => a.symbol === 'WETH');
if (wethSupported) {
await EvmBuilder(provider)
.to('Hydration')
.currency({ symbol: 'WETH', amount: amount })
.address(recipientAddress)
.signer(signer)
.build();
}
Complete index.ts script
import { EvmBuilder, getTokenBalance, approveToken, getBridgeStatus } from '@paraspell/sdk-pjs';
import { ethers } from 'ethers';
// Ethereum mainnet RPC endpoint (use your own API key)
const ETH_RPC = 'https://eth-mainnet.g.alchemy.com/v2/INSERT_API_KEY';
// WETH contract address on Ethereum mainnet
const WETH_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
// WETH has 18 decimals
const WETH_UNITS = 1_000_000_000_000_000_000n;
// Amount to bridge: 0.001 WETH
const AMOUNT = (WETH_UNITS / 1000n).toString();
// Your Polkadot address to receive the bridged tokens (SS58 format)
const RECIPIENT_ADDRESS = 'INSERT_YOUR_POLKADOT_ADDRESS';
// Connect to Ethereum mainnet
async function getProviderAndSigner() {
// For browser wallet (MetaMask)
// const provider = new ethers.BrowserProvider(window.ethereum);
// For script-based execution with private key
const provider = new ethers.JsonRpcProvider(ETH_RPC);
const signer = new ethers.Wallet('INSERT_PRIVATE_KEY', provider);
return { provider, signer };
}
async function approveTokens() {
const { signer } = await getProviderAndSigner();
// Check current WETH balance
const balance = await getTokenBalance(signer, 'WETH');
console.log(`Current WETH balance: ${balance}`);
if (BigInt(balance) === 0n) {
console.log('No WETH balance. Please wrap some ETH to WETH first.');
console.log(`WETH contract: ${WETH_ADDRESS}`);
process.exit(1);
}
// Approve the Snowbridge Gateway contract to spend WETH
const { result: approveTx } = await approveToken(signer, BigInt(AMOUNT), 'WETH');
console.log(`Approval transaction hash: ${approveTx.hash}`);
// Wait for confirmation
await approveTx.wait();
console.log('Token approval confirmed!');
}
approveTokens();
async function bridgeToPolkadot() {
const { provider, signer } = await getProviderAndSigner();
console.log('Building bridge transaction...');
console.log(`Bridging ${AMOUNT} WETH to Polkadot Hub`);
console.log(`Recipient: ${RECIPIENT_ADDRESS}`);
// Build and execute the bridge transfer
// Note: 'AssetHubPolkadot' is the SDK identifier for Polkadot Hub
const result = await EvmBuilder(provider)
.to('AssetHubPolkadot')
.currency({
symbol: 'WETH',
amount: AMOUNT,
})
.address(RECIPIENT_ADDRESS)
.signer(signer)
.build();
console.log('Bridge transaction submitted!');
console.log(`Transaction hash: ${result.response.hash}`);
console.log('Transfer will arrive in approximately 30 minutes.');
// Wait for Ethereum confirmation
await result.response.wait();
console.log('Ethereum transaction confirmed! Waiting for bridge relay...');
}
bridgeToPolkadot();
async function checkBridgeStatus() {
// Check the current status of the Snowbridge
const status = await getBridgeStatus();
console.log('Bridge Status:', status);
}
checkBridgeStatus();
Transfer Assets Out of Polkadot
To transfer assets out of Polkadot, you can use the ParaSpell XCM SDK to build and execute a bridge transfer from Polkadot to Ethereum. Check out the Polkadot to Ethereum section of the ParaSpell XCM SDK documentation.
Where to Go Next¶
- External Documentation - Dive deeper into the ParaSpell XCM SDK and Snowbridge documentation resources.
- Learn about XCM - Understand the underlying protocol by visiting the Get Started with XCM guide
| Created: January 14, 2026