Warp Update — Generic Orchestrator
SkillSearchGeneric update orchestrator for an already-deployed warp route. Reads a Linear ticket describing any combination of warp-fee changes / ISM changes / hook changes / owner-address changes / additional-collateral-chain additions, edits the registry deploy.yaml accordingly, builds a per-chain strategy file from the artifact-context owner classifications, runs `hyperlane warp apply`, fork-simulates the resulting receipts via `/warp-route-check`, and hands off to `/warp-update-propose` to land the proposals. Chain extension (adding a brand-new chain with new on-chain contracts) is a separate skill — see `/warp-update-extend`.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Warp Update — Generic Orchestrator skill
What this skill tells your AI
The instructions your AI receives, as published by hyperlane-xyz/hyperlane-monorepo in .claude/skills/warp-update/SKILL.md and read by ahel’s review.
For UPDATES to an already-deployed warp route. Batches any combination of these change types in a single run:
- Warp Fee Change —
tokenFee.feeContracts.<chain>.bps, feeowner, feemaxFee/halfAmount - ISM Change —
<chain>.interchainSecurityModule: switching ISM type (default fallback → aggregation → multisig), adding/removing modules from an aggregation, changing rate-limit caps, toggling pausable state, etc. - Hook Change —
<chain>.hook: switching hook type, adjusting amount-routing thresholds, swapping fallback hooks, etc. - Owner Change — chain-level
owner, fee-contract owner, ISM/hook owner, ProxyAdmin owner; any post-deploy ownership rotation - Additional Collateral Chain — a new
<chain>entry withtype: collateralplus the corresponding extension oftokenFee.feeContracts(for multi-collateral routes)
For a brand-new warp route deployment use /warp-deploy-init-route. For adding a new chain that doesn't yet have any of the route's contracts on it use /warp-update-extend (it has the extra deploy-on-new-chain step this skill skips).
Run Log (mandatory)
Maintain the durable, per-ticket run log per /warp-run-log — that skill owns the storage contract (Linear-document-by-title primary, single-writer discipline, local-file fallback), the machine-row + prose entry shape, and the surface-the-URL-as-proof hard gate. Use warp-update as the skill name in each prose entry, and do not report this skill complete until the run-log URL has been surfaced.
Log at least: (a) skill entry with the ticket ID + warp route ID + the change types detected, (b) every [CONFIRM:] gate — before and after the response, (c) the surgical deploy.yaml edits made, (d) the warp apply run (any newly deployed contract addresses + tx/receipt refs), (e) the fork-simulate-verify verdict from /warp-route-check, (f) the propose handoff (batch → signer per /warp-update-propose), (g) the registry PR URL, (h) skill exit (success or bail-out). Log smooth steps too — success data grounds the retrospective as much as failure data.
Input
- Linear ticket ID (required, e.g.
AW-123) - Warp route ID (required, e.g.
ETH/arbitrum-base) - Receipts directory (optional) — where
warp applywrites per-chain Safe TX Builder / AltVMFile receipts. Defaults to a tmp path derived from the ticket and warp route ID.
If the ticket ID or warp route ID is missing, ask the user.
Prerequisite skills
This skill depends on three artifacts and three skill chains:
/warp-deploy-select-keys <ticket-id>must have produced~/.hyperlane/key-contexts/<ticket-id>.yaml. Required becausewarp applymay deploy new contracts during an update (new ISM, new hook, new fee contract on a chain that didn't have fees before, new router for an added collateral chain) and the deployer key signs those deploys viajsonRpcbefore any multisig sees a tx./warp-update-resolve-artifacts <ticket-id> <warp-route-id>must have produced~/.hyperlane/update-context/<ticket-id>.yaml. Required because the strategy file in Step 4 needs the per-artifact owner classification to dispatch each submission to the right submitter type./warp-deploy-fund-deployer <ticket-id>must have run successfully in Step 5 below. Required because the deployer's address needs sufficient balance on every chain that may see new deploys.
If any of those artifacts is missing when this skill starts, invoke the corresponding skill first.
Step 1: Read the Linear Ticket and Identify Requested Changes
Fetch the ticket per /fetch-linear-ticket. Parse the returned description for change instructions. Common targets:
| Ticket language | deploy.yaml target |
|---|---|
"Update warp fee on <chain> to N bps" / fee-direction changes | tokenFee.feeContracts.<chain>.bps (on the synthetic chain's tokenFee) |
"Change fee owner to <addr>" | tokenFee.feeContracts.<chain>.owner (per-chain) or tokenFee.owner (RoutingFee-level) |
| "Change ISM to / add module / remove module / pause / set rate limit" | <chain>.interchainSecurityModule — flat address or nested object; see Step 1a |
| "Change hook to / set threshold / swap fallback" | <chain>.hook — flat address or nested object; see Step 1a |
"Rotate chain owner on <chain> to <addr>" | <chain>.owner |
"Rotate ProxyAdmin owner on <chain> to <addr>" | <chain>.proxyAdmin.owner |
"Add <chain> as additional collateral" | new <chain>: entry with type: collateral + tokenFee.feeContracts.<chain>: { … } |
If the ticket combines multiple changes, parse them all. The skill applies the batched edits in a single warp apply run.
1c: xERC20 routes — token ownership is NOT a warp apply change
On an xERC20 / xERC20-lockbox route, the warp config's owner is the router owner. warp apply manages the router / ISM / hook / rate-limit config only — it does not transfer the underlying xERC20 token's ownership or its ProxyAdmin. Those are a separate contract (xerc20:token / xerc20:proxyAdmin — see /warp-update-resolve-artifacts) and move via hyperlane xerc20 apply, which is config-driven and submitted through the same strategy/submitter machinery (a Safe / ICA / timelock owner all work — do NOT route xERC20 ownership through the infra check-deploy path). In that command's config the top-level owner is the token owner, and the ProxyAdmin owner resolves as ownerOverrides.proxyAdmin ?? proxyAdmin.owner ?? owner (unspecified → token and ProxyAdmin both go to owner). Never conflate the two owner meanings: it is the router owner under warp apply, the token owner under xerc20 apply. The token may also be externally governed or non-Ownable (per /warp-update-resolve-artifacts), in which case we can't transfer it — surface that instead of emitting a doomed transferOwnership.
If a run changes both xERC20 bridge limits (setBufferCap / addBridge / setRateLimitPerSecond) AND token ownership, sequence limits/bridges first, ownership handoff last — both are onlyOwner, so once ownership moves to a Safe/timelock the deployer can no longer set limits.
1a: ISM and Hook Targeting (flat vs nested)
interchainSecurityModule and hook can each be either a flat string address (for "use mailbox default" or an existing custom contract) OR a nested object describing the on-chain structure. Common nested shapes:
staticAggregationIsmwithmodules: [...](each module potentially arateLimitedIsm,pausableIsm,defaultFallbackRoutingIsm, etc.)amountRoutingHookwithdomains: { <chain>: <inner-hook> },lowerHook,upperHook,threshold- Routing variants (
domainRoutingIsm,fallbackRoutingHook) withdomains: { <chainName>: <inner> }
For the target field, read the current deploy.yaml first (Step 2) and identify the actual on-chain shape. Then edit the specific node — e.g. for "set rate limit on solanamainnet to 200M", find the rateLimitedIsm node inside the aggregation tree on solanamainnet and update its maxCapacity field. Don't guess the structure — verify against the current state.
The same logic applies to hook updates: find the relevant node (lower/upper hook, a specific domain's routing entry, a fallback hook) and update it surgically. See the nested-tree walk pattern documented in /warp-update-resolve-artifacts Step 3.
Before committing to a new ISM or hook structure, research the semantics yourself. It is extremely easy to brick a warp route by misconfiguring an ISM or a hook — a deploy.yaml typo that survives warp apply and lands on chain costs everyone hours of recovery. The skill intentionally does not enumerate every safe composition; the right shape depends on what the ticket wants. Do the work:
For any new ISM module or hook component you are about to add, remove, or recompose:
- Read the source.
solidity/contracts/isms/<Name>.solorsolidity/contracts/hooks/<Name>.sol. Walkverify()(ISM) orpostDispatch()/quoteDispatch()(hook) line-by-line under the new config. - Trace both paths. Normal path: is the security / cost / gas-payment guarantee you expect actually enforced? Emergency path (paused, rate-limited, threshold crossed): is the failure mode what the ticket wants (revert / reroute / drop)?
- Aggregation math.
staticAggregationIsmwiththreshold = modules.lengthis AND across modules;threshold: 1is OR. Pick deliberately and confirm it matches the ticket's intent. - Preserve removed guarantees. Replacing
0x0(mailbox default) means removing whatever the mailbox default was enforcing — validator set, IGP gas payment, merkle-tree insertion. Compose so the guarantee survives — commonly viadefaultFallbackRoutingIsminside aggregation for ISMs, oraggregationHook/fallbackRoutingHookretaining the default hook for hooks.
Brick modes to specifically check against (non-exhaustive — there are more):
- No-op ISM standalone. Some ISMs (e.g.
pausableIsm) haveverify()that returnstrueunconditionally when their gate is open — they do no actual verification. Used alone, every message delivers. They MUST be composed with a real verifying ISM via aggregation. - Always-reverting ISM standalone. Some ISMs have preconditions (e.g.
rateLimitedIsmrequires the message to already be_isDelivered) that are false during normal verify-time. Used alone, no message can be delivered. - Threshold-1 with a guard module.
staticAggregationIsm({ pausableIsm, defaultFallbackRoutingIsm }, threshold: 1)is broken — the pause never gates anything because the default still passes. Threshold must equal the count of modules you want to AND together. - Hook that swallows IGP payment. Replacing the mailbox-default hook (which typically includes the InterchainGasPaymaster) with a single-purpose hook means gas is not paid and the relayer never picks up the message. Compose new hooks via
aggregationHook/fallbackRoutingHookso IGP payment stays on the success path. - Amount-routing threshold misconfigured.
amountRoutingHookwith an unrealisticthresholdsilently misroutes every dispatch (too high → all transfers go toupperHook; too low → all go tolowerHook).
When uncertain about a composition you have not used before, surface the question to the user with the relevant contract excerpt rather than guessing.
Canonical schema files — read these when authoring or editing nested ISM / hook / fee configs:
- ISMs:
typescript/sdk/src/ism/types.ts—IsmConfigSchemaunion, plus per-type schemas (PausableIsmConfigSchema,RateLimitedIsmConfigSchema,AggregationIsmConfigSchema,RoutingIsmConfigSchema, etc.). TheMUTABLE_ISM_TYPEconstant enumerates which types can be edited in place vs. which require a fresh deploy on any change. - Hooks:
typescript/sdk/src/hook/types.ts—HookConfigSchemaunion, plus per-type schemas.MUTABLE_HOOK_TYPEis the equivalent in-place-editable list. - Fees:
typescript/sdk/src/fee/types.ts—TokenFeeConfigSchemadiscriminated union (LinearFee,RoutingFee,CrossCollateralRoutingFee, etc.); note thatLinearFee.bpsis immutable sobpsedits trigger a redeploy. - Top-level token / per-chain router shape:
typescript/sdk/src/token/types.ts—HypTokenRouterConfigSchemaand theHypTokenConfigtoken-type union. - Shared mixins:
typescript/sdk/src/types.ts—OwnableSchema(owner+ optionalownerOverrides) andPausableSchema(Ownable +paused: boolean). Many ISM / hook configs.extend(OwnableSchema)or.and(PausableSchema), soowneris required on more types than the field name alone would suggest (every routing-variant ISM, the offchain-lookup ISM, the interchainAccountRouting ISM, the rate-limited ISM optionally, pausableIsm, etc.).
Reference existing production deploy.yamls using the same composition — grep deployments/warp_routes/*/*-deploy.yaml in the registry for the pattern you want. Copy the canonical shape; missing required fields (e.g. owner or domains: {} on defaultFallbackRoutingIsm, paused: false on pausableIsm) get rejected at Zod parse time by warp apply, but the resulting error spam is voluminous — starting from a known-good shape is faster.
1b: Show the Parsed Changes to the User
Surface the requested-changes summary as a table:
Ticket AW-123 requests:
- <chain> / tokenFee.feeContracts.<chain>.bps : 5 → 10
- <chain> / interchainSecurityModule.modules[1].maxCapacity (rateLimitedIsm) : 5e24 → 2e25
- <chain> / owner : 0xABC… → 0xDEF…
Ask the user to correct anything wrong before proceeding. No [CONFIRM:] here — this is information gathering, not destructive.
Step 2: Detect the Source of Truth (YAML or Config Getter)
Warp routes come in two flavors in this monorepo, and the edit path branches on which one:
- Plain YAML route — the
deployments/warp_routes/<TOKEN>/<chains>-deploy.yamlin the registry IS the source of truth. Edit it directly. - Config-getter route — the source of truth is a TypeScript getter in
typescript/infra/config/environments/<env>/warp/configGetters/, registered inwarpConfigGetterMapintypescript/infra/config/warp.ts. The registry'sdeploy.yamlis GENERATED from the getter viatypescript/infra/scripts/warp-routes/export-warp-configs.ts. Editing the registry YAML directly will be silently overwritten the next time the export script runs.
Determine which kind the route is by checking the map:
grep -E "^\s*'?<WARP_ROUTE_ID>'?:" typescript/infra/config/warp.ts
If the route ID is keyed in warpConfigGetterMap (or in the per-environment dispatch in that file), it's a getter-backed route. Note which getter file it points to (e.g. getRenzoPZETHWarpConfig.ts). If the route ID isn't in the map at all, it's a plain YAML route.
Then fetch the CURRENT state (regardless of source flavor):
REGISTRY_PATH="${HYPERLANE_REGISTRY:-$(pwd)/../hyperlane-registry}"
cat $REGISTRY_PATH/deployments/warp_routes/<TOKEN>/<chains-alphabetical>-deploy.yaml
Show the user the current YAML content + the flavor (YAML / getter-backed + which getter file). This is the baseline the warp-apply diff is computed against either way; the difference shows up in Step 3 (where the edit lands).
Step 3: Apply Edits
For each requested change from Step 1, target the precise field. Parse the YAML, mutate the target, serialize back. Do NOT do a global string replace — that's how field-targeting bugs sneak in.
Preserve the existing YAML formatting (alphabetical chain order at the top level AND alphabetical keys within each chain entry, per /registry-yaml-sort-policy). The registry CI / CodeRabbit enforces both invariants.
3a: Plain YAML Route — Edit the Registry File Directly
If Step 2 classified the route as plain YAML, edit $REGISTRY_PATH/deployments/warp_routes/<TOKEN>/<chains>-deploy.yaml in place. Show the user the diff (unified format, - old, + new). End your message with:
[CONFIRM: Apply deploy.yaml edits for <ticket-id>]
If confirmed, write the updated deploy.yaml back to the registry.
3b: Config-Getter Route — Edit the Getter, Then Regenerate
If Step 2 classified the route as getter-backed:
-
Edit the getter TS file in
typescript/infra/config/environments/<env>/warp/configGetters/<getter>.tsto encode the requested changes. The getter returns aChainMap<HypTokenRouterConfig>; mutate the relevant fields in the returned object. Show the user the unified TS diff. -
Regenerate the registry YAML by running the export script for ONLY this route:
pnpm -C typescript/infra tsx scripts/warp-routes/export-warp-configs.ts \ -e <env> \ --warpRouteIds <WARP_ROUTE_ID>This calls the getter, sorts the output per
WARP_YAML_SORT_CONFIG, and writes$REGISTRY_PATH/deployments/warp_routes/<TOKEN>/<chains>-deploy.yamlin place. -
Diff the regenerated YAML against the previous version (e.g.
git -C $REGISTRY_PATH diff deployments/warp_routes/<TOKEN>/). The diff should match the conceptual change requested in the ticket; if other fields drifted, the getter has a bug or the export script picked up unrelated env state. Halt and surface the unexpected diff to the user before continuing.
End your message with:
[CONFIRM: Apply getter edits + regenerated deploy.yaml for <ticket-id>]
Note:
[CONFIRM: ...]is a Haggis-specific harness primitive — Haggis renders it as an inline approve/reject button. In other Claude Code contexts it is just text.
For getter-backed routes, both changes ship as PRs in Step 11:
- Monorepo PR for the getter edit (no changeset needed —
@hyperlane-xyz/infraisprivate: true). - Registry PR for the regenerated YAML + changeset (per the standard Step 11 flow).
Step 11 covers the registry PR; for the monorepo PR open it separately with a normal gh pr create against main, scoping git add to the specific getter file + typescript/infra/config/warp.ts if you touched the map.
Step 4: Build the Strategy File
4a: Map Owner Types to Submitters
The strategy file tells warp apply how to submit each chain's transactions. For each chain in the route, look up the artifact owners from the auto-loaded artifact context (~/.hyperlane/update-context/<ticket-id>.yaml) and map the owner type — AND the controller type of any wrapping account — to the right submitter:
| Owner type from artifact context | Strategy submitter |
|---|---|
Safe | gnosisSafeTxBuilder with that chain's Safe address |
ICA (controller = Safe) | interchainAccount with origin: <controllingChain>, owner: <controllerAddress>, internalSubmitter: gnosisSafeTxBuilder targeting the controlling Safe |
ICA (controller = EOA) | interchainAccount with origin: <controllingChain>, owner: <controllerEOAaddress>, internalSubmitter: file (write the callRemote for the EOA to broadcast later) or internalSubmitter: jsonRpc (execute live with the EOA's key) |
Squads | file (no native Squads submitter in warp apply; the propose skill calls submitProposalToSquads against the file later) |
EOA | jsonRpc if it's the deployer key signing post-deploy ops; otherwise halt — EOAs should have been caught at /warp-deploy-validate-owners and rejected as production owners |
Turnkey / Privy / MPC / CustomMultisig / Timelock / Other | file (manual hand-off to the relevant tooling) |
Unknown | Halt — can't dispatch without an owner classification |
The ICA branch has two sub-cases, not one. Don't unconsciously default to internalSubmitter: gnosisSafeTxBuilder just because most production ICAs are Safe-controlled. Read the controller type from the artifact context (/warp-update-resolve-artifacts Step 3 walks each ICA's InterchainAccountRouter to resolve the controller and classifies it). If the controller is an EOA, the Safe-builder path will silently produce nonsensical output that no Safe app can consume — pick file or jsonRpc instead.
If different artifacts on the same chain have different owners (router → AW Safe vs. fee contracts → Foundation Safe is the common case), the strategy needs a feeSubmitter block in addition to the main submitter block — see /warp-update-extend Step 7's "fee contract with a separate fee Safe" pattern.
Write the strategy file to ~/.hyperlane/strategies/<ticket-id>-strategy.yaml. Show the user the strategy.
4b: Preview Expected Output Files Before Confirming
Before the user confirms the strategy, describe what warp apply will write to the receipts directory — per chain, per submitter type. This is the structural guardrail: catching a wrong-strategy-shape (raw owner-ops vs. ICA-wrapped, Safe-builder JSON vs. SVM versioned-tx array) BEFORE the apply phase deploys mainnet contracts means the user can reject the strategy here, not after seeing wrong-format files post-deploy.
For each chain entry in the strategy, surface:
- Expected filename pattern in the receipts directory
- The shape of the JSON / file body
- Who downstream consumes that file (so the user can sanity-check the strategy serves the intended consumer)
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 75
- Forks
- 601
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
warp-update- Source
- github.com/hyperlane-xyz/hyperlane-monorepo