Cross-chain token forwarding from Celestia to an EVM chain via Hyperlane.
When tokens (utia) are sent to a forwarding address on Celestia, the relayer detects the deposit and submits a MsgForward transaction. The Celestia forwarding module locks the tokens and dispatches a Hyperlane message. The Hyperlane relayer relays it to the destination EVM chain, where wrapped tokens (wTIA) are minted.
Celestia (69420) Anvil EVM (1234)
┌─────────────────────────┐ ┌─────────────────────┐
│ │ │ │
│ Forwarding Address │ │ wTIA (HypSynthetic)│
│ │ │ │ ▲ │
│ 1. deposit utia │ │ │ │
│ │ │ │ 4. mint wTIA │
│ ▼ │ 3. Hyperlane │ │ │
│ Forwarding Module ─────┼──── message ─────►│ Hyperlane Mailbox │
│ (collateral + mailbox) │ (Hyperlane relay)│ │
│ ▲ │ └─────────────────────┘
│ │ │
│ 2. MsgForward │
│ (forwarding relayer) │
└─────────────────────────┘
- Running the E2E Test: Start all services, forward tokens, and verify the result in one command
- Deploying a New Warp Route: Deploy Hyperlane contracts and set up a new token bridge between Celestia and an EVM chain
- Forwarding Tokens for Existing Warp Routes: Use the relayer to bridge tokens through an already-deployed warp route
- Forwarding Backend API: HTTP reference for deriving forwarding addresses, managing pending requests, and scraping backend metrics
- Running Under systemd: Install and manage the backend and relayer as separate host services
# Run the E2E test (starts containers, deploys contracts, forwards tokens, verifies)
make e2eThis will:
- Start Celestia, Anvil, and Hyperlane containers
- Wait for Hyperlane contract deployment
- Start the backend and forwarding relayer in-process
- Derive a forwarding address, create a forwarding request
- Fund the forwarding address with 1,000,000 utia
- Wait for the relayer to detect the deposit and submit
MsgForward - Wait for the Hyperlane relayer to relay the message to Anvil
- Verify the wTIA balance increased on Anvil
Expected output:
SUCCESS! 1000000 utia forwarded from Celestia to Anvil as wTIA
make startUse make docker-build-hyperlane only if you want to override the published hyperlane-init image locally while keeping the same Compose tag.
Wait for hyperlane-init to exit with code 0 (docker ps to check).
To also start Prometheus and Grafana for local metrics testing, enable the optional monitoring
profile:
docker compose --profile monitoring up --detachPrometheus will be available at http://localhost:9095 and Grafana at http://localhost:3000
with admin / admin.
The relayer needs gas for MsgForward transactions:
docker exec celestia-validator celestia-appd tx bank send \
default celestia1y3kf30y9zprqzr2g2gjjkw3wls0a35pfs3a58q 10000000utia \
--fees 800utia --yes --chain-id celestiadev --node http://localhost:26657./target/release/forwarding-relayer backend --port 8080# Derive the forwarding address (TOKEN_ID from Hyperlane deployment)
make derive-address TOKEN_ID=0x...
# Create the request
curl -X POST http://localhost:8080/forwarding-requests \
-H "Content-Type: application/json" \
-d '{
"forward_addr": "<address from derive-address>",
"dest_domain": 1234,
"dest_recipient": "0x000000000000000000000000f39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"token_id": "0x..."
}'RUST_LOG=info ./target/release/forwarding-relayer relayer \
--celestia-grpc http://localhost:9090 \
--backend-url http://localhost:8080 \
--private-key-hex "6e30efb1d3ebd30d1ba08c8d5fc9b190e08394009dc1dd787a69e60c33288a8c"# Fund the forwarding address
make send-to-address ADDR=<forwarding address> AMOUNT=1000000
# After ~30s, check wTIA balance on Anvil
WARP_TOKEN=$(awk '/addressOrDenom:/ {addr=$2} /chainName: rethlocal/ {print addr}' ./testnet/hyperlane/registry/deployments/warp_routes/TIA/celestiadev-rethlocal-config.yaml | tr -d '"')
cast call $WARP_TOKEN "balanceOf(address)(uint256)" 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://localhost:8545| Service | Port | Description |
|---|---|---|
| celestia-validator | 26657 (RPC), 9090 (gRPC) | Celestia node with Hyperlane + forwarding modules |
| anvil | 8545 | Standalone EVM chain (chain-id 1234) |
| relayer | - | Hyperlane relayer (v1.7.0) |
| celestia-bridge | 26658, 2121 | Celestia bridge node |
| hyperlane-init | - | Deploys Hyperlane contracts on both chains (runs once) |
| Parameter | Value |
|---|---|
| Celestia domain | 69420 |
| Anvil domain | 1234 |
| Anvil test account | 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 |
| Relayer address | celestia1y3kf30y9zprqzr2g2gjjkw3wls0a35pfs3a58q |
| ISM | TestISM (testing only) |
make start Start all Docker containers
make stop Stop containers and remove volumes
make e2e Full E2E test (start, deploy, forward, verify)
make docker-build-hyperlane Optional local override for the hyperlane-init image
make derive-address Derive forwarding address for Anvil
make transfer Direct Hyperlane warp transfer (no forwarding)
make send-to-address Send utia to a Celestia address
make query-balance Query wTIA balance on Anvil
To include Prometheus and Grafana when starting the stack manually, use:
docker compose --profile monitoring up --detach