Hyperlane

Warp Token Deployment

Deploy a cross-chain warp route between BitSong and Base Sepolia using Hyperlane.

This guide walks you through deploying a cross-chain warp route between BitSong (crescendo-1) and Base Sepolia. A warp route enables native token transfers across chains — tokens are locked as collateral on BitSong while a synthetic HypERC20 representation is minted on the EVM side.

You must complete the Hyperlane Setup (7 Cosmos-side transactions) before starting this guide. You need the MAILBOX_ID from Step 3.

Overview

The warp route consists of three components deployed across both chains:

Collateral Token

Locks native TBTSG on BitSong when tokens are transferred to the EVM chain.

HypERC20

Synthetic ERC-20 token on Base Sepolia that represents locked TBTSG.

Router Enrollment

Bidirectional router registration so each side knows the address of its counterpart.

Prerequisites

Before starting, ensure you have the following ready:

  • Completed Hyperlane Setup — you need MAILBOX_ID from Step 3
  • The bitsongd binary installed and available in your PATH
  • jq installed (sudo apt install jq)
  • Node.js v22+ installed (required by the Hyperlane CLI):
    Terminal
    curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
    sudo apt-get install -y nodejs
    node --version  # should print v22.x.x
    
  • Hyperlane CLI installed:
    Terminal
    sudo npm install -g @hyperlane-xyz/cli
    
  • Foundry (cast) installed:
    Terminal
    curl -L https://foundry.paradigm.xyz | bash && foundryup
    
  • An EVM private key funded with Base Sepolia ETH (for deploying the HypERC20 contract)
Need Base Sepolia ETH? Use the Coinbase Developer Platform faucet to fund your EVM deployer address.

Configuration Reference

These parameters are used throughout the deployment. Set them as environment variables:

MAILBOX_ID
string required
The Mailbox ID from Hyperlane Setup Step 3. This is a numeric ID, not an address.
EVM_KEY
string required
Your EVM deployer private key (hex string starting with 0x). Must be funded with Base Sepolia ETH.
BASESEPOLIA_MAILBOX
address
The Hyperlane Mailbox contract on Base Sepolia. Default: 0x6966b0E55883d49BFB24539356a2f8A673E02039.
EVM_RPC
string
Base Sepolia RPC endpoint. Default: https://sepolia.base.org.
REMOTE_DOMAIN
string
Hyperlane domain for Base Sepolia. Default: 84532.
DOMAIN_ID
string
Hyperlane domain for BitSong. Default: 7171.
VALIDATOR_ADDR
address required
Your validator EVM address from the Agent Keys guide. Needed to deploy the custom ISM on Base Sepolia.
Terminal
export CHAIN_ID="crescendo-1"
export DOMAIN_ID="7171"
export REMOTE_DOMAIN="84532"
export DENOM="utbtsg"
export KEY_NAME="<your-key-name>"
export NODE="tcp://localhost:26657"

# From the Hyperlane Setup (Step 3)
export MAILBOX_ID="<your-mailbox-id>"

# EVM deployer
export EVM_KEY="0x<your-evm-private-key>"
export EVM_RPC="https://sepolia.base.org"
export BASESEPOLIA_MAILBOX="0x6966b0E55883d49BFB24539356a2f8A673E02039"

# Validator address (from agent keys — needed for ISM deployment)
export VALIDATOR_ADDR="<your-validator-evm-address>"

Deployment Steps

Register Chain Metadata

The Hyperlane CLI needs to know about the BitSong chain. Create the metadata and addresses files in the CLI's chain registry:

Terminal
mkdir -p ~/.hyperlane/chains/bitsong

Create the chain metadata file:

~/.hyperlane/chains/bitsong/metadata.yaml
chainId: crescendo-1
domainId: 7171
name: bitsong
protocol: cosmos
bech32Prefix: bitsong
slip44: 639
rpcUrls:
  - http: http://localhost:26657
