diff --git a/typescript/cli/src/config/submit.ts b/typescript/cli/src/config/submit.ts index e2f66a003d..67446efb29 100644 --- a/typescript/cli/src/config/submit.ts +++ b/typescript/cli/src/config/submit.ts @@ -1,12 +1,9 @@ import { stringify as yamlStringify } from 'yaml'; import { - PopulatedTransactions, - PopulatedTransactionsSchema, + AnnotatedEV5Transaction, SubmissionStrategy, } from '@hyperlane-xyz/sdk'; -import { PopulatedTransaction } from '@hyperlane-xyz/sdk'; -import { MultiProvider } from '@hyperlane-xyz/sdk'; import { assert, errorToString } from '@hyperlane-xyz/utils'; import { WriteCommandContext } from '../context/types.js'; @@ -36,7 +33,7 @@ export async function runSubmit({ 'Submission strategy required to submit transactions.\nPlease create a submission strategy. See examples in cli/examples/submit/strategy/*.', ); const transactions = getTransactions(transactionsFilepath); - const chain = getChainFromTxs(multiProvider, transactions); + const chain = getChainFromTxs(transactions); const protocol = chainMetadata[chain].protocol; const submitterBuilder = await getSubmitterBuilder({ @@ -70,22 +67,17 @@ export async function runSubmit({ * @returns The name of the chain that the transactions are submitted on. * @throws If the transactions are not all on the same chain or chain is not found */ -function getChainFromTxs( - multiProvider: MultiProvider, - transactions: PopulatedTransactions, -) { +function getChainFromTxs(transactions: AnnotatedEV5Transaction[]) { const firstTransaction = transactions[0]; const sameChainIds = transactions.every( - (t: PopulatedTransaction) => t.chainId === firstTransaction.chainId, + (t: AnnotatedEV5Transaction) => t.chainId === firstTransaction.chainId, ); assert(sameChainIds, 'Transactions must be submitted on the same chains'); - - return multiProvider.getChainName(firstTransaction.chainId); + return firstTransaction.chain; } -function getTransactions(transactionsFilepath: string): PopulatedTransactions { - const transactionsFileContent = readYamlOrJson( - transactionsFilepath.trim(), - ); - return PopulatedTransactionsSchema.parse(transactionsFileContent); +function getTransactions( + transactionsFilepath: string, +): AnnotatedEV5Transaction[] { + return readYamlOrJson(transactionsFilepath.trim()); } diff --git a/typescript/infra/scripts/funding/fund-deterministic-key-from-deployer.ts b/typescript/infra/scripts/funding/fund-deterministic-key-from-deployer.ts index 633c98c26d..9f6cfc01e5 100644 --- a/typescript/infra/scripts/funding/fund-deterministic-key-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-deterministic-key-from-deployer.ts @@ -80,6 +80,7 @@ async function main() { `Funding ${address} on chain '${chain}' with ${value} native tokens`, ); await multiProvider.sendTransaction(chain, { + chain, to: address, value, }); diff --git a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts index d262e52c87..2330948093 100644 --- a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts @@ -623,10 +623,10 @@ class ContextFunder { if (igpBalance.gt(igpClaimThreshold)) { logger.info({ chain }, 'IGP balance exceeds claim threshold, claiming'); - await this.multiProvider.sendTransaction( + await this.multiProvider.sendTransaction(chain, { + ...(await igp.populateTransaction.claim()), chain, - await igp.populateTransaction.claim(), - ); + }); } else { logger.info( { chain }, @@ -720,6 +720,7 @@ class ContextFunder { } const tx = await this.multiProvider.sendTransaction(chain, { + chain, to: key.address, value: fundingAmount, }); diff --git a/typescript/infra/scripts/helloworld/kathy.ts b/typescript/infra/scripts/helloworld/kathy.ts index cea226d4fd..f17061bf2e 100644 --- a/typescript/infra/scripts/helloworld/kathy.ts +++ b/typescript/infra/scripts/helloworld/kathy.ts @@ -232,13 +232,15 @@ async function main(): Promise { messageReceiptSeconds.labels({ origin, remote }).inc(0); } - chains.map(async (chain) => { - return updateWalletBalanceMetricFor( - app, - chain, - coreConfig.owners[chain].owner, - ); - }); + await Promise.allSettled( + chains.map(async (chain) => { + return updateWalletBalanceMetricFor( + app, + chain, + coreConfig.owners[chain].owner, + ); + }), + ); // Incremented each time an entire cycle has occurred let currentCycle = 0; @@ -431,10 +433,10 @@ async function sendMessage( let txReceipt: TypedTransactionReceipt; if (tx.type == ProviderType.EthersV5) { // Utilize the legacy evm-specific multiprovider utils to send the transaction - const receipt = await multiProvider.sendTransaction( - origin, - tx.transaction, - ); + const receipt = await multiProvider.sendTransaction(origin, { + chain: origin, + ...tx.transaction, + }); txReceipt = { type: ProviderType.EthersV5, receipt, diff --git a/typescript/infra/src/govern/multisend.ts b/typescript/infra/src/govern/multisend.ts index 0b3792424a..1f2e4e598f 100644 --- a/typescript/infra/src/govern/multisend.ts +++ b/typescript/infra/src/govern/multisend.ts @@ -34,6 +34,7 @@ export class SignerMultiSend extends MultiSend { for (const call of calls) { const estimate = await this.multiProvider.estimateGas(this.chain, call); const receipt = await this.multiProvider.sendTransaction(this.chain, { + chain: this.chain, gasLimit: addBufferToGasLimit(estimate), ...call, }); diff --git a/typescript/infra/test/govern.hardhat-test.ts b/typescript/infra/test/govern.hardhat-test.ts index 6de17b11f6..feafc065c1 100644 --- a/typescript/infra/test/govern.hardhat-test.ts +++ b/typescript/infra/test/govern.hardhat-test.ts @@ -81,6 +81,7 @@ export class HyperlaneTestGovernor extends HyperlaneAppGovernor< ): Promise { for (const call of calls) { await this.checker.multiProvider.sendTransaction(chain, { + chain, to: call.to, data: call.data, value: call.value, diff --git a/typescript/sdk/src/core/AbstractHyperlaneModule.ts b/typescript/sdk/src/core/AbstractHyperlaneModule.ts index d4eadabaeb..5ef6d6ff4d 100644 --- a/typescript/sdk/src/core/AbstractHyperlaneModule.ts +++ b/typescript/sdk/src/core/AbstractHyperlaneModule.ts @@ -1,17 +1,8 @@ import { Logger } from 'pino'; -import { Ownable__factory } from '@hyperlane-xyz/core'; -import { - Address, - Annotated, - ProtocolType, - eqAddress, -} from '@hyperlane-xyz/utils'; +import { Annotated, ProtocolType } from '@hyperlane-xyz/utils'; -import { - AnnotatedEV5Transaction, - ProtocolTypedTransaction, -} from '../providers/ProviderType.js'; +import { ProtocolTypedTransaction } from '../providers/ProviderType.js'; import { ChainNameOrId } from '../types.js'; export type HyperlaneModuleParams< @@ -41,41 +32,7 @@ export abstract class HyperlaneModule< public abstract read(): Promise; public abstract update( config: TConfig, - ): Promise['transaction'][]>>; - - /** - * Transfers ownership of a contract to a new owner. - * - * @param actualOwner - The current owner of the contract. - * @param expectedOwner - The expected new owner of the contract. - * @param deployedAddress - The address of the deployed contract. - * @param chainId - The chain ID of the network the contract is deployed on. - * @returns An array of annotated EV5 transactions that need to be executed to update the owner. - */ - static createTransferOwnershipTx(params: { - actualOwner: Address; - expectedOwner: Address; - deployedAddress: Address; - chainId: number; - }): AnnotatedEV5Transaction[] { - const { actualOwner, expectedOwner, deployedAddress, chainId } = params; - const updateTransactions: AnnotatedEV5Transaction[] = []; - if (eqAddress(actualOwner, expectedOwner)) { - return []; - } - - updateTransactions.push({ - annotation: `Transferring ownership of ${deployedAddress} from current owner ${actualOwner} to new owner ${expectedOwner}`, - chainId, - to: deployedAddress, - data: Ownable__factory.createInterface().encodeFunctionData( - 'transferOwnership(address)', - [expectedOwner], - ), - }); - - return updateTransactions; - } + ): Promise['transaction']>[]>; // /* // Types and static methods can be challenging. Ensure each implementation includes a static create function. diff --git a/typescript/sdk/src/core/EvmCoreModule.ts b/typescript/sdk/src/core/EvmCoreModule.ts index 1ab4ef649e..8122c0265b 100644 --- a/typescript/sdk/src/core/EvmCoreModule.ts +++ b/typescript/sdk/src/core/EvmCoreModule.ts @@ -14,6 +14,7 @@ import { import { HyperlaneAddresses } from '../contracts/types.js'; import { DeployedCoreAddresses } from '../core/schemas.js'; import { CoreConfig } from '../core/types.js'; +import { EvmModuleDeployer } from '../deploy/EvmModuleDeployer.js'; import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; import { ProxyFactoryFactories, @@ -132,6 +133,7 @@ export class EvmCoreModule extends HyperlaneModule< data: contractToUpdate.interface.encodeFunctionData('setDefaultIsm', [ deployedIsm, ]), + chain: this.chainName, }); } @@ -196,11 +198,12 @@ export class EvmCoreModule extends HyperlaneModule< actualConfig: CoreConfig, expectedConfig: CoreConfig, ): AnnotatedEV5Transaction[] { - return EvmCoreModule.createTransferOwnershipTx({ + return EvmModuleDeployer.createTransferOwnershipTx({ actualOwner: actualConfig.owner, expectedOwner: expectedConfig.owner, deployedAddress: this.args.addresses.mailbox, chainId: this.domainId, + chain: this.chainName, }); } diff --git a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts index c30f5130b3..b67a2779b6 100644 --- a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts +++ b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts @@ -143,8 +143,12 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< mailbox, defaultHook.address, (_mailbox) => _mailbox.defaultHook(), - (_mailbox, _hook) => - _mailbox.populateTransaction.setDefaultHook(_hook, { ...txOverrides }), + async (_mailbox, _hook) => { + const tx = await _mailbox.populateTransaction.setDefaultHook(_hook, { + ...txOverrides, + }); + return { ...tx, chain }; + }, ); await this.configureHook( @@ -152,8 +156,12 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< mailbox, requiredHook.address, (_mailbox) => _mailbox.requiredHook(), - (_mailbox, _hook) => - _mailbox.populateTransaction.setRequiredHook(_hook, { ...txOverrides }), + async (_mailbox, _hook) => { + const tx = await _mailbox.populateTransaction.setRequiredHook(_hook, { + ...txOverrides, + }); + return { ...tx, chain }; + }, ); await this.configureIsm( @@ -161,8 +169,10 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< mailbox, defaultIsm, (_mailbox) => _mailbox.defaultIsm(), - (_mailbox, _module) => - _mailbox.populateTransaction.setDefaultIsm(_module), + async (_mailbox, _module) => { + const tx = await _mailbox.populateTransaction.setDefaultIsm(_module); + return { ...tx, chain }; + }, ); return mailbox; diff --git a/typescript/sdk/src/core/TestRecipientDeployer.ts b/typescript/sdk/src/core/TestRecipientDeployer.ts index 08097d7c45..2977e821e9 100644 --- a/typescript/sdk/src/core/TestRecipientDeployer.ts +++ b/typescript/sdk/src/core/TestRecipientDeployer.ts @@ -53,7 +53,12 @@ export class TestRecipientDeployer extends HyperlaneDeployer< testRecipient, config.interchainSecurityModule, (tr) => tr.interchainSecurityModule(), - (tr, ism) => tr.populateTransaction.setInterchainSecurityModule(ism), + async (tr, ism) => { + const tx = await tr.populateTransaction.setInterchainSecurityModule( + ism, + ); + return { ...tx, chain }; + }, ); } else { this.logger.warn(`No ISM config provided for TestRecipient on ${chain}`); diff --git a/typescript/sdk/src/deploy/EvmModuleDeployer.ts b/typescript/sdk/src/deploy/EvmModuleDeployer.ts index 377a1ffd2d..150f594a3e 100644 --- a/typescript/sdk/src/deploy/EvmModuleDeployer.ts +++ b/typescript/sdk/src/deploy/EvmModuleDeployer.ts @@ -2,15 +2,22 @@ import { ethers } from 'ethers'; import { Logger } from 'pino'; import { + Ownable__factory, StaticAddressSetFactory, StaticThresholdAddressSetFactory, TransparentUpgradeableProxy__factory, } from '@hyperlane-xyz/core'; import { buildArtifact as coreBuildArtifact } from '@hyperlane-xyz/core/buildArtifact.js'; -import { Address, addBufferToGasLimit, rootLogger } from '@hyperlane-xyz/utils'; +import { + Address, + addBufferToGasLimit, + eqAddress, + rootLogger, +} from '@hyperlane-xyz/utils'; import { HyperlaneContracts, HyperlaneFactories } from '../contracts/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; +import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; import { ChainMap, ChainName } from '../types.js'; import { isProxy, proxyConstructorArgs } from './proxy.js'; @@ -315,4 +322,41 @@ export class EvmModuleDeployer { return address; } + + /** + * Transfers ownership of a contract to a new owner. + * + * @param actualOwner - The current owner of the contract. + * @param expectedOwner - The expected new owner of the contract. + * @param deployedAddress - The address of the deployed contract. + * @param chainId - The chain ID of the network the contract is deployed on. + * @returns An array of annotated EV5 transactions that need to be executed to update the owner. + */ + static createTransferOwnershipTx(params: { + actualOwner: Address; + expectedOwner: Address; + deployedAddress: Address; + chainId: number; + chain: ChainName; + }): AnnotatedEV5Transaction[] { + const { actualOwner, expectedOwner, deployedAddress, chainId, chain } = + params; + const updateTransactions: AnnotatedEV5Transaction[] = []; + if (eqAddress(actualOwner, expectedOwner)) { + return []; + } + + updateTransactions.push({ + chain, + annotation: `Transferring ownership of ${deployedAddress} from current owner ${actualOwner} to new owner ${expectedOwner}`, + chainId, + to: deployedAddress, + data: Ownable__factory.createInterface().encodeFunctionData( + 'transferOwnership(address)', + [expectedOwner], + ), + }); + + return updateTransactions; + } } diff --git a/typescript/sdk/src/deploy/HyperlaneDeployer.ts b/typescript/sdk/src/deploy/HyperlaneDeployer.ts index 8203c79192..fb883ac5a2 100644 --- a/typescript/sdk/src/deploy/HyperlaneDeployer.ts +++ b/typescript/sdk/src/deploy/HyperlaneDeployer.ts @@ -1,4 +1,4 @@ -import { Contract, PopulatedTransaction, ethers } from 'ethers'; +import { Contract, ethers } from 'ethers'; import { Logger } from 'pino'; import { @@ -34,6 +34,7 @@ import { IsmConfig } from '../ism/types.js'; import { moduleMatchesConfig } from '../ism/utils.js'; import { InterchainAccount } from '../middleware/account/InterchainAccount.js'; import { MultiProvider } from '../providers/MultiProvider.js'; +import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; import { MailboxClientConfig } from '../router/types.js'; import { ChainMap, ChainName } from '../types.js'; @@ -260,7 +261,7 @@ export abstract class HyperlaneDeployer< contract: C, config: IsmConfig, getIsm: (contract: C) => Promise
, - setIsm: (contract: C, ism: Address) => Promise, + setIsm: (contract: C, ism: Address) => Promise, ): Promise { const configuredIsm = await getIsm(contract); let matches = false; @@ -307,7 +308,7 @@ export abstract class HyperlaneDeployer< contract: C, config: HookConfig, getHook: (contract: C) => Promise
, - setHook: (contract: C, hook: Address) => Promise, + setHook: (contract: C, hook: Address) => Promise, ): Promise { if (typeof config !== 'string') { throw new Error('Legacy deployer does not support hook objects'); @@ -347,7 +348,10 @@ export abstract class HyperlaneDeployer< client, config.hook, (_client) => _client.hook(), - (_client, _hook) => _client.populateTransaction.setHook(_hook), + async (_client, _hook) => { + const tx = await _client.populateTransaction.setHook(_hook); + return { ...tx, chain: local }; + }, ); } @@ -357,8 +361,13 @@ export abstract class HyperlaneDeployer< client, config.interchainSecurityModule, (_client) => _client.interchainSecurityModule(), - (_client, _module) => - _client.populateTransaction.setInterchainSecurityModule(_module), + async (_client, _module) => { + const tx = + await _client.populateTransaction.setInterchainSecurityModule( + _module, + ); + return { ...tx, chain: local }; + }, ); } diff --git a/typescript/sdk/src/hook/EvmHookModule.ts b/typescript/sdk/src/hook/EvmHookModule.ts index fb8e128422..c40bd57542 100644 --- a/typescript/sdk/src/hook/EvmHookModule.ts +++ b/typescript/sdk/src/hook/EvmHookModule.ts @@ -210,6 +210,7 @@ export class EvmHookModule extends HyperlaneModule< // Return an ownership transfer transaction if required if (!eqAddress(targetConfig.owner, owner)) { updateTxs.push({ + chain: this.chain, annotation: 'Transferring ownership of ownable Hook...', chainId: this.domainId, to: this.args.addresses.deployedHook, @@ -312,6 +313,7 @@ export class EvmHookModule extends HyperlaneModule< : pausableInterface.encodeFunctionData('unpause'); updateTxs.push({ + chain: this.chain, annotation: `Updating paused state to ${targetConfig.paused}`, chainId: this.domainId, to: this.args.addresses.deployedHook, @@ -335,6 +337,7 @@ export class EvmHookModule extends HyperlaneModule< // Update beneficiary if changed if (!eqAddress(currentConfig.beneficiary, targetConfig.beneficiary)) { updateTxs.push({ + chain: this.chain, annotation: `Updating beneficiary from ${currentConfig.beneficiary} to ${targetConfig.beneficiary}`, chainId: this.domainId, to: this.args.addresses.deployedHook, @@ -434,6 +437,7 @@ export class EvmHookModule extends HyperlaneModule< return [ { + chain: this.chain, annotation: `Updating overhead for domains ${Object.keys( targetOverheads, ).join(', ')}...`, @@ -500,6 +504,7 @@ export class EvmHookModule extends HyperlaneModule< return [ { + chain: this.chain, annotation: `Updating gas oracle config for domains ${Object.keys( targetOracleConfig, ).join(', ')}...`, @@ -533,6 +538,7 @@ export class EvmHookModule extends HyperlaneModule< // Update protocol fee if changed if (currentConfig.protocolFee !== targetConfig.protocolFee) { updateTxs.push({ + chain: this.chain, annotation: `Updating protocol fee from ${currentConfig.protocolFee} to ${targetConfig.protocolFee}`, chainId: this.domainId, to: this.args.addresses.deployedHook, @@ -546,6 +552,7 @@ export class EvmHookModule extends HyperlaneModule< // Update beneficiary if changed if (currentConfig.beneficiary !== targetConfig.beneficiary) { updateTxs.push({ + chain: this.chain, annotation: `Updating beneficiary from ${currentConfig.beneficiary} to ${targetConfig.beneficiary}`, chainId: this.domainId, to: this.args.addresses.deployedHook, @@ -594,6 +601,7 @@ export class EvmHookModule extends HyperlaneModule< // Create tx for setting hooks return [ { + chain: this.chain, annotation: 'Updating routing hooks...', chainId: this.domainId, to: this.args.addresses.deployedHook, diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 3270b49738..2e2e4eb503 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -312,15 +312,7 @@ export { excludeProviderMethods, } from './providers/SmartProvider/ProviderMethods.js'; export { HyperlaneSmartProvider } from './providers/SmartProvider/SmartProvider.js'; -export { - PopulatedTransactionSchema, - PopulatedTransactionsSchema, -} from './providers/transactions/schemas.js'; -export { - CallData, - PopulatedTransaction, - PopulatedTransactions, -} from './providers/transactions/types.js'; +export { CallData } from './providers/transactions/types.js'; export { SubmitterMetadataSchema } from './providers/transactions/submitter/schemas.js'; export { TxSubmitterInterface } from './providers/transactions/submitter/TxSubmitterInterface.js'; diff --git a/typescript/sdk/src/ism/EvmIsmModule.ts b/typescript/sdk/src/ism/EvmIsmModule.ts index 7ab88b027e..32c5aa6a4c 100644 --- a/typescript/sdk/src/ism/EvmIsmModule.ts +++ b/typescript/sdk/src/ism/EvmIsmModule.ts @@ -220,6 +220,7 @@ export class EvmIsmModule extends HyperlaneModule< // Return an ownership transfer transaction if required if (!eqAddress(targetConfig.owner, owner)) { updateTxs.push({ + chain: this.chain, annotation: 'Transferring ownership of ownable ISM...', chainId: this.domainId, to: this.args.addresses.deployedIsm, @@ -279,7 +280,7 @@ export class EvmIsmModule extends HyperlaneModule< logger: Logger; }): Promise { const routingIsmInterface = DomainRoutingIsm__factory.createInterface(); - const updateTxs = []; + const updateTxs: AnnotatedEV5Transaction[] = []; // filter out domains which are not part of the multiprovider current = { @@ -311,6 +312,7 @@ export class EvmIsmModule extends HyperlaneModule< const domainId = this.multiProvider.getDomainId(origin); updateTxs.push({ + chain: this.chain, annotation: `Setting new ISM for origin ${origin}...`, chainId: this.domainId, to: this.args.addresses.deployedIsm, @@ -325,6 +327,7 @@ export class EvmIsmModule extends HyperlaneModule< for (const origin of domainsToUnenroll) { const domainId = this.multiProvider.getDomainId(origin); updateTxs.push({ + chain: this.chain, annotation: `Unenrolling originDomain ${domainId} from preexisting routing ISM at ${this.args.addresses.deployedIsm}...`, chainId: this.domainId, to: this.args.addresses.deployedIsm, diff --git a/typescript/sdk/src/middleware/account/InterchainAccount.ts b/typescript/sdk/src/middleware/account/InterchainAccount.ts index 273efb587c..a3f9a8e73a 100644 --- a/typescript/sdk/src/middleware/account/InterchainAccount.ts +++ b/typescript/sdk/src/middleware/account/InterchainAccount.ts @@ -1,4 +1,4 @@ -import { BigNumber, PopulatedTransaction } from 'ethers'; +import { BigNumber } from 'ethers'; import { InterchainAccountRouter } from '@hyperlane-xyz/core'; import { @@ -14,6 +14,7 @@ import { HyperlaneContractsMap, } from '../../contracts/types.js'; import { MultiProvider } from '../../providers/MultiProvider.js'; +import { AnnotatedEV5Transaction } from '../../providers/ProviderType.js'; import { RouterApp } from '../../router/RouterApps.js'; import { ChainName } from '../../types.js'; @@ -147,7 +148,7 @@ export class InterchainAccount extends RouterApp { innerCalls, config, hookMetadata, - }: GetCallRemoteSettings): Promise { + }: GetCallRemoteSettings): Promise { const localRouter = this.router(this.contractsMap[chain]); const remoteDomain = this.multiProvider.getDomainId(destination); const quote = await localRouter['quoteGasPayment(uint32)'](remoteDomain); @@ -172,7 +173,7 @@ export class InterchainAccount extends RouterApp { hookMetadata ?? '0x', { value: quote }, ); - return callEncoded; + return { ...callEncoded, chain }; } async getAccountConfig( diff --git a/typescript/sdk/src/providers/MultiProvider.ts b/typescript/sdk/src/providers/MultiProvider.ts index b37aba1c57..a5caaf2079 100644 --- a/typescript/sdk/src/providers/MultiProvider.ts +++ b/typescript/sdk/src/providers/MultiProvider.ts @@ -404,18 +404,18 @@ export class MultiProvider extends ChainMetadataManager { * @throws if chain's metadata or signer has not been set or tx fails */ async sendTransaction( - chainNameOrId: ChainNameOrId, + _: ChainNameOrId, txProm: AnnotatedEV5Transaction | Promise, ): Promise { - const { annotation, ...tx } = await txProm; + const { chain, annotation, ...tx } = await txProm; if (annotation) { this.logger.info(annotation); } - const txReq = await this.prepareTx(chainNameOrId, tx); - const signer = this.getSigner(chainNameOrId); + const txReq = await this.prepareTx(chain, tx); + const signer = this.getSigner(chain); const response = await signer.sendTransaction(txReq); this.logger.info(`Sent tx ${response.hash}`); - return this.handleTx(chainNameOrId, response); + return this.handleTx(chain, response); } /** diff --git a/typescript/sdk/src/providers/ProviderType.ts b/typescript/sdk/src/providers/ProviderType.ts index d7d84f1525..682905ff77 100644 --- a/typescript/sdk/src/providers/ProviderType.ts +++ b/typescript/sdk/src/providers/ProviderType.ts @@ -24,6 +24,8 @@ import type { import { ProtocolType } from '@hyperlane-xyz/utils'; +import { ChainName } from '../types.js'; + export enum ProviderType { EthersV5 = 'ethers-v5', Viem = 'viem', @@ -192,6 +194,7 @@ export interface EthersV5Transaction } export interface AnnotatedEV5Transaction extends EV5Transaction { + chain: ChainName; annotation?: string; } diff --git a/typescript/sdk/src/providers/transactions/schemas.test.ts b/typescript/sdk/src/providers/transactions/schemas.test.ts index 248878d300..8631a3f556 100644 --- a/typescript/sdk/src/providers/transactions/schemas.test.ts +++ b/typescript/sdk/src/providers/transactions/schemas.test.ts @@ -2,43 +2,16 @@ import { expect } from 'chai'; import { Address } from '@hyperlane-xyz/utils'; -import { CallDataSchema, PopulatedTransactionSchema } from './schemas.js'; -import { CallData, PopulatedTransaction } from './types.js'; +import { CallDataSchema } from './schemas.js'; +import { CallData } from './types.js'; describe('transactions schemas', () => { const ADDRESS_MOCK: Address = '0x1234567890123456789012345678901234567890'; const DATA_MOCK: string = '0xabcdef'; - const CHAIN_ID_MOCK: number = 1; const VALUE_MOCK: string = '100'; const INVALID_ADDRESS: Address = '0x1'; - describe('PopulatedTransactionSchema', () => { - it('should parse valid PopulatedTransaction', () => { - const validPopulatedTransaction: PopulatedTransaction = { - to: ADDRESS_MOCK, - data: DATA_MOCK, - chainId: CHAIN_ID_MOCK, - }; - const result = PopulatedTransactionSchema.safeParse( - validPopulatedTransaction, - ); - expect(result.success).to.be.true; - }); - - it('should fail parsing invalid PopulatedTransaction', () => { - const invalidPopulatedTransaction: PopulatedTransaction = { - to: INVALID_ADDRESS, - data: DATA_MOCK, - chainId: CHAIN_ID_MOCK, - }; - const result = PopulatedTransactionSchema.safeParse( - invalidPopulatedTransaction, - ); - expect(result.success).to.be.false; - }); - }); - describe('CallDataSchema', () => { it('should parse valid CallData', () => { const validCallData: CallData = { diff --git a/typescript/sdk/src/providers/transactions/schemas.ts b/typescript/sdk/src/providers/transactions/schemas.ts index b9071ae1c8..7547f47e4e 100644 --- a/typescript/sdk/src/providers/transactions/schemas.ts +++ b/typescript/sdk/src/providers/transactions/schemas.ts @@ -4,17 +4,6 @@ import { ZHash } from '../../metadata/customZodTypes.js'; export const BigNumberSchema = z.string(); -export const PopulatedTransactionSchema = z.object({ - to: ZHash, - data: z.string(), - chainId: z.number(), -}); - -export const PopulatedTransactionsSchema = - PopulatedTransactionSchema.array().refine((txs) => txs.length > 0, { - message: 'Populated Transactions cannot be empty', - }); - export const CallDataSchema = z.object({ to: ZHash, data: z.string(), diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts index 195b0b454a..40827d4f67 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts @@ -6,7 +6,7 @@ import { Address, assert, rootLogger } from '@hyperlane-xyz/utils'; // @ts-ignore import { canProposeSafeTransactions, getSafe, getSafeService } from '../../../../utils/gnosisSafe.js'; import { MultiProvider } from '../../../MultiProvider.js'; -import { PopulatedTransaction, PopulatedTransactions } from '../../types.js'; +import { AnnotatedEV5Transaction } from '../../../ProviderType.js'; import { TxSubmitterType } from '../TxSubmitterTypes.js'; import { EV5TxSubmitterInterface } from './EV5TxSubmitterInterface.js'; @@ -62,16 +62,15 @@ export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface { ); } - public async submit(...txs: PopulatedTransactions): Promise { + public async submit(...txs: AnnotatedEV5Transaction[]): Promise { const nextNonce: number = await this.safeService.getNextNonce( this.props.safeAddress, ); const safeTransactionBatch: any[] = txs.map( - ({ to, data, value, chainId }: PopulatedTransaction) => { - const txChain = this.multiProvider.getChainName(chainId); + ({ to, data, value, chain }: AnnotatedEV5Transaction) => { assert( - txChain === this.props.chain, - `Invalid PopulatedTransaction: Cannot submit ${txChain} tx to ${this.props.chain} submitter.`, + chain === this.props.chain, + `Invalid PopulatedTransaction: Cannot submit ${chain} tx to ${this.props.chain} submitter.`, ); return { to, data, value: value?.toString() ?? '0' }; }, diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts index 9a6866e353..f0fea448aa 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts @@ -8,7 +8,7 @@ import { stopImpersonatingAccount, } from '../../../../utils/fork.js'; import { MultiProvider } from '../../../MultiProvider.js'; -import { PopulatedTransactions } from '../../types.js'; +import { AnnotatedEV5Transaction } from '../../../ProviderType.js'; import { TxSubmitterType } from '../TxSubmitterTypes.js'; import { EV5JsonRpcTxSubmitter } from './EV5JsonRpcTxSubmitter.js'; @@ -30,7 +30,7 @@ export class EV5ImpersonatedAccountTxSubmitter extends EV5JsonRpcTxSubmitter { } public async submit( - ...txs: PopulatedTransactions + ...txs: AnnotatedEV5Transaction[] ): Promise { const impersonatedAccount = await impersonateAccount( this.props.userAddress, diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.ts index 30b60137d5..4a3d11d473 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.ts @@ -5,7 +5,7 @@ import { Logger } from 'pino'; import { assert, rootLogger } from '@hyperlane-xyz/utils'; import { MultiProvider } from '../../../MultiProvider.js'; -import { PopulatedTransactions } from '../../types.js'; +import { AnnotatedEV5Transaction } from '../../../ProviderType.js'; import { TxSubmitterType } from '../TxSubmitterTypes.js'; import { EV5TxSubmitterInterface } from './EV5TxSubmitterInterface.js'; @@ -20,7 +20,7 @@ export class EV5JsonRpcTxSubmitter implements EV5TxSubmitterInterface { constructor(public readonly multiProvider: MultiProvider) {} public async submit( - ...txs: PopulatedTransactions + ...txs: AnnotatedEV5Transaction[] ): Promise { const receipts: TransactionReceipt[] = []; for (const tx of txs) { diff --git a/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts index 62aefcf09c..0fcdcd1b4f 100644 --- a/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts +++ b/typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts @@ -9,11 +9,8 @@ import { } from '../../../../middleware/account/InterchainAccount.js'; import { ChainName } from '../../../../types.js'; import { MultiProvider } from '../../../MultiProvider.js'; -import { - CallData, - PopulatedTransaction, - PopulatedTransactions, -} from '../../types.js'; +import { AnnotatedEV5Transaction } from '../../../ProviderType.js'; +import { CallData } from '../../types.js'; import { TxTransformerType } from '../TxTransformerTypes.js'; import { EV5TxTransformerInterface } from './EV5TxTransformerInterface.js'; @@ -39,16 +36,17 @@ export class EV5InterchainAccountTxTransformer } public async transform( - ...txs: PopulatedTransactions + ...txs: AnnotatedEV5Transaction[] ): Promise { const txChainsToInnerCalls: Record = txs.reduce( ( txChainToInnerCalls: Record, - { to, data, chainId }: PopulatedTransaction, + { to, data, chain }: AnnotatedEV5Transaction, ) => { - const txChain = this.multiProvider.getChainName(chainId); - txChainToInnerCalls[txChain] ||= []; - txChainToInnerCalls[txChain].push({ to, data }); + assert(to, 'Invalid transaction: Missing "to" address'); + assert(data, 'Invalid transaction: Missing "data"'); + txChainToInnerCalls[chain] ||= []; + txChainToInnerCalls[chain].push({ to, data }); return txChainToInnerCalls; }, {}, diff --git a/typescript/sdk/src/providers/transactions/types.ts b/typescript/sdk/src/providers/transactions/types.ts index 4ed4e23196..49585f2154 100644 --- a/typescript/sdk/src/providers/transactions/types.ts +++ b/typescript/sdk/src/providers/transactions/types.ts @@ -1,17 +1,5 @@ -import { ethers } from 'ethers'; import { z } from 'zod'; -import { - CallDataSchema, - PopulatedTransactionSchema, - PopulatedTransactionsSchema, -} from './schemas.js'; - -export type PopulatedTransaction = z.infer & - ethers.PopulatedTransaction; -export type PopulatedTransactions = z.infer< - typeof PopulatedTransactionsSchema -> & - ethers.PopulatedTransaction[]; +import { CallDataSchema } from './schemas.js'; export type CallData = z.infer; diff --git a/typescript/sdk/src/token/EvmERC20WarpModule.ts b/typescript/sdk/src/token/EvmERC20WarpModule.ts index d124093c79..0f9f88fbac 100644 --- a/typescript/sdk/src/token/EvmERC20WarpModule.ts +++ b/typescript/sdk/src/token/EvmERC20WarpModule.ts @@ -19,11 +19,12 @@ import { HyperlaneModule, HyperlaneModuleParams, } from '../core/AbstractHyperlaneModule.js'; +import { EvmModuleDeployer } from '../deploy/EvmModuleDeployer.js'; import { EvmIsmModule } from '../ism/EvmIsmModule.js'; import { DerivedIsmConfig } from '../ism/EvmIsmReader.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; -import { ChainNameOrId } from '../types.js'; +import { ChainName, ChainNameOrId } from '../types.js'; import { normalizeConfig } from '../utils/ism.js'; import { EvmERC20WarpRouteReader } from './EvmERC20WarpRouteReader.js'; @@ -41,6 +42,7 @@ export class EvmERC20WarpModule extends HyperlaneModule< module: 'EvmERC20WarpModule', }); reader: EvmERC20WarpRouteReader; + public readonly chainName: ChainName; // We use domainId here because MultiProvider.getDomainId() will always // return a number, and EVM the domainId and chainId are the same. public readonly domainId: Domain; @@ -58,6 +60,7 @@ export class EvmERC20WarpModule extends HyperlaneModule< super(args); this.reader = new EvmERC20WarpRouteReader(multiProvider, args.chain); this.domainId = multiProvider.getDomainId(args.chain); + this.chainName = multiProvider.getChainName(args.chain); this.contractVerifier ??= new ContractVerifier( multiProvider, {}, @@ -135,6 +138,7 @@ export class EvmERC20WarpModule extends HyperlaneModule< ); updateTransactions.push({ + chain: this.chainName, annotation: `Enrolling Router ${this.args.addresses.deployedTokenRoute} on ${this.args.chain}`, chainId: this.domainId, to: contractToUpdate.address, @@ -189,6 +193,7 @@ export class EvmERC20WarpModule extends HyperlaneModule< this.multiProvider.getProvider(this.domainId), ); updateTransactions.push({ + chain: this.chainName, annotation: `Setting ISM for Warp Route to ${expectedDeployedIsm}`, chainId: this.domainId, to: contractToUpdate.address, @@ -214,11 +219,12 @@ export class EvmERC20WarpModule extends HyperlaneModule< actualConfig: TokenRouterConfig, expectedConfig: TokenRouterConfig, ): AnnotatedEV5Transaction[] { - return EvmERC20WarpModule.createTransferOwnershipTx({ + return EvmModuleDeployer.createTransferOwnershipTx({ actualOwner: actualConfig.owner, expectedOwner: expectedConfig.owner, deployedAddress: this.args.addresses.deployedTokenRoute, chainId: this.domainId, + chain: this.chainName, }); } diff --git a/typescript/utils/src/types.ts b/typescript/utils/src/types.ts index 3d79f0058d..1a654d6949 100644 --- a/typescript/utils/src/types.ts +++ b/typescript/utils/src/types.ts @@ -113,5 +113,6 @@ export type ParsedLegacyMultisigIsmMetadata = { }; export type Annotated = T & { + chain: string; // TODO: Change to ChainName after moving to SDK from Utils annotation?: string; };