UNPKG

zksync-ethers

Version:

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

1,070 lines 115 kB
"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _Provider_connect, _BrowserProvider_request; Object.defineProperty(exports, "__esModule", { value: true }); exports.BrowserProvider = exports.Provider = exports.JsonRpcApiProvider = void 0; const ethers_1 = require("ethers"); const typechain_1 = require("./typechain"); const types_1 = require("./types"); const utils_1 = require("./utils"); const signer_1 = require("./signer"); const format_1 = require("./format"); const ethers_2 = require("ethers"); function JsonRpcApiProvider(ProviderType) { return class Provider extends ProviderType { /** * Sends a JSON-RPC `_payload` (or a batch) to the underlying channel. * * @param _payload The JSON-RPC payload or batch of payloads to send. * @returns A promise that resolves to the result of the JSON-RPC request(s). */ _send( // eslint-disable-next-line @typescript-eslint/no-unused-vars _payload) { throw new Error('Must be implemented by the derived class!'); } /** * Returns the addresses of the main contract and default ZKsync Era bridge contracts on both L1 and L2. */ contractAddresses() { throw new Error('Must be implemented by the derived class!'); } _getBlockTag(blockTag) { if (blockTag === 'committed') { return 'committed'; } else if (blockTag === 'l1_committed') { return 'l1_committed'; } return super._getBlockTag(blockTag); } _wrapLog(value) { return new types_1.Log((0, format_1.formatLog)(value), this); } _wrapBlock(value) { return new types_1.Block((0, format_1.formatBlock)(value), this); } _wrapTransactionResponse(value) { const tx = (0, format_1.formatTransactionResponse)(value); return new types_1.TransactionResponse(tx, this); } _wrapTransactionReceipt(value) { const receipt = (0, format_1.formatTransactionReceipt)(value); return new types_1.TransactionReceipt(receipt, this); } /** * Resolves to the transaction receipt for `txHash`, if mined. * If the transaction has not been mined, is unknown or on pruning nodes which discard old transactions * this resolves to `null`. * * @param txHash The hash of the transaction. */ async getTransactionReceipt(txHash) { return (await super.getTransactionReceipt(txHash)); } /** * Resolves to the transaction for `txHash`. * If the transaction is unknown or on pruning nodes which discard old transactions this resolves to `null`. * * @param txHash The hash of the transaction. */ async getTransaction(txHash) { return (await super.getTransaction(txHash)); } /** * Resolves to the block corresponding to the provided `blockHashOrBlockTag`. * If `includeTxs` is set to `true` and the backend supports including transactions with block requests, * all transactions will be included in the returned block object, eliminating the need for remote calls * to fetch transactions separately. * * @param blockHashOrBlockTag The hash or tag of the block to retrieve. * @param [includeTxs] A flag indicating whether to include transactions in the block. */ async getBlock(blockHashOrBlockTag, includeTxs) { return (await super.getBlock(blockHashOrBlockTag, includeTxs)); } /** * Resolves to the list of Logs that match `filter`. * * @param filter The filter criteria to apply. */ async getLogs(filter) { return (await super.getLogs(filter)); } /** * Returns the account balance for the specified account `address`, `blockTag`, and `tokenAddress`. * If `blockTag` and `tokenAddress` are not provided, the balance for the latest committed block and ETH token * is returned by default. * * @param address The account address for which the balance is retrieved. * @param [blockTag] The block tag for getting the balance on. Latest committed block is the default. * @param [tokenAddress] The token address. ETH is the default token. */ async getBalance(address, blockTag, tokenAddress) { if (!tokenAddress) { tokenAddress = utils_1.L2_BASE_TOKEN_ADDRESS; } else if ((0, utils_1.isAddressEq)(tokenAddress, utils_1.LEGACY_ETH_ADDRESS) || (0, utils_1.isAddressEq)(tokenAddress, utils_1.ETH_ADDRESS_IN_CONTRACTS)) { tokenAddress = await this.l2TokenAddress(tokenAddress); } if ((0, utils_1.isAddressEq)(tokenAddress, utils_1.L2_BASE_TOKEN_ADDRESS)) { return await super.getBalance(address, blockTag); } else { try { const token = typechain_1.IERC20__factory.connect(tokenAddress, this); return await token.balanceOf(address, { blockTag }); } catch { return 0n; } } } /** * Returns the L2 token address equivalent for a L1 token address as they are not equal. * ETH address is set to zero address. * * @remarks Only works for tokens bridged on default ZKsync Era bridges. * * @param token The address of the token on L1. * @param bridgeAddress The address of custom bridge, which will be used to get l2 token address. */ async l2TokenAddress(token, bridgeAddress) { if ((0, utils_1.isAddressEq)(token, utils_1.LEGACY_ETH_ADDRESS)) { token = utils_1.ETH_ADDRESS_IN_CONTRACTS; } const baseToken = await this.getBaseTokenContractAddress(); if ((0, utils_1.isAddressEq)(token, baseToken)) { return utils_1.L2_BASE_TOKEN_ADDRESS; } bridgeAddress ?? (bridgeAddress = (await this.getDefaultBridgeAddresses()).sharedL2); return await (await this.connectL2Bridge(bridgeAddress)).l2TokenAddress(token); } /** * Returns the L1 token address equivalent for a L2 token address as they are not equal. * ETH address is set to zero address. * * @remarks Only works for tokens bridged on default ZKsync Era bridges. * * @param token The address of the token on L2. */ async l1TokenAddress(token) { if ((0, utils_1.isAddressEq)(token, utils_1.LEGACY_ETH_ADDRESS)) { return utils_1.LEGACY_ETH_ADDRESS; } const bridgeAddresses = await this.getDefaultBridgeAddresses(); const sharedBridge = typechain_1.IL2Bridge__factory.connect(bridgeAddresses.sharedL2, this); return await sharedBridge.l1TokenAddress(token); } /** * Return the protocol version. * * Calls the {@link https://docs.zksync.io/build/api.html#zks_getprotocolversion zks_getProtocolVersion} JSON-RPC method. * * @param [id] Specific version ID. */ async getProtocolVersion(id) { return await this.send('zks_getProtocolVersion', [id]); } /** * Returns an estimate of the amount of gas required to submit a transaction from L1 to L2 as a bigint object. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-estimategasl1tol2 zks_estimateL1ToL2} JSON-RPC method. * * @param transaction The transaction request. */ async estimateGasL1(transaction) { return await this.send('zks_estimateGasL1ToL2', [ this.getRpcTransaction(transaction), ]); } /** * Returns an estimated {@link Fee} for requested transaction. * * @param transaction The transaction request. */ async estimateFee(transaction) { const fee = await this.send('zks_estimateFee', [ await this.getRpcTransaction(transaction), ]); return (0, format_1.formatFee)(fee); } /** * Returns the current fee parameters. * * Calls the {@link https://docs.zksync.io/build/api.html#zks_getFeeParams zks_getFeeParams} JSON-RPC method. */ async getFeeParams() { return await this.send('zks_getFeeParams', []); } /** * Returns an estimate (best guess) of the gas price to use in a transaction. */ async getGasPrice() { const feeData = await this.getFeeData(); return feeData.gasPrice; } /** * Returns the proof for a transaction's L2 to L1 log sent via the `L1Messenger` system contract. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-getl2tol1logproof zks_getL2ToL1LogProof} JSON-RPC method. * * @param txHash The hash of the L2 transaction the L2 to L1 log was produced within. * @param [index] The index of the L2 to L1 log in the transaction. */ async getLogProof(txHash, index) { return await this.send('zks_getL2ToL1LogProof', [ ethers_1.ethers.hexlify(txHash), index, ]); } /** * Returns the range of blocks contained within a batch given by batch number. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-getl1batchblockrange zks_getL1BatchBlockRange} JSON-RPC method. * * @param l1BatchNumber The L1 batch number. */ async getL1BatchBlockRange(l1BatchNumber) { const range = await this.send('zks_getL1BatchBlockRange', [ l1BatchNumber, ]); if (!range) { return null; } return [parseInt(range[0], 16), parseInt(range[1], 16)]; } /** * Returns the Bridgehub smart contract address. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-getbridgehubcontract zks_getBridgehubContract} JSON-RPC method. */ async getBridgehubContractAddress() { if (!this.contractAddresses().bridgehubContract) { this.contractAddresses().bridgehubContract = await this.send('zks_getBridgehubContract', []); } return this.contractAddresses().bridgehubContract; } /** * Returns the main ZKsync Era smart contract address. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-getmaincontract zks_getMainContract} JSON-RPC method. */ async getMainContractAddress() { if (!this.contractAddresses().mainContract) { this.contractAddresses().mainContract = await this.send('zks_getMainContract', []); } return this.contractAddresses().mainContract; } /** * Returns the L1 base token address. */ async getBaseTokenContractAddress() { if (!this.contractAddresses().baseToken) { this.contractAddresses().baseToken = await this.send('zks_getBaseTokenL1Address', []); } return ethers_1.ethers.getAddress(this.contractAddresses().baseToken); } /** * Returns whether the chain is ETH-based. */ async isEthBasedChain() { return (0, utils_1.isAddressEq)(await this.getBaseTokenContractAddress(), utils_1.ETH_ADDRESS_IN_CONTRACTS); } /** * Returns whether the `token` is the base token. */ async isBaseToken(token) { return ((0, utils_1.isAddressEq)(token, await this.getBaseTokenContractAddress()) || (0, utils_1.isAddressEq)(token, utils_1.L2_BASE_TOKEN_ADDRESS)); } /** * Returns the testnet {@link https://docs.zksync.io/build/developer-reference/account-abstraction.html#paymasters paymaster address} * if available, or `null`. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-gettestnetpaymaster zks_getTestnetPaymaster} JSON-RPC method. */ async getTestnetPaymasterAddress() { // Unlike contract's addresses, the testnet paymaster is not cached, since it can be trivially changed // on the fly by the server and should not be relied on to be constant return await this.send('zks_getTestnetPaymaster', []); } /** * Returns the addresses of the default ZKsync Era bridge contracts on both L1 and L2. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-getbridgecontracts zks_getBridgeContracts} JSON-RPC method. */ async getDefaultBridgeAddresses() { if (!this.contractAddresses().erc20BridgeL1) { const addresses = await this.send('zks_getBridgeContracts', []); this.contractAddresses().erc20BridgeL1 = addresses.l1Erc20DefaultBridge; this.contractAddresses().erc20BridgeL2 = addresses.l2Erc20DefaultBridge; this.contractAddresses().wethBridgeL1 = addresses.l1WethBridge; this.contractAddresses().wethBridgeL2 = addresses.l2WethBridge; this.contractAddresses().sharedBridgeL1 = addresses.l1SharedDefaultBridge; this.contractAddresses().sharedBridgeL2 = addresses.l2SharedDefaultBridge; } return { erc20L1: this.contractAddresses().erc20BridgeL1, erc20L2: this.contractAddresses().erc20BridgeL2, wethL1: this.contractAddresses().wethBridgeL1, wethL2: this.contractAddresses().wethBridgeL2, sharedL1: this.contractAddresses().sharedBridgeL1, sharedL2: this.contractAddresses().sharedBridgeL2, }; } _setL1NullifierAndNativeTokenVault(l1Nullifier, l1NativeTokenVault) { this.contractAddresses().l1Nullifier = l1Nullifier; this.contractAddresses().l1NativeTokenVault = l1NativeTokenVault; } /** * Returns contract wrapper. If given address is shared bridge address it returns Il2SharedBridge and if its legacy it returns Il2Bridge. ** * @param address The bridge address. * * @example * * import { Provider, types, utils } from "zksync-ethers"; * * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * const l2Bridge = await provider.connectL2Bridge("<L2_BRIDGE_ADDRESS>"); */ async connectL2Bridge(address) { if (await this.isL2BridgeLegacy(address)) { return typechain_1.IL2Bridge__factory.connect(address, this); } return typechain_1.IL2SharedBridge__factory.connect(address, this); } async connectL2NativeTokenVault() { return typechain_1.IL2NativeTokenVault__factory.connect(utils_1.L2_NATIVE_TOKEN_VAULT_ADDRESS, this); } async connectL2AssetRouter() { return typechain_1.IL2AssetRouter__factory.connect(utils_1.L2_ASSET_ROUTER_ADDRESS, this); } /** * Returns true if passed bridge address is legacy and false if its shared bridge. ** * @param address The bridge address. * * @example * * import { Provider, types, utils } from "zksync-ethers"; * * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * const isBridgeLegacy = await provider.isL2BridgeLegacy("<L2_BRIDGE_ADDRESS>"); * console.log(isBridgeLegacy); */ async isL2BridgeLegacy(address) { const bridge = typechain_1.IL2SharedBridge__factory.connect(address, this); try { await bridge.l1SharedBridge(); return false; } catch (e) { // skip } return true; } /** * Returns all balances for confirmed tokens given by an account address. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-getallaccountbalances zks_getAllAccountBalances} JSON-RPC method. * * @param address The account address. */ async getAllAccountBalances(address) { const balances = await this.send('zks_getAllAccountBalances', [address]); for (const token in balances) { balances[token] = BigInt(balances[token]); } return balances; } /** * Returns confirmed tokens. Confirmed token is any token bridged to ZKsync Era via the official bridge. * * Calls the {@link https://docs.zksync.io/build/api.html#zks_getconfirmedtokens zks_getConfirmedTokens} JSON-RPC method. * * @param start The token id from which to start. * @param limit The maximum number of tokens to list. */ async getConfirmedTokens(start = 0, limit = 255) { const tokens = await this.send('zks_getConfirmedTokens', [ start, limit, ]); return tokens.map(token => ({ address: token.l2Address, ...token })); } /** * @deprecated In favor of {@link getL1ChainId} * * Returns the L1 chain ID. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-l1chainid zks_L1ChainId} JSON-RPC method. */ async l1ChainId() { const res = await this.send('zks_L1ChainId', []); return Number(res); } /** * Returns the L1 chain ID. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-l1chainid zks_L1ChainId} JSON-RPC method. */ async getL1ChainId() { const res = await this.send('zks_L1ChainId', []); return Number(res); } /** * Returns the latest L1 batch number. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-l1batchnumber zks_L1BatchNumber} JSON-RPC method. */ async getL1BatchNumber() { const number = await this.send('zks_L1BatchNumber', []); return Number(number); } /** * Returns data pertaining to a given batch. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-getl1batchdetails zks_getL1BatchDetails} JSON-RPC method. * * @param number The L1 batch number. */ async getL1BatchDetails(number) { return await this.send('zks_getL1BatchDetails', [number]); } /** * Returns additional zkSync-specific information about the L2 block. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-getblockdetails zks_getBlockDetails} JSON-RPC method. * * @param number The block number. */ async getBlockDetails(number) { return await this.send('zks_getBlockDetails', [number]); } /** * Returns data from a specific transaction given by the transaction hash. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-gettransactiondetails zks_getTransactionDetails} JSON-RPC method. * * @param txHash The transaction hash. */ async getTransactionDetails(txHash) { return await this.send('zks_getTransactionDetails', [txHash]); } /** * Returns bytecode of a contract given by its hash. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-getbytecodebyhash zks_getBytecodeByHash} JSON-RPC method. * * @param bytecodeHash The bytecode hash. */ async getBytecodeByHash(bytecodeHash) { return await this.send('zks_getBytecodeByHash', [bytecodeHash]); } /** * Returns data of transactions in a block. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-getrawblocktransactions zks_getRawBlockTransactions} JSON-RPC method. * * @param number The block number. */ async getRawBlockTransactions(number) { return await this.send('zks_getRawBlockTransactions', [number]); } /** * Returns Merkle proofs for one or more storage values at the specified account along with a Merkle proof * of their authenticity. * * Calls the {@link https://docs.zksync.io/build/api.html#zks-getproof zks_getProof} JSON-RPC method. * * @param address The account to fetch storage values and proofs for. * @param keys The vector of storage keys in the account. * @param l1BatchNumber The number of the L1 batch specifying the point in time at which the requested values are returned. */ async getProof(address, keys, l1BatchNumber) { return await this.send('zks_getProof', [address, keys, l1BatchNumber]); } /** * Executes a transaction and returns its hash, storage logs, and events that would have been generated if the * transaction had already been included in the block. The API has a similar behaviour to `eth_sendRawTransaction` * but with some extra data returned from it. * * With this API Consumer apps can apply "optimistic" events in their applications instantly without having to * wait for ZKsync block confirmation time. * * It’s expected that the optimistic logs of two uncommitted transactions that modify the same state will not * have causal relationships between each other. * * Calls the {@link https://docs.zksync.io/build/api.html#zks_sendRawTransactionWithDetailedOutput zks_sendRawTransactionWithDetailedOutput} JSON-RPC method. * * @param signedTx The signed transaction that needs to be broadcasted. */ async sendRawTransactionWithDetailedOutput(signedTx) { return await this.send('zks_sendRawTransactionWithDetailedOutput', [ signedTx, ]); } /** * Returns the populated withdrawal transaction. * * @param transaction The transaction details. * @param transaction.amount The amount of token. * @param transaction.token The token address. * @param [transaction.from] The sender's address. * @param [transaction.to] The recipient's address. * @param [transaction.bridgeAddress] The bridge address. * @param [transaction.paymasterParams] Paymaster parameters. * @param [transaction.overrides] Transaction overrides including `gasLimit`, `gasPrice`, and `value`. */ async getWithdrawTx(transaction) { var _a, _b; const { ...tx } = transaction; tx.token ?? (tx.token = utils_1.L2_BASE_TOKEN_ADDRESS); if ((0, utils_1.isAddressEq)(tx.token, utils_1.LEGACY_ETH_ADDRESS) || (0, utils_1.isAddressEq)(tx.token, utils_1.ETH_ADDRESS_IN_CONTRACTS)) { tx.token = await this.l2TokenAddress(utils_1.ETH_ADDRESS_IN_CONTRACTS); } if ((tx.to === null || tx.to === undefined) && (tx.from === null || tx.from === undefined)) { throw new Error('Withdrawal target address is undefined!'); } tx.to ?? (tx.to = tx.from); tx.overrides ?? (tx.overrides = {}); (_a = tx.overrides).from ?? (_a.from = tx.from); (_b = tx.overrides).type ?? (_b.type = utils_1.EIP712_TX_TYPE); if ((0, utils_1.isAddressEq)(tx.token, utils_1.L2_BASE_TOKEN_ADDRESS)) { if (!tx.overrides.value) { tx.overrides.value = tx.amount; } const passedValue = BigInt(tx.overrides.value); if (passedValue !== BigInt(tx.amount)) { // To avoid users shooting themselves into the foot, we will always use the amount to withdraw // as the value throw new Error('The tx.value is not equal to the value withdrawn!'); } const ethL2Token = typechain_1.IEthToken__factory.connect(utils_1.L2_BASE_TOKEN_ADDRESS, this); const populatedTx = await ethL2Token.withdraw.populateTransaction(tx.to, tx.overrides); if (tx.paymasterParams) { return { ...populatedTx, customData: { paymasterParams: tx.paymasterParams, }, }; } return populatedTx; } let populatedTx; // we get the tokens data, assetId and originChainId const ntv = await this.connectL2NativeTokenVault(); const assetId = await ntv.assetId(tx.token); const originChainId = await ntv.originChainId(assetId); const l1ChainId = await this.getL1ChainId(); const isTokenL1Native = originChainId === BigInt(l1ChainId) || tx.token === utils_1.ETH_ADDRESS_IN_CONTRACTS; if (!tx.bridgeAddress) { const bridgeAddresses = await this.getDefaultBridgeAddresses(); // If the legacy L2SharedBridge is deployed we use it for l1 native tokens. tx.bridgeAddress = isTokenL1Native ? bridgeAddresses.sharedL2 : utils_1.L2_ASSET_ROUTER_ADDRESS; } // For non L1 native tokens we need to use the AssetRouter. // For L1 native tokens we can use the legacy withdraw method. if (!isTokenL1Native) { const bridge = await this.connectL2AssetRouter(); const chainId = Number((await this.getNetwork()).chainId); const assetId = (0, utils_1.encodeNativeTokenVaultAssetId)(BigInt(chainId), tx.token); const assetData = (0, utils_1.encodeNativeTokenVaultTransferData)(BigInt(tx.amount), tx.to, tx.token); populatedTx = await bridge.withdraw.populateTransaction(assetId, assetData, tx.overrides); } else { const bridge = await this.connectL2Bridge(tx.bridgeAddress); populatedTx = await bridge.withdraw.populateTransaction(tx.to, tx.token, tx.amount, tx.overrides); } if (tx.paymasterParams) { return { ...populatedTx, customData: { paymasterParams: tx.paymasterParams, }, }; } return populatedTx; } /** * Returns the gas estimation for a withdrawal transaction. * * @param transaction The transaction details. * @param transaction.token The token address. * @param transaction.amount The amount of token. * @param [transaction.from] The sender's address. * @param [transaction.to] The recipient's address. * @param [transaction.bridgeAddress] The bridge address. * @param [transaction.paymasterParams] Paymaster parameters. * @param [transaction.overrides] Transaction overrides including `gasLimit`, `gasPrice`, and `value`. */ async estimateGasWithdraw(transaction) { const withdrawTx = await this.getWithdrawTx(transaction); return await this.estimateGas(withdrawTx); } /** * Returns the populated transfer transaction. * * @param transaction Transfer transaction request. * @param transaction.to The address of the recipient. * @param transaction.amount The amount of the token to transfer. * @param [transaction.token] The address of the token. Defaults to ETH. * @param [transaction.paymasterParams] Paymaster parameters. * @param [transaction.overrides] Transaction's overrides which may be used to pass L2 `gasLimit`, `gasPrice`, `value`, etc. */ async getTransferTx(transaction) { var _a, _b; const { ...tx } = transaction; if (!tx.token) { tx.token = utils_1.L2_BASE_TOKEN_ADDRESS; } else if ((0, utils_1.isAddressEq)(tx.token, utils_1.LEGACY_ETH_ADDRESS) || (0, utils_1.isAddressEq)(tx.token, utils_1.ETH_ADDRESS_IN_CONTRACTS)) { tx.token = await this.l2TokenAddress(utils_1.ETH_ADDRESS_IN_CONTRACTS); } tx.overrides ?? (tx.overrides = {}); (_a = tx.overrides).from ?? (_a.from = tx.from); (_b = tx.overrides).type ?? (_b.type = utils_1.EIP712_TX_TYPE); if ((0, utils_1.isAddressEq)(tx.token, utils_1.L2_BASE_TOKEN_ADDRESS)) { if (tx.paymasterParams) { return { ...tx.overrides, type: utils_1.EIP712_TX_TYPE, to: tx.to, value: tx.amount, customData: { paymasterParams: tx.paymasterParams, }, }; } return { ...tx.overrides, to: tx.to, value: tx.amount, }; } else { const token = typechain_1.IERC20__factory.connect(tx.token, this); const populatedTx = await token.transfer.populateTransaction(tx.to, tx.amount, tx.overrides); if (tx.paymasterParams) { return { ...populatedTx, customData: { paymasterParams: tx.paymasterParams, }, }; } return populatedTx; } } /** * Returns the gas estimation for a transfer transaction. * * @param transaction Transfer transaction request. * @param transaction.to The address of the recipient. * @param transaction.amount The amount of the token to transfer. * @param [transaction.token] The address of the token. Defaults to ETH. * @param [transaction.paymasterParams] Paymaster parameters. * @param [transaction.overrides] Transaction's overrides which may be used to pass L2 `gasLimit`, `gasPrice`, `value`, etc. */ async estimateGasTransfer(transaction) { const transferTx = await this.getTransferTx(transaction); return await this.estimateGas(transferTx); } /** * Returns a new filter by calling {@link https://ethereum.github.io/execution-apis/api-documentation/ eth_newFilter} * and passing a filter object. * * @param filter The filter query to apply. */ async newFilter(filter) { const id = await this.send('eth_newFilter', [ await this._getFilter(filter), ]); return BigInt(id); } /** * Returns a new block filter by calling {@link https://ethereum.github.io/execution-apis/api-documentation/ eth_newBlockFilter}. */ async newBlockFilter() { const id = await this.send('eth_newBlockFilter', []); return BigInt(id); } /** * Returns a new pending transaction filter by calling {@link https://ethereum.github.io/execution-apis/api-documentation/ eth_newPendingTransactionFilter}. */ async newPendingTransactionsFilter() { const id = await this.send('eth_newPendingTransactionFilter', []); return BigInt(id); } /** * Returns an array of logs by calling {@link https://ethereum.github.io/execution-apis/api-documentation/ eth_getFilterChanges}. * * @param idx The filter index. */ async getFilterChanges(idx) { const logs = await this.send('eth_getFilterChanges', [ ethers_1.ethers.toBeHex(idx), ]); return typeof logs[0] === 'string' ? logs : logs.map((log) => this._wrapLog(log)); } /** * Returns the status of a specified transaction. * * @param txHash The hash of the transaction. */ // This is inefficient. Status should probably be indicated in the transaction receipt. async getTransactionStatus(txHash) { const tx = await this.getTransaction(txHash); if (!tx) { return types_1.TransactionStatus.NotFound; } if (!tx.blockNumber) { return types_1.TransactionStatus.Processing; } const verifiedBlock = (await this.getBlock('finalized')); if (tx.blockNumber <= verifiedBlock.number) { return types_1.TransactionStatus.Finalized; } return types_1.TransactionStatus.Committed; } /** * Broadcasts the `signedTx` to the network, adding it to the memory pool of any node for which the transaction * meets the rebroadcast requirements. * * @param signedTx The signed transaction that needs to be broadcasted. * @returns A promise that resolves with the transaction response. */ async broadcastTransaction(signedTx) { const { blockNumber, hash } = await (0, ethers_1.resolveProperties)({ blockNumber: this.getBlockNumber(), hash: this._perform({ method: 'broadcastTransaction', signedTransaction: signedTx, }), network: this.getNetwork(), }); const tx = types_1.Transaction.from(signedTx); if (tx.hash !== hash) { throw new Error('@TODO: the returned hash did not match!'); } return this._wrapTransactionResponse(tx).replaceableTransaction(blockNumber); } /** * Returns a L2 transaction response from L1 transaction response. * * @param l1TxResponse The L1 transaction response. */ async getL2TransactionFromPriorityOp(l1TxResponse) { const receipt = await l1TxResponse.wait(); const l2Hash = (0, utils_1.getL2HashFromPriorityOp)(receipt, await this.getMainContractAddress()); let status = null; do { status = await this.getTransactionStatus(l2Hash); await (0, utils_1.sleep)(this.pollingInterval); } while (status === types_1.TransactionStatus.NotFound); return await this.getTransaction(l2Hash); } /** * Returns a {@link PriorityOpResponse} from L1 transaction response. * * @param l1TxResponse The L1 transaction response. */ async getPriorityOpResponse(l1TxResponse) { const l2Response = { ...l1TxResponse }; l2Response.waitL1Commit = l1TxResponse.wait.bind(l1TxResponse); l2Response.wait = async () => { const l2Tx = await this.getL2TransactionFromPriorityOp(l1TxResponse); return await l2Tx.wait(); }; l2Response.waitFinalize = async () => { const l2Tx = await this.getL2TransactionFromPriorityOp(l1TxResponse); return await l2Tx.waitFinalize(); }; return l2Response; } async _getPriorityOpConfirmationL2ToL1Log(txHash, index = 0) { const hash = ethers_1.ethers.hexlify(txHash); const receipt = await this.getTransactionReceipt(hash); if (!receipt) { throw new Error('Transaction is not mined!'); } const messages = Array.from(receipt.l2ToL1Logs.entries()).filter(([, log]) => (0, utils_1.isAddressEq)(log.sender, utils_1.BOOTLOADER_FORMAL_ADDRESS)); const [l2ToL1LogIndex, l2ToL1Log] = messages[index]; return { l2ToL1LogIndex, l2ToL1Log, l1BatchTxId: receipt.l1BatchTxIndex, }; } /** * Returns the transaction confirmation data that is part of `L2->L1` message. * * @param txHash The hash of the L2 transaction where the message was initiated. * @param [index=0] In case there were multiple transactions in one message, you may pass an index of the * transaction which confirmation data should be fetched. * @throws {Error} If log proof can not be found. */ async getPriorityOpConfirmation(txHash, index = 0) { const { l2ToL1LogIndex, l2ToL1Log, l1BatchTxId } = await this._getPriorityOpConfirmationL2ToL1Log(txHash, index); const proof = await this.getLogProof(txHash, l2ToL1LogIndex); return { l1BatchNumber: l2ToL1Log.l1BatchNumber, l2MessageIndex: proof.id, l2TxNumberInBlock: l1BatchTxId, proof: proof.proof, }; } /** * Returns the version of the supported account abstraction and nonce ordering from a given contract address. * * @param address The contract address. */ async getContractAccountInfo(address) { const deployerContract = new ethers_1.Contract(utils_1.CONTRACT_DEPLOYER_ADDRESS, utils_1.CONTRACT_DEPLOYER.fragments, this); const data = await deployerContract.getAccountInfo(address); return { supportedAAVersion: Number(data.supportedAAVersion), nonceOrdering: Number(data.nonceOrdering), }; } /** * Returns an estimation of the L2 gas required for token bridging via the default ERC20 bridge. * * @param providerL1 The Ethers provider for the L1 network. * @param token The address of the token to be bridged. * @param amount The deposit amount. * @param to The recipient address on the L2 network. * @param from The sender address on the L1 network. * @param gasPerPubdataByte The current gas per byte of pubdata. */ async estimateDefaultBridgeDepositL2Gas(providerL1, token, amount, to, from, gasPerPubdataByte) { // If the `from` address is not provided, we use a random address, because // due to storage slot aggregation, the gas estimation will depend on the address // and so estimation for the zero address may be smaller than for the sender. from ?? (from = ethers_1.ethers.Wallet.createRandom().address); token = (0, utils_1.isAddressEq)(token, utils_1.LEGACY_ETH_ADDRESS) ? utils_1.ETH_ADDRESS_IN_CONTRACTS : token; if (await this.isBaseToken(token)) { return await this.estimateL1ToL2Execute({ contractAddress: to, gasPerPubdataByte: gasPerPubdataByte, caller: from, calldata: '0x', l2Value: amount, }); } else { const bridgeAddresses = await this.getDefaultBridgeAddresses(); const value = 0; const l1BridgeAddress = bridgeAddresses.sharedL1; const l2BridgeAddress = bridgeAddresses.sharedL2; const bridgeData = await (0, utils_1.getERC20DefaultBridgeData)(token, providerL1); return await this.estimateCustomBridgeDepositL2Gas(l1BridgeAddress, l2BridgeAddress, token, amount, to, bridgeData, from, gasPerPubdataByte, value); } } /** * Returns an estimation of the L2 gas required for token bridging via the custom ERC20 bridge. * * @param l1BridgeAddress The address of the custom L1 bridge. * @param l2BridgeAddress The address of the custom L2 bridge. * @param token The address of the token to be bridged. * @param amount The deposit amount. * @param to The recipient address on the L2 network. * @param bridgeData Additional bridge data. * @param from The sender address on the L1 network. * @param gasPerPubdataByte The current gas per byte of pubdata. * @param l2Value The `msg.value` of L2 transaction. */ async estimateCustomBridgeDepositL2Gas(l1BridgeAddress, l2BridgeAddress, token, amount, to, bridgeData, from, gasPerPubdataByte, l2Value) { const calldata = await (0, utils_1.getERC20BridgeCalldata)(token, from, to, amount, bridgeData); return await this.estimateL1ToL2Execute({ caller: (0, utils_1.applyL1ToL2Alias)(l1BridgeAddress), contractAddress: l2BridgeAddress, gasPerPubdataByte: gasPerPubdataByte, calldata: calldata, l2Value: l2Value, }); } /** * Returns gas estimation for an L1 to L2 execute operation. * * @param transaction The transaction details. * @param transaction.contractAddress The address of the contract. * @param transaction.calldata The transaction call data. * @param [transaction.caller] The caller's address. * @param [transaction.l2Value] The deposit amount. * @param [transaction.factoryDeps] An array of bytes containing contract bytecode. * @param [transaction.gasPerPubdataByte] The current gas per byte value. * @param [transaction.overrides] Transaction overrides including `gasLimit`, `gasPrice`, and `value`. */ async estimateL1ToL2Execute(transaction) { transaction.gasPerPubdataByte ?? (transaction.gasPerPubdataByte = utils_1.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT); // If the `from` address is not provided, we use a random address, because // due to storage slot aggregation, the gas estimation will depend on the address // and so estimation for the zero address may be smaller than for the sender. transaction.caller ?? (transaction.caller = ethers_1.ethers.Wallet.createRandom().address); const customData = { gasPerPubdata: transaction.gasPerPubdataByte, }; if (transaction.factoryDeps) { Object.assign(customData, { factoryDeps: transaction.factoryDeps }); } return await this.estimateGasL1({ from: transaction.caller, data: transaction.calldata, to: transaction.contractAddress, value: transaction.l2Value, customData, }); } /** * Returns `tx` as a normalized JSON-RPC transaction request, which has all values `hexlified` and any numeric * values converted to Quantity values. * @param tx The transaction request that should be normalized. */ getRpcTransaction(tx) { const result = super.getRpcTransaction(tx); if (!tx.customData) { return result; } result.type = ethers_1.ethers.toBeHex(utils_1.EIP712_TX_TYPE); result.eip712Meta = { gasPerPubdata: ethers_1.ethers.toBeHex(tx.customData.gasPerPubdata ?? 0), }; if (tx.customData.factoryDeps) { result.eip712Meta.factoryDeps = tx.customData.factoryDeps.map((dep) => // TODO (SMA-1605): we arraify instead of hexlifying because server expects Vec<u8>. // We should change deserialization there. Array.from(ethers_1.ethers.getBytes(dep))); } if (tx.customData.customSignature) { result.eip712Meta.customSignature = Array.from(ethers_1.ethers.getBytes(tx.customData.customSignature)); } if (tx.customData.paymasterParams) { result.eip712Meta.paymasterParams = { paymaster: ethers_1.ethers.hexlify(tx.customData.paymasterParams.paymaster), paymasterInput: Array.from(ethers_1.ethers.getBytes(tx.customData.paymasterParams.paymasterInput)), }; } return result; } }; } exports.JsonRpcApiProvider = JsonRpcApiProvider; /** * A `Provider` extends {@link ethers.JsonRpcProvider} and includes additional features for interacting with ZKsync Era. * It supports RPC endpoints within the `zks` namespace. */ class Provider extends JsonRpcApiProvider(ethers_1.ethers.JsonRpcProvider) { contractAddresses() { return this._contractAddresses; } /** * Creates a new `Provider` instance for connecting to an L2 network. * Caching is disabled for local networks. * @param [url] The network RPC URL. Defaults to the local network. * @param [network] The network name, chain ID, or object with network details. * @param [options] Additional options for the provider. */ constructor(url, network, options) { if (!url) { url = 'http://127.0.0.1:3050'; } const isLocalNetwork = typeof url === 'string' ? url.includes('localhost') || url.includes('127.0.0.1') || url.includes('0.0.0.0') : url.url.includes('localhost') || url.url.includes('127.0.0.1') || url.url.includes('0.0.0.0'); const optionsWithDisabledCache = isLocalNetwork ? { ...options, cacheTimeout: -1 } : options; super(url, network, optionsWithDisabledCache); _Provider_connect.set(this, void 0); typeof url === 'string' ? (__classPrivateFieldSet(this, _Provider_connect, new ethers_1.FetchRequest(url), "f")) : (__classPrivateFieldSet(this, _Provider_connect, url.clone(), "f")); this.pollingInterval = 500; this._contractAddresses = {}; } /** * @inheritDoc * * @example * * import { Provider, types, utils } from "zksync-ethers"; * * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * const TX_HASH = "<YOUR_TX_HASH_ADDRESS>"; * console.log(`Transaction receipt: ${utils.toJSON(await provider.getTransactionReceipt(TX_HASH))}`); */ async getTransactionReceipt(txHash) { return super.getTransactionReceipt(txHash); } /** * @inheritDoc * * @example * * import { Provider, types } from "zksync-ethers"; * * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * * const TX_HASH = "<YOUR_TX_HASH_ADDRESS>"; * const tx = await provider.getTransaction(TX_HASH); * * // Wait until the transaction is processed by the server. * await tx.wait(); * // Wait until the transaction is finalized. * await tx.waitFinalize(); */ async getTransaction(txHash) { return super.getTransaction(txHash); } /** * @inheritDoc * * @example * * import { Provider, types, utils } from "zksync-ethers"; * * const provider = Provider.getDefaultProvider(types.Network.Sepolia); * console.log(`Block: ${utils.toJSON(await provider.getBlock("latest", true))}`); */ async getBlock(blockHashOrBlockTag, includeTxs) { return super.getBlock(blockHashOrBlockTag, includeTxs); } /** * @inheritDoc * * @example * * import { Provider, types, utils } from "zksync-ethers"; * * const provider = Provider.getDef