一键导入
warp-route-check
A skill that allows a user to validate that a warp route deployment will be in the expected state after applying some configuration
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
A skill that allows a user to validate that a warp route deployment will be in the expected state after applying some configuration
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Update standing fee quotes for the CROSS/moonpay warp route. Use when asked to set, update, or change fees/bps for specific chains, tokens, or directions in the MoonPay route. Also use when asked to show or read current standing quotes.
Encode and decode raw Hyperlane messages to/from packed hex bytes using the CLI. Use when you need to construct a message for testing, inspect a raw message from logs or a transaction, or decode a warp transfer body.
Helper skill for using Hyperlane CLI address conversion utilities (addressToBytes32 and bytes32ToAddress) for cross-chain message formatting
Simulate pending Safe (multisig) governance txs for a warp route by replaying the literal calldata onto anvil forks, self-relaying any ICA messages, then running warp check against the desired registry config. Use to verify that a not-yet-signed Safe batch produces the intended warp route config before signing.
Add a new chain to an existing warp route owned by a customer. Reads a Linear ticket, adds the new chain to the deploy.yaml, builds a customer-specific strategy file for existing chains, runs warp apply, and outputs transaction files for the customer to sign via their multisig.
Review code changes using Hyperlane monorepo coding standards. Use when reviewing PRs, checking your own changes, or doing self-review before committing.
| name | warp-route-check |
| description | A skill that allows a user to validate that a warp route deployment will be in the expected state after applying some configuration |
You are a specialized agent for checking warp route deployments and updates using the Hyperlane CLI and Heimdall CLI.
This skill requires the following tools and setup:
npm install -g @hyperlane-xyz/clicurl -L https://foundry.paradigm.xyz | bash && foundryupbrew install jq or apt-get install jq)Hyperlane Registry cloned (default location: $HOME/hyperlane-registry):
git clone https://github.com/hyperlane-xyz/hyperlane-registry.git $HOME/hyperlane-registry
Hyperlane Monorepo cloned (required for http-registry):
git clone https://github.com/hyperlane-xyz/hyperlane-monorepo.git
export HYPERLANE_MONOREPO="/path/to/hyperlane-monorepo"
Optional: http_registry shell function (add to ~/.zshrc or ~/.bashrc):
function http_registry() {
pnpm -C $HYPERLANE_MONOREPO/typescript/infra start:http-registry
}
export HYPERLANE_REGISTRY="$HOME/hyperlane-registry" # Custom registry location
export HYPERLANE_MONOREPO="/path/to/monorepo" # Monorepo location
Follow these steps to verify a warp route deployment:
Verify all required tools and paths before proceeding:
# Check required CLI tools
command -v hyperlane >/dev/null 2>&1 || { echo "❌ hyperlane CLI not installed. Run: npm install -g @hyperlane-xyz/cli"; exit 1; }
command -v heimdall >/dev/null 2>&1 || { echo "❌ heimdall not installed"; exit 1; }
command -v cast >/dev/null 2>&1 || { echo "❌ foundry not installed. Run: curl -L https://foundry.paradigm.xyz | bash"; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "❌ jq not installed"; exit 1; }
command -v python3 >/dev/null 2>&1 || { echo "❌ python3 not installed"; exit 1; }
echo "✅ All required tools installed"
# Determine registry location (configurable via env var)
REGISTRY_PATH="${HYPERLANE_REGISTRY:-$HOME/hyperlane-registry}"
# Verify registry exists
if [ ! -d "$REGISTRY_PATH" ]; then
echo "❌ Registry not found at $REGISTRY_PATH"
echo "Clone it: git clone https://github.com/hyperlane-xyz/hyperlane-registry.git $REGISTRY_PATH"
exit 1
fi
echo "✅ Registry found at $REGISTRY_PATH"
# Determine HTTP registry start command
if type http_registry >/dev/null 2>&1; then
HTTP_REGISTRY_CMD="http_registry"
elif [ -n "$HYPERLANE_MONOREPO" ] && [ -d "$HYPERLANE_MONOREPO/typescript/infra" ]; then
HTTP_REGISTRY_CMD="pnpm -C $HYPERLANE_MONOREPO/typescript/infra start:http-registry"
else
echo "❌ Cannot start HTTP registry. Please either:"
echo " 1. Set HYPERLANE_MONOREPO env var pointing to monorepo, OR"
echo " 2. Define http_registry function in your shell"
exit 1
fi
echo "✅ HTTP registry command available"
Common Chain IDs Reference (for parsing transactions):
1 = ethereum 8453 = base
10 = optimism 42161 = arbitrum
56 = bsc 43114 = avalanche
88 = viction 59144 = linea
130 = unichain 137 = polygon
Check fork output for complete mappings
$REGISTRY_PATH (default: $HOME/hyperlane-registry)git -C "$REGISTRY_PATH" branch --show-currentmain at the endrun_in_background=true parameter for Bash tool# Use command determined in Step 0
$HTTP_REGISTRY_CMD > /tmp/http-registry.log 2>&1 &
hyperlane warp fork also blocks - it runs a server for the forked registryhyperlane commandhyperlane warp fork with the provided warp route ID in the background{ port: 8535 } Server running)http://localhost:<actual-port># Use ACTUAL registry URL from Step 3
hyperlane warp fork --id <warp-route-id> --registry <actual-registry-url-from-step-3>
Read fork output carefully to extract:
Create a port mapping from the fork output for later use.
IMPORTANT: Before submitting transactions, you MUST analyze the transaction file to detect if it targets multiple owners per chain.
# Get all unique chain IDs
jq -r '.[].chainId' <transactions-file> | sort -u
# Get all unique contract addresses per chain
jq -r '.[] | "\(.chainId)|\(.to)"' <transactions-file> | sort -u
Map chain IDs to names using the fork output (don't assume - verify actual chain names).
CRITICAL: For EACH unique contract address being targeted, query its current owner from the forked chain.
# For each contract on each chain (use ACTUAL ports from fork output):
cast call <contract-address> "owner()(address)" --rpc-url http://localhost:<ACTUAL-PORT-FOR-CHAIN>
Example workflow (ports shown are examples - use ACTUAL ports from YOUR fork output):
# Example assumes fork output showed: ethereum at port 8548, arbitrum at port 8545
# YOUR ports may differ - always check YOUR fork output
# Query ethereum contracts (use YOUR actual port from fork output)
cast call 0xe1De... "owner()(address)" --rpc-url http://localhost:<YOUR-ETH-PORT>
cast call 0xcf4ec... "owner()(address)" --rpc-url http://localhost:<YOUR-ETH-PORT>
# Query arbitrum contracts (use YOUR actual port from fork output)
cast call 0xAd435... "owner()(address)" --rpc-url http://localhost:<YOUR-ARB-PORT>
Create complete owner mapping:
{
"<chainId>_<contractAddress>": "<actual-owner-address>",
"1_0xe1De9910fe71cC216490AC7FCF019e13a34481D7": "0x3965...",
"1_0xcf4ecA86606372B975FaF04a97e8eE3AfeA5a02D": "0x8Ff4..."
}
Note: Owners may be contract addresses (multisigs, timelocks) - this is OK, anvil can impersonate them.
Group transactions by chain and owner to detect if splitting is needed:
# Pseudocode logic
for each chain:
unique_owners = set of owners for contracts on this chain
if len(unique_owners) > 1:
# Chain has multiple owners - splitting required
splitting_needed = True
If ANY chain has multiple owners: Proceed to Step 5d (split transactions) If all chains have single owner: Skip to Step 5e (create single strategy)
When to do this: If Step 5c detected multiple owners per chain.
How to split:
# Split transactions by owner
owner_groups = group_by(transactions, lambda tx: owner_map[f"{tx.chainId}_{tx.to}"])
for owner, txs in owner_groups:
write_file(f"/tmp/transactions-owner-{owner}.json", txs)
Example: If 171 transactions target 17 different owners across 6 chains, create 17 separate transaction files.
Proceed to Step 5f for each split file.
When to do this: If Step 5c found no multiple owners per chain.
Create one strategy file with one owner per chain:
<chain-name>:
submitter:
chain: <chain-name>
type: impersonatedAccount
userAddress: '<actual-owner-from-step-5b>'
Save to /tmp/<route>-strategy.yaml
Proceed to Step 5g.
When to do this: If Step 5d split transactions.
For each split transaction file, create a corresponding strategy file:
<chain-name>:
submitter:
chain: <chain-name>
type: impersonatedAccount
userAddress: '<owner-for-this-split>'
Save each as /tmp/strategy-owner-<owner>.yaml
Use anvil_setBalance to fund ALL unique owner addresses on their respective chains (use ACTUAL ports from fork output):
BALANCE="0x56BC75E2D63100000" # 100 ETH
# For each owner on each chain (use YOUR ACTUAL port from fork output):
cast rpc anvil_setBalance <owner-address> $BALANCE --rpc-url http://localhost:<YOUR-ACTUAL-PORT>
Example (ports shown are examples - use YOUR actual ports from fork output):
# Example assumes fork output showed ethereum at port 8548
# YOUR port may differ - use YOUR actual port from fork output
cast rpc anvil_setBalance 0x3965... $BALANCE --rpc-url http://localhost:<YOUR-ETH-PORT>
cast rpc anvil_setBalance 0x8Ff4... $BALANCE --rpc-url http://localhost:<YOUR-ETH-PORT>
Fund ALL unique owners you identified in Step 5b.
For each transaction file and its corresponding strategy:
# Set dummy HYP_KEY (required even with impersonation)
HYP_KEY="0x0000000000000000000000000000000000000000000000000000000000000001"
# Submit transactions using installed hyperlane command with ACTUAL forked registry URL
hyperlane submit \
--transactions <transactions-file> \
--strategy <corresponding-strategy-file> \
--registry <YOUR-ACTUAL-forked-registry-url-from-step-4> \
--yes
CRITICAL:
hyperlane commandTrack submission results:
If using split files: Submit all files sequentially, tracking success/failure for each.
IMPORTANT: After submission, decode ALL transactions using Heimdall for the final report.
# For each transaction in the original combined file:
for tx in transactions:
decoded = run_command(f"heimdall decode {tx.data} --default")
store_decoded(tx.chainId, tx.to, tx.annotation, decoded)
Save decoded output as JSON for inclusion in final report:
[
{
"index": 1,
"chain": "ethereum",
"to": "0x...",
"annotation": "...",
"decoded": "..."
}
]
Save to /tmp/decoded-transactions.json
Execute hyperlane warp check against the forked registry (use YOUR ACTUAL URL from Step 4):
hyperlane warp check \
--id <warp-route-id> \
--registry <YOUR-ACTUAL-forked-registry-url-from-step-4> \
--yes
Capture full output to /tmp/warp-check.log for the final report.
NOTE: Warp check may show provider errors for newly added chains - this is expected.
After verifying configuration with warp check, test that an actual token transfer works on the forked chains. This catches issues that config checks miss: broken transferRemote calldata, token accounting bugs (lock/mint/burn/unlock), and message dispatch failures.
Choose one origin → destination pair from the warp route. Prefer a pair where both chains have transactions (i.e., both were modified), so you're testing the freshly configured path.
Forked mainnet chains have real multisig ISMs that require validator signatures. Since there are no validators on anvil forks, you must set a permissive ISM on the destination so self-relay can deliver the message.
# 1. Get the mailbox address for the destination chain from the forked registry
DEST_MAILBOX=$(cast call <warp-router-address> "mailbox()(address)" --rpc-url http://localhost:<DEST-PORT>)
# 2. Get the mailbox owner
MAILBOX_OWNER=$(cast call $DEST_MAILBOX "owner()(address)" --rpc-url http://localhost:<DEST-PORT>)
# 3. Get the current default ISM (to restore later if needed)
OLD_ISM=$(cast call $DEST_MAILBOX "defaultIsm()(address)" --rpc-url http://localhost:<DEST-PORT>)
# 4. Deploy Hyperlane's TestIsm.
# TestIsm returns Types.NULL from moduleType() and true from verify(bytes,bytes),
# so `hyperlane warp send --relay` uses null metadata instead of trying to derive
# multisig/routing metadata on the fork.
TEST_DEPLOYER_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
# Ensure Foundry dependencies are materialized in fresh worktrees/checkouts.
pnpm -C solidity deps:soldeer
TEST_ISM_ADDRESS=$(forge create \
--root solidity \
--broadcast \
--rpc-url http://localhost:<DEST-PORT> \
--private-key $TEST_DEPLOYER_KEY \
contracts/test/TestIsm.sol:TestIsm \
| awk '/Deployed to:/ {print $3}')
# Confirm the fork bypass ISM has the expected Hyperlane behavior:
# moduleType() == 6 (Types.NULL) and verify(...) == true.
cast call $TEST_ISM_ADDRESS "moduleType()(uint8)" --rpc-url http://localhost:<DEST-PORT>
cast call $TEST_ISM_ADDRESS "verify(bytes,bytes)(bool)" 0x 0x --rpc-url http://localhost:<DEST-PORT>
# 5. Impersonate mailbox owner and set the permissive ISM
cast rpc anvil_impersonateAccount $MAILBOX_OWNER --rpc-url http://localhost:<DEST-PORT>
BALANCE="0x56BC75E2D63100000"
cast rpc anvil_setBalance $MAILBOX_OWNER $BALANCE --rpc-url http://localhost:<DEST-PORT>
cast send $DEST_MAILBOX "setDefaultIsm(address)" $TEST_ISM_ADDRESS --from $MAILBOX_OWNER --rpc-url http://localhost:<DEST-PORT> --unlocked
cast rpc anvil_stopImpersonatingAccount $MAILBOX_OWNER --rpc-url http://localhost:<DEST-PORT>
NOTE: If the warp router has a custom ISM set (not using the mailbox default), you'll need to impersonate the router owner and call setInterchainSecurityModule(address) on the router instead.
# Generate a test private key (or use a well-known anvil key)
TEST_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
TEST_SENDER="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" # Anvil account 0
# Fund with native gas on origin
cast rpc anvil_setBalance $TEST_SENDER "0x56BC75E2D63100000" --rpc-url http://localhost:<ORIGIN-PORT>
# For collateral/ERC20 routes: mint or transfer tokens to the test sender
# Option A: If the token has a mint function (e.g., test tokens)
# cast send <token-address> "mint(address,uint256)" $TEST_SENDER 1000000000000000000 ...
# Option B: Impersonate a whale holder and transfer
# Find a top holder from the fork state, impersonate, and transfer
# cast rpc anvil_impersonateAccount <whale> --rpc-url http://localhost:<ORIGIN-PORT>
# cast send <token-address> "transfer(address,uint256)" $TEST_SENDER <amount> --from <whale> --rpc-url http://localhost:<ORIGIN-PORT> --unlocked
# Option C: For native token routes, setBalance is sufficient (already done above)
# Also fund on destination for self-relay gas
cast rpc anvil_setBalance $TEST_SENDER "0x56BC75E2D63100000" --rpc-url http://localhost:<DEST-PORT>
HYP_KEY=$TEST_KEY \
hyperlane warp send \
--registry <YOUR-ACTUAL-forked-registry-url-from-step-4> \
--warp-route-id <warp-route-id> \
--origin <origin-chain> \
--destination <destination-chain> \
--amount 1 \
--relay \
--skip-validation \
--yes
Key flags:
--relay — Self-relay the message (no real relayer on forks)--skip-validation — Skip balance/collateral checks that may fail on forks--amount 1 — Use smallest practical amount (1 wei or 1 unit)Transfer was self-relayed! and show the message IDOwnable: caller is not the owner on ISM bypass → wrong mailbox owner or router has custom ISMinsufficient funds → test sender not funded with tokens (for collateral routes)message already delivered → ISM bypass not working, retry with router-level ISM overrideCapture output to /tmp/warp-transfer-test.log for the final report.
NOTE: This step is optional but strongly recommended. If it fails due to ISM complexity (e.g., routing ISMs, aggregation ISMs), document the failure and move on — the config check in Step 8 still validates the deployment.
Analyze the warp check output carefully:
CRITICAL: Focus on violations for chains that have transactions. Violations on other chains are expected since those chains aren't being modified.
1. Violations on chains WITHOUT transactions: ALWAYS EXPECTED
avalanche: # ← No transactions for this chain
proxyAdmin:
owner:
EXPECTED: '0x...'
ACTUAL: '0x...'
Why: Chains not in the transactions file are not being modified. Their violations show the gap between current and target state.
2. Ownership mismatches on chains WITH transactions: EXPECTED if transactions don't include ownership transfers
ethereum: # ← Has transactions, but ownership not transferred yet
proxyAdmin:
owner:
EXPECTED: '0x...'
ACTUAL: '0x...'
Why: The registry shows TARGET state after full deployment. If transactions configure functionality but don't transfer ownership, ownership differences are expected.
3. Fee configuration differences: MAY BE EXPECTED
ethereum:
tokenFee:
maxFee:
ACTUAL: '115792...'
EXPECTED: ''
Why: Transactions may SET fee values that aren't in the registry config yet.
These indicate actual configuration issues on chains WITH transactions:
ethereum: # ← Has transactions but missing expected config
remoteRouters:
42161: # arbitrum domain
ACTUAL: ''
EXPECTED: '0x...'
Real problems to look for:
Key: Only violations on chains that have transactions AND should have been fixed by those transactions are real problems.
Create a comprehensive markdown report with the following sections:
# <WARP_ROUTE_ID> Validation Report
**Date:** <timestamp>
**Branch:** <registry-branch>
**Transaction File:** <filename>
## Executive Summary
[Overall pass/fail verdict]
[Key metrics: total transactions, chains modified, success rate]
## Port Detection (Dynamic)
[List all ports extracted from fork output]
- HTTP Registry: <actual-port>
- Forked Registry: <actual-port>
- Chain Ports: [chain: port mapping]
## Transaction Breakdown
[Table showing chains with transactions, counts, owners]
## Submission Results
[Detailed results of transaction submission]
- Split files created (if applicable)
- Submission success/failure counts
- Any errors encountered
## Warp Check Results
### Chains WITH Transactions
[Analysis of functional configuration]
- Routers: [status]
- Destination gas: [status]
- ISM: [status]
- Bridge allowances: [status]
### Chains WITHOUT Transactions
[List of chains not modified and their expected violations]
## Transfer Test Results
- **Origin → Destination**: <chain-a> → <chain-b>
- **Amount**: <amount>
- **Result**: ✅ Success / ❌ Failed (reason)
- **Message ID**: <id> (if successful)
- **ISM Bypass**: Yes (TestIsm deployed at <address> set on destination mailbox)
## Decoded Transactions (Heimdall Output)
```json
[Full decoded transaction output from /tmp/decoded-transactions.json]
```
[Full warp check output from /tmp/warp-check.log]
[Final verdict with supporting rationale]
**Save report** to `/tmp/<route>-validation-report.md`
### Step 11: Cleanup
- Clean up **both** background processes:
- Stop the `hyperlane warp fork` server
- Stop the `http_registry` server
- **Switch registry branch back to main**: `git -C "$REGISTRY_PATH" checkout main`
- Confirm cleanup is complete
```bash
# Kill background processes
pkill -f "hyperlane warp fork"
pkill -f "http_registry"
# Switch back to main (use $REGISTRY_PATH variable)
git -C "$REGISTRY_PATH" checkout main
anvil_setBalance (Step 5g) for ALL unique ownershyperlane warp read as fallback validation if neededCRITICAL: Never assume port numbers. Always extract from actual output.
# ❌ WRONG - Hardcoded port
cast call <addr> "owner()" --rpc-url http://localhost:8550
# ✅ CORRECT - Use actual port from YOUR fork output
# First: Extract port from YOUR fork output file
PORT=$(grep "Successfully started Anvil node for chain ethereum" /tmp/warp-fork.log | \
grep -oE "http://127.0.0.1:([0-9]+)" | cut -d: -f3)
cast call <addr> "owner()" --rpc-url http://localhost:$PORT
Build a port mapping dictionary from YOUR fork output and reference it throughout.
| Variable | Default | Purpose |
|---|---|---|
HYPERLANE_REGISTRY | $HOME/hyperlane-registry | Registry location |
HYPERLANE_MONOREPO | (required) | Monorepo location for http-registry |
REGISTRY_PATH | $HYPERLANE_REGISTRY | Used internally by skill |
HTTP_REGISTRY_CMD | (auto-detected) | Command to start HTTP registry |