restUrls:
  - http: http://localhost:1317
grpcUrls:
  - http: http://localhost:9090
nativeToken:
  name: BitSong
  symbol: BTSG
  decimals: 6
  denom: utbtsg
blocks:
  confirmations: 1
  estimateBlockTime: 6
isTestnet: true

Create the addresses file referencing your Mailbox:

~/.hyperlane/chains/bitsong/addresses.yaml
mailbox: "<your-mailbox-id>"

Replace <your-mailbox-id> with the MAILBOX_ID value from your Hyperlane Setup.

Create the Warp Route Config

Use the Hyperlane CLI interactive wizard to generate the warp route config:

Terminal
hyperlane warp init

The wizard will prompt you for a series of choices. Select the following:

PromptValue
Network typetestnet
Chainbasesepolia
Token typesynthetic
Token nameBitSong
Token symbolBTSG
Token decimals6

This generates a config file at ./configs/warp-route-deployment.yaml.

After generation, open the file and set interchainSecurityModule to the zero address (0x0000000000000000000000000000000000000000). This is required as a placeholder for the initial deployment.

The zero address ISM is used as the initial deployment value only. In the next step (Deploy Custom ISM), you will deploy a custom MultisigISM and update the HypERC20 to use it. Without this, messages from BitSong (domain 7171) will fail verification on Base Sepolia.

Deploy HypERC20 on Base Sepolia

Export the deployer key for the Hyperlane CLI and run the deployment:

Terminal
export HYP_KEY="$EVM_KEY"
hyperlane warp deploy --config ./configs/warp-route-deployment.yaml --yes

The CLI deploys the HypERC20 contract and prints the deployment artifacts at the end. Look for the addressOrDenom field in the output:

tokens:
  - chainName: basesepolia
    standard: EvmHypSynthetic
    decimals: 6
    symbol: TBTSG
    name: Test BitSong
    addressOrDenom: "0x7b2BD29..."

Copy the address and export it:

Terminal
export HYP_ERC20_ADDR="<addressOrDenom-from-deploy-output>"
echo "HypERC20 Address: $HYP_ERC20_ADDR"

Convert it to bytes32 format (needed for Cosmos-side enrollment):

