Bridging $EWT Between EWX <--> ETH (Manual)

This guide explains the steps required for bridging $EWT tokens between Energy Web X (EWX) and Ethereum Mainnet (ETH) manually via Polkadot.js explorer and Energy-Bridge-ETH contract interactions.

Key Components

  • Tier-1 (T1): Ethereum Mainnet (ETH, EVM)

  • Tier-2 (T2): Energy Web X (EWX, Substrate parachain)

  • Energy Bridge Contract: Deployed on Ethereum, coordinates token transfers to/from EWX

  • Token Manager Pallet: Handles bridge operations on EWX

  • t2PublicKey: the EWX Substrate account’s public key in hex bytes

Lowering Tokens: EWX → Ethereum

This guide explains how to move $EWT from Energy Web X (EWX, Tier-2) to Ethereum (Tier-1) using the on-chain bridge. The process involves scheduling a lower on EWX and then claiming it through the Energy Bridge smart contract on Ethereum.

Step 1: Schedule a Lower on EWX

  1. Navigate to Polkadot.JS Portal connected to the EWX network.

  2. Go to DeveloperExtrinsics.

  3. Under using the selected account, choose the EWX account that holds the tokens to be lowered.

  4. Under submit the following extrinsic, select: tokenManager → scheduleDirectLower(from, tokenId, amount, t1Recipient) .

  5. Fill in the parameters:

    • from: EWX account to lower from (your account)

    • tokenId: Ethereum address of the token to lower (e.g., the EWT ERC-20 address)

    • amount: Amount to lower (in wei units, e.g., 1000000000000000000 for 1 EWT)

    • t1Recipient: Ethereum address to receive the lowered tokens

  6. Submit and sign the transaction.

  7. The transaction will schedule a lowering event after a predefined delay (check tokenManager.lowerSchedulePeriod in DeveloperChain State for the exact number of blocks).

Step 2: Monitor the Scheduled Lower

  • Go to NetworkExplorer and look for the event: TokenManager.LowerRequested

  • Click on the event to view details and copy the lowerId.

  • Alternatively:

    • NetworkScheduler shows pending scheduled lowers.

    • NetworkEvent Calendar displays the approximate execution date and time.

Step 3: Wait for Execution and Obtain the Proof

After the scheduled number of blocks elapse, the system will execute the lower. Once executed, a new event TokenManager.LowerReadyToClaim will appear.

  1. Go to Developer Chain State.

  2. Select tokenManagerlowersReadyToClaim.

  3. Toggle include option and enter your lowerId to filter results.

  4. Retrieve the proof, specifically the encodedLowerData field. This proof will be required on Ethereum.

Step 4: Claim the Lower on Ethereum

  1. Visit the Energy Bridge Contract (Ethereum mainnet): Energy Bridge Proxy Contract

  2. Connect your Ethereum wallet.

  3. Under the Write Proxy tab, select claimLower.

  4. Paste the encodedLowerData retrieved from EWX.

  5. Click Write, sign the transaction, and wait for confirmation.

Upon success, the tokens will be unlocked and transferred to your Ethereum address, and a new Lower transaction will appear on the contract.

Notes

  • Ensure your Ethereum account has sufficient ETH to cover gas fees.

  • The lowering process is asynchronous, the on-chain scheduler on EWX must reach the required block height before the proof becomes claimable.

  • The bridge only supports whitelisted tokens (e.g., $EWT).


Lifting Tokens: Ethereum → EWX

There are two methods for lifting EW tokens: Lift, which first requires approving the bridge contract to spend your tokens; and PermitLift, which combines an ERC-2612 permit (EIP-712 signature) with the lift, allowing tokens to be lifted in a single transaction with no prior approve() needed.

Alternative 1: Lift

Step 1: Access the Energy Bridge Contract

  1. Visit the Energy Bridge Contract (Ethereum mainnet): Energy Bridge Proxy Contract

  2. Select Write as Proxy

  3. Connect your Ethereum wallet holding $EWT (e.g., MetaMask) by clicking on Connect web3 .

Step 2: Approve the Bridge to Spend Your Tokens

Before lifting, the bridge must be authorized to transfer your tokens.

  1. Identify the ERC-20 EWT token contract on Ethereum: EWT ERC-20 Proxy Contract

    1. Click the link above, or find the address through the Polkadot.JS Portal:

      1. DeveloperChain StatetokenManageravtTokenContract():H160

      2. Click "+", and copy the address in the tokenManager.avtTokenContract: H160 field.

  2. Once the contract is open on Etherscan → ContractWrite as Proxyapprove(spender, amount).

  3. In the spender field, enter the Energy Bridge address: 0x5dDed30f8cd557257CcDC4a530cB77AC45f0259D

  4. In the amount field, enter the number of tokens (in wei) you wish to lift.

    • Example: 1000000000000000000 = 1 EWT

  5. Click Write, sign, and wait for confirmation.

