Skip to content

feat(sdk): use routing ISM setBatch/removeBatch when available#8656

Draft
paulbalaji wants to merge 1 commit intopbio/routing-ism-batch-opsfrom
pbio/routing-ism-batch-sdk
Draft

feat(sdk): use routing ISM setBatch/removeBatch when available#8656
paulbalaji wants to merge 1 commit intopbio/routing-ism-batch-opsfrom
pbio/routing-ism-batch-sdk

Conversation

@paulbalaji
Copy link
Copy Markdown
Collaborator

@paulbalaji paulbalaji commented Apr 22, 2026

Summary

Wires HyperlaneIsmFactory to use the setBatch / removeBatch functions added to DomainRoutingIsm in #8655 (targets @hyperlane-xyz/core 11.4.0). Version-gated via PACKAGE_VERSION() on the target routing ISM — if it's ≥ 11.4.0, enrollments / unenrollments are consolidated into chunked batched txs sized by domainRoutingInitializationSize(destination). Older ISMs fall back to the existing per-domain set() / remove() loops.

Stacked PR — depends on #8655. Review / merge that first.

Why

#8583 shipped per-chain sharding of the routing ISM initializer to stay under block gas limits on citrea / shibarium / etc. The follow-on loop is still one tx per domain, so a 150-domain ISM deploy on a 120-per-shard chain = 1 proxy deploy + 30 individual enrollment txs. With setBatch, that becomes 1 proxy deploy + 1 tx for the remaining 30 domains.

Paths touched

  1. Reconfigure existing ISM (deployOwnableRoutingIsm, existingIsmAddress && isOwner branch). Enrollments + unenrollments both use setBatch / removeBatch when supported.
  2. New DomainRoutingIsm / IncrementalDomainRoutingIsm deploy (post-factory.deploy(initial) remainder loop). Uses setBatch for the tail.
  3. New DefaultFallbackRoutingIsm deploy. Previously called the batched initialize(address,uint32[],address[]) with all domains in one tx (same gas-limit risk as the pre-fix: batch deployer transactions and bump gas buffers #8583 factory path). Now shards: initialize with initial chunk + setBatch for the remainder.

Version gate

const version = await DomainRoutingIsm__factory.connect(address, provider).PACKAGE_VERSION();
// parse semver, return major > 11 || (major === 11 && minor >= 4)

Returns false on any failure (absent PACKAGE_VERSION, RPC error, malformed version) → safe fallback to per-domain loop.

Test plan

  • pnpm -C typescript/sdk build — no new errors in HyperlaneIsmFactory.ts (35 pre-existing errors in unrelated Aleo / Cosmos / Radix / Tron adapters remain on main)
  • CLI e2e: deploy a routing ISM with many domains on a low-gas testnet chain; verify txs use setBatch
  • Deploy reconfigure against an existing pre-11.4.0 ISM; verify fallback to per-domain loop still works

Local dev caveat

Until a release cycle bumps @hyperlane-xyz/core to 11.4.0, locally-built routing ISMs still report PACKAGE_VERSION = "11.3.1" and the SDK will take the fallback path during local testing. This resolves automatically on the first release PR that picks up #8655's changeset.


Note

Medium Risk
Changes on-chain deployment/reconfiguration transaction behavior for routing ISMs by switching from per-domain calls to chunked batched calls when available, which could affect gas usage and failure modes. Risk is mitigated by PACKAGE_VERSION() gating with an automatic per-domain fallback for older contracts.

Overview
HyperlaneIsmFactory now consolidates routing ISM domain enrollments and unenrollments into chunked batched transactions via setBatch/removeBatch when the target routing ISM reports PACKAGE_VERSION() >= 11.4.0, falling back to the existing per-domain set/remove loops otherwise.

This also shards DefaultFallbackRoutingIsm initialization by calling initialize with only the first domainRoutingInitializationSize(destination) domains and enrolling the remainder via the same batched/fallback path, reducing tx count and avoiding block gas limit issues on low-capacity chains. A changeset bumps @hyperlane-xyz/sdk minor to reflect the behavior change.

Reviewed by Cursor Bugbot for commit c432f9d. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-project-automation github-project-automation Bot moved this to In Review in Hyperlane Tasks Apr 22, 2026
@paulbalaji paulbalaji force-pushed the pbio/routing-ism-batch-sdk branch 2 times, most recently from 27c8ea9 to d98449d Compare April 22, 2026 17:40
Adds a PACKAGE_VERSION gate on the target routing ISM. When it reports
>= 11.4.0 (where setBatch/removeBatch were introduced), consolidate
enrollments and unenrollments into chunked setBatch/removeBatch txs
sized by domainRoutingInitializationSize(destination). Older ISMs fall
back to per-domain set()/remove() loops.

Applies to three paths:
- Reconfigure-existing-ISM enrollments and unenrollments
- Post-initializer follow-on enrollments on new DomainRoutingIsm /
  IncrementalDomainRoutingIsm deploys
- DefaultFallbackRoutingIsm new deploys (initialize with initial batch
  + setBatch remainder, matching the sharding already done on the
  proxy-factory path)
@paulbalaji paulbalaji force-pushed the pbio/routing-ism-batch-sdk branch from d98449d to c432f9d Compare April 22, 2026 17:42
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c432f9d. Configure here.

overrides,
destination,
logger,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fallback routing ISM enrollments fail when signer differs from owner

High Severity

The initialize call passes config.owner as the owner, which immediately transfers ownership away from the deploying signer. The subsequent enrollDomains call for remaining domains (beyond initialBatchSize) uses the signer to invoke set/setBatch, which are onlyOwner. When config.owner differs from the signer, all remaining enrollments revert. The DomainRoutingIsm path handles this correctly by deploying with signerAddress as owner, enrolling all domains, and then calling transferOwnership — the DefaultFallbackRoutingIsm path needs the same pattern.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c432f9d. Configure here.

`Unenrolling originDomain ${originDomain} from preexisting routing ISM at ${existingIsmAddress}...`,
);
const tx = await routingIsm.remove(originDomain, overrides);
await this.multiProvider.handleTx(destination, tx);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead isms record written but never read

Low Severity

The isms record declared at line 558 is assigned at line 578 (isms[originDomain] = ism.address) but is never read afterward. It became dead code when the inline routingIsm.set(originDomain, isms[originDomain], overrides) calls were replaced by the new enrollDomains helper, which uses the separate enrollAddresses array instead. Both the declaration and the write can be removed.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c432f9d. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

1 participant