Repository: hyperlane-xyz/hyperlane-monorepo
Pull request #5986: feat: vanguard funding
Author: login: paulbalaji
Created: 2025-04-21T14:47:02Z | Merged: 2025-04-22T17:25:00Z

### Description

feat: vanguard funding
- script to manually fund vanguards with a special config
- add vanguards to keyfunder

### Drive-by changes

<!--
Are there any minor or drive-by changes also included?
-->

### Related issues

<!--
- Fixes #[issue number here]
-->

### Backward compatibility

<!--
Are these changes backward compatible? Are there any infrastructure implications, e.g. changes that would prohibit deploying older commits using this infra tooling?

Yes/No
-->

### Testing

![image](https://github.com/user-attachments/assets/ce44b06e-4583-4125-bc99-89224abeecdb)



Diff:
diff --git a/typescript/infra/config/environments/mainnet3/funding.ts b/typescript/infra/config/environments/mainnet3/funding.ts
index f47f18b4ea0..d89c7e3c8d5 100644
--- a/typescript/infra/config/environments/mainnet3/funding.ts
+++ b/typescript/infra/config/environments/mainnet3/funding.ts
@@ -19,7 +19,7 @@ export const keyFunderConfig: KeyFunderConfig<
 > = {
   docker: {
     repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo',
-    tag: '4fd2990-20250414-150005',
+    tag: '2d0234b-20250422-165314',
   },
   // We're currently using the same deployer/key funder key as mainnet2.
   // To minimize nonce clobbering we offset the key funder cron
@@ -32,6 +32,7 @@ export const keyFunderConfig: KeyFunderConfig<
   contextsAndRolesToFund: {
     [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy],
     [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy],
+    [Contexts.Vanguard0]: [Role.Relayer],
   },
   chainsToSkip: [],
   // desired balance config, must be set for each chain
diff --git a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts
index b36934564e2..03537689af6 100644
--- a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts
+++ b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts
@@ -91,6 +91,10 @@ const RC_FUNDING_DISCOUNT_DENOMINATOR = ethers.BigNumber.from(10);
 const CONTEXT_FUNDING_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
 const CHAIN_FUNDING_TIMEOUT_MS = 1 * 60 * 1000; // 1 minute
 
+// Need to ensure we don't fund non-vanguard chains in the vanguard contexts
+const VANGUARD_CHAINS = ['base', 'arbitrum', 'optimism', 'ethereum', 'bsc'];
+const VANGUARD_CONTEXTS: Contexts[] = [Contexts.Vanguard0];
+
 // Funds key addresses for multiple contexts from the deployer key of the context
 // specified via the `--context` flag.
 // The --contexts-and-roles flag is used to specify the contexts and the key roles
@@ -259,6 +263,18 @@ class ContextFunder {
     roleKeysPerChain = objFilter(
       roleKeysPerChain,
       (chain, _roleKeys): _roleKeys is Record<Role, BaseAgentKey[]> => {
+        // Skip funding for vanguard contexts on non-vanguard chains
+        if (
+          VANGUARD_CONTEXTS.includes(this.context) &&
+          !VANGUARD_CHAINS.includes(chain)
+        ) {
+          logger.warn(
+            { chain, context: this.context },
+            'Skipping funding for vanguard context on non-vanguard chain',
+          );
+          return false;
+        }
+
         const valid =
           isEthereumProtocolChain(chain) &&
           multiProvider.tryGetChainName(chain) !== null;
diff --git a/typescript/infra/scripts/funding/vanguard.ts b/typescript/infra/scripts/funding/vanguard.ts
new file mode 100644
index 00000000000..0f8a65484c9
--- /dev/null
+++ b/typescript/infra/scripts/funding/vanguard.ts
@@ -0,0 +1,244 @@
+import chalk from 'chalk';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { DEPLOYER } from '../../config/environments/mainnet3/owners.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1',
+    vanguard1: '1',
+    vanguard2: '1',
+    vanguard3: '1',
+    vanguard4: '1',
+    vanguard5: '1',
+  },
+  arbitrum: {
+    vanguard0: '1',
+    vanguard1: '1',
+    vanguard2: '1',
+    vanguard3: '1',
+    vanguard4: '1',
+    vanguard5: '1',
+  },
+  optimism: {
+    vanguard0: '1',
+    vanguard1: '1',
+    vanguard2: '1',
+    vanguard3: '1',
+    vanguard4: '1',
+    vanguard5: '1',
+  },
+  ethereum: {
+    vanguard0: '5',
+    vanguard1: '5',
+    vanguard2: '5',
+    vanguard3: '5',
+    vanguard4: '5',
+    vanguard5: '5',
+  },
+  bsc: {
+    vanguard0: '10',
+    vanguard1: '10',
+    vanguard2: '5',
+    vanguard3: '5',
+    vanguard4: '5',
+    vanguard5: '5',
+  },
+} as const;
+
+const ACTIVE_VANGUARDS: VanguardName[] = ['vanguard0'];
+
+async function fundVanguards() {
+  const { fund } = await yargs(process.argv.slice(2))
+    .describe('fund', 'Fund vanguards')
+    .boolean('fund')
+    .default('fund', false).argv;
+
+  const envConfig = getEnvironmentConfig(VANGUARD_ENVIRONMENT);
+  const multiProvider = await envConfig.getMultiProvider(
+    Contexts.Hyperlane,
+    Role.Deployer,
+    true,
+  );
+
+  // Print balances before funding
+  const currentBalances: ChainMap<Record<string, string>> = {};
+  for (const chain of VANGUARD_NETWORKS) {
+    currentBalances[chain] = {};
+    const provider = multiProvider.getProvider(chain);
+    const deployerBalance = await provider.getBalance(DEPLOYER);
+    currentBalances[chain]['deployer'] = Number(
+      formatUnits(deployerBalance, TOKEN_DECIMALS),
+    ).toFixed(3); // Round to 3 decimal places
+
+    for (const vanguard of VANGUARDS) {
+      const address = VANGUARD_ADDRESSES[vanguard];
+      const currentBalance = await provider.getBalance(address);
+      currentBalances[chain][vanguard] = Number(
+        formatUnits(currentBalance, TOKEN_DECIMALS),
+      ).toFixed(3); // Round to 3 decimal places
+    }
+  }
+
+  rootLogger.info('\nCurrent balances:');
+  // eslint-disable-next-line no-console
+  console.table(currentBalances);
+
+  // Track which vanguards were topped up
+  const topUpsNeeded: ChainMap<VanguardBalance[]> = {};
+
+  await Promise.all(
+    VANGUARD_NETWORKS.map(async (chain) => {
+      for (const vanguard of ACTIVE_VANGUARDS) {
+        const address = VANGUARD_ADDRESSES[vanguard];
+        try {
+          const provider = multiProvider.getProvider(chain);
+          const currentBalance = await provider.getBalance(address);
+          const desiredBalance = parseUnits(
+            VANGUARD_FUNDING_CONFIGS[chain][vanguard],
+            TOKEN_DECIMALS,
+          );
+          const delta = desiredBalance.sub(currentBalance);
+
+          if (delta.gt(MIN_FUNDING_AMOUNT)) {
+            topUpsNeeded[chain] = topUpsNeeded[chain] || [];
+            topUpsNeeded[chain].push({
+              chain,
+              vanguard,
+              balance: Number(formatUnits(delta, TOKEN_DECIMALS)).toFixed(3),
+            });
+          }
+        } catch (error) {
+          rootLogger.error(
+            chalk.bold.red(
+              `Error topping up ${vanguard} on chain ${chain}:`,
+              error,
+            ),
+          );
+        }
+      }
+    }),
+  );
+
+  // Print summary of topped up vanguards
+  if (Object.keys(topUpsNeeded).length > 0) {
+    rootLogger.info('\nTop ups needed for the following:');
+    // eslint-disable-next-line no-console
+    console.table(
+      Object.entries(topUpsNeeded).reduce((acc, [chain, topUps]) => {
+        const chainEntries: Record<VanguardName, string> = {} as Record<
+          VanguardName,
+          string
+        >;
+        VANGUARDS.forEach((vanguard) => {
+          const match = topUps.find((t) => t.vanguard === vanguard);
+          chainEntries[vanguard] = match ? match.balance : '-';
+        });
+        acc[chain] = chainEntries;
+        return acc;
+      }, {} as Record<string, Record<VanguardName, string>>),
+    );
+
+    if (fund) {
+      rootLogger.info(chalk.italic.blue('\nFunding vanguards...'));
+    } else {
+      rootLogger.info(chalk.italic.yellow('\nDry run - not funding vanguards'));
+      process.exit(0);
+    }
+
+    await Promise.all(
+      Object.entries(topUpsNeeded).map(async ([chain, topUps]) => {
+        for (const { vanguard, balance: topUpAmount } of topUps) {
+          try {
+            const signer = multiProvider.getSigner(chain);
+            const signerBalance = await signer.getBalance();
+
+            // Convert balance to a BigNumber by using parseUnits
+            const amount = parseUnits(topUpAmount, TOKEN_DECIMALS);
+
+            if (signerBalance.lt(amount)) {
+              rootLogger.warn(
+                chalk.bold.yellow(
+                  `Insufficient balance for ${vanguard} on ${chain}. Required: ${formatUnits(
+                    amount,
+                    TOKEN_DECIMALS,
+                  )}, Available: ${formatUnits(signerBalance, TOKEN_DECIMALS)}`,
+                ),
+              );
+              continue;
+            }
+
+            await multiProvider.sendTransaction(chain, {
+              to: VANGUARD_ADDRESSES[vanguard],
+              value: amount,
+            });
+          } catch (error) {
+            rootLogger.error(
+              chalk.bold.red(
+                `Error checking balance for ${vanguard} on ${chain}:`,
+                error,
+              ),
+            );
+            continue;
+          }
+        }
+      }),
+    );
+  } else {
+    rootLogger.info(chalk.bold.green('\nNo vanguards needed topping up'));
+  }
+
+  process.exit(0);
+}
+
+fundVanguards().catch((error) => {
+  rootLogger.error(chalk.bold.red('Error funding agents:', error));
+  process.exit(1);
+});


Reviews:
user: login: paulbalaji
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T14:54:40Z
user: login: paulbalaji
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T14:55:25Z
user: login: Mo-Hussain
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T15:38:56Z
user: login: Mo-Hussain
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T15:46:09Z
user: login: tkporter
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T16:16:22Z
user: login: paulbalaji
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T16:17:05Z
user: login: paulbalaji
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T16:17:09Z
user: login: paulbalaji
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T16:20:07Z
user: login: paulbalaji
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T16:20:57Z
user: login: paulbalaji
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T16:22:05Z
user: login: paulbalaji
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T19:03:44Z
user: login: Mo-Hussain
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-22T16:46:37Z
user: login: tkporter
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-22T16:57:58Z
user: login: paulbalaji
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T16:58:15Z
user: login: paulbalaji
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T16:58:42Z

Review comments:
path: typescript/infra/scripts/funding/vanguard.ts
user: login: paulbalaji
body: mostly ripped from the script written for load testing, with an arg for funding + additional printing of the expected amounts to top up per {chain,vanguard}
created_at: 2025-04-21T14:54:39Z
author_association: COLLABORATOR
diff_hunk: @@ -32,6 +32,12 @@ export const keyFunderConfig: KeyFunderConfig<
   contextsAndRolesToFund: {
     [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy],
     [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy],
+    [Contexts.Vanguard0]: [Role.Relayer],
+    [Contexts.Vanguard1]: [Role.Relayer],
+    [Contexts.Vanguard2]: [Role.Relayer],
+    [Contexts.Vanguard3]: [Role.Relayer],
+    [Contexts.Vanguard4]: [Role.Relayer],
+    [Contexts.Vanguard5]: [Role.Relayer],
path: typescript/infra/config/environments/mainnet3/funding.ts
user: login: paulbalaji
body: ideally use the kf to keep up the balance hourly to the minimum thresholds, and using the script for manual funding where required (especially at the start when we expect a surge)
created_at: 2025-04-21T14:55:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  // 1.3x keyfunder config
+  base: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  // 3.6x keyfunder config
+  arbitrum: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  // 2x keyfunder config
+  optimism: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  // minimum 2x keyfunder, max 10x keyfunder
+  ethereum: {
+    vanguard0: '10', // 1x gas cap
+    vanguard1: '10', // 1x gas cap
+    vanguard2: '2', // 1x gas cap
+    vanguard3: '2', // 5x gas cap
+    vanguard4: '2', // 5x gas cap
+    vanguard5: '2', // 10x gas cap
+  },
path: typescript/infra/scripts/funding/vanguard.ts
user: login: Mo-Hussain
body: Discussed offline that assuming peak throughput and ~2.5x effective gas price spike, these balances will be depleted and claiming IGP will be necessary.  We will aim to have at least 30 mins to balance to delivery at peak throughput at a ~2.5x effective gas price spike. This is approximately 5 Eth
created_at: 2025-04-21T15:38:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,238 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  // 1.3x keyfunder config
+  base: {
path: typescript/infra/scripts/funding/vanguard.ts
user: login: Mo-Hussain
body: For base, arbitrum, optimism, bsc at peak throughput, we don't expect to spike gas prices as the relayer can deliver a tx at most every 3s which is more than the block time for these chains.  
created_at: 2025-04-21T15:46:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,233 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  arbitrum: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  optimism: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  ethereum: {
+    vanguard0: '5', // 1x gas cap
+    vanguard1: '5', // 1x gas cap
+    vanguard2: '5', // 1x gas cap
+    vanguard3: '5', // 5x gas cap
+    vanguard4: '5', // 5x gas cap
+    vanguard5: '5', // 10x gas cap
+  },
+  bsc: {
+    vanguard0: '10', // 1x gas cap
+    vanguard1: '10', // 1x gas cap
path: typescript/infra/scripts/funding/vanguard.ts
user: login: tkporter
body: are these 2 intentionally 10?
created_at: 2025-04-21T16:11:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,233 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
path: typescript/infra/scripts/funding/vanguard.ts
user: login: tkporter
body: the comments here are just so we are aware that e.g. vanguard 5 is configured with a 10 higher gas cap but this doesn't directly impact the funding right? it's just for context?
created_at: 2025-04-21T16:12:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,233 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
path: typescript/infra/scripts/funding/vanguard.ts
user: login: tkporter
body: all these numbers are denominated in the native gas token right
created_at: 2025-04-21T16:13:03Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,233 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  arbitrum: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  optimism: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  ethereum: {
+    vanguard0: '5', // 1x gas cap
+    vanguard1: '5', // 1x gas cap
+    vanguard2: '5', // 1x gas cap
+    vanguard3: '5', // 5x gas cap
+    vanguard4: '5', // 5x gas cap
+    vanguard5: '5', // 10x gas cap
+  },
+  bsc: {
+    vanguard0: '10', // 1x gas cap
+    vanguard1: '10', // 1x gas cap
+    vanguard2: '5', // 1x gas cap
+    vanguard3: '5', // 5x gas cap
+    vanguard4: '5', // 5x gas cap
+    vanguard5: '5', // 10x gas cap
+  },
+} as const;
+
+async function fundVanguards() {
+  const { fund } = await yargs(process.argv.slice(2))
+    .describe('fund', 'Fund vanguards')
+    .boolean('fund')
+    .default('fund', false).argv;
+
+  const envConfig = getEnvironmentConfig(VANGUARD_ENVIRONMENT);
+  const multiProvider = await envConfig.getMultiProvider(
+    Contexts.Hyperlane,
+    Role.Deployer,
+    true,
+  );
+
+  // Print balances before funding
+  const initialBalances: ChainMap<Record<string, string>> = {};
+  for (const chain of VANGUARD_NETWORKS) {
+    initialBalances[chain] = {};
+    for (const vanguard of VANGUARDS) {
+      const address = VANGUARD_ADDRESSES[vanguard];
+      const provider = multiProvider.getProvider(chain);
+      const currentBalance = await provider.getBalance(address);
+      initialBalances[chain][vanguard] = Number(
+        formatUnits(currentBalance, TOKEN_DECIMALS),
+      ).toFixed(3); // Round to 3 decimal places
+    }
+  }
+
+  rootLogger.info('\nInitial balances:');
+  // eslint-disable-next-line no-console
+  console.table(initialBalances);
+
+  // Track which vanguards were topped up
+  const topUpsNeeded: ChainMap<VanguardBalance[]> = {};
+
+  await Promise.all(
+    VANGUARD_NETWORKS.map(async (chain) => {
+      for (const vanguard of VANGUARDS) {
+        const address = VANGUARD_ADDRESSES[vanguard];
+        try {
+          const provider = multiProvider.getProvider(chain);
+          const currentBalance = await provider.getBalance(address);
+          const desiredBalance = parseUnits(
+            VANGUARD_FUNDING_CONFIGS[chain][vanguard],
+            TOKEN_DECIMALS,
+          );
+          const delta = desiredBalance.sub(currentBalance);
+
+          if (delta.gt(MIN_FUNDING_AMOUNT)) {
+            topUpsNeeded[chain] = topUpsNeeded[chain] || [];
+            topUpsNeeded[chain].push({
+              chain,
+              vanguard,
+              balance: Number(formatUnits(delta, TOKEN_DECIMALS)).toFixed(3),
+            });
+          }
+        } catch (error) {
+          rootLogger.error(
+            chalk.bold.red(
+              `Error topping up ${vanguard} on chain ${chain}:`,
+              error,
+            ),
+          );
+        }
+      }
+    }),
+  );
+
+  // Print summary of topped up vanguards
+  if (Object.keys(topUpsNeeded).length > 0) {
+    rootLogger.info('\nTop ups needed for the following:');
+    // eslint-disable-next-line no-console
+    console.table(
+      Object.entries(topUpsNeeded).reduce((acc, [chain, topUps]) => {
+        const chainEntries: Record<VanguardName, string> = {} as Record<
+          VanguardName,
+          string
+        >;
+        VANGUARDS.forEach((vanguard) => {
+          const match = topUps.find((t) => t.vanguard === vanguard);
+          chainEntries[vanguard] = match ? match.balance : '-';
+        });
+        acc[chain] = chainEntries;
+        return acc;
+      }, {} as Record<string, Record<VanguardName, string>>),
+    );
+
+    if (fund) {
+      rootLogger.info(chalk.italic.blue('\nFunding vanguards...'));
+    } else {
+      rootLogger.info(chalk.italic.yellow('\nDry run - not funding vanguards'));
+      return;
+    }
+
+    await Promise.all(
+      Object.entries(topUpsNeeded).map(([chain, topUps]) =>
+        topUps.map(async ({ vanguard, balance }) => {
+          try {
+            const signer = multiProvider.getSigner(chain);
+            const signerBalance = await signer.getBalance();
+            const amount = BigNumber.from(balance);
+
+            if (signerBalance.lt(amount)) {
+              rootLogger.warn(
+                chalk.bold.yellow(
+                  `Insufficient balance for ${vanguard} on ${chain}. Required: ${formatUnits(
+                    amount,
+                    TOKEN_DECIMALS,
+                  )}, Available: ${formatUnits(signerBalance, TOKEN_DECIMALS)}`,
+                ),
+              );
+              return;
+            }
+
+            return multiProvider.sendTransaction(chain, {
path: typescript/infra/scripts/funding/vanguard.ts
user: login: tkporter
body: do we see any nonce problems when sending multiple txs to the same chain concurrently?
created_at: 2025-04-21T16:15:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,233 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  arbitrum: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  optimism: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  ethereum: {
+    vanguard0: '5', // 1x gas cap
+    vanguard1: '5', // 1x gas cap
+    vanguard2: '5', // 1x gas cap
+    vanguard3: '5', // 5x gas cap
+    vanguard4: '5', // 5x gas cap
+    vanguard5: '5', // 10x gas cap
+  },
+  bsc: {
+    vanguard0: '10', // 1x gas cap
+    vanguard1: '10', // 1x gas cap
+    vanguard2: '5', // 1x gas cap
+    vanguard3: '5', // 5x gas cap
+    vanguard4: '5', // 5x gas cap
+    vanguard5: '5', // 10x gas cap
+  },
+} as const;
+
+async function fundVanguards() {
+  const { fund } = await yargs(process.argv.slice(2))
+    .describe('fund', 'Fund vanguards')
+    .boolean('fund')
+    .default('fund', false).argv;
+
+  const envConfig = getEnvironmentConfig(VANGUARD_ENVIRONMENT);
+  const multiProvider = await envConfig.getMultiProvider(
+    Contexts.Hyperlane,
+    Role.Deployer,
+    true,
+  );
+
+  // Print balances before funding
+  const initialBalances: ChainMap<Record<string, string>> = {};
+  for (const chain of VANGUARD_NETWORKS) {
+    initialBalances[chain] = {};
+    for (const vanguard of VANGUARDS) {
+      const address = VANGUARD_ADDRESSES[vanguard];
+      const provider = multiProvider.getProvider(chain);
+      const currentBalance = await provider.getBalance(address);
+      initialBalances[chain][vanguard] = Number(
+        formatUnits(currentBalance, TOKEN_DECIMALS),
+      ).toFixed(3); // Round to 3 decimal places
+    }
+  }
+
+  rootLogger.info('\nInitial balances:');
+  // eslint-disable-next-line no-console
+  console.table(initialBalances);
+
+  // Track which vanguards were topped up
+  const topUpsNeeded: ChainMap<VanguardBalance[]> = {};
+
+  await Promise.all(
+    VANGUARD_NETWORKS.map(async (chain) => {
+      for (const vanguard of VANGUARDS) {
+        const address = VANGUARD_ADDRESSES[vanguard];
+        try {
+          const provider = multiProvider.getProvider(chain);
+          const currentBalance = await provider.getBalance(address);
+          const desiredBalance = parseUnits(
+            VANGUARD_FUNDING_CONFIGS[chain][vanguard],
+            TOKEN_DECIMALS,
+          );
+          const delta = desiredBalance.sub(currentBalance);
+
+          if (delta.gt(MIN_FUNDING_AMOUNT)) {
+            topUpsNeeded[chain] = topUpsNeeded[chain] || [];
+            topUpsNeeded[chain].push({
+              chain,
+              vanguard,
+              balance: Number(formatUnits(delta, TOKEN_DECIMALS)).toFixed(3),
+            });
+          }
+        } catch (error) {
+          rootLogger.error(
+            chalk.bold.red(
+              `Error topping up ${vanguard} on chain ${chain}:`,
+              error,
+            ),
+          );
+        }
+      }
+    }),
+  );
+
+  // Print summary of topped up vanguards
+  if (Object.keys(topUpsNeeded).length > 0) {
+    rootLogger.info('\nTop ups needed for the following:');
+    // eslint-disable-next-line no-console
+    console.table(
+      Object.entries(topUpsNeeded).reduce((acc, [chain, topUps]) => {
+        const chainEntries: Record<VanguardName, string> = {} as Record<
+          VanguardName,
+          string
+        >;
+        VANGUARDS.forEach((vanguard) => {
+          const match = topUps.find((t) => t.vanguard === vanguard);
+          chainEntries[vanguard] = match ? match.balance : '-';
+        });
+        acc[chain] = chainEntries;
+        return acc;
+      }, {} as Record<string, Record<VanguardName, string>>),
+    );
+
+    if (fund) {
+      rootLogger.info(chalk.italic.blue('\nFunding vanguards...'));
+    } else {
+      rootLogger.info(chalk.italic.yellow('\nDry run - not funding vanguards'));
+      return;
+    }
+
+    await Promise.all(
+      Object.entries(topUpsNeeded).map(([chain, topUps]) =>
+        topUps.map(async ({ vanguard, balance }) => {
+          try {
+            const signer = multiProvider.getSigner(chain);
+            const signerBalance = await signer.getBalance();
+            const amount = BigNumber.from(balance);
+
+            if (signerBalance.lt(amount)) {
+              rootLogger.warn(
+                chalk.bold.yellow(
+                  `Insufficient balance for ${vanguard} on ${chain}. Required: ${formatUnits(
+                    amount,
+                    TOKEN_DECIMALS,
+                  )}, Available: ${formatUnits(signerBalance, TOKEN_DECIMALS)}`,
+                ),
+              );
+              return;
+            }
+
+            return multiProvider.sendTransaction(chain, {
+              to: VANGUARD_ADDRESSES[`${vanguard}`],
path: typescript/infra/scripts/funding/vanguard.ts
user: login: tkporter
body: why not just use vanguard directly?
created_at: 2025-04-21T16:15:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,233 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
path: typescript/infra/scripts/funding/vanguard.ts
user: login: paulbalaji
body: yup just for context
created_at: 2025-04-21T16:17:05Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,233 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
path: typescript/infra/scripts/funding/vanguard.ts
user: login: paulbalaji
body: that's right
created_at: 2025-04-21T16:17:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,233 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  arbitrum: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  optimism: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  ethereum: {
+    vanguard0: '5', // 1x gas cap
+    vanguard1: '5', // 1x gas cap
+    vanguard2: '5', // 1x gas cap
+    vanguard3: '5', // 5x gas cap
+    vanguard4: '5', // 5x gas cap
+    vanguard5: '5', // 10x gas cap
+  },
+  bsc: {
+    vanguard0: '10', // 1x gas cap
+    vanguard1: '10', // 1x gas cap
+    vanguard2: '5', // 1x gas cap
+    vanguard3: '5', // 5x gas cap
+    vanguard4: '5', // 5x gas cap
+    vanguard5: '5', // 10x gas cap
+  },
+} as const;
+
+async function fundVanguards() {
+  const { fund } = await yargs(process.argv.slice(2))
+    .describe('fund', 'Fund vanguards')
+    .boolean('fund')
+    .default('fund', false).argv;
+
+  const envConfig = getEnvironmentConfig(VANGUARD_ENVIRONMENT);
+  const multiProvider = await envConfig.getMultiProvider(
+    Contexts.Hyperlane,
+    Role.Deployer,
+    true,
+  );
+
+  // Print balances before funding
+  const initialBalances: ChainMap<Record<string, string>> = {};
+  for (const chain of VANGUARD_NETWORKS) {
+    initialBalances[chain] = {};
+    for (const vanguard of VANGUARDS) {
+      const address = VANGUARD_ADDRESSES[vanguard];
+      const provider = multiProvider.getProvider(chain);
+      const currentBalance = await provider.getBalance(address);
+      initialBalances[chain][vanguard] = Number(
+        formatUnits(currentBalance, TOKEN_DECIMALS),
+      ).toFixed(3); // Round to 3 decimal places
+    }
+  }
+
+  rootLogger.info('\nInitial balances:');
+  // eslint-disable-next-line no-console
+  console.table(initialBalances);
+
+  // Track which vanguards were topped up
+  const topUpsNeeded: ChainMap<VanguardBalance[]> = {};
+
+  await Promise.all(
+    VANGUARD_NETWORKS.map(async (chain) => {
+      for (const vanguard of VANGUARDS) {
+        const address = VANGUARD_ADDRESSES[vanguard];
+        try {
+          const provider = multiProvider.getProvider(chain);
+          const currentBalance = await provider.getBalance(address);
+          const desiredBalance = parseUnits(
+            VANGUARD_FUNDING_CONFIGS[chain][vanguard],
+            TOKEN_DECIMALS,
+          );
+          const delta = desiredBalance.sub(currentBalance);
+
+          if (delta.gt(MIN_FUNDING_AMOUNT)) {
+            topUpsNeeded[chain] = topUpsNeeded[chain] || [];
+            topUpsNeeded[chain].push({
+              chain,
+              vanguard,
+              balance: Number(formatUnits(delta, TOKEN_DECIMALS)).toFixed(3),
+            });
+          }
+        } catch (error) {
+          rootLogger.error(
+            chalk.bold.red(
+              `Error topping up ${vanguard} on chain ${chain}:`,
+              error,
+            ),
+          );
+        }
+      }
+    }),
+  );
+
+  // Print summary of topped up vanguards
+  if (Object.keys(topUpsNeeded).length > 0) {
+    rootLogger.info('\nTop ups needed for the following:');
+    // eslint-disable-next-line no-console
+    console.table(
+      Object.entries(topUpsNeeded).reduce((acc, [chain, topUps]) => {
+        const chainEntries: Record<VanguardName, string> = {} as Record<
+          VanguardName,
+          string
+        >;
+        VANGUARDS.forEach((vanguard) => {
+          const match = topUps.find((t) => t.vanguard === vanguard);
+          chainEntries[vanguard] = match ? match.balance : '-';
+        });
+        acc[chain] = chainEntries;
+        return acc;
+      }, {} as Record<string, Record<VanguardName, string>>),
+    );
+
+    if (fund) {
+      rootLogger.info(chalk.italic.blue('\nFunding vanguards...'));
+    } else {
+      rootLogger.info(chalk.italic.yellow('\nDry run - not funding vanguards'));
+      return;
+    }
+
+    await Promise.all(
+      Object.entries(topUpsNeeded).map(([chain, topUps]) =>
+        topUps.map(async ({ vanguard, balance }) => {
+          try {
+            const signer = multiProvider.getSigner(chain);
+            const signerBalance = await signer.getBalance();
+            const amount = BigNumber.from(balance);
+
+            if (signerBalance.lt(amount)) {
+              rootLogger.warn(
+                chalk.bold.yellow(
+                  `Insufficient balance for ${vanguard} on ${chain}. Required: ${formatUnits(
+                    amount,
+                    TOKEN_DECIMALS,
+                  )}, Available: ${formatUnits(signerBalance, TOKEN_DECIMALS)}`,
+                ),
+              );
+              return;
+            }
+
+            return multiProvider.sendTransaction(chain, {
path: typescript/infra/scripts/funding/vanguard.ts
user: login: paulbalaji
body: ah i was dry-running so didn't hit issue. this was before a refactor, will make the fix
created_at: 2025-04-21T16:20:06Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,233 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  arbitrum: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  optimism: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  ethereum: {
+    vanguard0: '5', // 1x gas cap
+    vanguard1: '5', // 1x gas cap
+    vanguard2: '5', // 1x gas cap
+    vanguard3: '5', // 5x gas cap
+    vanguard4: '5', // 5x gas cap
+    vanguard5: '5', // 10x gas cap
+  },
+  bsc: {
+    vanguard0: '10', // 1x gas cap
+    vanguard1: '10', // 1x gas cap
+    vanguard2: '5', // 1x gas cap
+    vanguard3: '5', // 5x gas cap
+    vanguard4: '5', // 5x gas cap
+    vanguard5: '5', // 10x gas cap
+  },
+} as const;
+
+async function fundVanguards() {
+  const { fund } = await yargs(process.argv.slice(2))
+    .describe('fund', 'Fund vanguards')
+    .boolean('fund')
+    .default('fund', false).argv;
+
+  const envConfig = getEnvironmentConfig(VANGUARD_ENVIRONMENT);
+  const multiProvider = await envConfig.getMultiProvider(
+    Contexts.Hyperlane,
+    Role.Deployer,
+    true,
+  );
+
+  // Print balances before funding
+  const initialBalances: ChainMap<Record<string, string>> = {};
+  for (const chain of VANGUARD_NETWORKS) {
+    initialBalances[chain] = {};
+    for (const vanguard of VANGUARDS) {
+      const address = VANGUARD_ADDRESSES[vanguard];
+      const provider = multiProvider.getProvider(chain);
+      const currentBalance = await provider.getBalance(address);
+      initialBalances[chain][vanguard] = Number(
+        formatUnits(currentBalance, TOKEN_DECIMALS),
+      ).toFixed(3); // Round to 3 decimal places
+    }
+  }
+
+  rootLogger.info('\nInitial balances:');
+  // eslint-disable-next-line no-console
+  console.table(initialBalances);
+
+  // Track which vanguards were topped up
+  const topUpsNeeded: ChainMap<VanguardBalance[]> = {};
+
+  await Promise.all(
+    VANGUARD_NETWORKS.map(async (chain) => {
+      for (const vanguard of VANGUARDS) {
+        const address = VANGUARD_ADDRESSES[vanguard];
+        try {
+          const provider = multiProvider.getProvider(chain);
+          const currentBalance = await provider.getBalance(address);
+          const desiredBalance = parseUnits(
+            VANGUARD_FUNDING_CONFIGS[chain][vanguard],
+            TOKEN_DECIMALS,
+          );
+          const delta = desiredBalance.sub(currentBalance);
+
+          if (delta.gt(MIN_FUNDING_AMOUNT)) {
+            topUpsNeeded[chain] = topUpsNeeded[chain] || [];
+            topUpsNeeded[chain].push({
+              chain,
+              vanguard,
+              balance: Number(formatUnits(delta, TOKEN_DECIMALS)).toFixed(3),
+            });
+          }
+        } catch (error) {
+          rootLogger.error(
+            chalk.bold.red(
+              `Error topping up ${vanguard} on chain ${chain}:`,
+              error,
+            ),
+          );
+        }
+      }
+    }),
+  );
+
+  // Print summary of topped up vanguards
+  if (Object.keys(topUpsNeeded).length > 0) {
+    rootLogger.info('\nTop ups needed for the following:');
+    // eslint-disable-next-line no-console
+    console.table(
+      Object.entries(topUpsNeeded).reduce((acc, [chain, topUps]) => {
+        const chainEntries: Record<VanguardName, string> = {} as Record<
+          VanguardName,
+          string
+        >;
+        VANGUARDS.forEach((vanguard) => {
+          const match = topUps.find((t) => t.vanguard === vanguard);
+          chainEntries[vanguard] = match ? match.balance : '-';
+        });
+        acc[chain] = chainEntries;
+        return acc;
+      }, {} as Record<string, Record<VanguardName, string>>),
+    );
+
+    if (fund) {
+      rootLogger.info(chalk.italic.blue('\nFunding vanguards...'));
+    } else {
+      rootLogger.info(chalk.italic.yellow('\nDry run - not funding vanguards'));
+      return;
+    }
+
+    await Promise.all(
+      Object.entries(topUpsNeeded).map(([chain, topUps]) =>
+        topUps.map(async ({ vanguard, balance }) => {
+          try {
+            const signer = multiProvider.getSigner(chain);
+            const signerBalance = await signer.getBalance();
+            const amount = BigNumber.from(balance);
+
+            if (signerBalance.lt(amount)) {
+              rootLogger.warn(
+                chalk.bold.yellow(
+                  `Insufficient balance for ${vanguard} on ${chain}. Required: ${formatUnits(
+                    amount,
+                    TOKEN_DECIMALS,
+                  )}, Available: ${formatUnits(signerBalance, TOKEN_DECIMALS)}`,
+                ),
+              );
+              return;
+            }
+
+            return multiProvider.sendTransaction(chain, {
+              to: VANGUARD_ADDRESSES[`${vanguard}`],
path: typescript/infra/scripts/funding/vanguard.ts
user: login: paulbalaji
body: again was from an old version before i fixed the types
created_at: 2025-04-21T16:20:57Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,233 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
path: typescript/infra/scripts/funding/vanguard.ts
user: login: paulbalaji
body: i'll remove the comments from the config and leave it just at the `VANGUARDS` const up top to reduce confusion
created_at: 2025-04-21T16:22:04Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,233 @@
+import chalk from 'chalk';
+import { BigNumber } from 'ethers';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  arbitrum: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  optimism: {
+    vanguard0: '1', // 1x gas cap
+    vanguard1: '1', // 1x gas cap
+    vanguard2: '1', // 1x gas cap
+    vanguard3: '1', // 5x gas cap
+    vanguard4: '1', // 5x gas cap
+    vanguard5: '1', // 10x gas cap
+  },
+  ethereum: {
+    vanguard0: '5', // 1x gas cap
+    vanguard1: '5', // 1x gas cap
+    vanguard2: '5', // 1x gas cap
+    vanguard3: '5', // 5x gas cap
+    vanguard4: '5', // 5x gas cap
+    vanguard5: '5', // 10x gas cap
+  },
+  bsc: {
+    vanguard0: '10', // 1x gas cap
+    vanguard1: '10', // 1x gas cap
path: typescript/infra/scripts/funding/vanguard.ts
user: login: paulbalaji
body: yup
created_at: 2025-04-21T19:03:44Z
author_association: COLLABORATOR
diff_hunk: @@ -91,6 +91,10 @@ const RC_FUNDING_DISCOUNT_DENOMINATOR = ethers.BigNumber.from(10);
 const CONTEXT_FUNDING_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
 const CHAIN_FUNDING_TIMEOUT_MS = 1 * 60 * 1000; // 1 minute
 
+// Need to ensure we don't find non-vanguard chains in the vanguard contexts
path: typescript/infra/scripts/funding/fund-keys-from-deployer.ts
user: login: tkporter
body: ```suggestion
// Need to ensure we don't fund non-vanguard chains in the vanguard contexts
```
created_at: 2025-04-22T16:49:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,244 @@
+import chalk from 'chalk';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { DEPLOYER } from '../../config/environments/mainnet3/owners.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1',
+    vanguard1: '1',
+    vanguard2: '1',
+    vanguard3: '1',
+    vanguard4: '1',
+    vanguard5: '1',
+  },
+  arbitrum: {
+    vanguard0: '1',
+    vanguard1: '1',
+    vanguard2: '1',
+    vanguard3: '1',
+    vanguard4: '1',
+    vanguard5: '1',
+  },
+  optimism: {
+    vanguard0: '1',
+    vanguard1: '1',
+    vanguard2: '1',
+    vanguard3: '1',
+    vanguard4: '1',
+    vanguard5: '1',
+  },
+  ethereum: {
+    vanguard0: '5',
+    vanguard1: '5',
+    vanguard2: '5',
+    vanguard3: '5',
+    vanguard4: '5',
+    vanguard5: '5',
+  },
+  bsc: {
+    vanguard0: '10',
+    vanguard1: '10',
path: typescript/infra/scripts/funding/vanguard.ts
user: login: tkporter
body: intentional as 10 for these 2 or no?
created_at: 2025-04-22T16:50:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,244 @@
+import chalk from 'chalk';
+import { formatUnits, parseUnits } from 'ethers/lib/utils.js';
+import yargs from 'yargs';
+
+import { ChainMap } from '@hyperlane-xyz/sdk';
+import { Address, rootLogger } from '@hyperlane-xyz/utils';
+
+import { Contexts } from '../../config/contexts.js';
+import { DEPLOYER } from '../../config/environments/mainnet3/owners.js';
+import { Role } from '../../src/roles.js';
+import { getEnvironmentConfig } from '../core-utils.js';
+
+const VANGUARDS = [
+  'vanguard0', // regular
+  'vanguard1', // regular
+  'vanguard2', // regular
+  'vanguard3', // 5x gas cap
+  'vanguard4', // 5x gas cap
+  'vanguard5', // 10x gas cap
+] as const;
+
+type VanguardName = (typeof VANGUARDS)[number];
+type VanguardBalance = {
+  chain: string;
+  vanguard: VanguardName;
+  balance: string;
+};
+
+const TOKEN_DECIMALS = 18;
+const MIN_FUNDING_AMOUNT = parseUnits('0.05', TOKEN_DECIMALS);
+
+const VANGUARD_ENVIRONMENT = 'mainnet3';
+const VANGUARD_ADDRESSES: Record<VanguardName, Address> = {
+  vanguard0: '0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d',
+  vanguard1: '0xdbcd22e5223f5d0040398e66dbb525308f27c655',
+  vanguard2: '0x226b721316ea44aad50a10f4cc67fc30658ab4a9',
+  vanguard3: '0xcdd728647ecd9d75413c9b780de303b1d1eb12a5',
+  vanguard4: '0x5401627b69f317da9adf3d6e1e1214724ce49032',
+  vanguard5: '0x6fd953d1cbdf3a79663b4238898147a6cf36d459',
+};
+
+const VANGUARD_NETWORKS = [
+  'base',
+  'arbitrum',
+  'optimism',
+  'ethereum',
+  'bsc',
+] as const;
+
+const VANGUARD_FUNDING_CONFIGS: Record<
+  (typeof VANGUARD_NETWORKS)[number],
+  Record<VanguardName, string>
+> = {
+  base: {
+    vanguard0: '1',
+    vanguard1: '1',
+    vanguard2: '1',
+    vanguard3: '1',
+    vanguard4: '1',
+    vanguard5: '1',
+  },
+  arbitrum: {
+    vanguard0: '1',
+    vanguard1: '1',
+    vanguard2: '1',
+    vanguard3: '1',
+    vanguard4: '1',
+    vanguard5: '1',
+  },
+  optimism: {
+    vanguard0: '1',
+    vanguard1: '1',
+    vanguard2: '1',
+    vanguard3: '1',
+    vanguard4: '1',
+    vanguard5: '1',
+  },
+  ethereum: {
+    vanguard0: '5',
+    vanguard1: '5',
+    vanguard2: '5',
+    vanguard3: '5',
+    vanguard4: '5',
+    vanguard5: '5',
+  },
+  bsc: {
+    vanguard0: '10',
+    vanguard1: '10',
path: typescript/infra/scripts/funding/vanguard.ts
user: login: paulbalaji
body: yup intentional!
created_at: 2025-04-22T16:58:15Z
author_association: COLLABORATOR
diff_hunk: @@ -91,6 +91,10 @@ const RC_FUNDING_DISCOUNT_DENOMINATOR = ethers.BigNumber.from(10);
 const CONTEXT_FUNDING_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
 const CHAIN_FUNDING_TIMEOUT_MS = 1 * 60 * 1000; // 1 minute
 
+// Need to ensure we don't find non-vanguard chains in the vanguard contexts
path: typescript/infra/scripts/funding/fund-keys-from-deployer.ts
user: login: paulbalaji
body: ha so this wasn't actually a typo but funny enough both words work here
created_at: 2025-04-22T16:58:42Z
author_association: COLLABORATOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-04-21T14:47:06Z
body: ###  ⚠️  No Changeset found

Latest commit: ff4a4f229309d045d63781833a806bcddbba5440

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/hyperlane-xyz/hyperlane-monorepo/new/pb/vanguard-funding?filename=.changeset/brave-fishes-bathe.md&value=---%0A%22%40hyperlane-xyz%2Finfra%22%3A%20patch%0A---%0A%0Afeat%3A%20vanguard%20funding%0A)


author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-21T14:53:59Z
body: ## [Codecov](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/5986?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 76.92%. Comparing base [(`4c867b8`)](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/commit/4c867b80a3915f5ec678bfc1d64562dae747b38d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) to head [(`ff4a4f2`)](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/commit/ff4a4f229309d045d63781833a806bcddbba5440?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #5986   +/-   ##
=======================================
  Coverage   76.92%   76.92%           
=======================================
  Files         109      109           
  Lines        2158     2158           
  Branches      195      195           
=======================================
  Hits         1660     1660           
  Misses        477      477           
  Partials       21       21           
```

| [Components](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/5986/components?src=pr&el=components&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) | Coverage Δ | |
|---|---|---|
| [core](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/5986/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) | `87.80% <ø> (ø)` | |
| [hooks](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/5986/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) | `78.14% <ø> (ø)` | |
| [isms](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/5986/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) | `81.60% <ø> (ø)` | |
| [token](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/5986/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) | `90.54% <ø> (ø)` | |
| [middlewares](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/5986/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) | `79.80% <ø> (ø)` | |

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: paulbalaji
created_at: 2025-04-21T19:04:19Z
body: > Discussed offline that assuming peak throughput and ~2.5x effective gas price spike, these balances will be depleted and claiming IGP will be necessary. We will aim to have at least 30 mins to balance to delivery at peak throughput at a ~2.5x effective gas price spike. This is approximately 5 Eth
> For base, arbitrum, optimism, bsc at peak throughput, we don't expect to spike gas prices as the relayer can deliver a tx at most every 3s which is more than the block time for these chains.

copying @Mo-Hussain's points out of comments
author_association: COLLABORATOR