@mantleio/sdk
Version:
[Mantle] Tools for working with Mantle
939 lines • 46.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CrossChainMessenger = void 0;
const abstract_provider_1 = require("@ethersproject/abstract-provider");
const ethers_1 = require("ethers");
const core_utils_1 = require("@mantleio/core-utils");
const contracts_1 = require("@mantleio/contracts");
const rlp = __importStar(require("rlp"));
const interfaces_1 = require("./interfaces");
const utils_1 = require("./utils");
class CrossChainMessenger {
constructor(opts) {
var _a;
this.populateTransaction = {
sendMessage: async (message, opts) => {
if (message.direction === interfaces_1.MessageDirection.L1_TO_L2) {
return this.contracts.l1.L1CrossDomainMessenger.populateTransaction.sendMessage(message.target, message.message, (opts === null || opts === void 0 ? void 0 : opts.l2GasLimit) || (await this.estimateL2MessageGasLimit(message)), (opts === null || opts === void 0 ? void 0 : opts.overrides) || {});
}
else {
return this.contracts.l2.L2CrossDomainMessenger.populateTransaction.sendMessage(message.target, message.message, 0, (opts === null || opts === void 0 ? void 0 : opts.overrides) || {});
}
},
resendMessage: async (message, messageGasLimit, opts) => {
const resolved = await this.toCrossChainMessage(message);
if (resolved.direction === interfaces_1.MessageDirection.L2_TO_L1) {
throw new Error(`cannot resend L2 to L1 message`);
}
if (this.bedrock) {
return this.populateTransaction.finalizeMessage(resolved, Object.assign(Object.assign({}, (opts || {})), { overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts.overrides), { gasLimit: messageGasLimit }) }));
}
else {
const legacyL1XDM = new ethers_1.ethers.Contract(this.contracts.l1.L1CrossDomainMessenger.address, (0, contracts_1.getContractInterface)('L1CrossDomainMessenger'), this.l1SignerOrProvider);
return legacyL1XDM.populateTransaction.replayMessage(resolved.target, resolved.sender, resolved.message, resolved.messageNonce, resolved.minGasLimit, messageGasLimit, (opts === null || opts === void 0 ? void 0 : opts.overrides) || {});
}
},
proveMessage: async (message, opts) => {
const resolved = await this.toCrossChainMessage(message);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
throw new Error('cannot finalize L1 to L2 message');
}
if (!this.bedrock) {
throw new Error('message proving only applies after the bedrock upgrade');
}
const withdrawal = await this.toLowLevelMessage(resolved);
const proof = await this.getBedrockMessageProof(resolved);
const args = [
[
withdrawal.messageNonce,
withdrawal.sender,
withdrawal.target,
withdrawal.mntValue,
withdrawal.ethValue,
withdrawal.minGasLimit,
withdrawal.message,
],
proof.l2OutputIndex,
[
proof.outputRootProof.version,
proof.outputRootProof.stateRoot,
proof.outputRootProof.messagePasserStorageRoot,
proof.outputRootProof.latestBlockhash,
],
proof.withdrawalProof,
(opts === null || opts === void 0 ? void 0 : opts.overrides) || {},
];
return this.contracts.l1.OptimismPortal.populateTransaction.proveWithdrawalTransaction(...args);
},
finalizeMessage: async (message, opts) => {
const resolved = await this.toCrossChainMessage(message);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
throw new Error(`cannot finalize L1 to L2 message`);
}
if (this.bedrock) {
const withdrawal = await this.toLowLevelMessage(resolved);
return this.contracts.l1.OptimismPortal.populateTransaction.finalizeWithdrawalTransaction([
withdrawal.messageNonce,
withdrawal.sender,
withdrawal.target,
withdrawal.mntValue,
withdrawal.ethValue,
withdrawal.minGasLimit,
withdrawal.message,
], (opts === null || opts === void 0 ? void 0 : opts.overrides) || {});
}
else {
const proof = await this.getMessageProof(resolved);
const legacyL1XDM = new ethers_1.ethers.Contract(this.contracts.l1.L1CrossDomainMessenger.address, (0, contracts_1.getContractInterface)('L1CrossDomainMessenger'), this.l1SignerOrProvider);
return legacyL1XDM.populateTransaction.relayMessage(resolved.target, resolved.sender, resolved.message, resolved.messageNonce, proof, (opts === null || opts === void 0 ? void 0 : opts.overrides) || {});
}
},
depositETH: async (amount, opts) => {
return this.bridges.ETH.populateTransaction.deposit(ethers_1.ethers.constants.AddressZero, contracts_1.predeploys.BVM_ETH, amount, opts);
},
withdrawETH: async (amount, opts) => {
return this.bridges.ETH.populateTransaction.withdraw(ethers_1.ethers.constants.AddressZero, contracts_1.predeploys.BVM_ETH, amount, opts);
},
depositMNT: async (amount, opts) => {
if (this.bedrock) {
return this.bridges.MNT.populateTransaction.deposit((opts === null || opts === void 0 ? void 0 : opts.l1MNTAddr) !== undefined
? opts.l1MNTAddr
: contracts_1.l1DevPredeploys.L1_MNT, ethers_1.ethers.constants.AddressZero, amount, opts);
}
else {
throw new Error(`call this function must require bedrock param is true`);
}
},
withdrawMNT: async (amount, opts) => {
if (this.bedrock) {
return this.bridges.MNT.populateTransaction.withdraw((opts === null || opts === void 0 ? void 0 : opts.l1MNTAddr) !== undefined
? opts.l1MNTAddr
: contracts_1.l1DevPredeploys.L1_MNT, ethers_1.ethers.constants.AddressZero, amount, opts);
}
else {
throw new Error(`call this function must require bedrock param is true`);
}
},
approveERC20: async (l1Token, l2Token, amount, opts) => {
const bridge = await this.getBridgeForTokenPair(l1Token, l2Token);
return bridge.populateTransaction.approve(l1Token, l2Token, amount, opts);
},
depositERC20: async (l1Token, l2Token, amount, opts) => {
const bridge = await this.getBridgeForTokenPair(l1Token, l2Token);
return bridge.populateTransaction.deposit(l1Token, l2Token, amount, opts);
},
approveERC721: async (l1Token, l2Token, opts) => {
const bridge = (await this.getBridgeForTokenPair(l1Token, l2Token));
return bridge.populateTransaction.approve(l1Token, l2Token, opts);
},
depositERC721: async (l1Token, l2Token, tokenId, opts) => {
const bridge = await this.getBridgeForTokenPair(l1Token, l2Token);
return bridge.populateTransaction.deposit(l1Token, l2Token, tokenId, opts);
},
withdrawERC20: async (l1Token, l2Token, amount, opts) => {
const bridge = await this.getBridgeForTokenPair(l1Token, l2Token);
return bridge.populateTransaction.withdraw(l1Token, l2Token, amount, opts);
},
withdrawERC721: async (l1Token, l2Token, tokenId, opts) => {
const bridge = await this.getBridgeForTokenPair(l1Token, l2Token);
return bridge.populateTransaction.withdraw(l1Token, l2Token, tokenId, opts);
},
};
this.estimateGas = {
sendMessage: async (message, opts) => {
const tx = await this.populateTransaction.sendMessage(message, opts);
if (message.direction === interfaces_1.MessageDirection.L1_TO_L2) {
return this.l1Provider.estimateGas(tx);
}
else {
return this.l2Provider.estimateGas(tx);
}
},
resendMessage: async (message, messageGasLimit, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.resendMessage(message, messageGasLimit, opts));
},
proveMessage: async (message, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.proveMessage(message, opts));
},
finalizeMessage: async (message, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.finalizeMessage(message, opts));
},
depositETH: async (amount, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.depositETH(amount, opts));
},
withdrawETH: async (amount, opts) => {
return this.l2Provider.estimateGas(await this.populateTransaction.withdrawETH(amount, opts));
},
depositMNT: async (amount, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.depositMNT(amount, opts));
},
withdrawMNT: async (amount, opts) => {
return this.l2Provider.estimateGas(await this.populateTransaction.withdrawMNT(amount, opts));
},
approveERC20: async (l1Token, l2Token, amount, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.approveERC20(l1Token, l2Token, amount, opts));
},
depositERC20: async (l1Token, l2Token, amount, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.depositERC20(l1Token, l2Token, amount, opts));
},
withdrawERC20: async (l1Token, l2Token, amount, opts) => {
return this.l2Provider.estimateGas(await this.populateTransaction.withdrawERC20(l1Token, l2Token, amount, opts));
},
};
this.bedrock = (_a = opts.bedrock) !== null && _a !== void 0 ? _a : false;
if (!this.bedrock) {
console.warn('Bedrock compatibility is disabled in CrossChainMessenger. Please enable it if you are using Bedrock.');
}
this.l1SignerOrProvider = (0, utils_1.toSignerOrProvider)(opts.l1SignerOrProvider);
this.l2SignerOrProvider = (0, utils_1.toSignerOrProvider)(opts.l2SignerOrProvider);
try {
this.l1ChainId = (0, utils_1.toNumber)(opts.l1ChainId);
}
catch (err) {
throw new Error(`L1 chain ID is missing or invalid: ${opts.l1ChainId}`);
}
try {
this.l2ChainId = (0, utils_1.toNumber)(opts.l2ChainId);
}
catch (err) {
throw new Error(`L2 chain ID is missing or invalid: ${opts.l2ChainId}`);
}
this.depositConfirmationBlocks =
(opts === null || opts === void 0 ? void 0 : opts.depositConfirmationBlocks) !== undefined
? (0, utils_1.toNumber)(opts.depositConfirmationBlocks)
: utils_1.DEPOSIT_CONFIRMATION_BLOCKS[this.l2ChainId] || 0;
this.l1BlockTimeSeconds =
(opts === null || opts === void 0 ? void 0 : opts.l1BlockTimeSeconds) !== undefined
? (0, utils_1.toNumber)(opts.l1BlockTimeSeconds)
: utils_1.CHAIN_BLOCK_TIMES[this.l1ChainId] || 1;
this.contracts = (0, utils_1.getAllOEContracts)(this.l2ChainId, {
l1SignerOrProvider: this.l1SignerOrProvider,
l2SignerOrProvider: this.l2SignerOrProvider,
overrides: opts.contracts,
});
this.bridges = (0, utils_1.getBridgeAdapters)(this.l2ChainId, this, {
overrides: opts.bridges,
contracts: opts.contracts,
});
}
get l1Provider() {
if (abstract_provider_1.Provider.isProvider(this.l1SignerOrProvider)) {
return this.l1SignerOrProvider;
}
else {
return this.l1SignerOrProvider.provider;
}
}
get l2Provider() {
if (abstract_provider_1.Provider.isProvider(this.l2SignerOrProvider)) {
return this.l2SignerOrProvider;
}
else {
return this.l2SignerOrProvider.provider;
}
}
get l1Signer() {
if (abstract_provider_1.Provider.isProvider(this.l1SignerOrProvider)) {
throw new Error(`messenger has no L1 signer`);
}
else {
return this.l1SignerOrProvider;
}
}
get l2Signer() {
if (abstract_provider_1.Provider.isProvider(this.l2SignerOrProvider)) {
throw new Error(`messenger has no L2 signer`);
}
else {
return this.l2SignerOrProvider;
}
}
async getMessagesByTransaction(transaction, opts = {}) {
var _a, _b;
await ((_b = (_a = transaction).wait) === null || _b === void 0 ? void 0 : _b.call(_a));
const txHash = (0, utils_1.toTransactionHash)(transaction);
let receipt;
if (opts.direction !== undefined) {
if (opts.direction === interfaces_1.MessageDirection.L1_TO_L2) {
receipt = await this.l1Provider.getTransactionReceipt(txHash);
}
else {
receipt = await this.l2Provider.getTransactionReceipt(txHash);
}
}
else {
receipt = await this.l1Provider.getTransactionReceipt(txHash);
if (receipt) {
opts.direction = interfaces_1.MessageDirection.L1_TO_L2;
}
else {
receipt = await this.l2Provider.getTransactionReceipt(txHash);
opts.direction = interfaces_1.MessageDirection.L2_TO_L1;
}
}
if (!receipt) {
throw new Error(`unable to find transaction receipt for ${txHash}`);
}
const messenger = opts.direction === interfaces_1.MessageDirection.L1_TO_L2
? this.contracts.l1.L1CrossDomainMessenger
: this.contracts.l2.L2CrossDomainMessenger;
return receipt.logs
.filter((log) => {
return log.address === messenger.address;
})
.filter((log) => {
const parsed = messenger.interface.parseLog(log);
return parsed.name === 'SentMessage';
})
.map((log) => {
let _mntValue = ethers_1.ethers.BigNumber.from(0);
let _ethValue = ethers_1.ethers.BigNumber.from(0);
const next = receipt.logs.find((l) => {
return (l.logIndex === log.logIndex + 1 && l.address === messenger.address);
});
if (next) {
const nextParsed = messenger.interface.parseLog(next);
if (nextParsed.name === 'SentMessageExtension1') {
_mntValue = nextParsed.args.mntValue;
_ethValue = nextParsed.args.ethValue;
}
}
const parsed = messenger.interface.parseLog(log);
return {
direction: opts.direction,
target: parsed.args.target,
sender: parsed.args.sender,
message: parsed.args.message,
messageNonce: parsed.args.messageNonce,
mntValue: _mntValue,
ethValue: _ethValue,
minGasLimit: parsed.args.gasLimit,
logIndex: log.logIndex,
blockNumber: log.blockNumber,
transactionHash: log.transactionHash,
};
});
}
async toBedrockCrossChainMessage(message) {
const resolved = await this.toCrossChainMessage(message);
const { version } = (0, core_utils_1.decodeVersionedNonce)(resolved.messageNonce);
if (version.eq(1)) {
return resolved;
}
let mntValue = ethers_1.BigNumber.from(0);
let ethValue = ethers_1.BigNumber.from(0);
mntValue = resolved.mntValue;
ethValue = resolved.ethValue;
if (resolved.direction === interfaces_1.MessageDirection.L2_TO_L1 &&
resolved.sender === this.contracts.l2.L2StandardBridge.address &&
resolved.target === this.contracts.l1.L1StandardBridge.address) {
try {
;
[, , ethValue] =
this.contracts.l1.L1StandardBridge.interface.decodeFunctionData('finalizeETHWithdrawal', resolved.message);
}
catch (err) {
}
try {
;
[, , mntValue] =
this.contracts.l1.L1StandardBridge.interface.decodeFunctionData('finalizeMantleWithdrawal', resolved.message);
}
catch (error) {
}
}
return Object.assign(Object.assign({}, resolved), { mntValue,
ethValue, minGasLimit: ethers_1.BigNumber.from(0), messageNonce: (0, core_utils_1.encodeVersionedNonce)(ethers_1.BigNumber.from(0), resolved.messageNonce) });
}
async toLowLevelMessage(message) {
const resolved = await this.toCrossChainMessage(message);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
throw new Error(`can only convert L2 to L1 messages to low level`);
}
const { version } = (0, core_utils_1.decodeVersionedNonce)(resolved.messageNonce);
let updated;
if (version.eq(0)) {
updated = await this.toBedrockCrossChainMessage(resolved);
}
else {
updated = resolved;
}
const encoded = (0, core_utils_1.encodeCrossDomainMessageV1)(updated.messageNonce, updated.sender, updated.target, updated.mntValue, updated.ethValue, updated.minGasLimit, updated.message);
let gasLimit;
let messageNonce;
if (version.eq(0)) {
const chainID = await (0, core_utils_1.getChainId)(this.l2Provider);
gasLimit = (0, utils_1.migratedWithdrawalGasLimit)(encoded, chainID);
messageNonce = resolved.messageNonce;
}
else {
const receipt = await this.l2Provider.getTransactionReceipt(resolved.transactionHash);
const withdrawals = [];
for (const log of receipt.logs) {
if (log.address === this.contracts.l2.BedrockMessagePasser.address) {
const decoded = this.contracts.l2.L2ToL1MessagePasser.interface.parseLog(log);
if (decoded.name === 'MessagePassed') {
withdrawals.push(decoded.args);
}
}
}
if (withdrawals.length === 0) {
throw new Error(`no withdrawals found in receipt`);
}
if (withdrawals.length > 1) {
throw new Error(`multiple withdrawals found in receipt`);
}
const withdrawal = withdrawals[0];
messageNonce = withdrawal.nonce;
gasLimit = withdrawal.gasLimit;
}
return {
messageNonce,
sender: this.contracts.l2.L2CrossDomainMessenger.address,
target: this.contracts.l1.L1CrossDomainMessenger.address,
mntValue: updated.mntValue,
ethValue: updated.ethValue,
minGasLimit: gasLimit,
message: encoded,
};
}
async getBridgeForTokenPair(l1Token, l2Token) {
const bridges = [];
for (const bridge of Object.values(this.bridges)) {
if (await bridge.supportsTokenPair(l1Token, l2Token)) {
bridges.push(bridge);
}
}
if (bridges.length === 0) {
throw new Error(`no supported bridge for token pair`);
}
if (bridges.length > 1) {
throw new Error(`found more than one bridge for token pair`);
}
return bridges[0];
}
async getDepositsByAddress(address, opts = {}) {
return (await Promise.all(Object.values(this.bridges).map(async (bridge) => {
return bridge.getDepositsByAddress(address, opts);
})))
.reduce((acc, val) => {
return acc.concat(val);
}, [])
.sort((a, b) => {
return b.blockNumber - a.blockNumber;
});
}
async getWithdrawalsByAddress(address, opts = {}) {
return (await Promise.all(Object.values(this.bridges).map(async (bridge) => {
return bridge.getWithdrawalsByAddress(address, opts);
})))
.reduce((acc, val) => {
return acc.concat(val);
}, [])
.sort((a, b) => {
return b.blockNumber - a.blockNumber;
});
}
async toCrossChainMessage(message) {
if (!message) {
throw new Error('message is undefined');
}
if (message.message) {
return message;
}
else if (message.l1Token &&
message.l2Token &&
message.transactionHash) {
const messages = await this.getMessagesByTransaction(message.transactionHash);
const found = messages
.sort((a, b) => {
return a.logIndex - b.logIndex;
})
.find((m) => {
return m.logIndex > message.logIndex;
});
if (!found) {
throw new Error(`could not find SentMessage event for message`);
}
return found;
}
else {
const messages = await this.getMessagesByTransaction(message);
if (messages.length !== 1) {
throw new Error(`expected 1 message, got ${messages.length}`);
}
return messages[0];
}
}
async getMessageStatus(message) {
const resolved = await this.toCrossChainMessage(message);
const receipt = await this.getMessageReceipt(resolved);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
if (receipt === null) {
return interfaces_1.MessageStatus.UNCONFIRMED_L1_TO_L2_MESSAGE;
}
else {
if (receipt.receiptStatus === interfaces_1.MessageReceiptStatus.RELAYED_SUCCEEDED) {
return interfaces_1.MessageStatus.RELAYED;
}
else {
return interfaces_1.MessageStatus.FAILED_L1_TO_L2_MESSAGE;
}
}
}
else {
if (receipt === null) {
let timestamp;
if (this.bedrock) {
const output = await this.getMessageBedrockOutput(resolved);
if (output === null) {
return interfaces_1.MessageStatus.STATE_ROOT_NOT_PUBLISHED;
}
const withdrawal = await this.toLowLevelMessage(resolved);
const provenWithdrawal = await this.contracts.l1.OptimismPortal.provenWithdrawals((0, utils_1.hashLowLevelMessage)(withdrawal));
if (provenWithdrawal.timestamp.eq(ethers_1.BigNumber.from(0))) {
return interfaces_1.MessageStatus.READY_TO_PROVE;
}
timestamp = provenWithdrawal.timestamp.toNumber();
}
else {
const stateRoot = await this.getMessageStateRoot(resolved);
if (stateRoot === null) {
return interfaces_1.MessageStatus.STATE_ROOT_NOT_PUBLISHED;
}
const bn = stateRoot.batch.blockNumber;
const block = await this.l1Provider.getBlock(bn);
timestamp = block.timestamp;
}
const challengePeriod = await this.getChallengePeriodSeconds();
const latestBlock = await this.l1Provider.getBlock('latest');
if (timestamp + challengePeriod > latestBlock.timestamp) {
return interfaces_1.MessageStatus.IN_CHALLENGE_PERIOD;
}
else {
return interfaces_1.MessageStatus.READY_FOR_RELAY;
}
}
else {
if (receipt.receiptStatus === interfaces_1.MessageReceiptStatus.RELAYED_SUCCEEDED) {
return interfaces_1.MessageStatus.RELAYED;
}
else {
return interfaces_1.MessageStatus.READY_FOR_RELAY;
}
}
}
}
async getMessageReceipt(message) {
const resolved = await this.toCrossChainMessage(message);
const messageHash = (0, core_utils_1.hashCrossDomainMessage)(resolved.messageNonce, resolved.sender, resolved.target, resolved.mntValue, resolved.ethValue, resolved.minGasLimit, resolved.message);
const messenger = resolved.direction === interfaces_1.MessageDirection.L1_TO_L2
? this.contracts.l2.L2CrossDomainMessenger
: this.contracts.l1.L1CrossDomainMessenger;
const relayedMessageEvents = await messenger.queryFilter(messenger.filters.RelayedMessage(messageHash));
if (relayedMessageEvents.length === 1) {
return {
receiptStatus: interfaces_1.MessageReceiptStatus.RELAYED_SUCCEEDED,
transactionReceipt: await relayedMessageEvents[0].getTransactionReceipt(),
};
}
else if (relayedMessageEvents.length > 1) {
throw new Error(`multiple successful relays for message`);
}
const failedRelayedMessageEvents = await messenger.queryFilter(messenger.filters.FailedRelayedMessage(messageHash));
if (failedRelayedMessageEvents.length > 0) {
return {
receiptStatus: interfaces_1.MessageReceiptStatus.RELAYED_FAILED,
transactionReceipt: await failedRelayedMessageEvents[failedRelayedMessageEvents.length - 1].getTransactionReceipt(),
};
}
return null;
}
async waitForMessageReceipt(message, opts = {}) {
const resolved = await this.toCrossChainMessage(message);
let totalTimeMs = 0;
while (totalTimeMs < (opts.timeoutMs || Infinity)) {
const tick = Date.now();
const receipt = await this.getMessageReceipt(resolved);
if (receipt !== null) {
return receipt;
}
else {
await (0, core_utils_1.sleep)(opts.pollIntervalMs || 4000);
totalTimeMs += Date.now() - tick;
}
}
throw new Error(`timed out waiting for message receipt`);
}
async waitForMessageStatus(message, status, opts = {}) {
const resolved = await this.toCrossChainMessage(message);
let totalTimeMs = 0;
while (totalTimeMs < (opts.timeoutMs || Infinity)) {
const tick = Date.now();
const currentStatus = await this.getMessageStatus(resolved);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
if (currentStatus === status) {
return;
}
if (status === interfaces_1.MessageStatus.UNCONFIRMED_L1_TO_L2_MESSAGE &&
currentStatus > status) {
return;
}
if (status === interfaces_1.MessageStatus.FAILED_L1_TO_L2_MESSAGE &&
currentStatus === interfaces_1.MessageStatus.RELAYED) {
throw new Error(`incompatible message status, expected FAILED_L1_TO_L2_MESSAGE got RELAYED`);
}
if (status === interfaces_1.MessageStatus.RELAYED &&
currentStatus === interfaces_1.MessageStatus.FAILED_L1_TO_L2_MESSAGE) {
throw new Error(`incompatible message status, expected RELAYED got FAILED_L1_TO_L2_MESSAGE`);
}
}
if (resolved.direction === interfaces_1.MessageDirection.L2_TO_L1) {
if (currentStatus >= status) {
return;
}
}
await (0, core_utils_1.sleep)(opts.pollIntervalMs || 4000);
totalTimeMs += Date.now() - tick;
}
throw new Error(`timed out waiting for message status change`);
}
async estimateL2MessageGasLimit(message, opts) {
let resolved;
let from;
if (message.messageNonce === undefined) {
resolved = message;
from = opts === null || opts === void 0 ? void 0 : opts.from;
}
else {
resolved = await this.toCrossChainMessage(message);
from = (opts === null || opts === void 0 ? void 0 : opts.from) || resolved.sender;
}
if (resolved.direction === interfaces_1.MessageDirection.L2_TO_L1) {
throw new Error(`cannot estimate gas limit for L2 => L1 message`);
}
const estimate = await this.l2Provider.estimateGas({
from,
to: resolved.target,
data: resolved.message,
});
const bufferPercent = (opts === null || opts === void 0 ? void 0 : opts.bufferPercent) || 20;
return estimate.mul(100 + bufferPercent).div(100);
}
async estimateMessageWaitTimeSeconds(message) {
const resolved = await this.toCrossChainMessage(message);
const status = await this.getMessageStatus(resolved);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
if (status === interfaces_1.MessageStatus.RELAYED ||
status === interfaces_1.MessageStatus.FAILED_L1_TO_L2_MESSAGE) {
return 0;
}
else {
const receipt = await this.l1Provider.getTransactionReceipt(resolved.transactionHash);
const blocksLeft = Math.max(this.depositConfirmationBlocks - receipt.confirmations, 0);
return blocksLeft * this.l1BlockTimeSeconds;
}
}
else {
if (status === interfaces_1.MessageStatus.RELAYED ||
status === interfaces_1.MessageStatus.READY_FOR_RELAY) {
return 0;
}
else if (status === interfaces_1.MessageStatus.STATE_ROOT_NOT_PUBLISHED) {
return this.getChallengePeriodSeconds();
}
else if (status === interfaces_1.MessageStatus.IN_CHALLENGE_PERIOD) {
const stateRoot = await this.getMessageStateRoot(resolved);
const challengePeriod = await this.getChallengePeriodSeconds();
const targetBlock = await this.l1Provider.getBlock(stateRoot.batch.blockNumber);
const latestBlock = await this.l1Provider.getBlock('latest');
return Math.max(challengePeriod - (latestBlock.timestamp - targetBlock.timestamp), 0);
}
else {
throw new Error(`unexpected message status`);
}
}
}
async getChallengePeriodSeconds() {
if (!this.bedrock) {
return (await this.contracts.l1.StateCommitmentChain.FRAUD_PROOF_WINDOW()).toNumber();
}
const oracleVersion = await this.contracts.l1.L2OutputOracle.version();
const challengePeriod = oracleVersion === '1.0.0'
?
ethers_1.BigNumber.from(await this.contracts.l1.OptimismPortal.provider.call({
to: this.contracts.l1.OptimismPortal.address,
data: '0xf4daa291',
}))
: await this.contracts.l1.L2OutputOracle.FINALIZATION_PERIOD_SECONDS();
return challengePeriod.toNumber();
}
async getProvenWithdrawal(withdrawalHash) {
if (!this.bedrock) {
throw new Error('message proving only applies after the bedrock upgrade');
}
return this.contracts.l1.OptimismPortal.provenWithdrawals(withdrawalHash);
}
async getMessageBedrockOutput(message) {
const resolved = await this.toCrossChainMessage(message);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
throw new Error(`cannot get a state root for an L1 to L2 message`);
}
let l2OutputIndex;
try {
l2OutputIndex =
await this.contracts.l1.L2OutputOracle.getL2OutputIndexAfter(resolved.blockNumber);
}
catch (err) {
if (err.message.includes('L2OutputOracle: cannot get output')) {
return null;
}
else {
throw err;
}
}
const proposal = await this.contracts.l1.L2OutputOracle.getL2Output(l2OutputIndex);
return {
outputRoot: proposal.outputRoot,
l1Timestamp: proposal.timestamp.toNumber(),
l2BlockNumber: proposal.l2BlockNumber.toNumber(),
l2OutputIndex: l2OutputIndex.toNumber(),
};
}
async getMessageStateRoot(message) {
const resolved = await this.toCrossChainMessage(message);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
throw new Error(`cannot get a state root for an L1 to L2 message`);
}
const messageTxReceipt = await this.l2Provider.getTransactionReceipt(resolved.transactionHash);
const messageTxIndex = messageTxReceipt.blockNumber - 1;
const stateRootBatch = await this.getStateRootBatchByTransactionIndex(messageTxIndex);
if (stateRootBatch === null) {
return null;
}
const indexInBatch = messageTxIndex - stateRootBatch.header.prevTotalElements.toNumber();
if (stateRootBatch.stateRoots.length <= indexInBatch) {
throw new Error(`state root does not exist in batch`);
}
return {
stateRoot: stateRootBatch.stateRoots[indexInBatch],
stateRootIndexInBatch: indexInBatch,
batch: stateRootBatch,
};
}
async getStateBatchAppendedEventByBatchIndex(batchIndex) {
const events = await this.contracts.l1.StateCommitmentChain.queryFilter(this.contracts.l1.StateCommitmentChain.filters.StateBatchAppended(batchIndex));
if (events.length === 0) {
return null;
}
else if (events.length > 1) {
throw new Error(`found more than one StateBatchAppended event`);
}
else {
return events[0];
}
}
async getStateBatchAppendedEventByTransactionIndex(transactionIndex) {
const isEventHi = (event, index) => {
const prevTotalElements = event.args._prevTotalElements.toNumber();
return index < prevTotalElements;
};
const isEventLo = (event, index) => {
const prevTotalElements = event.args._prevTotalElements.toNumber();
const batchSize = event.args._batchSize.toNumber();
return index >= prevTotalElements + batchSize;
};
const totalBatches = await this.contracts.l1.StateCommitmentChain.getTotalBatches();
if (totalBatches.eq(0)) {
return null;
}
let lowerBound = 0;
let upperBound = totalBatches.toNumber() - 1;
let batchEvent = await this.getStateBatchAppendedEventByBatchIndex(upperBound);
if (batchEvent === null) {
return null;
}
if (isEventLo(batchEvent, transactionIndex)) {
return null;
}
else if (!isEventHi(batchEvent, transactionIndex)) {
return batchEvent;
}
while (lowerBound < upperBound) {
const middleOfBounds = Math.floor((lowerBound + upperBound) / 2);
batchEvent = await this.getStateBatchAppendedEventByBatchIndex(middleOfBounds);
if (isEventHi(batchEvent, transactionIndex)) {
upperBound = middleOfBounds;
}
else if (isEventLo(batchEvent, transactionIndex)) {
lowerBound = middleOfBounds;
}
else {
break;
}
}
return batchEvent;
}
async getStateRootBatchByTransactionIndex(transactionIndex) {
const stateBatchAppendedEvent = await this.getStateBatchAppendedEventByTransactionIndex(transactionIndex);
if (stateBatchAppendedEvent === null) {
return null;
}
const stateBatchTransaction = await stateBatchAppendedEvent.getTransaction();
let stateRoots;
try {
stateRoots =
this.contracts.l1.StateCommitmentChain.interface.decodeFunctionData('appendStateBatch', stateBatchTransaction.data)[0];
}
catch (e) {
stateRoots = this.contracts.l1.Rollup.interface.decodeFunctionData('createAssertionWithStateBatch', stateBatchTransaction.data)[2];
}
let signature;
if (this.bedrock) {
signature = '';
}
else {
signature = stateBatchAppendedEvent.args._signature;
}
return {
blockNumber: stateBatchAppendedEvent.blockNumber,
stateRoots,
header: {
batchIndex: stateBatchAppendedEvent.args._batchIndex,
batchRoot: stateBatchAppendedEvent.args._batchRoot,
batchSize: stateBatchAppendedEvent.args._batchSize,
prevTotalElements: stateBatchAppendedEvent.args._prevTotalElements,
extraData: stateBatchAppendedEvent.args._extraData,
signature,
},
};
}
async getMessageProof(message) {
const resolved = await this.toCrossChainMessage(message);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
throw new Error(`can only generate proofs for L2 to L1 messages`);
}
const stateRoot = await this.getMessageStateRoot(resolved);
if (stateRoot === null) {
throw new Error(`state root for message not yet published`);
}
const messageSlot = ethers_1.ethers.utils.keccak256(ethers_1.ethers.utils.keccak256((0, core_utils_1.encodeCrossDomainMessageV0)(resolved.target, resolved.sender, resolved.message, resolved.messageNonce) + (0, core_utils_1.remove0x)(this.contracts.l2.L2CrossDomainMessenger.address)) + '00'.repeat(32));
const stateTrieProof = await (0, utils_1.makeStateTrieProof)(this.l2Provider, resolved.blockNumber, this.contracts.l2.BVM_L2ToL1MessagePasser.address, messageSlot);
return {
stateRoot: stateRoot.stateRoot,
stateRootBatchHeader: stateRoot.batch.header,
stateRootProof: {
index: stateRoot.stateRootIndexInBatch,
siblings: (0, utils_1.makeMerkleTreeProof)(stateRoot.batch.stateRoots, stateRoot.stateRootIndexInBatch),
},
stateTrieWitness: (0, core_utils_1.toHexString)(rlp.encode(stateTrieProof.accountProof)),
storageTrieWitness: (0, core_utils_1.toHexString)(rlp.encode(stateTrieProof.storageProof)),
};
}
async getBedrockMessageProof(message) {
const resolved = await this.toCrossChainMessage(message);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
throw new Error(`can only generate proofs for L2 to L1 messages`);
}
const output = await this.getMessageBedrockOutput(resolved);
if (output === null) {
throw new Error(`state root for message not yet published`);
}
const withdrawal = await this.toLowLevelMessage(resolved);
const hash = (0, utils_1.hashLowLevelMessage)(withdrawal);
const messageSlot = (0, utils_1.hashMessageHash)(hash);
const stateTrieProof = await (0, utils_1.makeStateTrieProof)(this.l2Provider, output.l2BlockNumber, this.contracts.l2.BedrockMessagePasser.address, messageSlot);
const block = await this.l2Provider.send('eth_getBlockByNumber', [
(0, core_utils_1.toRpcHexString)(output.l2BlockNumber),
false,
]);
return {
outputRootProof: {
version: ethers_1.ethers.constants.HashZero,
stateRoot: block.stateRoot,
messagePasserStorageRoot: stateTrieProof.storageRoot,
latestBlockhash: block.hash,
},
withdrawalProof: stateTrieProof.storageProof,
l2OutputIndex: output.l2OutputIndex,
};
}
async sendMessage(message, opts) {
const tx = await this.populateTransaction.sendMessage(message, opts);
if (message.direction === interfaces_1.MessageDirection.L1_TO_L2) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l1Signer).sendTransaction(tx);
}
else {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l2Signer).sendTransaction(tx);
}
}
async resendMessage(message, messageGasLimit, opts) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l1Signer).sendTransaction(await this.populateTransaction.resendMessage(message, messageGasLimit, opts));
}
async proveMessage(message, opts) {
const tx = await this.populateTransaction.proveMessage(message, opts);
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l1Signer).sendTransaction(tx);
}
async finalizeMessage(message, opts) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l1Signer).sendTransaction(await this.populateTransaction.finalizeMessage(message, opts));
}
async depositETH(amount, opts) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l1Signer).sendTransaction(await this.populateTransaction.depositETH(amount, opts));
}
async withdrawETH(amount, opts) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l2Signer).sendTransaction(await this.populateTransaction.withdrawETH(amount, opts));
}
async depositMNT(amount, opts) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l1Signer).sendTransaction(await this.populateTransaction.depositMNT(amount, opts));
}
async withdrawMNT(amount, opts) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l2Signer).sendTransaction(await this.populateTransaction.withdrawMNT(amount, opts));
}
async approval(l1Token, l2Token, opts) {
const bridge = await this.getBridgeForTokenPair(l1Token, l2Token);
return bridge.approval(l1Token, l2Token, (opts === null || opts === void 0 ? void 0 : opts.signer) || this.l1Signer);
}
async approveERC20(l1Token, l2Token, amount, opts) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l1Signer).sendTransaction(await this.populateTransaction.approveERC20(l1Token, l2Token, amount, opts));
}
async depositERC20(l1Token, l2Token, amount, opts) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l1Signer).sendTransaction(await this.populateTransaction.depositERC20(l1Token, l2Token, amount, opts));
}
async depositERC721(l1Token, l2Token, tokenId, opts) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l1Signer).sendTransaction(await this.populateTransaction.depositERC721(l1Token, l2Token, tokenId, opts));
}
async withdrawERC721(l1Token, l2Token, tokenId, opts) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l2Signer).sendTransaction(await this.populateTransaction.withdrawERC721(l1Token, l2Token, tokenId, opts));
}
async approveERC721(l1Token, l2Token, opts) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l1Signer).sendTransaction(await this.populateTransaction.approveERC721(l1Token, l2Token, opts));
}
async withdrawERC20(l1Token, l2Token, amount, opts) {
return ((opts === null || opts === void 0 ? void 0 : opts.signer) || this.l2Signer).sendTransaction(await this.populateTransaction.withdrawERC20(l1Token, l2Token, amount, opts));
}
}
exports.CrossChainMessenger = CrossChainMessenger;
//# sourceMappingURL=cross-chain-messenger.js.map