Step 3: Execute the Lift

  1. Return to the Energy Bridge Proxy Contract page.

  2. Under Write as Proxy, locate the lift function.

  3. Fill in the parameters:

Parameter
Description
Example

token

ERC-20 token address to lift

0xB66a5D30D04f076E78ffB0d045C55846Fdcde928

t2PublicKey (bytes)

Hexadecimal public key of your EWX account

Convert your EWX address using Polkadot.js → Developer → Utilities → Address Converter

amount

Number of tokens (in wei) to lift

1000000000000000000 (= 1 EWT)

  1. Click Write, confirm the transaction, and wait for it to be mined.

After submission, your tokens are locked in the bridge contract, and a Lifted event is emitted on Ethereum.

Step 4: Monitor the Lift on EWX

Once the bridge relayer processes the Ethereum Lifted event (typically within 5–10 minutes):

  1. On the EWX explorer, open NetworkExplorer.

  2. Look for tokenManager.AVTLifted events.

  3. Optionally, check DeveloperChain StateethereumBridgeactiveEthereumRange() to see the Ethereum block range being processed.

  4. When your transaction is within that range, your lifted tokens will appear in your EWX account.

To verify, open AccountsMy Accounts and confirm the updated balance.


Alternative 2: PermitLift

permitLift simplifies the process by embedding an ERC-2612 permit signature directly into the lift call. This removes the need for a separate approve() step.

Steps:

  1. On Etherscan, open the same Energy Bridge Proxy Contract → Write as Proxy.

  2. Locate the permitLift function.

  3. Prepare an EIP-712 signature (v, r, s) offline for the ERC-2612 permit corresponding to your token.

  4. Fill the parameters:

Parameter
Description

token

ERC-20 token address (0xB66a5D30D04f076E78ffB0d045C55846Fdcde928)

t2PublicKey

Hex of your EWX account (as above)

amount

Amount in wei

deadline

Expiration timestamp for the permit

v, r, s

Components of the EIP-712 signature

  1. Click Write, sign, and confirm. Your lift is processed in a single transaction.

Monitoring on EWX follows the same path as the standard lift.

Programmatic example permitLift.js

#!/usr/bin/env node

const { ethers } = require("ethers");
require("dotenv").config();

async function main() {
  const {
    RPC_URL,
    PRIVATE_KEY,
    BRIDGE_ADDR,
    TOKEN_ADDR,
    T2_PUBKEY,
    AMOUNT = "0.1",
    DECIMALS = "18",
    DEADLINE_SECS = "3600",
  } = process.env;

  if (!RPC_URL || !PRIVATE_KEY) {
    console.error("Missing RPC_URL or PRIVATE_KEY env vars");
    process.exit(1);
  }
  if (!BRIDGE_ADDR || !TOKEN_ADDR || !T2_PUBKEY) {
    console.error("Missing BRIDGE_ADDR, TOKEN_ADDR or T2_PUBKEY env vars");
    process.exit(1);
  }

  const provider = new ethers.JsonRpcProvider(RPC_URL);
  const signer = new ethers.Wallet(PRIVATE_KEY, provider);

  // Minimal ABIs required for this flow
  const BRIDGE_ABI = [
    "function permitLift(address token, bytes32 t2PubKey, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external returns (bool)"
  ];
  const TOKEN_ABI = [
    "function name() view returns (string)",
    "function nonces(address owner) view returns (uint256)"
  ];

  const bridge = new ethers.Contract(BRIDGE_ADDR, BRIDGE_ABI, signer);
  const token  = new ethers.Contract(TOKEN_ADDR, TOKEN_ABI, signer);

  const amount   = ethers.parseUnits(AMOUNT, Number(DECIMALS));
  const deadline = Math.floor(Date.now() / 1000) + Number(DEADLINE_SECS);
  const owner    = await signer.getAddress();

  const domain = {
    name: await token.name(),
    version: "1",
    chainId: (await signer.provider.getNetwork()).chainId,
    verifyingContract: await token.getAddress(),
  };

  const types = {
    Permit: [
      { name: "owner",   type: "address" },
      { name: "spender", type: "address" },
      { name: "value",   type: "uint256" },
      { name: "nonce",   type: "uint256" },
      { name: "deadline",type: "uint256" },
    ],
  };

  const message = {
    owner,
    spender: await bridge.getAddress(),
    value: amount,
    nonce: await token.nonces(owner),
    deadline,
  };

  const sig = await signer.signTypedData(domain, types, message);
  const { v, r, s } = ethers.Signature.from(sig);

  const tx = await bridge.permitLift(await token.getAddress(), T2_PUBKEY, amount, deadline, v, r, s);
  const receipt = await tx.wait();

  console.log("permitLift tx: ", receipt.hash);
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

Last updated