Hyperlane

Rotate the Multisig

Owner procedure to change the Hyperlane validator set or threshold on the live BitSong crescendo-1 bridge by creating a new MultisigISM and re-pointing both sides.

This guide covers how the bridge owner changes the validator set or signature threshold of the Hyperlane bridge between BitSong (crescendo-1) and Base Sepolia — for example, to add a new validator or move from a 1-of-1 setup to a 2-of-3 multisig.

Every transaction here must be signed with the owner key of the bridge (--from <owner-key>). Operators who only run a validator do not perform these steps — they generate a key and send their EVM address to the owner. See Join the Multisig for the operator side.

Why rotation is needed

On Hyperlane the MultisigISM is immutable: there is no command to add or remove a validator from an existing ISM. To change the set, you create a new MultisigISM with the updated validator list and threshold, then re-point the routes that use it.

A two-way bridge is secured by two ISMs, and both must be updated to fully rotate the set:

BitSong side — RoutingISM

Verifies Base Sepolia → BitSong messages. You create a new MultisigISM on BitSong and re-point the RoutingISM route for domain 84532 to it.

Base Sepolia side — Static ISM

Verifies BitSong → Base Sepolia messages. You deploy a new StaticMessageIdMultisigISM via the ISM factory and set it on the HypERC20 contract.
If you update only one side, that direction keeps using the old validator set. Update both to complete the rotation.

Prerequisites

  • The bridge owner key on BitSong (for the RoutingISM) and the owner key of the HypERC20 contract on Base Sepolia.
  • The EVM addresses of all validators in the new set, collected from each operator (derived from their key — see Agent Keys).
  • bitsongd, Foundry (cast), and jq.
  • A Base Sepolia RPC and a small amount of Base Sepolia ETH to deploy the EVM-side ISM.

Set the environment for the live bridge:

Terminal
export CHAIN_ID="crescendo-1"
export REMOTE_DOMAIN="84532"
export DENOM="utbtsg"
export NODE="tcp://localhost:26657"
export EVM_RPC="https://<your-base-sepolia-rpc>"

# Live bridge IDs (crescendo-1)
export ROUTING_ISM_ID="0x726f757465725f69736d00000000000000000000000000010000000000000001"
export HYP_ERC20_ADDR="0x27d0F8cBF125995f91407e9E5F70EF52c4412878"
export ISM_FACTORY="0xfc6e546510dC9d76057F1f76633FCFfC188CB213"

1. Create the new MultisigISM

Collect the validator EVM addresses and sort them in ascending byte order, then create the new ISM with the desired threshold (here, 2 of 3).

create-message-id-multisig requires the validator list sorted in ascending byte order (hexadecimal, case-insensitive), otherwise it fails with validator addresses are not sorted correctly in ascending order. Example: 0x45ac… < 0x740b… < 0xdb4e….
Owner — Terminal
# Sort V1,V2,V3 ascending (by byte value) before creating the ISM
export V1="0x<lowest-addr>"; export V2="0x<middle-addr>"; export V3="0x<highest-addr>"

# With --gas auto the binary prints "gas estimate: N" before the JSON,
# so extract the txhash defensively rather than piping straight to jq:
TX_HASH=$(bitsongd tx hyperlane ism create-message-id-multisig "$V1,$V2,$V3" 2 \
    --from <owner-key> --keyring-backend test --chain-id $CHAIN_ID --node $NODE \
    --gas auto --gas-adjustment 1.5 --fees 10000${DENOM} --output json -y 2>&1 \
    | grep -o '"txhash":"[A-Fa-f0-9]*"' | head -1 | cut -d'"' -f4)
echo "TX: $TX_HASH"

NEW_ISM_ID=$(bitsongd query tx $TX_HASH --output json --node $NODE | \
  jq -r 'first(.events[] | select(.type | test("MultisigIsm")) | .attributes[] | select(.key=="ism_id") | .value | fromjson)')
