feat(cli): add submit command (#3818)

### Description

* adds `hyperlane submit -t ./transactions.json -s ./strategy.json`
* given a set of `transactions` and a submission `strategy`, submit
those transactions according to that strategy
  * example strategies provided under `/cli/examples/submit/`
* detailed design:
https://www.notion.so/hyperlanexyz/CLI-Submitter-Integration-8a14486803ca4ab18f229e8e7f89b57a?pvs=4

### Drive-by changes

* none

### Related issues

- Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3740

### Backward compatibility

- yes

### Testing

#### Nice to have:
- [ ] ci-test

#### Manual:
- [x] `hl submit -t ./examples/submit/transactions/transactions.json -s
./examples/submit/strategy/json-rpc-strategy.yaml`
```
Hyperlane Submit
----------------
Submitting 1 transactions to the jsonRpc submitter...
Sent tx 0x7158756345a913630497dac948a98b1c73921626f9c17cc936994ff22dcd908f
Pending https://alfajores.celoscan.io/tx/0x7158756345a913630497dac948a98b1c73921626f9c17cc936994ff22dcd908f (waiting 1 blocks for confirmation)
 Successfully submitted 1 transactions to the jsonRpc submitter.
🧾 Transaction receipts: [
  {
    to: '0x9a4a3124F2a86bB5BE46267De85D31762b7a05Fd',
    from: '0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb',
    contractAddress: null,
    transactionIndex: 4,
    gasUsed: BigNumber { _hex: '0x53b8', _isBigNumber: true },
    logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
    blockHash: '0x174c3355800d73e3ab9643257bf49a60fc539a6961fc6cc8adcfb27d84ab6347',
    transactionHash: '0x7158756345a913630497dac948a98b1c73921626f9c17cc936994ff22dcd908f',
    logs: [],
    blockNumber: 24221122,
    confirmations: 1,
    cumulativeGasUsed: BigNumber { _hex: '0x072f3f', _isBigNumber: true },
    effectiveGasPrice: BigNumber { _hex: '0x01836e2100', _isBigNumber: true },
    status: 1,
    type: 2,
    byzantium: true
  }
]
 Hyperlane submission complete
```
- [x] `hl submit -t ./examples/submit/transactions/transactions.json -s
./examples/submit/strategy/json-rpc-strategy.yaml --dry-run`
```
...
Dry-running against chain: alfajores
🔎 Verifying anvil node is running...
 Successfully verified anvil node is running
Forking alfajores for dry-run...
 Successfully forked alfajores for dry-run
Impersonating account (0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb)...
 Successfully impersonated account (0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb)
Hyperlane Submit
----------------
Submitting 1 transactions to the jsonRpc submitter...
Sent tx 0x89fbd7d38d6fc19df0ef24f844756153dece1e0f179b76b036c50596356dc5ce
Pending https://alfajores.celoscan.io/tx/0x89fbd7d38d6fc19df0ef24f844756153dece1e0f179b76b036c50596356dc5ce (waiting 1 blocks for confirmation)
 Successfully submitted 1 transactions to the jsonRpc submitter.
🧾 Transaction receipts: [
  {
    to: '0x9a4a3124F2a86bB5BE46267De85D31762b7a05Fd',
    from: '0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb',
    contractAddress: null,
    transactionIndex: 0,
    gasUsed: BigNumber { _hex: '0x53b8', _isBigNumber: true },
    logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
    blockHash: '0x5a5379771a3a6d173a8dcbfc189e24f288c30abcd0a754f1df4734f465917cc7',
    transactionHash: '0x89fbd7d38d6fc19df0ef24f844756153dece1e0f179b76b036c50596356dc5ce',
    logs: [],
    blockNumber: 24221165,
    confirmations: 1,
    cumulativeGasUsed: BigNumber { _hex: '0x53b8', _isBigNumber: true },
    effectiveGasPrice: BigNumber { _hex: '0x0104fd914d', _isBigNumber: true },
    status: 1,
    type: 2,
    byzantium: true
  }
]
 Hyperlane submission complete
```
- [x] `hl submit -t ./examples/submit/transactions/transactions.json -s
./examples/submit/strategy/impersonated-account-strategy.yaml`
- [x] `hl submit -t ./examples/submit/transactions/transactions.json -s
./examples/submit/strategy/impersonated-account-strategy.yaml --dry-run`
- `Error: Impersonated account submitters may only be used during
dry-runs.`
- [x] `hl submit -t ./examples/submit/transactions/transactions.json -s
./examples/submit/strategy/gnosis-strategy.yaml`
- Successfully generated proposal:
https://app.safe.global/transactions/queue?safe=avax:0x7fd32493Ca3A38cDf78A4cb74F32f6292f822aBe
- [ ] `hl submit -t ./examples/submit/transactions/transactions.json -s
./examples/submit/strategy/gnosis-ica-strategy.yaml`
- [ ] `hl submit -t ./examples/submit/transactions/transactions.json -s
./examples/submit/strategy/gnosis-ica-strategy.yaml --dry-run`
pull/4126/head
Noah Bayindirli 🥂 4 months ago committed by GitHub
parent 80ac5d28e5
commit bb470aec25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/wild-fans-repair.md
  2. 1
      typescript/cli/.gitignore
  3. 2
      typescript/cli/cli.ts
  4. 11
      typescript/cli/examples/submit/strategy/gnosis-ica-strategy.yaml
  5. 5
      typescript/cli/examples/submit/strategy/gnosis-strategy.yaml
  6. 4
      typescript/cli/examples/submit/strategy/impersonated-account-strategy.yaml
  7. 3
      typescript/cli/examples/submit/strategy/json-rpc-strategy.yaml
  8. 8
      typescript/cli/examples/submit/transactions/alfajores-transactions.json
  9. 8
      typescript/cli/examples/submit/transactions/avalanche-transactions.json
  10. 14
      typescript/cli/src/commands/options.ts
  11. 2
      typescript/cli/src/commands/signCommands.ts
  12. 42
      typescript/cli/src/commands/submit.ts
  13. 65
      typescript/cli/src/config/submit.ts
  14. 48
      typescript/cli/src/context/context.ts
  15. 3
      typescript/cli/src/context/types.ts
  16. 48
      typescript/cli/src/submit/submit.ts
  17. 28
      typescript/cli/src/submit/types.ts
  18. 6
      typescript/sdk/src/index.ts
  19. 2
      typescript/sdk/src/providers/transactions/schemas.ts
  20. 4
      typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts
  21. 4
      typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts
  22. 5
      typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.ts
  23. 5
      typescript/sdk/src/providers/transactions/submitter/schemas.ts
  24. 18
      typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts
  25. 2
      typescript/sdk/src/providers/transactions/transformer/schemas.ts
  26. 10
      typescript/sdk/src/providers/transactions/types.ts

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/cli': minor
---
Add 'submit' command to CLI.

@ -7,6 +7,7 @@
/artifacts
/chains
/deployments
/generated
# Test artifacts
/test-configs/**/addresses.yaml

@ -22,6 +22,7 @@ import {
import { registryCommand } from './src/commands/registry.js';
import { sendCommand } from './src/commands/send.js';
import { statusCommand } from './src/commands/status.js';
import { submitCommand } from './src/commands/submit.js';
import { validatorCommand } from './src/commands/validator.js';
import { warpCommand } from './src/commands/warp.js';
import { contextMiddleware } from './src/context/context.js';
@ -61,6 +62,7 @@ try {
.command(registryCommand)
.command(sendCommand)
.command(statusCommand)
.command(submitCommand)
.command(validatorCommand)
.command(warpCommand)
.version(VERSION)

@ -0,0 +1,11 @@
chain: avalanche
submitter:
type: gnosisSafe
chain: avalanche
safeAddress: '0x7fd32493Ca3A38cDf78A4cb74F32f6292f822aBe'
transforms:
- type: interchainAccount
chain: ethereum
config:
origin: avalanche
owner: '0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb'

@ -0,0 +1,5 @@
chain: avalanche
submitter:
type: gnosisSafe
chain: avalanche
safeAddress: '0x7fd32493Ca3A38cDf78A4cb74F32f6292f822aBe'

@ -0,0 +1,4 @@
chain: alfajores
submitter:
type: impersonatedAccount
userAddress: '0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb'

@ -0,0 +1,3 @@
chain: alfajores
submitter:
type: jsonRpc

@ -0,0 +1,8 @@
[
{
"data": "0x0e72cc06000000000000000000000000744ad987ee7c65d3b3333bfc3e6ecbf963eb872a",
"to": "0x9a4a3124F2a86bB5BE46267De85D31762b7a05Fd",
"from": "0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb",
"chainId": 44787
}
]

@ -0,0 +1,8 @@
[
{
"data": "0x0e72cc06000000000000000000000000744ad987ee7c65d3b3333bfc3e6ecbf963eb872a",
"to": "0x9a4a3124F2a86bB5BE46267De85D31762b7a05Fd",
"from": "0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb",
"chainId": 43114
}
]

@ -163,6 +163,20 @@ export const validatorCommandOption: Options = {
demandOption: true,
};
export const transactionsCommandOption: Options = {
type: 'string',
description: 'The transaction input file path.',
alias: ['t', 'txs', 'txns'],
demandOption: true,
};
export const strategyCommandOption: Options = {
type: 'string',
description: 'The submission strategy input file path.',
alias: 's',
demandOption: true,
};
export const addressCommandOption = (
description: string,
demandOption = false,

@ -1,7 +1,7 @@
// Commands that send tx and require a key to sign.
// It's useful to have this listed here so the context
// middleware can request keys up front when required.
export const SIGN_COMMANDS = ['deploy', 'send'];
export const SIGN_COMMANDS = ['deploy', 'send', 'submit'];
export function isSignCommand(argv: any): boolean {
return (

@ -0,0 +1,42 @@
import { runSubmit } from '../config/submit.js';
import { CommandModuleWithWriteContext } from '../context/types.js';
import { logBlue, logGray } from '../logger.js';
import {
dryRunCommandOption,
outputFileCommandOption,
strategyCommandOption,
transactionsCommandOption,
} from './options.js';
/**
* Submit command
*/
export const submitCommand: CommandModuleWithWriteContext<{
transactions: string;
strategy: string;
'dry-run': string;
receipts: string;
}> = {
command: 'submit',
describe: 'Submit transactions',
builder: {
transactions: transactionsCommandOption,
strategy: strategyCommandOption,
'dry-run': dryRunCommandOption,
receipts: outputFileCommandOption('./generated/transactions/receipts.yaml'),
},
handler: async ({ context, transactions, receipts }) => {
logGray(`Hyperlane Submit`);
logGray(`----------------`);
await runSubmit({
context,
transactionsFilepath: transactions,
receiptsFilepath: receipts,
});
logBlue(`✅ Submission complete`);
process.exit(0);
},
};

@ -0,0 +1,65 @@
import { stringify as yamlStringify } from 'yaml';
import {
PopulatedTransactions,
PopulatedTransactionsSchema,
} from '@hyperlane-xyz/sdk';
import { assert, errorToString } from '@hyperlane-xyz/utils';
import { WriteCommandContext } from '../context/types.js';
import { logGray, logRed } from '../logger.js';
import { getSubmitterBuilder } from '../submit/submit.js';
import {
indentYamlOrJson,
readYamlOrJson,
writeYamlOrJson,
} from '../utils/files.js';
export async function runSubmit({
context,
transactionsFilepath,
receiptsFilepath,
}: {
context: WriteCommandContext;
transactionsFilepath: string;
receiptsFilepath: string;
}) {
const { submissionStrategy, chainMetadata, multiProvider } = context;
assert(
submissionStrategy,
'Submission strategy required to submit transactions.\nPlease create a submission strategy. See examples in cli/examples/submit/strategy/*.',
);
const chain = submissionStrategy.chain;
const protocol = chainMetadata[chain].protocol;
const submitterBuilder = await getSubmitterBuilder<typeof protocol>({
submissionStrategy,
multiProvider,
});
const transactions = getTransactions(transactionsFilepath);
try {
const transactionReceipts = await submitterBuilder.submit(...transactions);
if (transactionReceipts) {
logGray(
'🧾 Transaction receipts:\n\n',
indentYamlOrJson(yamlStringify(transactionReceipts, null, 2), 4),
);
writeYamlOrJson(receiptsFilepath, transactionReceipts, 'yaml');
}
} catch (error) {
logRed(
` Failed to submit ${transactions.length} transactions:`,
errorToString(error),
);
throw new Error('Failed to submit transactions.');
}
}
function getTransactions(transactionsFilepath: string): PopulatedTransactions {
const transactionsFileContent = readYamlOrJson<any[]>(
transactionsFilepath.trim(),
);
return PopulatedTransactionsSchema.parse(transactionsFileContent);
}

@ -6,13 +6,20 @@ import {
MergedRegistry,
} from '@hyperlane-xyz/registry';
import { FileSystemRegistry } from '@hyperlane-xyz/registry/fs';
import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk';
import {
ChainName,
MultiProvider,
SubmissionStrategy,
SubmissionStrategySchema,
TxSubmitterType,
} from '@hyperlane-xyz/sdk';
import { isHttpsUrl, isNullish, rootLogger } from '@hyperlane-xyz/utils';
import { isSignCommand } from '../commands/signCommands.js';
import { forkNetworkToMultiProvider, verifyAnvil } from '../deploy/dry-run.js';
import { logBlue } from '../logger.js';
import { runSingleChainSelectionStep } from '../utils/chains.js';
import { readYamlOrJson } from '../utils/files.js';
import { getImpersonatedSigner, getSigner } from '../utils/keys.js';
import {
@ -22,7 +29,7 @@ import {
} from './types.js';
export async function contextMiddleware(argv: Record<string, any>) {
const isDryRun = !isNullish(argv.dryRun);
let isDryRun = !isNullish(argv.dryRun);
const requiresKey = isSignCommand(argv);
const settings: ContextSettings = {
registryUri: argv.registry,
@ -36,6 +43,15 @@ export async function contextMiddleware(argv: Record<string, any>) {
throw new Error(
"'--from-address' or '-f' should only be used for dry-runs",
);
if (argv.strategy) {
settings.submissionStrategy = getSubmissionStrategy(argv.strategy);
if (
settings.submissionStrategy.submitter.type ===
TxSubmitterType.IMPERSONATED_ACCOUNT
) {
isDryRun = true;
}
}
const context = isDryRun
? await getDryRunContext(settings, argv.dryRun)
: await getContext(settings);
@ -52,6 +68,7 @@ export async function getContext({
key,
requiresKey,
skipConfirmation,
submissionStrategy,
}: ContextSettings): Promise<CommandContext> {
const registry = getRegistry(registryUri, registryOverrideUri);
@ -68,6 +85,7 @@ export async function getContext({
key,
signer,
skipConfirmation: !!skipConfirmation,
submissionStrategy,
} as CommandContext;
}
@ -82,6 +100,7 @@ export async function getDryRunContext(
key,
fromAddress,
skipConfirmation,
submissionStrategy,
}: ContextSettings,
chain?: ChainName,
): Promise<CommandContext> {
@ -90,10 +109,12 @@ export async function getDryRunContext(
if (!chain) {
if (skipConfirmation) throw new Error('No chains provided');
chain = await runSingleChainSelectionStep(
chainMetadata,
'Select chain to dry-run against:',
);
chain = submissionStrategy
? submissionStrategy.chain
: await runSingleChainSelectionStep(
chainMetadata,
'Select chain to dry-run against:',
);
}
logBlue(`Dry-running against chain: ${chain}`);
@ -117,6 +138,7 @@ export async function getDryRunContext(
skipConfirmation: !!skipConfirmation,
isDryRun: true,
dryRunChain: chain,
submissionStrategy,
} as WriteCommandContext;
}
@ -163,3 +185,17 @@ async function getMultiProvider(registry: IRegistry, signer?: ethers.Signer) {
if (signer) multiProvider.setSharedSigner(signer);
return multiProvider;
}
/**
* Retrieves a submission strategy from the provided filepath.
* @param submissionStrategyFilepath a filepath to the submission strategy file
* @returns a formatted submission strategy
*/
function getSubmissionStrategy(
submissionStrategyFilepath: string,
): SubmissionStrategy {
const submissionStrategyFileContent = readYamlOrJson(
submissionStrategyFilepath.trim(),
);
return SubmissionStrategySchema.parse(submissionStrategyFileContent);
}

@ -6,6 +6,7 @@ import type {
ChainMap,
ChainMetadata,
MultiProvider,
SubmissionStrategy,
} from '@hyperlane-xyz/sdk';
export interface ContextSettings {
@ -15,6 +16,7 @@ export interface ContextSettings {
fromAddress?: string;
requiresKey?: boolean;
skipConfirmation?: boolean;
submissionStrategy?: SubmissionStrategy;
}
export interface CommandContext {
@ -22,6 +24,7 @@ export interface CommandContext {
chainMetadata: ChainMap<ChainMetadata>;
multiProvider: MultiProvider;
skipConfirmation: boolean;
submissionStrategy?: SubmissionStrategy;
key?: string;
signer?: ethers.Signer;
}

@ -1,11 +1,11 @@
import {
EV5GnosisSafeTxSubmitter,
EV5GnosisSafeTxSubmitterProps,
EV5ImpersonatedAccountTxSubmitter,
EV5ImpersonatedAccountTxSubmitterProps,
EV5InterchainAccountTxTransformer,
EV5JsonRpcTxSubmitter,
MultiProvider,
SubmitterMetadata,
TransformerMetadata,
TxSubmitterBuilder,
TxSubmitterInterface,
TxSubmitterType,
@ -14,24 +14,19 @@ import {
} from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
import {
SubmitterBuilderSettings,
SubmitterMetadata,
TransformerMetadata,
} from './types.js';
import { SubmitterBuilderSettings } from './types.js';
export async function getSubmitterBuilder<TProtocol extends ProtocolType>({
submitterMetadata,
transformersMetadata,
submissionStrategy,
multiProvider,
}: SubmitterBuilderSettings): Promise<TxSubmitterBuilder<TProtocol>> {
const submitter = await getSubmitter<TProtocol>(
multiProvider,
submitterMetadata,
submissionStrategy.submitter,
);
const transformers = await getTransformers<TProtocol>(
multiProvider,
transformersMetadata,
submissionStrategy.transforms ?? [],
);
return new TxSubmitterBuilder<TProtocol>(submitter, transformers);
@ -45,27 +40,25 @@ async function getSubmitter<TProtocol extends ProtocolType>(
case TxSubmitterType.JSON_RPC:
return new EV5JsonRpcTxSubmitter(multiProvider);
case TxSubmitterType.IMPERSONATED_ACCOUNT:
return new EV5ImpersonatedAccountTxSubmitter(
multiProvider,
submitterMetadata.props as EV5ImpersonatedAccountTxSubmitterProps,
);
return new EV5ImpersonatedAccountTxSubmitter(multiProvider, {
...submitterMetadata,
});
case TxSubmitterType.GNOSIS_SAFE:
return new EV5GnosisSafeTxSubmitter(
multiProvider,
submitterMetadata.props as EV5GnosisSafeTxSubmitterProps,
);
return new EV5GnosisSafeTxSubmitter(multiProvider, {
...submitterMetadata,
});
default:
throw new Error(`Invalid TxSubmitterType: ${submitterMetadata.type}`);
throw new Error(`Invalid TxSubmitterType.`);
}
}
async function getTransformers<TProtocol extends ProtocolType>(
multiProvider: MultiProvider,
metadata: TransformerMetadata[],
transformersMetadata: TransformerMetadata[],
): Promise<TxTransformerInterface<TProtocol>[]> {
return Promise.all(
metadata.map(({ type, props: settings }) =>
getTransformer<TProtocol>(multiProvider, { type, props: settings }),
transformersMetadata.map((transformerMetadata) =>
getTransformer<TProtocol>(multiProvider, transformerMetadata),
),
);
}
@ -76,11 +69,10 @@ async function getTransformer<TProtocol extends ProtocolType>(
): Promise<TxTransformerInterface<TProtocol>> {
switch (transformerMetadata.type) {
case TxTransformerType.INTERCHAIN_ACCOUNT:
return new EV5InterchainAccountTxTransformer(
multiProvider,
transformerMetadata.props,
);
return new EV5InterchainAccountTxTransformer(multiProvider, {
...transformerMetadata,
});
default:
throw new Error(`Invalid TxTransformerType: ${transformerMetadata.type}`);
throw new Error('Invalid TxTransformerType.');
}
}

@ -1,27 +1,11 @@
import { z } from 'zod';
import type {
EV5GnosisSafeTxSubmitterProps,
EV5ImpersonatedAccountTxSubmitterProps,
EV5InterchainAccountTxTransformerProps,
MultiProvider,
TxSubmitterType,
TxTransformerType,
SubmissionStrategySchema,
} from '@hyperlane-xyz/sdk';
export interface SubmitterBuilderSettings {
submitterMetadata: SubmitterMetadata;
transformersMetadata: TransformerMetadata[];
export type SubmitterBuilderSettings = {
submissionStrategy: z.infer<typeof SubmissionStrategySchema>;
multiProvider: MultiProvider;
}
export interface SubmitterMetadata {
type: TxSubmitterType;
props: SubmitterProps;
}
export interface TransformerMetadata {
type: TxTransformerType;
props: TransformerProps;
}
type SubmitterProps =
| EV5ImpersonatedAccountTxSubmitterProps
| EV5GnosisSafeTxSubmitterProps;
type TransformerProps = EV5InterchainAccountTxTransformerProps;
};

@ -308,10 +308,14 @@ export {
defaultViemProviderBuilder,
protocolToDefaultProviderBuilder,
} from './providers/providerBuilders.js';
export { PopulatedTransactionSchema } from './providers/transactions/schemas.js';
export {
PopulatedTransactionSchema,
PopulatedTransactionsSchema,
} from './providers/transactions/schemas.js';
export {
CallData,
PopulatedTransaction,
PopulatedTransactions,
} from './providers/transactions/types.js';
export { TxSubmitterType } from './providers/transactions/submitter/TxSubmitterTypes.js';

@ -10,6 +10,8 @@ export const PopulatedTransactionSchema = z.object({
chainId: z.number(),
});
export const PopulatedTransactionsSchema = PopulatedTransactionSchema.array();
export const CallDataSchema = z.object({
to: ZHash,
data: z.string(),

@ -5,7 +5,7 @@ import { Address, assert, rootLogger } from '@hyperlane-xyz/utils';
// @ts-ignore
import { getSafe, getSafeService } from '../../../../utils/gnosisSafe.js';
import { MultiProvider } from '../../../MultiProvider.js';
import { PopulatedTransaction } from '../../types.js';
import { PopulatedTransaction, PopulatedTransactions } from '../../types.js';
import { TxSubmitterType } from '../TxSubmitterTypes.js';
import { EV5TxSubmitterInterface } from './EV5TxSubmitterInterface.js';
@ -24,7 +24,7 @@ export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface {
public readonly props: EV5GnosisSafeTxSubmitterProps,
) {}
public async submit(...txs: PopulatedTransaction[]): Promise<void> {
public async submit(...txs: PopulatedTransactions): Promise<void> {
const safe = await getSafe(
this.props.chain,
this.multiProvider,

@ -1,5 +1,4 @@
import { TransactionReceipt } from '@ethersproject/providers';
import { PopulatedTransaction } from 'ethers';
import { Logger } from 'pino';
import { rootLogger } from '@hyperlane-xyz/utils';
@ -9,6 +8,7 @@ import {
stopImpersonatingAccount,
} from '../../../../utils/fork.js';
import { MultiProvider } from '../../../MultiProvider.js';
import { PopulatedTransactions } from '../../types.js';
import { TxSubmitterType } from '../TxSubmitterTypes.js';
import { EV5JsonRpcTxSubmitter } from './EV5JsonRpcTxSubmitter.js';
@ -30,7 +30,7 @@ export class EV5ImpersonatedAccountTxSubmitter extends EV5JsonRpcTxSubmitter {
}
public async submit(
...txs: PopulatedTransaction[]
...txs: PopulatedTransactions
): Promise<TransactionReceipt[]> {
const impersonatedAccount = await impersonateAccount(
this.props.userAddress,

@ -1,10 +1,11 @@
import { TransactionReceipt } from '@ethersproject/providers';
import { ContractReceipt, PopulatedTransaction } from 'ethers';
import { ContractReceipt } from 'ethers';
import { Logger } from 'pino';
import { assert, rootLogger } from '@hyperlane-xyz/utils';
import { MultiProvider } from '../../../MultiProvider.js';
import { PopulatedTransactions } from '../../types.js';
import { TxSubmitterType } from '../TxSubmitterTypes.js';
import { EV5TxSubmitterInterface } from './EV5TxSubmitterInterface.js';
@ -19,7 +20,7 @@ export class EV5JsonRpcTxSubmitter implements EV5TxSubmitterInterface {
constructor(public readonly multiProvider: MultiProvider) {}
public async submit(
...txs: PopulatedTransaction[]
...txs: PopulatedTransactions
): Promise<TransactionReceipt[]> {
const receipts: TransactionReceipt[] = [];
for (const tx of txs) {

@ -9,14 +9,13 @@ import {
export const SubmitterMetadataSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal(TxSubmitterType.JSON_RPC),
props: z.object({}).optional(),
}),
z.object({
type: z.literal(TxSubmitterType.IMPERSONATED_ACCOUNT),
props: EV5ImpersonatedAccountTxSubmitterPropsSchema,
...EV5ImpersonatedAccountTxSubmitterPropsSchema.shape,
}),
z.object({
type: z.literal(TxSubmitterType.GNOSIS_SAFE),
props: EV5GnosisSafeTxSubmitterPropsSchema,
...EV5GnosisSafeTxSubmitterPropsSchema.shape,
}),
]);

@ -1,7 +1,7 @@
import { ethers } from 'ethers';
import { Logger } from 'pino';
import { assert, objKeys, rootLogger } from '@hyperlane-xyz/utils';
import { assert, objMap, rootLogger } from '@hyperlane-xyz/utils';
import {
InterchainAccount,
@ -9,7 +9,11 @@ import {
} from '../../../../middleware/account/InterchainAccount.js';
import { ChainName } from '../../../../types.js';
import { MultiProvider } from '../../../MultiProvider.js';
import { CallData, PopulatedTransaction } from '../../types.js';
import {
CallData,
PopulatedTransaction,
PopulatedTransactions,
} from '../../types.js';
import { TxTransformerType } from '../TxTransformerTypes.js';
import { EV5TxTransformerInterface } from './EV5TxTransformerInterface.js';
@ -35,7 +39,7 @@ export class EV5InterchainAccountTxTransformer
}
public async transform(
...txs: PopulatedTransaction[]
...txs: PopulatedTransactions
): Promise<ethers.PopulatedTransaction[]> {
const txChainsToInnerCalls: Record<ChainName, CallData[]> = txs.reduce(
(
@ -57,17 +61,17 @@ export class EV5InterchainAccountTxTransformer
);
const transformedTxs: ethers.PopulatedTransaction[] = [];
for (const txChain of objKeys(txChainsToInnerCalls)) {
objMap(txChainsToInnerCalls, async (destination, innerCalls) => {
transformedTxs.push(
await interchainAccountApp.getCallRemote({
chain: this.props.chain,
destination: txChain,
innerCalls: txChainsToInnerCalls[txChain],
destination,
innerCalls,
config: this.props.config,
hookMetadata: this.props.hookMetadata,
}),
);
}
});
return transformedTxs;
}

@ -6,6 +6,6 @@ import { EV5InterchainAccountTxTransformerPropsSchema } from './ethersV5/schemas
export const TransformerMetadataSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal(TxTransformerType.INTERCHAIN_ACCOUNT),
props: EV5InterchainAccountTxTransformerPropsSchema,
...EV5InterchainAccountTxTransformerPropsSchema.shape,
}),
]);

@ -1,9 +1,17 @@
import { ethers } from 'ethers';
import { z } from 'zod';
import { CallDataSchema, PopulatedTransactionSchema } from './schemas.js';
import {
CallDataSchema,
PopulatedTransactionSchema,
PopulatedTransactionsSchema,
} from './schemas.js';
export type PopulatedTransaction = z.infer<typeof PopulatedTransactionSchema> &
ethers.PopulatedTransaction;
export type PopulatedTransactions = z.infer<
typeof PopulatedTransactionsSchema
> &
ethers.PopulatedTransaction[];
export type CallData = z.infer<typeof CallDataSchema>;

Loading…
Cancel
Save