@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
86 lines • 4.76 kB
JavaScript
import { TimelockController__factory, } from '@hyperlane-xyz/core';
import { assert } from '@hyperlane-xyz/utils';
import { EMPTY_BYTES_32 } from '../../../../timelock/evm/constants.js';
import { TxSubmitterType } from '../TxSubmitterTypes.js';
import { getSubmitter } from '../submitterBuilderGetter.js';
export class EV5TimelockSubmitter {
config;
multiProvider;
proposerSubmitter;
timelockInstance;
txSubmitterType = TxSubmitterType.TIMELOCK_CONTROLLER;
constructor(config, multiProvider, proposerSubmitter, timelockInstance) {
this.config = config;
this.multiProvider = multiProvider;
this.proposerSubmitter = proposerSubmitter;
this.timelockInstance = timelockInstance;
}
static async fromConfig(config, multiProvider, coreAddressesByChain) {
const provider = multiProvider.getProvider(config.chain);
const timelockInstance = TimelockController__factory.connect(config.timelockAddress, provider);
const minDelay = (await timelockInstance.getMinDelay()).toBigInt();
const delay = config.delay ?? minDelay;
assert(delay >= minDelay, `Expected user supplied delay ${delay} to be greater or equal than the configured minDelay ${minDelay}`);
const proposerSubmitter = await getSubmitter(multiProvider, config.proposerSubmitter, coreAddressesByChain);
return new EV5TimelockSubmitter({
chain: config.chain,
delay,
predecessor: config.predecessor ?? EMPTY_BYTES_32,
salt: config.salt ?? EMPTY_BYTES_32,
}, multiProvider, proposerSubmitter, timelockInstance);
}
async submit(...txs) {
if (txs.length === 0) {
return [];
}
const transactionChains = new Set(txs.map((tx) => tx.chainId));
if (transactionChains.size !== 1) {
throw new Error('TimelockController transactions should have all the same destination chain');
}
// If no domain id is set on the transactions
// assume they are to be sent on the current configured chain
const [domainId] = transactionChains.values();
const destinationChainDomainId = this.multiProvider.getDomainId(domainId ?? this.config.chain);
const calldata = txs.map((transaction) => {
assert(transaction.data, 'Invalid Transaction: data must be defined');
assert(transaction.to, 'Invalid Transaction: target address must be defined');
return {
to: transaction.to,
data: transaction.data,
value: transaction.value?.toString(),
};
});
const [to, data, value] = calldata.reduce(([targets, data, values], item) => {
targets.push(item.to);
data.push(item.data);
values.push(item.value ?? '0');
return [targets, data, values];
}, [[], [], []]);
// The Timelock keeps track of past operations so even if it has been
// executed we need to check if there was in the past an operation with the same id
// as this one to avoid having it fail as it would have the same id if no salt is given
const operationId = await this.timelockInstance.hashOperationBatch(to, value, data, this.config.predecessor, this.config.salt);
const checkStatus = await this.timelockInstance.isOperation(operationId);
assert(!checkStatus, `Operation with id "${operationId}" already exists. If this is a new operation with the same input as another one provide a salt to generate a different operation id or cancel the existing one if it is still pending.`);
const [proposeCallData, executeCallData] = await Promise.all([
this.timelockInstance.populateTransaction.scheduleBatch(to, value, data, this.config.predecessor, this.config.salt, this.config.delay),
this.timelockInstance.populateTransaction.executeBatch(to, value, data, this.config.predecessor, this.config.salt),
]);
const proposeFormattedCallData = await this.proposerSubmitter.submit({
chainId: destinationChainDomainId,
...proposeCallData,
});
if (!proposeFormattedCallData) {
// Appending the execute transaction so that it can be written to a file
// by the caller
return [executeCallData];
}
const proposeTransactions = Array.isArray(proposeFormattedCallData)
? proposeFormattedCallData
: [proposeFormattedCallData];
// Appending the execute transaction so that it can be written to a file
// by the caller
return [...proposeTransactions, executeCallData];
}
}
//# sourceMappingURL=EV5TimelockSubmitter.js.map