Runbook Validator, Relayer e Multisig
Runbook operativo, in ordine di esecuzione, per gestire il bridge Hyperlane tra BitSong crescendo-1 e Base Sepolia. Copre sette operazioni:
- Validator Hyperlane
- Relayer
- Creare un nuovo multisig 2/3
- Rimuovere l'attuale multisig
- Impostare il nuovo multisig
- Test transfer BitSong → Base Sepolia
- Test transfer Base Sepolia → BitSong
Prerequisiti
- Un VPS (Ubuntu 24.04/26.04, 2-4 vCPU, 4-8 GB RAM) con Docker, Foundry (
cast), AWS CLI v2,jq. - Un account AWS (per validator/relayer in produzione): utente IAM con access key, chiavi KMS e un bucket S3.
- Accesso ai nodi pubblici BitSong + un RPC Base Sepolia con buoni limiti (vedi sotto). Non serve un nodo BitSong locale.
- Account Cosmos finanziato con TBTSG (gas dell'announce) e indirizzi EVM dei validator/relayer finanziati con ETH su Base Sepolia (announce + gas relay).
- Le operazioni 3-5 (rotazione multisig) richiedono la chiave owner del bridge.
POST https://faucet.testnet.bitsong.io/api/v1/faucet {address} → poll /api/v1/faucet/status/{runId}). ETH Base Sepolia: faucet Coinbase / Alchemy sugli indirizzi EVM derivati da KMS.Parametri reali del bridge
ID on-chain del bridge su crescendo-1 (verificati). Se cambiano, recuperali coi comandi in coda.
0x68797065726c616e6500000000000000000000000000000000000000000000000x726f757465725f706f73745f64697370617463680000000300000000000000000x726f757465725f706f73745f64697370617463680000000400000000000000010x726f757465725f69736d0000000000000000000000000001000000000000000171717171 — Base Sepolia: 845320x6966b0E55883d49BFB24539356a2f8A673E02039 — ISM factory 0xfc6e546510dC9d76057F1f76633FCFfC188CB21371717171, non 7171. Usare il valore sbagliato produce No router enrolled for domain.bitsongd query hyperlane mailboxes --output json --node $NODE | jq '.mailboxes'
bitsongd query hyperlane hooks merkle-tree-hooks --output json --node $NODE | jq '.merkle_tree_hooks'
bitsongd query hyperlane hooks igps --output json --node $NODE | jq '.igps'
bitsongd query hyperlane ism isms --output json --node $NODE | jq '.isms'
Endpoint dei nodi pubblici (verificati)
- BitSong: usa l'IP diretto del nodo — RPC
http://178.104.0.132:26657, gRPChttp://178.104.0.132:9090. Il proxyhttps://rpc.testnet.bitsong.iopuò dare 502 sotto carico di indicizzazione e non espone un gRPC pubblico TLS. - Base Sepolia: usa un RPC con limiti alti, es. Coinbase CDP
https://api.developer.coinbase.com/rpc/v1/base-sepolia/<API_KEY>. Attenzione: il path/base/è la mainnet (8453), serve/base-sepolia/(84532). CDP limitagetLogsa 1000 blocchi →index.chunk≤ 999. L'RPC pubblicohttps://sepolia.base.orglimita a 2000 e va in rate-limit.
Variabili d'ambiente
export CHAIN_ID="crescendo-1"
export DOMAIN_ID="71717171"
export REMOTE_DOMAIN="84532"
export DENOM="utbtsg"
export NODE="tcp://178.104.0.132:26657" # nodo pubblico BitSong (IP diretto)
export EVM_RPC="https://api.developer.coinbase.com/rpc/v1/base-sepolia/<API_KEY>"
export KEY_NAME="<tua-chiave>"
# Bridge (valori reali sopra)
export MAILBOX_ID="0x68797065726c616e650000000000000000000000000000000000000000000000"
export MERKLE_HOOK_ID="0x726f757465725f706f73745f6469737061746368000000030000000000000000"
export IGP_ID="0x726f757465725f706f73745f6469737061746368000000040000000000000001"
export ROUTING_ISM_ID="0x726f757465725f69736d00000000000000000000000000010000000000000001"
# AWS (validator/relayer in produzione)
export AWS_REGION="eu-central-1"
export S3_BUCKET="<tuo-bucket-checkpoint>"
export HYPERLANE_IMAGE="ghcr.io/hyperlane-xyz/hyperlane-agent:agents-v2.2.0"
1. Validator Hyperlane
Il validator firma i checkpoint dei messaggi. Serve una istanza per ciascuna chain di origine: una osserva BitSong, una osserva Base Sepolia. Per 3 validator in un multisig 2/3 ripeti questa sezione per ognuno (3 chiavi KMS distinte → 3 indirizzi EVM distinti). Questo runbook usa la configurazione AWS KMS (firma) + S3 (checkpoint), validata sul campo.
--validator.type hexKey --validator.key 0x… --checkpointSyncer.type localStorage --checkpointSyncer.path /checkpoints (e il relayer con --allowLocalCheckpointSyncers true). Con localStorage l'announce è un percorso file:///… leggibile solo da relayer sullo stesso host. Per un multisig multi-operatore usa S3.Crea le risorse AWS (IAM, KMS, S3)
Crea un utente IAM con access key e configura aws sul server. Poi una chiave KMS per ogni validator + una per il relayer (asimmetriche, ECC_SECG_P256K1, Sign and verify), e ricava l'indirizzo EVM (è quello che entra nel multisig).
# Chiave KMS del validator-N (ripeti per ogni validator + relayer)
KID=$(aws kms create-key --key-spec ECC_SECG_P256K1 --key-usage SIGN_VERIFY \
--query KeyMetadata.KeyId --output text)
aws kms create-alias --alias-name alias/hl-validator-1 --target-key-id "$KID"
# Indirizzo EVM derivato dalla chiave KMS (da registrare nel multisig)
AWS_KMS_KEY_ID=alias/hl-validator-1 cast wallet address --aws
kms:Sign, kms:GetPublicKey, kms:DescribeKey) e leggere/scrivere S3. Puoi concederlo via IAM policy oppure, alla creazione della chiave, via key policy (statement che dà al tuo utente kms:Sign). Vedi Agent Keys → AWS.Crea il bucket S3 dei checkpoint (DEVE essere public-read)
anonymously_read_from_bucket). Con un bucket privato la GET anonima torna 403 e il validator non scrive mai i checkpoint_*.json (loop infinito su s3_storage.rs:101) → il relayer va in Unable to reach quorum. I checkpoint sono firme pubbliche non sensibili: il bucket va reso public-read (setup standard Hyperlane).aws s3api create-bucket --bucket "$S3_BUCKET" --region "$AWS_REGION" \
--create-bucket-configuration LocationConstraint="$AWS_REGION"
# Abilita le ACL (l'agente scrive gli oggetti con ACL public-read)
aws s3api put-bucket-ownership-controls --bucket "$S3_BUCKET" \
--ownership-controls 'Rules=[{ObjectOwnership=ObjectWriter}]'
# Consenti l'accesso pubblico in lettura
aws s3api put-public-access-block --bucket "$S3_BUCKET" \
--public-access-block-configuration BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false
# Policy: lettura pubblica (GetObject + ListBucket)
aws s3api put-bucket-policy --bucket "$S3_BUCKET" --policy '{"Version":"2012-10-17","Statement":[{"Sid":"PublicRead","Effect":"Allow","Principal":"*","Action":["s3:GetObject","s3:ListBucket"],"Resource":["arn:aws:s3:::'"$S3_BUCKET"'","arn:aws:s3:::'"$S3_BUCKET"'/*"]}]}'
# Verifica: chiave mancante deve dare 404 (non 403)
curl -s -o /dev/null -w "anon GET -> %{http_code} (atteso 404)\n" \
"https://$S3_BUCKET.s3.$AWS_REGION.amazonaws.com/bitsong-val1/checkpoint_latest_index.json"
Crea e finanzia la chiave Cosmos di announce
BitSong è una chain Cosmos: anche con la firma KMS, l'announce on-chain è una tx Cosmos e richiede una chiave Cosmos (hex) con saldo TBTSG. Una chiave condivisa tra i validator (e il relayer) è sufficiente.
bitsongd keys add hyperlane-signer --keyring-backend test
COSMOS_SIGNER_KEY=0x$(bitsongd keys export hyperlane-signer --unarmored-hex --unsafe --keyring-backend test 2>&1 | tail -1)
export COSMOS_SIGNER_KEY
# Finanzia l'indirizzo bitsong1… via faucet (vedi Prerequisiti)
Crea l'agent-config.json
mkdir -p $HOME/hl/config
{
"chains": {
"bitsong": {
"name": "bitsong",
"chainId": "crescendo-1",
"domainId": 71717171,
"protocol": "cosmosNative",
"bech32Prefix": "bitsong",
"slip44": 639,
"contractAddressBytes": 32,
"canonicalAsset": "utbtsg",
"rpcUrls": [{ "http": "http://178.104.0.132:26657" }],
"grpcUrls": [{ "http": "http://178.104.0.132:9090" }],
"nativeToken": { "name": "BitSong", "symbol": "BTSG", "decimals": 6, "denom": "utbtsg" },
"gasPrice": { "amount": "0.025", "denom": "utbtsg" },
"gasMultiplier": "1.5",
"blocks": { "confirmations": 1, "estimateBlockTime": 6, "reorgPeriod": 1 },
"index": { "from": 3895000, "chunk": 500 },
"mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000",
"validatorAnnounce": "0x68797065726c616e650000000000000000000000000000000000000000000000",
"merkleTreeHook": "0x726f757465725f706f73745f6469737061746368000000030000000000000000",
"interchainGasPaymaster": "0x726f757465725f706f73745f6469737061746368000000040000000000000001"
},
"basesepolia": {
"name": "basesepolia",
"chainId": 84532,
"domainId": 84532,
"protocol": "ethereum",
"rpcUrls": [{ "http": "https://api.developer.coinbase.com/rpc/v1/base-sepolia/<API_KEY>" }],
"nativeToken": { "name": "Ether", "symbol": "ETH", "decimals": 18 },
"blocks": { "confirmations": 1, "estimateBlockTime": 2, "reorgPeriod": 1 },
"index": { "from": 39040037, "chunk": 999 },
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"validatorAnnounce": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9",
"merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
"interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564"
}
}
}
bitsong.index.fromrecente (es. altezza attuale − qualche migliaio) l'albero è comunque corretto. Con index.from: 1 su una chain a milioni di blocchi, più agenti scansionano l'intera storia ed esauriscono max_open_connections dell'RPC CometBFT (il nodo smette di rispondere in HTTP). Tieni bitsong.index.chunk ~500 e basesepolia.index.chunk ≤ 999 (limite CDP).Avvia i due validator (per ciascun validator del multisig)
docker pull --platform linux/amd64 $HYPERLANE_IMAGE
# file ambiente: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, COSMOS_SIGNER_KEY, S3_BUCKET
docker run -d \
--name hl-val1-bitsong --restart unless-stopped --network host \
--user 0:0 --ulimit nofile=65536:65536 \
--env-file $HOME/hl/agent.env \
-e CONFIG_FILES=/config/agent-config.json \
-v $HOME/hl/config/agent-config.json:/config/agent-config.json:ro \
-v $HOME/hl/db/val1-bitsong:/hyperlane_db \
$HYPERLANE_IMAGE ./validator \
--db /hyperlane_db --originChainName bitsong --reorgPeriod 1 --interval 10 \
--validator.type aws --validator.region $AWS_REGION --validator.id alias/hl-validator-1 \
--chains.bitsong.signer.type cosmosKey --chains.bitsong.signer.key $COSMOS_SIGNER_KEY --chains.bitsong.signer.prefix bitsong \
--checkpointSyncer.type s3 --checkpointSyncer.bucket $S3_BUCKET --checkpointSyncer.region $AWS_REGION --checkpointSyncer.folder bitsong-val1 \
--metrics-port 9101 --log.format json --log.level info
docker run -d \
--name hl-val1-basesepolia --restart unless-stopped --network host \
--user 0:0 --ulimit nofile=65536:65536 \
--env-file $HOME/hl/agent.env \
-e CONFIG_FILES=/config/agent-config.json \
-v $HOME/hl/config/agent-config.json:/config/agent-config.json:ro \
-v $HOME/hl/db/val1-basesepolia:/hyperlane_db \
$HYPERLANE_IMAGE ./validator \
--db /hyperlane_db --originChainName basesepolia --reorgPeriod 1 --interval 10 \
--validator.type aws --validator.region $AWS_REGION --validator.id alias/hl-validator-1 \
--chains.basesepolia.signer.type aws --chains.basesepolia.signer.region $AWS_REGION --chains.basesepolia.signer.id alias/hl-validator-1 \
--checkpointSyncer.type s3 --checkpointSyncer.bucket $S3_BUCKET --checkpointSyncer.region $AWS_REGION --checkpointSyncer.folder basesepolia-val1 \
--metrics-port 9201 --log.format json --log.level info
- Il validator BitSong usa sempre
cosmosKeyper il signer della chain (firma l'announce on-chain); la firma dei checkpoint usaaws(KMS). - Il validator Base Sepolia usa
awssia per il checkpoint sia per il signer della chain. Finanzia il suo indirizzo EVM (derivato da KMS) con ETH su Base Sepolia prima di avviarlo. --user 0:0evitaPermission deniedsui volumi db;--ulimit nofileevitaToo many open files.
Verifica l'announcement e i checkpoint
# Announce BitSong (fino a ~2 min) — VALIDATOR_ADDR = indirizzo KMS del validator
bitsongd query hyperlane ism announced-storage-locations \
$MAILBOX_ID $(echo $VALIDATOR_ADDR | tr '[:upper:]' '[:lower:]') --output json --node $NODE | jq '.storage_locations'
# Announce Base Sepolia
cast call 0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9 \
"getAnnouncedStorageLocations(address[])(string[][])" "[$VALIDATOR_ADDR]" --rpc-url $EVM_RPC
# Checkpoint scritti su S3 (compaiono quando passa un nuovo messaggio)
aws s3 ls --recursive s3://$S3_BUCKET/bitsong-val1/ | grep checkpoint
2. Relayer
Il relayer legge i checkpoint firmati (da S3) e consegna i messaggi in entrambe le direzioni. Ne basta uno. Usa una chiave KMS dedicata per Base Sepolia e la stessa chiave Cosmos per BitSong.
docker run -d \
--name hl-relayer --restart unless-stopped --network host \
--user 0:0 --ulimit nofile=65536:65536 \
--env-file $HOME/hl/agent.env \
-e CONFIG_FILES=/config/agent-config.json \
-v $HOME/hl/config/agent-config.json:/config/agent-config.json:ro \
-v $HOME/hl/db/relayer:/hyperlane_db \
$HYPERLANE_IMAGE ./relayer \
--db /hyperlane_db --relayChains bitsong,basesepolia \
--gasPaymentEnforcement '[{"type":"none"}]' \
--chains.bitsong.signer.type cosmosKey --chains.bitsong.signer.key $COSMOS_SIGNER_KEY --chains.bitsong.signer.prefix bitsong \
--chains.basesepolia.signer.type aws --chains.basesepolia.signer.region $AWS_REGION --chains.basesepolia.signer.id alias/hl-relayer \
--metrics-port 9301 --log.format json --log.level info
- Con i checkpoint su S3 il relayer li legge dall'URL annunciato — non serve
--allowLocalCheckpointSyncers(è solo perlocalStorage). --gasPaymentEnforcement '[{"type":"none"}]'disabilita l'enforcement del gas (solo testnet).
docker logs -f --tail 100 hl-relayer
EVM_PADDED=$(printf "0x%064s" "${HYP_ERC20_ADDR#0x}" | tr ' ' '0')
--whitelist "[{\"senderAddress\":\"$TOKEN_ID\",\"destinationDomain\":\"$REMOTE_DOMAIN\"},{\"senderAddress\":\"$EVM_PADDED\",\"destinationDomain\":\"$DOMAIN_ID\"}]"
3. Creare un nuovo multisig 2/3
Su Hyperlane il MultisigISM è immutabile: se ne crea uno nuovo con la lista aggiornata di validator e la soglia, poi si ripunta la rotta del RoutingISM (operazioni 4 e 5).
--from <owner-key>).Raccogli i 3 indirizzi EVM dei validator (derivati da KMS in sezione 1) e crea il MultisigISM con soglia 2 su 3.
create-message-id-multisig richiede la lista di validator ordinata in modo crescente per valore di byte (esadecimale, case-insensitive), altrimenti fallisce con validator addresses are not sorted correctly in ascending order. Es.: 0x45ac… < 0x740b… < 0xdb4e….# Ordina V1,V2,V3 in modo crescente (per byte) prima di creare l'ISM
export V1="0x<addr-piu-basso>"; export V2="0x<addr-medio>"; export V3="0x<addr-piu-alto>"
# Nota: con --gas auto il binario stampa "gas estimate: N" prima del JSON → estrai il txhash così
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 "Nuovo MultisigISM (2/3): $NEW_ISM_ID"
2 su 3: servono 2 firme su 3. Il bridge resta operativo anche con un validator offline. Questo ISM verifica la direzione Base Sepolia → BitSong (lato Cosmos); per la direzione opposta aggiorna l'ISM lato EVM (sotto).4. Rimuovere l'attuale multisig
Verifica quale ISM è attualmente sulla rotta del dominio 84532, poi rimuovi quella mappatura dal 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 "ISM attuale (da sostituire): $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-domainnon sovrascrive un dominio già mappato (emette l'evento ma lo stato non cambia): bisogna prima rimuovere e poi impostare. Tra i due comandi c'è una breve finestra in cui il dominio 84532 resta senza ISM — pianifica la rotazione a basso traffico. Per il rollback rimetti la rotta sull'ISM precedente (remove + set verso il vecchio ID).5. Impostare il nuovo multisig
Attendi che la rimozione sia in un blocco, poi ripunta la rotta del dominio Base Sepolia sul nuovo ISM 2/3.
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 "ISM attivo: $ACTIVE_ISM (atteso: $NEW_ISM_ID)"
bitsongd query hyperlane ism isms --output json --node $NODE \
| jq --arg id "$ACTIVE_ISM" '.isms[] | select(.id==$id) | {id, validators, threshold}'
Aggiornare l'ISM lato EVM
La direzione BitSong → Base Sepolia è verificata da un StaticMessageIdMultisigISM su Base Sepolia, agganciato all'HypERC20. Portalo a 2/3: l'ISM puoi deployarlo con qualsiasi chiave finanziata (anche la KMS del relayer via --aws); solo setInterchainSecurityModule richiede la owner key dell'HypERC20.
export HYP_ERC20_ADDR="0x27d0F8cBF125995f91407e9E5F70EF52c4412878" # verifica l'indirizzo corrente
FACTORY="0xfc6e546510dC9d76057F1f76633FCFfC188CB213"
VALS="[$V1,$V2,$V3]" # stessi 3 indirizzi, ordinati
# 1. Predici e deploya l'ISM 2/3 (qui con la KMS del relayer: AWS_KMS_KEY_ID=alias/hl-relayer ... --aws)
NEW_EVM_ISM=$(cast call $FACTORY "deploy(address[],uint8)(address)" "$VALS" 2 --rpc-url $EVM_RPC)
cast send $FACTORY "deploy(address[],uint8)" "$VALS" 2 --aws --rpc-url $EVM_RPC
echo "Nuovo ISM EVM: $NEW_EVM_ISM"
# 2. Aggancialo all'HypERC20 (OWNER key dell'HypERC20)
cast send $HYP_ERC20_ADDR "setInterchainSecurityModule(address)" $NEW_EVM_ISM \
--private-key <owner-evm-key> --rpc-url $EVM_RPC
# 3. Verifica
cast call $HYP_ERC20_ADDR "interchainSecurityModule()(address)" --rpc-url $EVM_RPC
6. Test transfer BitSong → Base Sepolia
I token vengono bloccati come collaterale su BitSong e mintati come HypERC20 su Base Sepolia.
export TOKEN_ID="0x726f757465725f61707000000000000000000000000000010000000000000000"
export HYP_ERC20_ADDR="0x27d0F8cBF125995f91407e9E5F70EF52c4412878"
RECIPIENT_ADDR="0x<evm-recipient>"
RECIPIENT_BYTES32=$(printf "0x%064s" "${RECIPIENT_ADDR#0x}" | tr ' ' '0')
REQUIRED_FEE=$(bitsongd query hyperlane hooks quote-gas-payment $IGP_ID $REMOTE_DOMAIN 300000 -o json --node $NODE | jq -r '.gas_payment[0].amount')
bitsongd tx warp transfer $TOKEN_ID $REMOTE_DOMAIN $RECIPIENT_BYTES32 7777 \
--max-hyperlane-fee "${REQUIRED_FEE}utbtsg" \
--from $KEY_NAME --keyring-backend test --chain-id $CHAIN_ID --node $NODE \
--gas auto --gas-adjustment 1.5 --fees 10000${DENOM} --output json -y
Dopo la consegna (i validator firmano il checkpoint del nuovo messaggio → S3 → relayer), verifica su Base Sepolia (balanceOf e totalSupply aumentano di 7777):
cast call "$HYP_ERC20_ADDR" "totalSupply()(uint256)" --rpc-url "$EVM_RPC"
cast call "$HYP_ERC20_ADDR" "balanceOf(address)(uint256)" "$RECIPIENT_ADDR" --rpc-url "$EVM_RPC"
7. Test transfer Base Sepolia → BitSong
Ritorno: l'HypERC20 viene bruciato su Base Sepolia, il collaterale sbloccato su BitSong.
transferRemote brucia i token del chiamante: devi già possedere HypERC20 (dalla direzione BitSong → Base), altrimenti ERC20: burn amount exceeds balance.# Destinatario Cosmos (bech32) → bytes32
COSMOS_RECIPIENT="bitsong1..."
ADDR_HEX=$(bitsongd keys parse "$COSMOS_RECIPIENT" 2>&1 | grep -i bytes | awk '{print $2}')
RECIPIENT_BYTES32="0x$(printf '%064s' "$ADDR_HEX" | tr ' ' '0')"
# Quota il gas EVM e brucia verso BitSong (dominio 71717171).
# Se il detentore è una chiave KMS, firma con --aws (AWS_KMS_KEY_ID=alias/…); altrimenti --private-key.
VAL=$(cast call "$HYP_ERC20_ADDR" "quoteGasPayment(uint32)(uint256)" $DOMAIN_ID --rpc-url $EVM_RPC)
cast send "$HYP_ERC20_ADDR" "transferRemote(uint32,bytes32,uint256)" \
"$DOMAIN_ID" "$RECIPIENT_BYTES32" 4444 --value "$VAL" \
--aws --rpc-url "$EVM_RPC"
Verifica lo sblocco su BitSong (il saldo utbtsg del destinatario aumenta di 4444):
bitsongd query bank balances "$COSMOS_RECIPIENT" --node $NODE --output json | jq '.balances'
71717171. Su testnet (enforcement off) il --value minimo richiesto è quello di quoteGasPayment (spesso 1 wei).Operazioni e troubleshooting
docker ps --filter "name=hl-" --format "table {{.Names}}\t{{.Status}}"
docker logs -f --tail 100 hl-val1-bitsong
docker logs -f --tail 100 hl-val1-basesepolia
docker logs -f --tail 100 hl-relayer
for f in bitsong-val1 basesepolia-val1; do echo "$f: $(aws s3 ls --recursive s3://$S3_BUCKET/$f/ | grep -c checkpoint)"; done
TipCheckpointSubmitter ritenta all'infinito su hyperlane-base/src/types/s3_storage.rs:101 (stessa riga di Failed to read reorg status) senza scrivere checkpoint_*.json. Applica la bucket policy public-read (vedi sezione 1): GET anonima su chiave mancante deve dare 404, non 403. Le scritture richiedono ACL abilitate (ObjectOwnership=ObjectWriter). A un tip tranquillo checkpoint_queue_len: 0 è normale finché non arriva un nuovo messaggio.bitsong.index.from recente, non 1. Con index.from: 1 più agenti scansionano milioni di blocchi ed esauriscono max_open_connections dell'RPC (HTTP non risponde, 502 / connection reset; la chain resta sana). Riduci il carico: index.from recente, chunk ~500, --ulimit nofile=65536, e usa l'IP diretto del nodo.getLogs a 1000 blocchi: imposta basesepolia.index.chunk a 999 (un range di 1000 inclusi = 1001 → rifiutato). L'RPC pubblico sepolia.base.org limita a 2000 e va in rate-limit: preferisci CDP /base-sepolia/ (il path /base/ è la mainnet 8453).create-message-id-multisig (e per il factory ISM lato EVM) va ordinata crescente per byte (hex). Riordina e riprova.remove-routing-ism-domain, attendi l'inclusione, poi set-routing-ism-domain.--gas auto stampa gas estimate: N prima del JSON, rompendo jq. Estrai così: ... -y 2>&1 | grep -o '"txhash":"[A-Fa-f0-9]*"' | head -1 | cut -d'"' -f4.--user 0:0 (o $(id -u):$(id -g)) e --ulimit nofile=65536:65536.