From e2e487725a9937f87df8c7f1218c2dbafa1dfa7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20Bayindirli=20=F0=9F=A5=82?= Date: Wed, 1 May 2024 13:45:14 -0400 Subject: [PATCH] address @pbio comments; expand on multi-protocol support + minor cleanup --- .../transactions/TransactionTypes.ts | 7 ++++ .../submitter/TxSubmitterInterface.ts | 25 +++++++---- .../submitter/builder/TxSubmitterBuilder.ts | 38 ++++++++--------- ...bmitter.ts => EV5GnosisSafeTxSubmitter.ts} | 25 +++++------ ...s => EV5ImpersonatedAccountTxSubmitter.ts} | 28 +++++-------- ...xSubmitter.ts => EV5JsonRpcTxSubmitter.ts} | 24 ++++------- .../ethersV5/EV5TxSubmitterInterface.ts | 12 ++++++ .../transformer/TxTransformerInterface.ts | 11 +++-- ...s => EV5InterchainAccountTxTransformer.ts} | 41 +++++++++---------- .../ethersV5/EV5TxTransformerInterface.ts | 6 +++ 10 files changed, 116 insertions(+), 101 deletions(-) create mode 100644 typescript/sdk/src/providers/transactions/TransactionTypes.ts rename typescript/sdk/src/providers/transactions/submitter/ethersV5/{GnosisSafeTxSubmitter.ts => EV5GnosisSafeTxSubmitter.ts} (79%) rename typescript/sdk/src/providers/transactions/submitter/ethersV5/{ImpersonatedAccountTxSubmitter.ts => EV5ImpersonatedAccountTxSubmitter.ts} (63%) rename typescript/sdk/src/providers/transactions/submitter/ethersV5/{JsonRpcTxSubmitter.ts => EV5JsonRpcTxSubmitter.ts} (61%) create mode 100644 typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterInterface.ts rename typescript/sdk/src/providers/transactions/transformer/ethersV5/{InterchainAccountTxTransformer.ts => EV5InterchainAccountTxTransformer.ts} (58%) create mode 100644 typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerInterface.ts diff --git a/typescript/sdk/src/providers/transactions/TransactionTypes.ts b/typescript/sdk/src/providers/transactions/TransactionTypes.ts new file mode 100644 index 0000000000..9ba6ec7f52 --- /dev/null +++ b/typescript/sdk/src/providers/transactions/TransactionTypes.ts @@ -0,0 +1,7 @@ +import { + EthersV5Transaction, + EthersV5TransactionReceipt, +} from '../ProviderType.js'; + +export type EV5Tx = EthersV5Transaction['transaction']; +export type EV5Receipt = EthersV5TransactionReceipt['receipt']; diff --git a/typescript/sdk/src/providers/transactions/submitter/TxSubmitterInterface.ts b/typescript/sdk/src/providers/transactions/submitter/TxSubmitterInterface.ts index e42d5be5e4..b857bd990d 100644 --- a/typescript/sdk/src/providers/transactions/submitter/TxSubmitterInterface.ts +++ b/typescript/sdk/src/providers/transactions/submitter/TxSubmitterInterface.ts @@ -1,25 +1,32 @@ +import { ProtocolType } from '@hyperlane-xyz/utils'; + import { ChainName } from '../../../types.js'; -import { MultiProvider } from '../../MultiProvider.js'; import { - TypedTransaction, - TypedTransactionReceipt, + ProtocolTypedProvider, + ProtocolTypedReceipt, + ProtocolTypedTransaction, } from '../../ProviderType.js'; import { TxSubmitterType } from './TxSubmitterTypes.js'; -export interface TxSubmitterInterface< - TX extends TypedTransaction, - TR extends TypedTransactionReceipt, -> { +export interface TxSubmitterInterface { /** * Defines the type of tx submitter. */ txSubmitterType: TxSubmitterType; - multiProvider: MultiProvider; + /** + * The chain to submit transactions on. + */ chain: ChainName; + /** + * The provider to use for transaction submission. + */ + provider?: ProtocolTypedProvider['provider']; /** * Should execute all transactions and return their receipts. * @param txs The array of transactions to execute */ - submit(...txs: TX[]): Promise; + submit( + ...txs: ProtocolTypedTransaction['transaction'][] + ): Promise['receipt'][] | void>; } diff --git a/typescript/sdk/src/providers/transactions/submitter/builder/TxSubmitterBuilder.ts b/typescript/sdk/src/providers/transactions/submitter/builder/TxSubmitterBuilder.ts index 530e1f2bae..4ddfe93d20 100644 --- a/typescript/sdk/src/providers/transactions/submitter/builder/TxSubmitterBuilder.ts +++ b/typescript/sdk/src/providers/transactions/submitter/builder/TxSubmitterBuilder.ts @@ -1,10 +1,11 @@ import { Logger } from 'pino'; import { rootLogger } from '@hyperlane-xyz/utils'; +import { ProtocolType } from '@hyperlane-xyz/utils'; import { - TypedTransaction, - TypedTransactionReceipt, + ProtocolTypedReceipt, + ProtocolTypedTransaction, } from '../../../ProviderType.js'; import { TxTransformerInterface } from '../../transformer/TxTransformerInterface.js'; import { TxSubmitterInterface } from '../TxSubmitterInterface.js'; @@ -15,30 +16,27 @@ import { TxSubmitterInterface } from '../TxSubmitterInterface.js'; * Example use-cases: * const eV5builder = new TxSubmitterBuilder(); * let txReceipts = eV5builder.for( - * new GnosisSafeTxSubmitter(chainA) + * new EV5GnosisSafeTxSubmitter(chainA) * ).transform( - * InterchainAccountTxTransformer(chainB) + * EV5InterchainAccountTxTransformer(chainB) * ).submit( * txs * ); * txReceipts = eV5builder.for( - * new ImpersonatedAccountTxSubmitter(chainA) + * new EV5ImpersonatedAccountTxSubmitter(chainA) * ).submit(txs); * txReceipts = eV5builder.for( - * new JsonRpcTxSubmitter(chainC) + * new EV5JsonRpcTxSubmitter(chainC) * ).submit(txs); */ -export class TxSubmitterBuilder< - TX extends TypedTransaction, - TR extends TypedTransactionReceipt, -> { +export class TxSubmitterBuilder { protected readonly logger: Logger = rootLogger.child({ module: 'submitter-builder', }); constructor( - private currentSubmitter: TxSubmitterInterface, - private readonly currentTransformers: TxTransformerInterface[] = [], + private currentSubmitter: TxSubmitterInterface, + private readonly currentTransformers: TxTransformerInterface[] = [], ) {} /** @@ -46,8 +44,8 @@ export class TxSubmitterBuilder< * @param txSubmitterOrType The submitter to add to the builder */ public for( - txSubmitter: TxSubmitterInterface, - ): TxSubmitterBuilder { + txSubmitter: TxSubmitterInterface, + ): TxSubmitterBuilder { this.currentSubmitter = txSubmitter; return this; } @@ -57,8 +55,8 @@ export class TxSubmitterBuilder< * @param txTransformerOrType The transformer to add to the builder */ public transform( - txTransformer: TxTransformerInterface, - ): TxSubmitterBuilder { + txTransformer: TxTransformerInterface, + ): TxSubmitterBuilder { this.currentTransformers.push(txTransformer); return this; } @@ -67,14 +65,16 @@ export class TxSubmitterBuilder< * Submits a set of transactions to the builder. * @param txs The transactions to submit */ - public async submit(...txs: TX[]): Promise { + public async submit( + ...txs: ProtocolTypedTransaction['transaction'][] + ): Promise['receipt'][] | void> { this.logger.info( `Submitting ${txs.length} transactions to the ${this.currentSubmitter.txSubmitterType} submitter...`, ); let transformedTxs = txs; while (this.currentTransformers.length > 0) { - const currentTransformer: TxTransformerInterface = + const currentTransformer: TxTransformerInterface = this.currentTransformers.pop()!; transformedTxs = await currentTransformer.transform(...transformedTxs); this.logger.info( @@ -87,6 +87,6 @@ export class TxSubmitterBuilder< `✅ Successfully submitted ${transformedTxs.length} transactions to the ${this.currentSubmitter.txSubmitterType} submitter.`, ); - return txReceipts ?? []; + return txReceipts; } } diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/GnosisSafeTxSubmitter.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts similarity index 79% rename from typescript/sdk/src/providers/transactions/submitter/ethersV5/GnosisSafeTxSubmitter.ts rename to typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts index 9767413034..1d09e98222 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/GnosisSafeTxSubmitter.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts @@ -5,6 +5,7 @@ import { SafeTransactionData, } from '@safe-global/safe-core-sdk-types'; import assert from 'assert'; +import { PopulatedTransaction } from 'ethers'; import { Logger } from 'pino'; import { Address, rootLogger } from '@hyperlane-xyz/utils'; @@ -12,22 +13,17 @@ import { Address, rootLogger } from '@hyperlane-xyz/utils'; import { ChainName } from '../../../../types.js'; import { getSafe, getSafeService } from '../../../../utils/gnosisSafe.js'; import { MultiProvider } from '../../../MultiProvider.js'; -import { - EthersV5Transaction, - EthersV5TransactionReceipt, -} from '../../../ProviderType.js'; -import { TxSubmitterInterface } from '../TxSubmitterInterface.js'; +import { EV5Tx } from '../../TransactionTypes.js'; import { TxSubmitterType } from '../TxSubmitterTypes.js'; -interface GnosisSafeTxSubmitterProps { +import { EV5TxSubmitterInterface } from './EV5TxSubmitterInterface.js'; + +interface EV5GnosisSafeTxSubmitterProps { safeAddress: Address; signerAddress?: Address; } -export class GnosisSafeTxSubmitter - implements - TxSubmitterInterface -{ +export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface { public readonly txSubmitterType: TxSubmitterType = TxSubmitterType.GNOSIS_SAFE; @@ -38,10 +34,10 @@ export class GnosisSafeTxSubmitter constructor( public readonly multiProvider: MultiProvider, public readonly chain: ChainName, - public readonly props: GnosisSafeTxSubmitterProps, + public readonly props: EV5GnosisSafeTxSubmitterProps, ) {} - public async submit(...txs: EthersV5Transaction[]): Promise { + public async submit(...txs: EV5Tx[]): Promise { const safe: Safe.default = await getSafe( this.chain, this.multiProvider, @@ -55,11 +51,10 @@ export class GnosisSafeTxSubmitter this.props.safeAddress, ); const safeTransactionBatch: MetaTransactionData[] = txs.map( - ({ transaction }: EthersV5Transaction) => { - const { to, data, value } = transaction; + ({ to, data, value }: PopulatedTransaction) => { assert( to && data, - 'Invalid EthersV5Transaction: Missing required field to or data.', + 'Invalid PopulatedTransaction: Missing required field to or data.', ); return { to, data, value: value?.toString() ?? '0' }; }, diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/ImpersonatedAccountTxSubmitter.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts similarity index 63% rename from typescript/sdk/src/providers/transactions/submitter/ethersV5/ImpersonatedAccountTxSubmitter.ts rename to typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts index 80afcb2591..ef0c5d0eeb 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/ImpersonatedAccountTxSubmitter.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts @@ -7,21 +7,17 @@ import { Address } from '@hyperlane-xyz/utils'; import { ChainName } from '../../../../types.js'; import { impersonateAccount } from '../../../../utils/fork.js'; import { MultiProvider } from '../../../MultiProvider.js'; -import { - EthersV5Transaction, - EthersV5TransactionReceipt, - ProviderType, -} from '../../../ProviderType.js'; -import { TxSubmitterInterface } from '../TxSubmitterInterface.js'; +import { EV5Receipt, EV5Tx } from '../../TransactionTypes.js'; import { TxSubmitterType } from '../TxSubmitterTypes.js'; -interface ImpersonatedAccountTxSubmitterProps { +import { EV5TxSubmitterInterface } from './EV5TxSubmitterInterface.js'; + +interface EV5ImpersonatedAccountTxSubmitterProps { address: Address; } -export class ImpersonatedAccountTxSubmitter - implements - TxSubmitterInterface +export class EV5ImpersonatedAccountTxSubmitter + implements EV5TxSubmitterInterface { public readonly txSubmitterType: TxSubmitterType = TxSubmitterType.IMPERSONATED_ACCOUNT; @@ -33,26 +29,24 @@ export class ImpersonatedAccountTxSubmitter constructor( public readonly multiProvider: MultiProvider, public readonly chain: ChainName, - public readonly props: ImpersonatedAccountTxSubmitterProps, + public readonly props: EV5ImpersonatedAccountTxSubmitterProps, ) {} - public async submit( - ...txs: EthersV5Transaction[] - ): Promise { - const receipts: EthersV5TransactionReceipt[] = []; + public async submit(...txs: EV5Tx[]): Promise { + const receipts: EV5Receipt[] = []; for (const tx of txs) { const signer = await impersonateAccount(this.props.address); this.multiProvider.setSigner(this.chain, signer); const receipt: ContractReceipt = await this.multiProvider.sendTransaction( this.chain, - tx.transaction, + tx, ); this.logger.debug( `Submitted EthersV5Transaction on ${this.chain}: ${receipt.transactionHash}`, ); - receipts.push({ type: ProviderType.EthersV5, receipt }); + receipts.push(receipt); } return receipts; } diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/JsonRpcTxSubmitter.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.ts similarity index 61% rename from typescript/sdk/src/providers/transactions/submitter/ethersV5/JsonRpcTxSubmitter.ts rename to typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.ts index 2e6d76de5b..6cbf1469e1 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/JsonRpcTxSubmitter.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.ts @@ -5,18 +5,12 @@ import { rootLogger } from '@hyperlane-xyz/utils'; import { ChainName } from '../../../../types.js'; import { MultiProvider } from '../../../MultiProvider.js'; -import { - EthersV5Transaction, - EthersV5TransactionReceipt, - ProviderType, -} from '../../../ProviderType.js'; -import { TxSubmitterInterface } from '../TxSubmitterInterface.js'; +import { EV5Receipt, EV5Tx } from '../../TransactionTypes.js'; import { TxSubmitterType } from '../TxSubmitterTypes.js'; -export class JsonRpcTxSubmitter - implements - TxSubmitterInterface -{ +import { EV5TxSubmitterInterface } from './EV5TxSubmitterInterface.js'; + +export class EV5JsonRpcTxSubmitter implements EV5TxSubmitterInterface { public readonly txSubmitterType: TxSubmitterType = TxSubmitterType.JSON_RPC; protected readonly logger: Logger = rootLogger.child({ @@ -28,19 +22,17 @@ export class JsonRpcTxSubmitter public readonly chain: ChainName, ) {} - public async submit( - ...txs: EthersV5Transaction[] - ): Promise { - const receipts: EthersV5TransactionReceipt[] = []; + public async submit(...txs: EV5Tx[]): Promise { + const receipts: EV5Receipt[] = []; for (const tx of txs) { const receipt: ContractReceipt = await this.multiProvider.sendTransaction( this.chain, - tx.transaction, + tx, ); this.logger.debug( `Submitted EthersV5Transaction on ${this.chain}: ${receipt.transactionHash}`, ); - receipts.push({ type: ProviderType.EthersV5, receipt }); + receipts.push(receipt); } return receipts; } diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterInterface.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterInterface.ts new file mode 100644 index 0000000000..d1c452f7d1 --- /dev/null +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterInterface.ts @@ -0,0 +1,12 @@ +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { MultiProvider } from '../../../MultiProvider.js'; +import { TxSubmitterInterface } from '../TxSubmitterInterface.js'; + +export interface EV5TxSubmitterInterface + extends TxSubmitterInterface { + /** + * The EV5 multi-provider to use for transaction submission. + */ + multiProvider: MultiProvider; +} diff --git a/typescript/sdk/src/providers/transactions/transformer/TxTransformerInterface.ts b/typescript/sdk/src/providers/transactions/transformer/TxTransformerInterface.ts index 49f025d66f..5f2476d9fa 100644 --- a/typescript/sdk/src/providers/transactions/transformer/TxTransformerInterface.ts +++ b/typescript/sdk/src/providers/transactions/transformer/TxTransformerInterface.ts @@ -1,16 +1,19 @@ -import { TypedTransaction } from '../../ProviderType.js'; +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { ProtocolTypedTransaction } from '../../ProviderType.js'; import { TxTransformerType } from './TxTransformerTypes.js'; -export interface TxTransformerInterface { +export interface TxTransformerInterface { /** * Defines the type of tx transformer. */ txTransformerType: TxTransformerType; - /** * Should transform all transactions of type TX into transactions of type TX. * @param txs The array of transactions to transform */ - transform(...txs: TX[]): Promise; + transform( + ...txs: ProtocolTypedTransaction['transaction'][] + ): Promise['transaction'][]>; } diff --git a/typescript/sdk/src/providers/transactions/transformer/ethersV5/InterchainAccountTxTransformer.ts b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts similarity index 58% rename from typescript/sdk/src/providers/transactions/transformer/ethersV5/InterchainAccountTxTransformer.ts rename to typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts index 3c82acf479..73e9755a6a 100644 --- a/typescript/sdk/src/providers/transactions/transformer/ethersV5/InterchainAccountTxTransformer.ts +++ b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts @@ -1,4 +1,5 @@ import assert from 'assert'; +import { PopulatedTransaction } from 'ethers'; import { Logger } from 'pino'; import { CallData, rootLogger } from '@hyperlane-xyz/utils'; @@ -8,18 +9,19 @@ import { InterchainAccount } from '../../../../middleware/account/InterchainAcco import { AccountConfig } from '../../../../middleware/account/types.js'; import { ChainName } from '../../../../types.js'; import { MultiProvider } from '../../../MultiProvider.js'; -import { EthersV5Transaction, ProviderType } from '../../../ProviderType.js'; -import { TxTransformerInterface } from '../TxTransformerInterface.js'; +import { EV5Tx } from '../../TransactionTypes.js'; import { TxTransformerType } from '../TxTransformerTypes.js'; -interface InterchainAccountTxTransformerProps { +import { EV5TxTransformerInterface } from './EV5TxTransformerInterface.js'; + +interface EV5InterchainAccountTxTransformerProps { interchainAccount: InterchainAccount; accountConfig: AccountConfig; hookMetadata?: string; } -export class InterchainAccountTxTransformer - implements TxTransformerInterface +export class EV5InterchainAccountTxTransformer + implements EV5TxTransformerInterface { public readonly txTransformerType: TxTransformerType = TxTransformerType.ICA; protected readonly logger: Logger = rootLogger.child({ @@ -29,21 +31,18 @@ export class InterchainAccountTxTransformer constructor( public readonly multiProvider: MultiProvider, public readonly chain: ChainName, - public readonly props: InterchainAccountTxTransformerProps, + public readonly props: EV5InterchainAccountTxTransformerProps, ) {} - public async transform( - ...txs: EthersV5Transaction[] - ): Promise { - const destinationChainId = txs[0].transaction.chainId; + public async transform(...txs: EV5Tx[]): Promise { + const destinationChainId = txs[0].chainId; assert( destinationChainId, 'Missing destination chainId in EthersV5Transaction.', ); const innerCalls: CallData[] = txs.map( - ({ transaction }: EthersV5Transaction) => { - const { to, data, value } = transaction; + ({ to, data, value }: PopulatedTransaction) => { assert( to && data, 'Invalid EthersV5Transaction: Missing required field to or data.', @@ -52,14 +51,14 @@ export class InterchainAccountTxTransformer }, ); - const transaction = await this.props.interchainAccount.getCallRemote( - this.chain, - chainIdToMetadata[destinationChainId].name, - innerCalls, - this.props.accountConfig, - this.props.hookMetadata, - ); - - return [{ type: ProviderType.EthersV5, transaction }]; + return [ + await this.props.interchainAccount.getCallRemote( + this.chain, + chainIdToMetadata[destinationChainId].name, + innerCalls, + this.props.accountConfig, + this.props.hookMetadata, + ), + ]; } } diff --git a/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerInterface.ts b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerInterface.ts new file mode 100644 index 0000000000..32e3c23f2b --- /dev/null +++ b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerInterface.ts @@ -0,0 +1,6 @@ +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { TxTransformerInterface } from '../TxTransformerInterface.js'; + +export interface EV5TxTransformerInterface + extends TxTransformerInterface {}