Rotate the Multisig
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.
--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
84532 to it.Base Sepolia side — Static ISM
StaticMessageIdMultisigISM via the ISM factory and set it on the HypERC20 contract.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), andjq.- 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:
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….# 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"
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.
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"
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.
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
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.
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
cast call is exactly what cast send deploys. Without this update, BitSong → Base Sepolia would keep being verified by the old validator set.Troubleshooting
create-message-id-multisig (and for the EVM ISM factory) must be sorted ascending by byte value (hex). Reorder the addresses and retry.remove-routing-ism-domain, wait for inclusion in a block, then set-routing-ism-domain.--gas auto prints gas estimate: N before the JSON, which breaks a direct jq parse. Extract it defensively: ... -y 2>&1 | grep -o '"txhash":"[A-Fa-f0-9]*"' | head -1 | cut -d'"' -f4.Next Steps
Join the Multisig
Join the BitSong Hyperlane validator multisig on an existing bridge using a production AWS setup (KMS signing + S3 checkpoint storage).
Runbook Validator, Relayer e Multisig
Runbook operativo (validato end-to-end) per validator e relayer in AWS KMS + S3, rotazione del multisig 2/3 e test di trasferimento warp sul bridge Hyperlane BitSong (crescendo-1) ↔ Base Sepolia.