echo "New MultisigISM (2/3): $NEW_ISM_ID"
A threshold of 2 out of 3 means two signatures are required — the bridge stays operational even if one validator is offline. This ISM verifies the Base Sepolia → BitSong direction; the opposite direction is updated on the EVM side.

2. Remove the current route

Find which ISM is currently mapped for the Base Sepolia domain (84532), then remove that mapping from the RoutingISM.

Terminal
ACTIVE_ISM=$(bitsongd query hyperlane ism isms --output json --node $NODE \
  | jq -r '.isms[] | select(."@type" | test("RoutingISM")) | .routes[] | select(.domain==84532) | .ism')
echo "Current ISM (to be replaced): $ACTIVE_ISM"
Owner — Terminal
bitsongd tx hyperlane ism remove-routing-ism-domain $ROUTING_ISM_ID $REMOTE_DOMAIN \
    --from <owner-key> --keyring-backend test --chain-id $CHAIN_ID --node $NODE \
    --gas auto --gas-adjustment 1.5 --fees 10000${DENOM} -y
set-routing-ism-domaindoes not overwrite a domain that is already mapped (it emits the event but the state does not change). You must remove first, then set. Between the two commands there is a short window where domain 84532 has no ISM — schedule the rotation during low traffic. To roll back, re-point the route at the previous ISM (remove + set back to the old ID).

3. Set the new route

Once the removal is included in a block, point the Base Sepolia route at the new 2/3 ISM and verify.

Owner — Terminal
bitsongd tx hyperlane ism set-routing-ism-domain $ROUTING_ISM_ID $REMOTE_DOMAIN $NEW_ISM_ID \
    --from <owner-key> --keyring-backend test --chain-id $CHAIN_ID --node $NODE \
    --gas auto --gas-adjustment 1.5 --fees 10000${DENOM} -y
Verify
ACTIVE_ISM=$(bitsongd query hyperlane ism isms --output json --node $NODE \
  | jq -r '.isms[] | select(."@type" | test("RoutingISM")) | .routes[] | select(.domain==84532) | .ism')
echo "Active ISM: $ACTIVE_ISM  (expected: $NEW_ISM_ID)"

bitsongd query hyperlane ism isms --output json --node $NODE \
  | jq --arg id "$ACTIVE_ISM" '.isms[] | select(.id==$id) | {id, validators, threshold}'

The validators array should list your new set and threshold should be 2.

4. Update the Base Sepolia-side ISM

The BitSong → Base Sepolia direction is verified by a StaticMessageIdMultisigISM on Base Sepolia, attached to the HypERC20. Deploy a new 2/3 ISM through the official factory and set it on the token.

The ISM can be deployed with any funded key (even the relayer's KMS key via --aws); only setInterchainSecurityModule requires the owner key of the HypERC20.

Terminal
VALS="[$V1,$V2,$V3]"   # same three addresses, sorted

# 1. Predict (read-only) and deploy the 2/3 ISM
NEW_EVM_ISM=$(cast call $ISM_FACTORY "deploy(address[],uint8)(address)" "$VALS" 2 --rpc-url $EVM_RPC)
cast send $ISM_FACTORY "deploy(address[],uint8)" "$VALS" 2 --private-key <deployer-key> --rpc-url $EVM_RPC
echo "New EVM ISM: $NEW_EVM_ISM"

# 2. Attach it to the HypERC20 (OWNER key of the HypERC20)
cast send $HYP_ERC20_ADDR "setInterchainSecurityModule(address)" $NEW_EVM_ISM \
    --private-key <owner-evm-key> --rpc-url $EVM_RPC

# 3. Verify
cast call $HYP_ERC20_ADDR "interchainSecurityModule()(address)" --rpc-url $EVM_RPC
The factory uses CREATE2, so the address from the read-only cast call is exactly what cast send deploys. Without this update, BitSong → Base Sepolia would keep being verified by the old validator set.

Troubleshooting

Next Steps

Join the Multisig

The operator side: generate a key and send your validator address to the owner.

Run Validators

Start and operate the validator and relayer agents.
Copyright © 2026