Terminal
HYP_ERC20_BYTES32=$(printf "0x%064s" "${HYP_ERC20_ADDR#0x}" | tr ' ' '0')
echo "HypERC20 Bytes32: $HYP_ERC20_BYTES32"
You can confirm the contract is live on Base Sepolia:
Terminal
cast call "$HYP_ERC20_ADDR" "decimals()(uint8)" --rpc-url "$EVM_RPC"
This should return 6 (matching BTSG's 6 decimal places).

Deploy Custom ISM on Base Sepolia

The default Mailbox ISM on Base Sepolia does not know about BitSong (domain 7171), so you must deploy a custom StaticMessageIdMultisigISM that recognizes your validator's signatures. This ISM verifies checkpoints from your BitSong validator when processing BitSong → Base Sepolia messages.

The ISM is deployed via Hyperlane's official ISM factory on Base Sepolia, which uses CREATE2 for deterministic addressing:

Terminal
# ISM factory on Base Sepolia (official Hyperlane contract)
BASESEPOLIA_ISM_FACTORY="0xfc6e546510dC9d76057F1f76633FCFfC188CB213"

# 1. Predict the ISM address (read-only call)
CUSTOM_ISM=$(cast call "$BASESEPOLIA_ISM_FACTORY" \
    "deploy(address[],uint8)(address)" "[$VALIDATOR_ADDR]" 1 \
    --rpc-url "$EVM_RPC")
echo "Custom ISM address: $CUSTOM_ISM"

# 2. Deploy the ISM (state-changing transaction)
cast send "$BASESEPOLIA_ISM_FACTORY" \
    "deploy(address[],uint8)" "[$VALIDATOR_ADDR]" 1 \
    --private-key "$EVM_KEY" \
    --rpc-url "$EVM_RPC"

# 3. Set the ISM on the HypERC20 contract
cast send "$HYP_ERC20_ADDR" \
    "setInterchainSecurityModule(address)" "$CUSTOM_ISM" \
    --private-key "$EVM_KEY" \
    --rpc-url "$EVM_RPC"
The deploy() function on the ISM factory creates a StaticMessageIdMultisigISM with your validator address and a threshold of 1 (single-validator setup). The factory uses CREATE2, so the ISM address is deterministic — calling cast call (read-only) before cast send lets you predict the address without spending gas.
Confirm the HypERC20 now points to your custom ISM (not the zero address):
Terminal
cast call "$HYP_ERC20_ADDR" "interchainSecurityModule()(address)" --rpc-url "$EVM_RPC"
This should return the $CUSTOM_ISM address, not 0x0000000000000000000000000000000000000000.

Create the Cosmos Collateral Token

The Collateral Warp Token locks native TBTSG on BitSong when tokens are transferred to the EVM chain. A synthetic representation is minted on the destination chain via the HypERC20 contract.

If you've previously created a collateral token (e.g., from an earlier deployment attempt), you can skip the creation and reuse it. Check existing tokens:
Terminal
bitsongd query warp tokens --output json --node $NODE | jq '.'
If you see a token with token_type: "HYP_TOKEN_TYPE_COLLATERAL" and origin_denom: "utbtsg", export its id and skip to the next step:
Terminal
export TOKEN_ID="<existing-token-id>"
Terminal
TX_HASH=$(bitsongd tx warp create-collateral-token \
    $MAILBOX_ID $DENOM \
    --from $KEY_NAME \
    --keyring-backend test \
    --chain-id $CHAIN_ID \
    --node $NODE \
    --gas auto --gas-adjustment 1.5 \
    --fees 10000${DENOM} \
    --output json -y | jq -r '.txhash')
echo "TX Hash: $TX_HASH"

Extract the Token ID:

Terminal
TOKEN_ID=$(bitsongd query tx $TX_HASH --output json | \
  jq -r 'first(.events[] | select(.type | test("warp")) | .attributes[] | select(.key == "token_id") | .value | fromjson)')
echo "Token ID: $TOKEN_ID"

Enroll the EVM Router on Cosmos

Register the HypERC20 contract address as the remote router on the Cosmos side. This tells the collateral token where to send messages on the EVM chain.

Terminal
bitsongd tx warp enroll-remote-router \
    $TOKEN_ID $REMOTE_DOMAIN $HYP_ERC20_BYTES32 300000 \
    --from $KEY_NAME \
    --keyring-backend test \
    --chain-id $CHAIN_ID \
    --node $NODE \
    --gas auto --gas-adjustment 1.5 \
    --fees 10000${DENOM} \
    --output json -y
Hyperlane uses 32-byte addresses internally. To convert a standard 20-byte EVM address (0x...) to the 32-byte format, left-pad it with zeros:
Terminal
EVM_ADDR="0x<your-evm-contract-address>"
EVM_BYTES32=$(printf "0x%064s" "${EVM_ADDR#0x}" | tr ' ' '0')
echo "Bytes32: $EVM_BYTES32"
This produces a 0x + 64 hex character string.

Enroll the Cosmos Router on EVM

Register the Cosmos collateral token ID as the remote router on the EVM side. This completes the bidirectional link so the HypERC20 contract knows where to route messages back to BitSong.

Terminal
cast send "$HYP_ERC20_ADDR" \
    "enrollRemoteRouter(uint32,bytes32)" "$DOMAIN_ID" "$TOKEN_ID" \
    --private-key "$EVM_KEY" \
    --rpc-url "$EVM_RPC"
The TOKEN_ID from Step 4 is already in bytes32 format (it's a numeric ID that Hyperlane stores as a 32-byte value). No conversion is needed.

Verify the Deployment

After completing all steps, verify both sides of the warp route.

EVM Side

Query the HypERC20 contract to confirm its configuration:

Terminal
echo "=== HypERC20 ($HYP_ERC20_ADDR) ==="
echo "decimals:    $(cast call "$HYP_ERC20_ADDR" "decimals()(uint8)" --rpc-url "$EVM_RPC")"
echo "mailbox:     $(cast call "$HYP_ERC20_ADDR" "mailbox()(address)" --rpc-url "$EVM_RPC")"
echo "ism:         $(cast call "$HYP_ERC20_ADDR" "interchainSecurityModule()(address)" --rpc-url "$EVM_RPC")"
echo "totalSupply: $(cast call "$HYP_ERC20_ADDR" "totalSupply()(uint256)" --rpc-url "$EVM_RPC")"
echo "router(${DOMAIN_ID}): $(cast call "$HYP_ERC20_ADDR" "routers(uint32)(bytes32)" "$DOMAIN_ID" --rpc-url "$EVM_RPC")"

Expected values:

  • decimals: 6
  • mailbox: the Base Sepolia Hyperlane Mailbox address
  • ism: your $CUSTOM_ISM address (not 0x0000...0000 — see Deploy Custom ISM)
  • totalSupply: 0 (no tokens minted yet)
  • router: your TOKEN_ID value (non-zero confirms enrollment)

Cosmos Side

Query the warp token and its remote routers:

bitsongd query warp tokens --output json | jq '.'

The remote routers query should show the HypERC20 address enrolled for domain 84532.

Update Remote Router Enrollment

If you need to update the enrolled EVM address after the initial setup (for example, after redeploying the HypERC20 contract), re-enroll with the new address on both sides.

If you reused an existing collateral token and your HypERC20 contract address hasn't changed, your routers are already enrolled. Verify with:
Terminal
bitsongd query warp remote-routers $TOKEN_ID --output json | jq '.'
If the receiver_contract matches your current $HYP_ERC20_ADDR, skip this section and proceed to Testing Warp Route.

Update the Cosmos side

Convert the new EVM address and submit the enrollment:

Terminal
NEW_EVM_ADDR="0xYourNewContractAddress"
NEW_EVM_BYTES32=$(printf "0x%064s" "${NEW_EVM_ADDR#0x}" | tr ' ' '0')
echo "Bytes32: $NEW_EVM_BYTES32"

bitsongd tx warp enroll-remote-router \
    $TOKEN_ID $REMOTE_DOMAIN $NEW_EVM_BYTES32 300000 \
    --from $KEY_NAME \
    --keyring-backend test \
    --chain-id $CHAIN_ID \
    --node $NODE \
    --gas auto --gas-adjustment 1.5 \
    --fees 10000${DENOM} \
    --output json -y

Update the EVM side

Enroll the Cosmos token on the new HypERC20 contract:

Terminal
cast send "$NEW_EVM_ADDR" \
    "enrollRemoteRouter(uint32,bytes32)" "$DOMAIN_ID" "$TOKEN_ID" \
    --private-key "$EVM_KEY" \
    --rpc-url "$EVM_RPC"

Verify both sides

Terminal
# Cosmos side
bitsongd query warp remote-routers $TOKEN_ID --output json | jq '.'

# EVM side
cast call "$NEW_EVM_ADDR" "routers(uint32)(bytes32)" "$DOMAIN_ID" --rpc-url "$EVM_RPC"

Troubleshooting

Next Steps

Test the Warp Route

Send test transfers between BitSong and Base Sepolia to verify your warp route deployment.

Run Validators

Start the Hyperlane validator and relayer Docker agents to relay messages between BitSong and Base Sepolia.

Agent Keys Reference

Review key management options including AWS KMS for production environments.
Copyright © 2026