UNPKG

zksync-ethers

Version:

A Web3 library for interacting with the ZkSync Layer 2 scaling solution.

245 lines 12.1 kB
"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.InteropClient = exports.resolveGateway = exports.GATEWAY_PRESETS = void 0; // src/interop-client.ts const ethers_1 = require("ethers"); const interop_utils_1 = require("./interop-utils"); const utils_1 = require("./utils"); const utils = __importStar(require("./utils")); /** * Gateway presets for mainnet and testnet. * @public */ exports.GATEWAY_PRESETS = { testnet: { chainId: 32657n, rpcUrl: 'https://rpc.era-gateway-testnet.zksync.dev/', }, mainnet: { chainId: 9075n, rpcUrl: 'https://rpc.era-gateway-mainnet.zksync.dev/', }, }; /** * Resolve a Gateway provider + chainId from a flexible config. * * Precedence: * 1) explicit `gwProvider` + `gwChainId` * 2) `env` ('testnet' | 'mainnet') with optional overrides * 3) `env: 'local'` requires `gwRpcUrl` and `gwChainId` (or explicit provider + chainId) * * @public * @param config - Flexible Gateway configuration. Defaults to the **testnet** preset. * @returns `{ gwProvider, gwChainId }` ready to use. * @throws If `env: 'local'` is selected without enough information to construct a provider. * @example * ```ts * const { gwProvider, gwChainId } = resolveGateway(); // testnet by default * const onMainnet = resolveGateway({ env: 'mainnet' }); * const local = resolveGateway({ env: 'local', gwRpcUrl: 'http://localhost:3250', gwChainId: 506n }); * ``` */ function resolveGateway(config = {}) { if (config.gwProvider && config.gwChainId !== null && config.gwChainId !== undefined) { return { gwProvider: config.gwProvider, gwChainId: config.gwChainId }; } const env = config.env ?? 'testnet'; // Presets if (env === 'testnet' || env === 'mainnet') { const preset = exports.GATEWAY_PRESETS[env]; const gwChainId = config.gwChainId ?? preset.chainId; const rpcUrl = config.gwRpcUrl ?? preset.rpcUrl; const gwProvider = config.gwProvider ?? new ethers_1.JsonRpcProvider(rpcUrl); return { gwProvider, gwChainId }; } // Local if (env === 'local') { const gwProvider = config.gwProvider ?? (config.gwRpcUrl ? new ethers_1.JsonRpcProvider(config.gwRpcUrl) : undefined); if (!gwProvider || config.gwChainId === null || config.gwChainId === undefined) { throw new Error('Gateway config for env="local" requires gwRpcUrl and gwChainId (or explicit gwProvider + gwChainId).'); } return { gwProvider, gwChainId: config.gwChainId }; } throw new Error('Invalid gateway configuration'); } exports.resolveGateway = resolveGateway; class InteropClient { constructor(opts = {}) { const { gwProvider, gwChainId } = resolveGateway(opts.gateway); this.gwProvider = gwProvider; this.gwChainId = gwChainId; } /** * Send a message via the L1Messenger on the source chain and return the `Sent` bundle. * * @param srcWallet - Wallet connected to the source L2. * @param message - Bytes or string; strings are UTF-8 encoded. * * @returns txHash — The transaction hash. */ async sendMessage(srcWallet, message) { const messenger = new ethers_1.ethers.Contract(utils.L1_MESSENGER_ADDRESS, utils.L1_MESSENGER, srcWallet); const bytes = typeof message === 'string' ? ethers_1.ethers.toUtf8Bytes(message) : message; const tx = await messenger.sendToL1(bytes); await (await srcWallet.provider.getTransaction(tx.hash)).wait(); return { txHash: tx.hash }; } /** * Verify inclusion of a previously sent message on a target chain. * This is a read-only check against the target's L2MessageVerification contract. * * @param params.txHash - Returned txHash from `sendMessage`. * @param params.srcProvider - Provider for the source chain (to fetch proof nodes + batch details). * @param params.targetChain - Provider for the target chain (to read interop roots + call verifier). This can be any chain that imports the Gateway roots. * @param params.includeProofInputs - If true, include raw proof positioning info in the result (for debugging). * @param params.timeoutMs - Max time to wait for the interop root on the target chain (ms). Default: 120_000. * @returns InteropResult — compact verification outcome (plus optional proof inputs). */ async verifyMessage(params) { const { txHash, srcProvider, targetChain, includeProofInputs, timeoutMs } = params; const { srcChainId, l1BatchNumber, l2MessageIndex, msgData, gatewayProof, gwBlock, l2ToL1LogIndex, l1BatchTxIndex, interopRoot, } = await this.getVerificationArgs({ txHash, srcProvider, targetChain, includeProofInputs: true, timeoutMs, }); const verifier = new ethers_1.ethers.Contract(utils_1.L2_MESSAGE_VERIFICATION_ADDRESS, utils_1.L2_MESSAGE_VERIFICATION_ABI, targetChain); const included = await verifier.proveL2MessageInclusionShared(srcChainId, l1BatchNumber, l2MessageIndex, msgData, gatewayProof); if (!included) throw new Error('Verification failed.'); const result = { source: { chainId: srcChainId, txHash, sender: msgData.sender, messageHash: ethers_1.ethers.keccak256(msgData.data), }, interopRoot: interopRoot, verified: included, }; if (includeProofInputs) { result.proof = { l1BatchNumber, l2MessageIndex, l1BatchTxIndex: l1BatchTxIndex, l2ToL1LogIndex: l2ToL1LogIndex, gwBlockNumber: gwBlock, }; } return result; } /** * Get the input arguments for proveL2MessageInclusionShared to verify a previously sent message on a target chain. * * @param params.txHash - Returned txHash from `sendMessage`. * @param params.srcProvider - Provider for the source chain (to fetch proof nodes + batch details). * @param params.targetChain - Provider for the target chain (to read interop roots + call verifier). This can be any chain that imports the Gateway roots. * @param params.includeProofInputs - If true, include raw proof positioning info in the result (for debugging). * @param params.timeoutMs - Max time to wait for the interop root on the target chain (ms). Default: 120_000. * @returns ProveL2MessageInclusionSharedArgs & { interopRoot: string; gwBlock?: bigint; l2ToL1LogIndex?: number; } - An object with all the required input arguments to verify a previously sent message using the proveL2MessageInclusionShared method on the target's L2MessageVerification contract. */ async getVerificationArgs(params) { const { txHash, srcProvider, targetChain, includeProofInputs, timeoutMs = 120000, } = params; const phase = await this.getMessageStatus(srcProvider, txHash); if (phase !== 'EXECUTED') { switch (phase) { case 'QUEUED': throw new Error('Status: Pending → Transaction is included on L2 but the batch has not yet been committed. Not ready for verification.'); case 'SENDING': throw new Error('Status: Included → Batch has been committed and is being sent to Gateway. Not ready for verification.'); case 'PROVING': throw new Error('Status: Verified → Batch proof is being generated and submitted. Not ready for verification.'); case 'FAILED': throw new Error('Status: Failed → Transaction did not verify successfully.'); case 'REJECTED': throw new Error('Status: Failed → Transaction was rejected by the sequencer.'); default: throw new Error('Status: Unknown → Transaction status could not be determined.'); } } const tx = await srcProvider.getTransaction(txHash); const finalizedRcpt = await tx.wait(); const sender = tx.from; const { l2ToL1LogIndex, messageSentInContract } = (0, interop_utils_1.findInteropLogIndex)(finalizedRcpt, sender); if (l2ToL1LogIndex < 0) { throw new Error('Interop log not found in source receipt for L1Messenger'); } const log = finalizedRcpt.logs.filter(log => (0, utils_1.isAddressEq)(log.address, utils.L1_MESSENGER_ADDRESS) && log.topics[0] === ethers_1.ethers.id('L1MessageSent(address,bytes32,bytes)'))[l2ToL1LogIndex]; const messageHex = ethers_1.ethers.AbiCoder.defaultAbiCoder().decode(['bytes'], log.data)[0]; if (!messageHex) { throw new Error('Missing message value on matched L1Messenger log'); } const l1BatchNumber = finalizedRcpt.l1BatchNumber; const l1BatchTxIndex = finalizedRcpt.l1BatchTxIndex; const { nodes, proofId } = await (0, interop_utils_1.getGatewayProof)(srcProvider, txHash, l2ToL1LogIndex); const gwBlock = await (0, interop_utils_1.getGwBlockForBatch)(BigInt(l1BatchNumber), srcProvider, this.gwProvider); const interopRoot = await (0, interop_utils_1.waitForGatewayInteropRoot)(this.gwChainId, targetChain, gwBlock, { timeoutMs }); const srcChainId = Number((await srcProvider.getNetwork()).chainId); const result = { srcChainId, l1BatchNumber, l2MessageIndex: proofId, msgData: { txNumberInBatch: l1BatchTxIndex, sender: messageSentInContract ? tx.to : sender, data: messageHex, }, gatewayProof: nodes, }; if (includeProofInputs) { return { ...result, gwBlock, l2ToL1LogIndex, l1BatchTxIndex, interopRoot }; } return result; } /** * Check the current lifecycle phase of a sent message on the source chain. * * @param srcProvider - Source chain provider (supports `getTransactionDetails`). * @param txHash - Transaction hash returned by {@link sendMessage}. * @returns Phase classification: * 'QUEUED' | 'SENDING' | 'PROVING' | 'EXECUTED' | 'FAILED' | 'REJECTED' | 'UNKNOWN' */ async getMessageStatus(srcProvider, txHash) { const d = await srcProvider.getTransactionDetails(txHash); if (!d) return { phase: 'UNKNOWN', message: 'No details available' }; return (0, interop_utils_1.classifyPhase)({ status: d.status, ethCommitTxHash: d.ethCommitTxHash ?? null, ethProveTxHash: d.ethProveTxHash ?? null, ethExecuteTxHash: d.ethExecuteTxHash ?? null, }); } } exports.InteropClient = InteropClient; //# sourceMappingURL=interop-client.js.map