@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
82 lines • 4.54 kB
JavaScript
import { assert, retryAsync, rootLogger } from '@hyperlane-xyz/utils';
import { SAFE_API_BASE_RETRY_MS, SAFE_API_RETRIES, canProposeSafeTransactions, getSafe, getSafeService, } from '../../../../utils/gnosisSafe.js';
import { TxSubmitterType } from '../TxSubmitterTypes.js';
export class EV5GnosisSafeTxSubmitter {
multiProvider;
props;
safe;
safeService;
txSubmitterType = TxSubmitterType.GNOSIS_SAFE;
logger = rootLogger.child({
module: 'gnosis-safe-submitter',
});
constructor(multiProvider, props, safe, safeService) {
this.multiProvider = multiProvider;
this.props = props;
this.safe = safe;
this.safeService = safeService;
}
static async initSafeAndService(chain, multiProvider, safeAddress, signerKey) {
const { gnosisSafeTransactionServiceUrl } = multiProvider.getChainMetadata(chain);
assert(gnosisSafeTransactionServiceUrl, `Must set gnosisSafeTransactionServiceUrl in the Registry metadata for ${chain}`);
const safe = await getSafe(chain, multiProvider, safeAddress, signerKey);
const safeService = await getSafeService(chain, multiProvider);
return { safe, safeService };
}
static async create(multiProvider, props) {
const { chain, safeAddress } = props;
const signer = multiProvider.getSigner(chain);
const signerAddress = await signer.getAddress();
const authorized = await canProposeSafeTransactions(signerAddress, chain, multiProvider, safeAddress);
assert(authorized, `Signer ${signerAddress} is not an authorized Safe Proposer for ${safeAddress}`);
const safeSignerKey = 'privateKey' in signer
? signer.privateKey
: undefined;
assert(safeSignerKey, 'Signer must have a private key to propose Safe transactions');
const { safe, safeService } = await EV5GnosisSafeTxSubmitter.initSafeAndService(chain, multiProvider, safeAddress, safeSignerKey);
return new EV5GnosisSafeTxSubmitter(multiProvider, props, safe, safeService);
}
async getNextNonce() {
const nextNonce = await retryAsync(() => this.safeService.getNextNonce(this.props.safeAddress), SAFE_API_RETRIES, SAFE_API_BASE_RETRY_MS);
return parseInt(nextNonce);
}
async createSafeTransaction(...transactions) {
const nextNonce = await this.getNextNonce();
const submitterChainId = this.multiProvider.getChainId(this.props.chain);
const safeTransactionData = transactions.map(({ to, data, value, chainId }) => {
assert(chainId, 'Invalid AnnotatedEV5Transaction: chainId is required');
assert(chainId === submitterChainId, `Invalid AnnotatedEV5Transaction: Cannot submit tx for chain ID ${chainId} to submitter for chain ID ${submitterChainId}.`);
assert(data, `Invalid AnnotatedEV5Transaction: calldata is required for gnosis safe transaction on chain with ID ${submitterChainId}`);
assert(to, `Invalid AnnotatedEV5Transaction: target address is required for gnosis safe transaction on chain with ID ${submitterChainId}`);
return { to, data, value: value?.toString() ?? '0' };
});
const isMultiSend = transactions.length > 1;
const safeTransaction = await this.safe.createTransaction({
transactions: safeTransactionData,
onlyCalls: isMultiSend,
options: {
nonce: nextNonce,
},
});
return safeTransaction;
}
async submit(...txs) {
const safeTransaction = await this.createSafeTransaction(...txs);
return this.proposeSafeTransaction(safeTransaction);
}
async proposeSafeTransaction(safeTransaction) {
const safeTxHash = await this.safe.getTransactionHash(safeTransaction);
const senderAddress = await this.multiProvider.getSignerAddress(this.props.chain);
const safeSignature = await this.safe.signTypedData(safeTransaction);
const senderSignature = safeSignature.data;
this.logger.info(`Submitting transaction proposal to ${this.props.safeAddress} on ${this.props.chain}: ${safeTxHash}`);
return retryAsync(() => this.safeService.proposeTransaction({
safeAddress: this.props.safeAddress,
safeTransactionData: safeTransaction.data,
safeTxHash,
senderAddress,
senderSignature,
}), SAFE_API_RETRIES, SAFE_API_BASE_RETRY_MS);
}
}
//# sourceMappingURL=EV5GnosisSafeTxSubmitter.js.map