diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31ca21f..3bcf60d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ jobs: test: strategy: matrix: - node: [18, 20] + node: [20] name: Install and test runs-on: ubuntu-latest steps: @@ -17,4 +17,5 @@ jobs: node-version: ${{ matrix.node }} cache: yarn - run: yarn install - - run: yarn test + - run: yarn build + - run: export PRIVATE_KEY=${{secrets.PRIVATE_KEY}} && yarn test diff --git a/.gitignore b/.gitignore index 9b26ed0..6db120b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules -lib \ No newline at end of file +lib +.history +.idea diff --git a/.npmignore b/.npmignore index 44d5996..ea16e65 100644 --- a/.npmignore +++ b/.npmignore @@ -9,6 +9,7 @@ .prettierrc.json .prettierignore *.log +.idea coverage/ benchmark/ diff --git a/package.json b/package.json index 62f32ee..61c70c1 100644 --- a/package.json +++ b/package.json @@ -1,44 +1,50 @@ { - "name": "web3-plugin-zksync", - "version": "0.1.5", - "description": "web3.js plugin for ZkSync", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "homepage": "https://github.com/web3/web3-plugin-zksync#readme", - "bugs": { - "url": "https://github.com/web3/web3-plugin-zksync/issues" - }, - "scripts": { - "lint": "eslint '{src,test}/**/*.ts'", - "build": "tsc --project tsconfig.build.json", - "test": "jest --config=./test/jest.config.js" - }, - "contributors": [ - "ChainSafe " - ], - "license": "MIT", - "repository": { - "type": "git", - "url": "git+ssh://git@github.com/web3/web3-plugin-zksync.git" - }, - "dependencies": { - "hardhat": "^2.19.4", - "web3-utils": "^4.1.1" - }, - "devDependencies": { - "@chainsafe/eslint-config": "^2.1.1", - "@types/jest": "^29.5.11", - "@types/node": "^20.11.10", - "eslint": "8.56.0", - "jest": "^29.7.0", - "jest-extended": "^4.0.2", - "ts-jest": "^29.1.2", - "ts-node": "^10.9.2", - "typedoc": "^0.25.13", - "typescript": "^5.3.3", - "web3": "^4.4.0" - }, - "peerDependencies": { - "web3": ">= 4.0.3" - } -} + "name": "web3-plugin-zksync", + "version": "1.0.0-alpha.0", + "description": "web3.js plugin for ZkSync", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "homepage": "https://github.com/web3/web3-plugin-zksync#readme", + "bugs": { + "url": "https://github.com/web3/web3-plugin-zksync/issues" + }, + "scripts": { + "lint": "eslint '{src,test}/**/*.ts'", + "lint:fix": "eslint '{src,test}/**/*.ts' --fix", + "build": "tsc --project tsconfig.build.json", + "test": "jest --config=./test/jest.config.js" + }, + "contributors": [ + "ChainSafe " + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/web3/web3-plugin-zksync.git" + }, + "dependencies": { + "ethereum-cryptography": "^2.1.3", + "hardhat": "^2.19.4", + "web3": "4.10.1-dev.1436228.0", + "web3-core": "4.5.1-dev.1436228.0", + "web3-eth-abi": "^4.2.2", + "web3-eth-accounts": "^4.1.2", + "web3-eth-contract": "4.5.1-dev.1436228.0", + "web3-types": "1.7.1-dev.1436228.0", + "web3-utils": "4.3.1-dev.1436228.0" + }, + "devDependencies": { + "@chainsafe/eslint-config": "^2.1.1", + "@types/jest": "^29.5.11", + "@types/node": "^20.11.10", + "eslint": "8.56.0", + "jest": "^29.7.0", + "jest-extended": "^4.0.2", + "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + }, + "peerDependencies": { + "web3": ">= 4.0.3" + } +} \ No newline at end of file diff --git a/src/Eip712.ts b/src/Eip712.ts new file mode 100644 index 0000000..94070a5 --- /dev/null +++ b/src/Eip712.ts @@ -0,0 +1,466 @@ +import { bytesToHex, toBigInt, toHex } from 'web3-utils'; +import type { Bytes, Eip712TypedData, Numbers } from 'web3-types'; +import * as web3Abi from 'web3-eth-abi'; +import * as web3Utils from 'web3-utils'; +import type * as web3Accounts from 'web3-eth-accounts'; +import { BaseTransaction, bigIntToUint8Array, toUint8Array } from 'web3-eth-accounts'; +import { RLP } from '@ethereumjs/rlp'; +import type { Address } from 'web3'; +import { + DEFAULT_GAS_PER_PUBDATA_LIMIT, + EIP712_TX_TYPE, + EIP712_TYPES, + ZERO_ADDRESS, +} from './constants'; +import type { + Eip712Meta, + Eip712SignedInput, + Eip712TxData, + EthereumSignature, + PaymasterParams, +} from './types'; +import type { SignatureLike } from './utils'; +import { concat, hashBytecode, SignatureObject, toBytes } from './utils'; + +function handleAddress(value?: Uint8Array): string | null { + if (!value) { + return null; + } + const hexValue = bytesToHex(value); + if (hexValue === '0x') { + return null; + } + + return web3Utils.toChecksumAddress(hexValue); +} + +function handleNumber(value?: Uint8Array): bigint { + if (!value) { + return 0n; + } + const hexValue = bytesToHex(value); + if (hexValue === '0x') { + return 0n; + } + return toBigInt(hexValue); +} +function arrayToPaymasterParams(arr: Uint8Array): PaymasterParams | undefined { + if (arr.length === 0) { + return undefined; + } + if (arr.length !== 2) { + throw new Error( + `Invalid paymaster parameters, expected to have length of 2, found ${arr.length}!`, + ); + } + + return { + paymaster: web3Utils.toChecksumAddress(toHex(arr[0])), + paymasterInput: web3Utils.bytesToUint8Array(toHex(arr[1])), + }; +} + +export class EIP712 { + static getSignInput(transaction: Eip712TxData): Eip712SignedInput { + const maxFeePerGas = toBigInt(transaction.maxFeePerGas || transaction.gasPrice || 0n); + const maxPriorityFeePerGas = toBigInt(transaction.maxPriorityFeePerGas || maxFeePerGas); + const gasPerPubdataByteLimit = + transaction.customData?.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT; + return { + txType: transaction.type || EIP712_TX_TYPE, + from: transaction.from + ? typeof transaction.from === 'string' + ? transaction.from + : toHex(transaction.from) + : undefined, + to: transaction.to + ? typeof transaction.to === 'string' + ? transaction.to + : toHex(transaction.to) + : undefined, + gasLimit: transaction.gasLimit ? toBigInt(transaction.gasLimit) : 0n, + gasPerPubdataByteLimit: gasPerPubdataByteLimit, + maxFeePerGas, + maxPriorityFeePerGas, + paymaster: transaction.customData?.paymasterParams?.paymaster || ZERO_ADDRESS, + nonce: transaction.nonce ? toBigInt(transaction.nonce) : 0, + value: transaction.value ? toBigInt(transaction.value) : 0n, + data: transaction.data ? toHex(transaction.data) : '0x', + factoryDeps: + transaction.customData?.factoryDeps?.map((dep: Bytes) => hashBytecode(dep)) || [], + paymasterInput: transaction.customData?.paymasterParams?.paymasterInput || '0x', + customData: + transaction.customData && Object.keys(transaction.customData).length > 0 + ? transaction.customData + : undefined, + }; + } + + static txTypedData(transaction: Eip712TxData): Eip712TypedData { + return { + types: EIP712_TYPES, + primaryType: 'Transaction', + domain: { + name: 'zkSync', + version: '2', + chainId: Number(transaction.chainId), + }, + message: EIP712.getSignInput(transaction), + }; + } + /** + * Returns the hash of an EIP712 transaction. + * + * @param transaction The EIP-712 transaction. + * @param ethSignature The ECDSA signature of the transaction. + * + * @example + * + * + */ + static txHash(transaction: Eip712TxData, ethSignature?: EthereumSignature): string { + const bytes: string[] = []; + + const typedDataStruct = EIP712.txTypedData(transaction); + + bytes.push(web3Abi.getEncodedEip712Data(typedDataStruct, true)); + bytes.push(web3Utils.keccak256(EIP712.getSignature(typedDataStruct.message, ethSignature))); + return web3Utils.keccak256(concat(bytes)); + } + /** + * Parses an EIP712 transaction from a payload. + * + * @param payload The payload to parse. + * + * @example + * + * + * const serializedTx = + * "0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0"; + * const tx: types.TransactionLike = utils.parse(serializedTx); + * /* + * tx: types.Eip712TxData = { + * type: 113, + * nonce: 0, + * maxPriorityFeePerGas: BigInt(0), + * maxFeePerGas: BigInt(0), + * gasLimit: BigInt(0), + * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", + * value: BigInt(1000000), + * data: "0x", + * chainId: BigInt(270), + * from: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", + * customData: { + * gasPerPubdata: BigInt(50000), + * factoryDeps: [], + * customSignature: "0x", + * paymasterParams: null, + * }, + * hash: "0x9ed410ce33179ac1ff6b721060605afc72d64febfe0c08cacab5a246602131ee", + * }; + * *\/ + */ + + static fromSerializedTx(payload: Bytes): Eip712TxData { + const bytes = web3Utils.bytesToUint8Array(payload); + + const raw = RLP.decode(bytes.slice(1)) as Array; + const transaction: Eip712TxData = { + type: EIP712_TX_TYPE, + nonce: handleNumber(raw[0]), + maxPriorityFeePerGas: handleNumber(raw[1]), + maxFeePerGas: handleNumber(raw[2]), + gasLimit: handleNumber(raw[3]), + to: handleAddress(raw[4]) as Address, + value: handleNumber(raw[5]), + data: bytesToHex(raw[6]), + chainId: handleNumber(raw[10]), + from: handleAddress(raw[11]) as Address, + customData: { + gasPerPubdata: handleNumber(raw[12]), + factoryDeps: raw[13] as unknown as string[], + customSignature: bytesToHex(raw[14]), + paymasterParams: arrayToPaymasterParams(raw[15]), + }, + }; + const ethSignature = { + v: Number(handleNumber(raw[7])), + r: raw[8], + s: raw[9], + }; + + if ( + (web3Utils.toHex(ethSignature.r) === '0x' || + web3Utils.toHex(ethSignature.s) === '0x') && + !transaction.customData?.customSignature + ) { + return transaction; + } + + if ( + ethSignature.v !== 0 && + ethSignature.v !== 1 && + !transaction.customData?.customSignature + ) { + throw new Error('Failed to parse signature!'); + } + + if (!transaction.customData?.customSignature) { + transaction.signature = new SignatureObject(ethSignature).toString(); + } + + transaction.hash = EIP712.txHash(transaction, ethSignature); + + return transaction; + } + + static getSignature(transaction: Eip712TxData, ethSignature?: EthereumSignature): Uint8Array { + if ( + transaction?.customData?.customSignature && + transaction.customData.customSignature.length + ) { + return web3Utils.bytesToUint8Array(transaction.customData.customSignature); + } + + if (!ethSignature) { + throw new Error('No signature provided!'); + } + + const r = web3Utils.bytesToUint8Array( + web3Utils.padLeft(web3Utils.toHex(ethSignature.r), 32 * 2), + ); + const s = web3Utils.bytesToUint8Array( + web3Utils.padLeft(web3Utils.toHex(ethSignature.s), 32 * 2), + ); + const v = ethSignature.v; + + return new Uint8Array([...r, ...s, v]); + } + static raw(transaction: Eip712TxData, signature?: SignatureLike) { + if (!transaction.chainId) { + throw Error("Transaction chainId isn't set!"); + } + + if (!transaction.from) { + throw new Error( + 'Explicitly providing `from` field is required for EIP712 transactions!', + ); + } + const from = transaction.from; + const meta: Eip712Meta = transaction.customData ?? {}; + const maxFeePerGas = toHex(transaction.maxFeePerGas || transaction.gasPrice || 0); + const maxPriorityFeePerGas = toHex(transaction.maxPriorityFeePerGas || maxFeePerGas); + + let gasLimitBytes = new Uint8Array(); + if (transaction.gasLimit && toHex(transaction.gasLimit) !== '0x0') { + gasLimitBytes = toBytes(transaction.gasLimit); + } + + const nonce = toBigInt(transaction.nonce || 0); + const fields: Array = [ + nonce === 0n ? new Uint8Array() : bigIntToUint8Array(nonce), + maxPriorityFeePerGas === '0x0' ? new Uint8Array() : toBytes(maxPriorityFeePerGas), + maxFeePerGas === '0x0' ? new Uint8Array() : toBytes(maxFeePerGas), + gasLimitBytes, + transaction.to ? web3Utils.toChecksumAddress(toHex(transaction.to)) : '0x', + toHex(transaction.value || 0) === '0x0' + ? new Uint8Array() + : toHex(transaction.value || 0), + toHex(transaction.data || '0x'), + ]; + + if (signature) { + const signatureObject = new SignatureObject(signature); + fields.push(toHex(Number(signatureObject.v) === 27 ? 0 : 1)); + fields.push(toHex(signatureObject.r)); + fields.push(toHex(signatureObject.s)); + } else { + fields.push(toHex(transaction.chainId)); + fields.push('0x'); + fields.push('0x'); + } + fields.push(toHex(transaction.chainId)); + fields.push(web3Utils.toChecksumAddress(from)); + + // Add meta + fields.push(toHex(meta.gasPerPubdata || DEFAULT_GAS_PER_PUBDATA_LIMIT)); + fields.push((meta.factoryDeps ?? []).map(dep => web3Utils.toHex(dep))); + + if ( + meta.customSignature && + web3Utils.bytesToUint8Array(meta.customSignature).length === 0 + ) { + throw new Error('Empty signatures are not supported!'); + } + fields.push(meta.customSignature || '0x'); + + if (meta.paymasterParams) { + fields.push([ + meta.paymasterParams.paymaster, + web3Utils.toHex(meta.paymasterParams.paymasterInput), + ]); + } else { + fields.push([]); + } + return fields; + } + static serialize(transaction: Eip712TxData, signature?: SignatureLike): string { + const fields = EIP712.raw(transaction, signature); + return concat([new Uint8Array([EIP712_TX_TYPE]), RLP.encode(fields)]); + } + + static sign(hash: string, privateKey: string) { + return new EIP712Transaction({}).ecsign( + toUint8Array(web3Utils.keccak256(hash)), + toUint8Array(privateKey), + ); + } +} + +export class EIP712Signer { + private eip712Domain: Eip712TypedData['domain']; + private web3Account: web3Accounts.Web3Account; + private chainId: number; + constructor(web3Account: web3Accounts.Web3Account, chainId: number) { + this.web3Account = web3Account; + this.chainId = Number(web3Utils.toNumber(chainId)); + this.eip712Domain = { + name: 'zkSync', + version: '2', + chainId: Number(this.chainId), + }; + } + + sign(tx: Eip712TxData): SignatureObject | undefined { + return new EIP712Transaction(tx).sign(toBytes(this.web3Account.privateKey)).getSignature(); + } + + /** + * Hashes the transaction request using EIP712. + * + * @param transaction The transaction request that needs to be hashed. + * @returns A hash (digest) of the transaction request. + * + * @throws {Error} If `transaction.chainId` is not set. + */ + static getSignedDigest(transaction: Eip712TxData): Bytes { + if (!transaction.chainId) { + throw Error("Transaction chainId isn't set!"); + } + + return EIP712.txHash(transaction); + + // const domain = { + // name: 'zkSync', + // version: '2', + // chainId: transaction.chainId, + // }; + // TODO: Implement replacement of the following line + // @ts-ignore + // return ethers.TypedDataEncoder.hash(domain, EIP712_TYPES, EIP712.getSignInput(transaction)); + } + + /** + * Returns zkSync Era EIP712 domain. + */ + getDomain(): Eip712TypedData['domain'] { + return this.eip712Domain; + } +} +export class EIP712Transaction extends BaseTransaction { + private txData: Eip712TxData; + private signature?: SignatureObject; + constructor(txData: Eip712TxData) { + super(txData, {} as web3Accounts.TxOptions); + const { v, r, s, ...data } = txData; + + if (r && s) { + this.signature = new SignatureObject(toUint8Array(r), toUint8Array(s), toBigInt(v)); + } + + this.txData = data; + } + public getSignature(): SignatureObject | undefined { + return this.signature; + } + public getMessageToSign(isHash = false): Uint8Array { + const typedDataStruct = EIP712.txTypedData(this.txData); + const message = web3Abi.getEncodedEip712Data(typedDataStruct, isHash); + return web3Utils.hexToBytes(message); + } + _processSignature( + v: Numbers, + r: EthereumSignature['r'], + s: EthereumSignature['s'], + ): EIP712Transaction { + const signature = new SignatureObject(toUint8Array(r), toUint8Array(s), toBigInt(v)); + return new EIP712Transaction({ + ...this.txData, + v: toBigInt(signature.v), + r: toHex(signature.r), + s: toHex(signature.s), + }); + } + public ecsign(msgHash: Uint8Array, privateKey: Uint8Array, chainId?: bigint) { + // @ts-ignore-next-time until new web3js release + const { s, r, v } = this._ecsign(msgHash, privateKey, chainId); + this.signature = new SignatureObject(toUint8Array(r), toUint8Array(s), toBigInt(v)); + return this.signature; + } + + protected _errorMsg(msg: string): string { + return `${msg} (${this.errorStr()})`; + } + + errorStr(): string { + return ''; + } + + getMessageToVerifySignature(): Uint8Array { + return this.getMessageToSign(); + } + + getSenderPublicKey(): Uint8Array { + // @TODO: implement recover transaction here + return new Uint8Array(); + } + + getUpfrontCost(): bigint { + return 0n; + } + + hash(): Uint8Array { + return toUint8Array(EIP712.txHash(this.txData)); + } + // @ts-ignore-next-line + raw(): web3Accounts.TxValuesArray[] { + return EIP712.raw(this.txData) as unknown as web3Accounts.TxValuesArray[]; + } + + serialize(): Uint8Array { + return toUint8Array(EIP712.serialize(this.txData)); + } + + toJSON(): web3Accounts.JsonTx { + const data = EIP712.getSignInput(this.txData); + return { + to: data.to && toHex(data.to), + gasLimit: toHex(data.gasLimit), + // @ts-ignore-next-line + gasPerPubdataByteLimit: data.gasPerPubdataByteLimit, + customData: data.customData, + maxFeePerGas: toHex(data.maxFeePerGas), + maxPriorityFeePerGas: toHex(data.maxPriorityFeePerGas), + paymaster: data.paymaster, + nonce: toHex(data.nonce), + value: toHex(data.value), + data: toHex(data.data), + factoryDeps: data.factoryDeps, + paymasterInput: data.paymasterInput, + type: toHex(data.txType), + v: this.signature?.v ? toHex(this.signature.v) : undefined, + r: this.signature?.r ? toHex(this.signature?.r) : undefined, + s: this.signature?.s ? toHex(this.signature?.s) : undefined, + }; + } +} diff --git a/src/adapters.ts b/src/adapters.ts new file mode 100644 index 0000000..2b153a3 --- /dev/null +++ b/src/adapters.ts @@ -0,0 +1,1932 @@ +import type * as web3Types from 'web3-types'; +import * as web3Utils from 'web3-utils'; +import * as Web3EthAbi from 'web3-eth-abi'; +import { DEFAULT_RETURN_FORMAT } from 'web3'; +import * as Web3 from 'web3'; +import type { PayableMethodObject, PayableTxOptions } from 'web3-eth-contract'; +import { toBigInt, toHex, toNumber } from 'web3-utils'; +import type { Transaction, TransactionHash, TransactionReceipt } from 'web3-types'; +import type { Web3ZkSyncL2 } from './web3zksync-l2'; + +import type { EIP712Signer } from './utils'; +import { + getPriorityOpResponse, + checkBaseCost, + estimateCustomBridgeDepositL2Gas, + estimateDefaultBridgeDepositL2Gas, + getERC20DefaultBridgeData, + isETH, + layer1TxDefaults, + scaleGasLimit, + undoL1ToL2Alias, + isAddressEq, + id, + dataSlice, + toBytes, +} from './utils'; + +import { + BOOTLOADER_FORMAL_ADDRESS, + L1_MESSENGER_ADDRESS, + L1_RECOMMENDED_MIN_ERC20_DEPOSIT_GAS_LIMIT, + L1_RECOMMENDED_MIN_ETH_DEPOSIT_GAS_LIMIT, + REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + NONCE_HOLDER_ADDRESS, + ETH_ADDRESS_IN_CONTRACTS, + LEGACY_ETH_ADDRESS, + EIP712_TX_TYPE, +} from './constants'; +import type { + Address, + FinalizeWithdrawalParams, + FullDepositFee, + TransactionOverrides, + PaymasterParams, + PriorityOpResponse, + WalletBalances, + Eip712TxData, +} from './types'; +import { ZeroAddress, ZeroHash } from './types'; +import { IZkSyncABI } from './contracts/IZkSyncStateTransition'; +import { IBridgehubABI } from './contracts/IBridgehub'; +import { IERC20ABI } from './contracts/IERC20'; +import { IL1BridgeABI } from './contracts/IL1Bridge'; +import { Abi as IL1SharedBridgeABI } from './contracts/IL1SharedBridge'; +import { IL2BridgeABI } from './contracts/IL2Bridge'; +import { INonceHolderABI } from './contracts/INonceHolder'; +import type { Web3ZkSyncL1 } from './web3zksync-l1'; + +interface TxSender { + getAddress(): Promise
; +} + +export class AdapterL1 implements TxSender { + /** + * Returns a provider instance for connecting to an L2 network. + */ + protected _contextL2(): Web3ZkSyncL2 { + throw new Error('Must be implemented by the derived class!'); + } + + /** + * Returns a context (provider + Signer) instance for connecting to a L1 network. + */ + protected _contextL1(): Web3ZkSyncL1 { + throw new Error('Must be implemented by the derived class!'); + } + + /** + * Returns `Contract` wrapper of the zkSync Era smart contract. + */ + async getMainContract( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise> { + const address = await this._contextL2().getMainContract(returnFormat); + const contract = new Web3.Contract(IZkSyncABI, address, returnFormat); + contract.setProvider(this._contextL2().provider); + return contract; + } + + /** + * Returns `Contract` wrapper of the Bridgehub smart contract. + */ + async getBridgehubContract( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise> { + const address = await this._contextL2().getBridgehubContractAddress(); + return new (this._contextL1().eth.Contract)(IBridgehubABI, address, returnFormat); + } + + /** + * Returns L1 bridge contracts. + * + * @remarks There is no separate Ether bridge contract, {@link getBridgehubContractAddress Bridgehub} is used instead. + */ + async getL1BridgeContracts( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise<{ + erc20: Web3.Contract; + weth: Web3.Contract; + shared: Web3.Contract; + }> { + const addresses = await this._contextL2().getDefaultBridgeAddresses(); + const erc20 = new (this._contextL1().eth.Contract)( + IERC20ABI, + addresses.erc20L1, + returnFormat, + ); + const weth = new (this._contextL1().eth.Contract)( + IERC20ABI, + addresses.wethL1, + returnFormat, + ); + const shared = new (this._contextL1().eth.Contract)( + IL1SharedBridgeABI, + addresses.sharedL1, + returnFormat, + ); + + return { + erc20, + weth, + shared, + }; + } + + /** + * Returns the address of the base token on L1. + */ + async getBaseToken(): Promise
{ + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + return bridgehub.methods.baseToken(chainId).call(); + } + + /** + * Returns whether the chain is ETH-based. + */ + async isETHBasedChain(): Promise { + return this._contextL2().isEthBasedChain(); + } + + /** + * Returns the amount of the token held by the account on the L1 network. + * + * @param [token] The address of the token. Defaults to ETH if not provided. + * @param [blockTag] The block in which the balance should be checked. + * Defaults to 'committed', i.e., the latest processed block. + */ + async getBalanceL1(token?: Address, blockTag?: web3Types.BlockNumberOrTag): Promise { + token ??= LEGACY_ETH_ADDRESS; + if (isETH(token)) { + return await this._contextL1().eth.getBalance(this.getAddress(), blockTag); + } else { + const erc20 = new (this._contextL1().eth.Contract)(IERC20ABI, token); + + return await erc20.methods.balanceOf(this.getAddress()).call(); + } + } + + /** + * Returns the amount of approved tokens for a specific L1 bridge. + * + * @param token The Ethereum address of the token. + * @param [bridgeAddress] The address of the bridge contract to be used. + * Defaults to the default zkSync Era bridge, either `L1EthBridge` or `L1Erc20Bridge`. + * @param [blockTag] The block in which an allowance should be checked. + * Defaults to 'committed', i.e., the latest processed block. + */ + async getAllowanceL1( + token: Address, + bridgeAddress?: Address, + blockTag?: web3Types.BlockNumberOrTag, + ): Promise { + if (!bridgeAddress) { + const bridgeContracts = await this.getL1BridgeContracts(); + bridgeAddress = bridgeContracts.shared.options.address; + } + + const erc20 = new (this._contextL1().eth.Contract)(IERC20ABI, token); + + return erc20.methods + .allowance(this.getAddress(), bridgeAddress, { + blockTag, + }) + .call(); + } + + /** + * Returns the L2 token address equivalent for a L1 token address as they are not necessarily equal. + * The ETH address is set to the zero address. + * + * @remarks Only works for tokens bridged on default zkSync Era bridges. + * + * @param token The address of the token on L1. + */ + async l2TokenAddress(token: Address): Promise { + return this._contextL2().l2TokenAddress(token); + } + + /** + * Bridging ERC20 tokens from L1 requires approving the tokens to the zkSync Era smart contract. + * + * @param token The L1 address of the token. + * @param amount The amount of the token to be approved. + * @param [overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + * @returns A promise that resolves to the response of the approval transaction. + * @throws {Error} If attempting to approve an ETH token. + */ + async approveERC20( + token: Address, + amount: web3Types.Numbers, + overrides?: TransactionOverrides & { bridgeAddress?: Address }, + ) { + if (isETH(token)) { + throw new Error( + "ETH token can't be approved! The address of the token does not exist on L1.", + ); + } + + overrides ??= {}; + let bridgeAddress = overrides.bridgeAddress; + + const erc20 = new (this._contextL1().eth.Contract)(IERC20ABI, token); + + if (!bridgeAddress) { + bridgeAddress = (await this.getL1BridgeContracts()).shared.options.address; + } else { + delete overrides.bridgeAddress; + } + + return erc20.methods.approve(bridgeAddress, amount, overrides).send({ + from: this.getAddress(), + }); + } + + /** + * Returns the base cost for an L2 transaction. + * + * @param params The parameters for calculating the base cost. + * @param params.gasLimit The gasLimit for the L2 contract call. + * @param [params.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [params.gasPrice] The L1 gas price of the L1 transaction that will send the request for an execute call. + */ + async getBaseCost(params: { + gasLimit: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + gasPrice?: web3Types.Numbers; + chainId?: web3Types.Numbers; + }): Promise { + const bridgehub = await this.getBridgehubContract(); + const parameters = { ...layer1TxDefaults(), ...params }; + parameters.gasPrice ??= (await this._contextL1().eth.calculateFeeData()).gasPrice!; + parameters.gasPerPubdataByte ??= REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + + return await bridgehub.methods + .l2TransactionBaseCost( + parameters.chainId ?? (await this._contextL2().eth.getChainId()), + parameters.gasPrice, + parameters.gasLimit, + parameters.gasPerPubdataByte, + ) + .call(); + } + + /** + * Returns the parameters for the approval token transaction based on the deposit token and amount. + * Some deposit transactions require multiple approvals. Existing allowance for the bridge is not checked; + * allowance is calculated solely based on the specified amount. + * + * @param token The address of the token to deposit. + * @param amount The amount of the token to deposit. + */ + async getDepositAllowanceParams( + token: Address, + amount: web3Types.Numbers, + ): Promise<{ token: Address; allowance: web3Types.Numbers }[]> { + if (isAddressEq(token, LEGACY_ETH_ADDRESS)) { + token = ETH_ADDRESS_IN_CONTRACTS; + } + const baseTokenAddress = await this.getBaseToken(); + const isETHBasedChain = await this.isETHBasedChain(); + + if (isETHBasedChain && isAddressEq(token, ETH_ADDRESS_IN_CONTRACTS)) { + throw new Error( + "ETH token can't be approved! The address of the token does not exist on L1.", + ); + } else if (isAddressEq(baseTokenAddress, ETH_ADDRESS_IN_CONTRACTS)) { + return [{ token, allowance: amount }]; + } else if (isAddressEq(token, ETH_ADDRESS_IN_CONTRACTS)) { + return [ + { + token: baseTokenAddress, + allowance: (await this._getDepositETHOnNonETHBasedChainTx({ token, amount })) + .mintValue, + }, + ]; + } else if (isAddressEq(token, baseTokenAddress)) { + return [ + { + token: baseTokenAddress, + allowance: ( + await this._getDepositBaseTokenOnNonETHBasedChainTx({ + token, + amount, + }) + ).mintValue, + }, + ]; + } else { + // A deposit of a non-base token to a non-ETH-based chain requires two approvals. + return [ + { + token: baseTokenAddress, + allowance: ( + await this._getDepositNonBaseTokenToNonETHBasedChainTx({ + token, + amount, + }) + ).mintValue, + }, + { + token: token, + allowance: amount, + }, + ]; + } + } + + /** + * Transfers the specified token from the associated account on the L1 network to the target account on the L2 network. + * The token can be either ETH or any ERC20 token. For ERC20 tokens, enough approved tokens must be associated with + * the specified L1 bridge (default one or the one defined in `transaction.bridgeAddress`). + * In this case, depending on is the chain ETH-based or not `transaction.approveERC20` or `transaction.approveBaseERC20` + * can be enabled to perform token approval. If there are already enough approved tokens for the L1 bridge, + * token approval will be skipped. To check the amount of approved tokens for a specific bridge, + * use the {@link getAllowanceL1} method. + * + * @param transaction The transaction object containing deposit details. + * @param transaction.token The address of the token to deposit. ETH by default. + * @param transaction.amount The amount of the token to deposit. + * @param [transaction.to] The address that will receive the deposited tokens on L2. + * @param [transaction.operatorTip] (currently not used) If the ETH value passed with the transaction is not + * explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of + * the base cost of the transaction. + * @param [transaction.bridgeAddress] The address of the bridge contract to be used. + * Defaults to the default zkSync Era bridge (either `L1EthBridge` or `L1Erc20Bridge`). + * @param [transaction.approveERC20] Whether or not token approval should be performed under the hood. + * Set this flag to true if you bridge an ERC20 token and didn't call the {@link approveERC20} function beforehand. + * @param [transaction.approveBaseERC20] Whether or not base token approval should be performed under the hood. + * Set this flag to true if you bridge a base token and didn't call the {@link approveERC20} function beforehand. + * @param [transaction.l2GasLimit] Maximum amount of L2 gas that the transaction can consume during execution on L2. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.refundRecipient] The address on L2 that will receive the refund for the transaction. + * If the transaction fails, it will also be the address to receive `l2Value`. + * @param [transaction.overrides] Transaction's overrides for deposit which may be used to pass + * L1 `gasLimit`, `gasPrice`, `value`, etc. + * @param [transaction.approveOverrides] Transaction's overrides for approval of an ERC20 token which may be used + * to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + * @param [transaction.approveBaseOverrides] Transaction's overrides for approval of a base token which may be used + * to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + * @param [transaction.customBridgeData] Additional data that can be sent to a bridge. + */ + async deposit(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }): Promise { + if (isAddressEq(transaction.token, LEGACY_ETH_ADDRESS)) { + transaction.token = ETH_ADDRESS_IN_CONTRACTS; + } + const baseTokenAddress = await this.getBaseToken(); + + const isETHBasedChain = isAddressEq(baseTokenAddress, ETH_ADDRESS_IN_CONTRACTS); + + if (isETHBasedChain && isAddressEq(transaction.token, ETH_ADDRESS_IN_CONTRACTS)) { + return await this._depositETHToETHBasedChain(transaction); + } else if (isAddressEq(baseTokenAddress, ETH_ADDRESS_IN_CONTRACTS)) { + return await this._depositTokenToETHBasedChain(transaction); + } else if (isAddressEq(transaction.token, ETH_ADDRESS_IN_CONTRACTS)) { + return await this._depositETHToNonETHBasedChain(transaction); + } else if (isAddressEq(transaction.token, baseTokenAddress)) { + return await this._depositBaseTokenToNonETHBasedChain(transaction); + } else { + return await this._depositNonBaseTokenToNonETHBasedChain(transaction); + } + } + + async _depositNonBaseTokenToNonETHBasedChain(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }): Promise { + // Deposit a non-ETH and non-base token to a non-ETH-based chain. + // Go through the BridgeHub and obtain approval for both tokens. + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const baseTokenAddress = await bridgehub.methods.baseToken(chainId).call(); + const bridgeContracts = await this.getL1BridgeContracts(); + const { tx, mintValue } = + await this._getDepositNonBaseTokenToNonETHBasedChainTx(transaction); + + if (transaction.approveBaseERC20) { + // Only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1( + baseTokenAddress, + bridgeContracts.shared.options.address, + ); + if (allowance < mintValue) { + await this.approveERC20(baseTokenAddress, mintValue, { + bridgeAddress: bridgeContracts.shared.options.address, + ...transaction.approveBaseOverrides, + }); + } + } + + if (transaction.approveERC20) { + const bridgeAddress = transaction.bridgeAddress + ? transaction.bridgeAddress + : bridgeContracts.shared.options.address; + + // Only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1(transaction.token, bridgeAddress); + if (allowance < BigInt(transaction.amount)) { + await this.approveERC20(transaction.token, transaction.amount, { + bridgeAddress, + ...transaction.approveOverrides, + }); + } + } + + const baseGasLimit = await tx.estimateGas(); + const gasLimit = scaleGasLimit(baseGasLimit); + + return this.signAndSend(tx.populateTransaction({ gasLimit } as PayableTxOptions)); + } + + async _depositBaseTokenToNonETHBasedChain(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }): Promise { + // Bridging the base token to a non-ETH-based chain. + // Go through the BridgeHub, and give approval. + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const baseTokenAddress = await bridgehub.methods.baseToken(chainId).call(); + const sharedBridge = (await this.getL1BridgeContracts()).shared.options.address; + const { tx, mintValue } = await this._getDepositBaseTokenOnNonETHBasedChainTx(transaction); + + if (transaction.approveERC20 || transaction.approveBaseERC20) { + const approveOverrides = + transaction.approveBaseOverrides ?? transaction.approveOverrides!; + // Only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1(baseTokenAddress, sharedBridge); + if (allowance < mintValue) { + await this.approveERC20(baseTokenAddress, mintValue, { + bridgeAddress: sharedBridge, + ...approveOverrides, + }); + } + } + const baseGasLimit = await this.estimateGasRequestExecute(tx); + const gasLimit = scaleGasLimit(baseGasLimit); + + tx.overrides ??= {}; + tx.overrides.gasLimit ??= gasLimit; + + return this.requestExecute(tx); + } + + async _depositETHToNonETHBasedChain(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }): Promise { + // Depositing ETH into a non-ETH-based chain. + // Use requestL2TransactionTwoBridges, secondBridge is the wETH bridge. + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const baseTokenAddress = await bridgehub.methods.baseToken(chainId).call(); + const sharedBridge = (await this.getL1BridgeContracts()).shared.options.address; + const { tx, overrides, mintValue } = + await this._getDepositETHOnNonETHBasedChainTx(transaction); + + if (transaction.approveBaseERC20) { + // Only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1(baseTokenAddress, sharedBridge); + if (allowance < mintValue) { + await this.approveERC20(baseTokenAddress, mintValue, { + bridgeAddress: sharedBridge, + ...transaction.approveBaseOverrides, + }); + } + } + + const baseGasLimit = await tx.estimateGas({ + value: overrides.value ? web3Utils.toHex(overrides.value) : undefined, + }); + const gasLimit = scaleGasLimit(baseGasLimit); + + overrides.gasLimit ??= gasLimit; + + return this.signAndSend(tx.populateTransaction(overrides as PayableTxOptions)); + } + + async _depositTokenToETHBasedChain(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }): Promise { + const bridgeContracts = await this.getL1BridgeContracts(); + const { tx, overrides } = await this._getDepositTokenOnETHBasedChainTx(transaction); + + if (transaction.approveERC20) { + const proposedBridge = bridgeContracts.shared.options.address; + const bridgeAddress = transaction.bridgeAddress + ? transaction.bridgeAddress + : proposedBridge; + + // Only request the allowance if the current one is not enough. + const allowance = await this.getAllowanceL1(transaction.token, bridgeAddress); + if (allowance < BigInt(transaction.amount)) { + await this.approveERC20(transaction.token, transaction.amount, { + bridgeAddress, + ...transaction.approveOverrides, + }); + } + } + + const baseGasLimit = await tx.estimateGas(overrides as PayableTxOptions); + const gasLimit = scaleGasLimit(baseGasLimit); + + overrides.gasLimit ??= gasLimit; + + return this.signAndSend(tx.populateTransaction(overrides as PayableTxOptions)); + } + + async _depositETHToETHBasedChain(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }): Promise { + const tx = await this._getDepositETHOnETHBasedChainTx(transaction); + const baseGasLimit = await this.estimateGasRequestExecute(tx); + const gasLimit = scaleGasLimit(baseGasLimit); + + tx.overrides ??= {}; + tx.overrides.gasLimit ??= gasLimit; + + return this.requestExecute(tx); + } + + /** + * Estimates the amount of gas required for a deposit transaction on the L1 network. + * Gas for approving ERC20 tokens is not included in the estimation. + * + * In order for estimation to work, enough token allowance is required in the following cases: + * - Depositing ERC20 tokens on an ETH-based chain. + * - Depositing any token (including ETH) on a non-ETH-based chain. + * + * @param transaction The transaction details. + * @param transaction.token The address of the token to deposit. ETH by default. + * @param transaction.amount The amount of the token to deposit. + * @param [transaction.to] The address that will receive the deposited tokens on L2. + * @param [transaction.operatorTip] (currently not used) If the ETH value passed with the transaction is not + * explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of the + * base cost of the transaction. + * @param [transaction.bridgeAddress] The address of the bridge contract to be used. + * Defaults to the default zkSync Era bridge (either `L1EthBridge` or `L1Erc20Bridge`). + * @param [transaction.l2GasLimit] Maximum amount of L2 gas that the transaction can consume during execution on L2. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.customBridgeData] Additional data that can be sent to a bridge. + * @param [transaction.refundRecipient] The address on L2 that will receive the refund for the transaction. + * If the transaction fails, it will also be the address to receive `l2Value`. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + */ + async estimateGasDeposit(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + customBridgeData?: web3Types.Bytes; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise { + if (isAddressEq(transaction.token, LEGACY_ETH_ADDRESS)) { + transaction.token = ETH_ADDRESS_IN_CONTRACTS; + } + const tx = await this.getDepositTx(transaction); + + let baseGasLimit: bigint; + if (tx.token && isAddressEq(tx.token, await this.getBaseToken())) { + baseGasLimit = await this.estimateGasRequestExecute(tx); + } else { + baseGasLimit = await this._contextL1().eth.estimateGas(tx); + } + + return scaleGasLimit(baseGasLimit); + } + + /** + * Returns a populated deposit transaction. + * + * @param transaction The transaction details. + * @param transaction.token The address of the token to deposit. ETH by default. + * @param transaction.amount The amount of the token to deposit. + * @param [transaction.to] The address that will receive the deposited tokens on L2. + * @param [transaction.operatorTip] (currently not used) If the ETH value passed with the transaction is not + * explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of the + * base cost of the transaction. + * @param [transaction.bridgeAddress] The address of the bridge contract to be used. Defaults to the default zkSync + * Era bridge (either `L1EthBridge` or `L1Erc20Bridge`). + * @param [transaction.l2GasLimit] Maximum amount of L2 gas that the transaction can consume during execution on L2. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.customBridgeData] Additional data that can be sent to a bridge. + * @param [transaction.refundRecipient] The address on L2 that will receive the refund for the transaction. + * If the transaction fails, it will also be the address to receive `l2Value`. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + */ + async getDepositTx(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise { + if (isAddressEq(transaction.token, LEGACY_ETH_ADDRESS)) { + transaction.token = ETH_ADDRESS_IN_CONTRACTS; + } + const baseTokenAddress = await this.getBaseToken(); + const isETHBasedChain = isAddressEq(baseTokenAddress, ETH_ADDRESS_IN_CONTRACTS); + + if (isETHBasedChain && isAddressEq(transaction.token, ETH_ADDRESS_IN_CONTRACTS)) { + return await this._getDepositETHOnETHBasedChainTx(transaction); + } else if (isETHBasedChain) { + return await this._getDepositTokenOnETHBasedChainTx(transaction); + } else if (isAddressEq(transaction.token, ETH_ADDRESS_IN_CONTRACTS)) { + return (await this._getDepositETHOnNonETHBasedChainTx(transaction)).tx; + } else if (isAddressEq(transaction.token, baseTokenAddress)) { + return (await this._getDepositBaseTokenOnNonETHBasedChainTx(transaction)).tx; + } else { + return (await this._getDepositNonBaseTokenToNonETHBasedChainTx(transaction)).tx; + } + } + + async _getDepositNonBaseTokenToNonETHBasedChainTx(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }) { + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const bridgeContracts = await this.getL1BridgeContracts(); + + const tx = await this._getDepositTxWithDefaults(transaction); + const { + token, + operatorTip, + amount, + overrides, + l2GasLimit, + to, + refundRecipient, + gasPerPubdataByte, + } = tx; + + const baseCost = await this.getBaseCost({ + gasPrice: overrides.maxFeePerGas || overrides.gasPrice, + gasLimit: l2GasLimit, + gasPerPubdataByte: gasPerPubdataByte, + chainId, + }); + + const mintValue = web3Utils.toBigInt(baseCost) + web3Utils.toBigInt(operatorTip); + await checkBaseCost(baseCost, mintValue); + overrides.value ??= 0; + + return { + tx: bridgehub.methods.requestL2TransactionTwoBridges({ + chainId: chainId, + mintValue, + l2Value: 0, + l2GasLimit: l2GasLimit, + l2GasPerPubdataByteLimit: gasPerPubdataByte, + refundRecipient: refundRecipient ?? ZeroAddress, + secondBridgeAddress: bridgeContracts.shared.options.address, + secondBridgeValue: 0, + secondBridgeCalldata: Web3EthAbi.encodeParameters( + ['address', 'uint256', 'address'], + [token, amount, to], + ), + }), + overrides, + mintValue: mintValue, + }; + } + + async _getDepositBaseTokenOnNonETHBasedChainTx(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }) { + // Depositing the base token to a non-ETH-based chain. + // Goes through the BridgeHub. + // Have to give approvals for the sharedBridge. + + const tx = await this._getDepositTxWithDefaults(transaction); + const { operatorTip, amount, to, overrides, l2GasLimit, gasPerPubdataByte } = tx; + + const baseCost = await this.getBaseCost({ + gasPrice: overrides.maxFeePerGas || overrides.gasPrice, + gasLimit: l2GasLimit, + gasPerPubdataByte: gasPerPubdataByte, + }); + + tx.overrides.value = 0; + return { + tx: { + contractAddress: to, + calldata: '0x', + mintValue: toBigInt(baseCost) + BigInt(operatorTip) + BigInt(amount), + l2Value: amount, + ...tx, + }, + mintValue: toBigInt(baseCost) + BigInt(operatorTip) + BigInt(amount), + }; + } + + async _getDepositETHOnNonETHBasedChainTx(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }) { + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const sharedBridge = (await this.getL1BridgeContracts()).shared.options.address; + + const tx = await this._getDepositTxWithDefaults(transaction); + const { + operatorTip, + amount, + overrides, + l2GasLimit, + to, + refundRecipient, + gasPerPubdataByte, + } = tx; + + const baseCost = await this.getBaseCost({ + gasPrice: overrides.maxFeePerGas || overrides.gasPrice, + gasLimit: l2GasLimit, + chainId: chainId, + gasPerPubdataByte: gasPerPubdataByte, + }); + + overrides.value ??= amount; + const mintValue = web3Utils.toBigInt(baseCost) + web3Utils.toBigInt(operatorTip); + await checkBaseCost(baseCost, mintValue); + + return { + tx: bridgehub.methods.requestL2TransactionTwoBridges({ + chainId, + mintValue, + l2Value: 0, + l2GasLimit: l2GasLimit, + l2GasPerPubdataByteLimit: gasPerPubdataByte, + refundRecipient: refundRecipient ?? ZeroAddress, + secondBridgeAddress: sharedBridge, + secondBridgeValue: amount, + secondBridgeCalldata: Web3EthAbi.encodeParameters( + ['address', 'uint256', 'address'], + [ETH_ADDRESS_IN_CONTRACTS, 0, to], + ), + }), + overrides, + mintValue: mintValue, + }; + } + + async _getDepositTokenOnETHBasedChainTx(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise<{ + tx: PayableMethodObject; + overrides: TransactionOverrides; + }> { + // Depositing token to an ETH-based chain. Use the ERC20 bridge as done before. + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + + const tx = await this._getDepositTxWithDefaults(transaction); + const { + token, + operatorTip, + amount, + overrides, + l2GasLimit, + to, + refundRecipient, + gasPerPubdataByte, + } = tx; + + const baseCost = await this.getBaseCost({ + gasPrice: overrides.maxFeePerGas || overrides.gasPrice, + gasLimit: l2GasLimit, + gasPerPubdataByte, + chainId, + }); + + const mintValue = web3Utils.toBigInt(baseCost) + web3Utils.toBigInt(operatorTip); + overrides.value ??= mintValue; + await checkBaseCost(baseCost, mintValue); + + let secondBridgeAddress: Address; + let secondBridgeCalldata: web3Types.Bytes; + if (tx.bridgeAddress) { + secondBridgeAddress = tx.bridgeAddress; + secondBridgeCalldata = await getERC20DefaultBridgeData( + transaction.token, + this._contextL1(), + ); + } else { + secondBridgeAddress = (await this.getL1BridgeContracts()).shared.options + .address as Address; + secondBridgeCalldata = Web3EthAbi.encodeParameters( + ['address', 'uint256', 'address'], + [token, amount, to], + ); + } + + return { + tx: bridgehub.methods.requestL2TransactionTwoBridges({ + chainId, + mintValue, + l2Value: 0, + l2GasLimit, + l2GasPerPubdataByteLimit: gasPerPubdataByte, + refundRecipient: refundRecipient ?? ZeroAddress, + secondBridgeAddress, + secondBridgeValue: 0, + secondBridgeCalldata, + }), + overrides, + }; + } + + async _getDepositETHOnETHBasedChainTx(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }) { + // Call the BridgeHub directly, like it's done with the DiamondProxy. + + const tx = await this._getDepositTxWithDefaults(transaction); + const { operatorTip, amount, overrides, l2GasLimit, gasPerPubdataByte, to } = tx; + const baseCost = await this.getBaseCost({ + gasPrice: overrides.maxFeePerGas || overrides.gasPrice, + gasLimit: l2GasLimit, + gasPerPubdataByte, + }); + + overrides.value ??= + web3Utils.toBigInt(baseCost) + + web3Utils.toBigInt(operatorTip) + + web3Utils.toBigInt(amount); + + return { + contractAddress: to, + calldata: '0x', + mintValue: overrides.value, + l2Value: amount, + ...tx, + }; + } + + // Creates a shallow copy of a transaction and populates missing fields with defaults. + async _getDepositTxWithDefaults(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise<{ + token: Address; + amount: web3Types.Numbers; + to: Address; + operatorTip: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit: web3Types.Numbers; + gasPerPubdataByte: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides: TransactionOverrides; + }> { + const { ...tx } = transaction; + tx.to = tx.to ?? this.getAddress(); + tx.operatorTip ??= 0; + tx.overrides ??= {}; + tx.overrides.from = this.getAddress(); + tx.gasPerPubdataByte ??= REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + tx.l2GasLimit ??= await this._getL2GasLimit(tx); + await insertGasPrice(this._contextL1(), tx.overrides); + + return tx as { + token: Address; + amount: web3Types.Numbers; + to: Address; + operatorTip: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit: web3Types.Numbers; + gasPerPubdataByte: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides: TransactionOverrides; + }; + } + + // Default behaviour for calculating l2GasLimit of deposit transaction. + async _getL2GasLimit(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise { + if (transaction.bridgeAddress) { + return await this._getL2GasLimitFromCustomBridge(transaction); + } else { + return await estimateDefaultBridgeDepositL2Gas( + this._contextL1(), + this._contextL2(), + transaction.token, + transaction.amount, + transaction.to!, + this.getAddress(), + transaction.gasPerPubdataByte, + ); + } + } + + // Calculates the l2GasLimit of deposit transaction using custom bridge. + async _getL2GasLimitFromCustomBridge(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + customBridgeData?: web3Types.Bytes; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise { + const customBridgeData = + transaction.customBridgeData ?? + (await getERC20DefaultBridgeData(transaction.token, this._contextL1())); + + const bridge = new (this._contextL1().eth.Contract)( + IL1BridgeABI, + transaction.bridgeAddress, + ); + const chainId = (await this._contextL2().eth.getChainId()) as web3Types.Numbers; + const l2Address = await bridge.methods.l2BridgeAddress(chainId).call(); + return await estimateCustomBridgeDepositL2Gas( + this._contextL2(), + transaction.bridgeAddress!, + l2Address, + transaction.token, + transaction.amount, + transaction.to!, + customBridgeData, + this.getAddress(), + transaction.gasPerPubdataByte, + ); + } + + /** + * Retrieves the full needed ETH fee for the deposit. Returns the L1 fee and the L2 fee {@link FullDepositFee}. + * + * @param transaction The transaction details. + * @param transaction.token The address of the token to deposit. ETH by default. + * @param [transaction.to] The address that will receive the deposited tokens on L2. + * @param [transaction.bridgeAddress] The address of the bridge contract to be used. + * Defaults to the default zkSync Era bridge (either `L1EthBridge` or `L1Erc20Bridge`). + * @param [transaction.customBridgeData] Additional data that can be sent to a bridge. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + * @throws {Error} If: + * - There's not enough balance for the deposit under the provided gas price. + * - There's not enough allowance to cover the deposit. + */ + async getFullRequiredDepositFee(transaction: { + token: Address; + to?: Address; + bridgeAddress?: Address; + customBridgeData?: web3Types.Bytes; + gasPerPubdataByte?: web3Types.Numbers; + overrides?: TransactionOverrides; + }): Promise { + if (isAddressEq(transaction.token, LEGACY_ETH_ADDRESS)) { + transaction.token = ETH_ADDRESS_IN_CONTRACTS; + } + // It is assumed that the L2 fee for the transaction does not depend on its value. + const dummyAmount = 1n; + const bridgehub = await this.getBridgehubContract(); + + const chainId = await this._contextL2().eth.getChainId(); + const baseTokenAddress = await this.getBaseToken(); + + const isETHBasedChain = isAddressEq(baseTokenAddress, ETH_ADDRESS_IN_CONTRACTS); + + const tx = await this._getDepositTxWithDefaults({ + ...transaction, + amount: dummyAmount, + }); + + const gasPriceForEstimation = tx.overrides.maxFeePerGas || tx.overrides.gasPrice; + const baseCost = await bridgehub.methods + .l2TransactionBaseCost( + chainId as web3Types.Numbers, + gasPriceForEstimation as web3Types.Numbers, + tx.l2GasLimit, + tx.gasPerPubdataByte, + ) + .call(); + + if (isETHBasedChain) { + // To ensure that L1 gas estimation succeeds when using estimateGasDeposit, + // the account needs to have a sufficient ETH balance. + const selfBalanceETH = await this.getBalanceL1(); + if (toBigInt(baseCost) >= toBigInt(selfBalanceETH) + toBigInt(dummyAmount)) { + const recommendedL1GasLimit = isAddressEq(tx.token, LEGACY_ETH_ADDRESS) + ? L1_RECOMMENDED_MIN_ETH_DEPOSIT_GAS_LIMIT + : L1_RECOMMENDED_MIN_ERC20_DEPOSIT_GAS_LIMIT; + const recommendedETHBalance = + BigInt(recommendedL1GasLimit) * BigInt(gasPriceForEstimation!) + + toBigInt(baseCost); + const formattedRecommendedBalance = web3Utils.fromWei( + recommendedETHBalance, + 'ether', + ); + throw new Error( + `Not enough balance for deposit! Under the provided gas price, the recommended balance to perform a deposit is ${formattedRecommendedBalance} ETH`, + ); + } + // In case of token deposit, a sufficient token allowance is also required. + if ( + !isAddressEq(tx.token, ETH_ADDRESS_IN_CONTRACTS) && + (await this.getAllowanceL1(tx.token, tx.bridgeAddress)) < dummyAmount + ) { + throw new Error('Not enough allowance to cover the deposit!'); + } + } else { + const mintValue = toBigInt(baseCost) + BigInt(tx.operatorTip); + if ((await this.getAllowanceL1(baseTokenAddress)) < mintValue) { + throw new Error('Not enough base token allowance to cover the deposit!'); + } + if ( + isAddressEq(tx.token, ETH_ADDRESS_IN_CONTRACTS) || + isAddressEq(tx.token, baseTokenAddress) + ) { + tx.overrides.value ??= tx.amount; + } else { + tx.overrides.value ??= 0; + if ((await this.getAllowanceL1(tx.token)) < dummyAmount) { + throw new Error('Not enough token allowance to cover the deposit!'); + } + } + } + + // Deleting the explicit gas limits in the fee estimation + // in order to prevent the situation where the transaction + // fails because the user does not have enough balance + const estimationOverrides = { ...tx.overrides }; + delete estimationOverrides.gasPrice; + delete estimationOverrides.maxFeePerGas; + delete estimationOverrides.maxPriorityFeePerGas; + + const l1GasLimit = await this.estimateGasDeposit({ + ...tx, + amount: dummyAmount, + overrides: estimationOverrides, + l2GasLimit: tx.l2GasLimit, + }); + + const fullCost: FullDepositFee = { + baseCost: toBigInt(baseCost), + l1GasLimit, + l2GasLimit: BigInt(tx.l2GasLimit), + }; + + if (tx.overrides.gasPrice) { + fullCost.gasPrice = BigInt(tx.overrides.gasPrice); + } else { + fullCost.maxFeePerGas = BigInt(tx.overrides.maxFeePerGas!); + fullCost.maxPriorityFeePerGas = BigInt(tx.overrides.maxPriorityFeePerGas!); + } + + return fullCost; + } + + /** + * 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: string, index = 0) { + return this._contextL2().getPriorityOpConfirmation(txHash, index); + } + + async _getWithdrawalLog(withdrawalHash: web3Types.Bytes, index = 0) { + const hash = web3Utils.toHex(withdrawalHash); + const receipt = await this._contextL2().getZKTransactionReceipt(hash); + if (!receipt) { + // @todo: or throw? + return {}; + } + // @ts-ignore + const log = (receipt?.logs || []).filter( + // @ts-ignore + log => + isAddressEq(String(log?.address), L1_MESSENGER_ADDRESS) && + log?.topics && + String(log?.topics[0]) === id('L1MessageSent(address,bytes32,bytes)'), + )[index]; + + return { + log, + l1BatchTxId: receipt.l1BatchTxIndex, + }; + } + + async _getWithdrawalL2ToL1Log(withdrawalHash: web3Types.Bytes, index = 0) { + const hash = web3Utils.toHex(withdrawalHash); + const receipt = await this._contextL2().getZKTransactionReceipt(hash); + if (!receipt) { + // @todo: or throw? + return {}; + } + const messages = Array.from(receipt.l2ToL1Logs.entries()).filter(([, log]) => + isAddressEq(log.sender, L1_MESSENGER_ADDRESS), + ); + const [l2ToL1LogIndex, l2ToL1Log] = messages[index]; + + return { + l2ToL1LogIndex, + l2ToL1Log, + }; + } + + /** + * Returns the {@link FinalizeWithdrawalParams parameters} required for finalizing a withdrawal from the + * withdrawal transaction's log on the L1 network. + * + * @param withdrawalHash Hash of the L2 transaction where the withdrawal was initiated. + * @param [index=0] In case there were multiple withdrawals in one transaction, you may pass an index of the + * withdrawal you want to finalize. + * @throws {Error} If log proof can not be found. + */ + async finalizeWithdrawalParams( + withdrawalHash: web3Types.Bytes, + index = 0, + ): Promise { + const { log, l1BatchTxId } = await this._getWithdrawalLog(withdrawalHash, index); + const { l2ToL1LogIndex } = await this._getWithdrawalL2ToL1Log(withdrawalHash, index); + const sender = log?.topics && dataSlice(toBytes(log?.topics[1]), 12); + const proof = await this._contextL2().getL2ToL1LogProof( + toHex(withdrawalHash), + l2ToL1LogIndex, + ); + if (!proof) { + throw new Error('Log proof not found!'); + } + const message = Web3EthAbi.decodeParameters(['bytes'], log?.data)[0]; + return { + l1BatchNumber: log.l1BatchNumber, + l2MessageIndex: Number(toNumber(proof.id)), + l2TxNumberInBlock: l1BatchTxId ? Number(toNumber(l1BatchTxId)) : null, + message, + sender, + proof: proof.proof, + }; + } + + /** + * Proves the inclusion of the `L2->L1` withdrawal message. + * + * @param withdrawalHash Hash of the L2 transaction where the withdrawal was initiated. + * @param [index=0] In case there were multiple withdrawals in one transaction, you may pass an index of the + * withdrawal you want to finalize. + * @param [overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + * @returns A promise that resolves to the proof of inclusion of the withdrawal message. + * @throws {Error} If log proof can not be found. + */ + async finalizeWithdrawal( + withdrawalHash: web3Types.Bytes, + index = 0, + overrides?: TransactionOverrides, + ) { + const { l1BatchNumber, l2MessageIndex, l2TxNumberInBlock, message, proof } = + await this.finalizeWithdrawalParams(withdrawalHash, index); + + const l1Contracts = await this.getL1BridgeContracts(); + const contract = new (this._contextL1().eth.Contract)( + IL1BridgeABI, + l1Contracts.shared.options.address, + ); + overrides = overrides ?? {}; + overrides.from ??= this.getAddress(); + + return ( + contract.methods + .finalizeWithdrawal( + (await this._contextL2().eth.getChainId()) as web3Types.Numbers, + l1BatchNumber as web3Types.Numbers, + l2MessageIndex as web3Types.Numbers, + l2TxNumberInBlock as web3Types.Numbers, + message, + proof, + ) + // @ts-ignore + .send(overrides ?? {}) + ); + } + + /** + * Returns whether the withdrawal transaction is finalized on the L1 network. + * + * @param withdrawalHash Hash of the L2 transaction where the withdrawal was initiated. + * @param [index=0] In case there were multiple withdrawals in one transaction, you may pass an index of the + * withdrawal you want to finalize. + * @throws {Error} If log proof can not be found. + */ + async isWithdrawalFinalized(withdrawalHash: web3Types.Bytes, index = 0): Promise { + const { log } = await this._getWithdrawalLog(withdrawalHash, index); + const { l2ToL1LogIndex } = await this._getWithdrawalL2ToL1Log(withdrawalHash, index); + const sender = dataSlice(log.topics[1], 12); + // `getLogProof` is called not to get proof but + // to get the index of the corresponding L2->L1 log, + // which is returned as `proof.id`. + const proof = await this._contextL2().getL2ToL1LogProof( + toHex(withdrawalHash), + l2ToL1LogIndex, + ); + if (!proof) { + throw new Error('Log proof not found!'); + } + + const chainId = await this._contextL2().eth.getChainId(); + + let l1Bridge: Web3.Contract | Web3.Contract; + + if (await this._contextL2().isBaseToken(sender)) { + l1Bridge = (await this.getL1BridgeContracts()).shared; + } else { + const l2BridgeContract = new (this._contextL2().eth.Contract)(IL2BridgeABI, sender); + const l1BridgeAddress = await l2BridgeContract.methods.l1Bridge().call(); + l1Bridge = new (this._contextL1().eth.Contract)(IL1BridgeABI, l1BridgeAddress); + } + + return await l1Bridge.methods + .isWithdrawalFinalized(chainId, log.l1BatchNumber, proof.id) + .call(); + } + + /** + * Withdraws funds from the initiated deposit, which failed when finalizing on L2. + * If the deposit L2 transaction has failed, it sends an L1 transaction calling `claimFailedDeposit` method of the + * L1 bridge, which results in returning L1 tokens back to the depositor. + * + * @param depositHash The L2 transaction hash of the failed deposit. + * @param [overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + * @returns A promise that resolves to the response of the `claimFailedDeposit` transaction. + * @throws {Error} If attempting to claim successful deposit. + */ + async claimFailedDeposit( + depositHash: web3Types.Bytes, + overrides?: TransactionOverrides, + ): Promise { + const receipt = await this._contextL2().getZKTransactionReceipt( + web3Utils.toHex(depositHash), + ); + if (!receipt) { + throw new Error('Transaction not found!'); + } + const successL2ToL1LogIndex = receipt.l2ToL1Logs.findIndex( + l2ToL1log => + isAddressEq(l2ToL1log.sender, BOOTLOADER_FORMAL_ADDRESS) && + l2ToL1log.key === depositHash, + ); + const successL2ToL1Log = receipt.l2ToL1Logs[successL2ToL1LogIndex]; + if (successL2ToL1Log.value !== ZeroHash) { + throw new Error('Cannot claim successful deposit!'); + } + + const tx = await this._contextL2().eth.getTransaction(web3Utils.toHex(depositHash)); + + // Undo the aliasing, since the Mailbox contract set it as for contract address. + const l1BridgeAddress = undoL1ToL2Alias(receipt.from); + const l2BridgeAddress = receipt.to; + if (!l2BridgeAddress) { + throw new Error('L2 bridge address not found!'); + } + const l1Bridge = new (this._contextL1().eth.Contract)(IL1BridgeABI, l1BridgeAddress); + + const l2Bridge = new Web3.Contract(IL2BridgeABI, l1BridgeAddress); + l2Bridge.setProvider(this._contextL2().provider); + + const calldata = l2Bridge.methods + .finalizeDeposit() + .decodeData(web3Utils.toHex(String(tx.data))); + + const proof = await this._contextL2().getL2ToL1LogProof( + web3Utils.toHex(depositHash), + successL2ToL1LogIndex, + ); + if (!proof) { + throw new Error('Log proof not found!'); + } + return ( + l1Bridge.methods + .claimFailedDeposit( + (await this._contextL2().eth.getChainId()) as web3Types.Numbers, + calldata[0], //_l1Sender + calldata[2], //_l1Token + calldata[3], //_amount + depositHash, + receipt.l1BatchNumber, + proof.id, + receipt.l1BatchTxIndex, + proof.proof, + ) + // @ts-ignore + .send(overrides ?? {}) + ); + } + + /** + * Requests execution of an L2 transaction from L1. + * + * @param transaction The transaction details. + * @param transaction.contractAddress The L2 contract to be called. + * @param transaction.calldata The input of the L2 transaction. + * @param [transaction.l2GasLimit] Maximum amount of L2 gas that transaction can consume during execution on L2. + * @param [transaction.mintValue] The amount of base token that needs to be minted on non-ETH-based L2. + * @param [transaction.l2Value] `msg.value` of L2 transaction. + * @param [transaction.factoryDeps] An array of L2 bytecodes that will be marked as known on L2. + * @param [transaction.operatorTip] (currently not used) If the ETH value passed with the transaction is not + * explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of + * the base cost of the transaction. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.refundRecipient] The address on L2 that will receive the refund for the transaction. + * If the transaction fails, it will also be the address to receive `l2Value`. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L2 `gasLimit`, `gasPrice`, `value`, etc. + * @returns A promise that resolves to the response of the execution request. + */ + async requestExecute(transaction: { + contractAddress: Address; + calldata: string; + l2GasLimit?: web3Types.Numbers; + mintValue?: web3Types.Numbers; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + operatorTip?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise { + const tx = await this.getRequestExecuteTx(transaction); + return this.signAndSend(tx); + } + async signAndSend(tx: Transaction, _context?: Web3ZkSyncL1 | Web3ZkSyncL2) { + const context = _context || this._contextL1(); + const populated = await context.populateTransaction(tx); + const signed = await context.signTransaction(populated as Transaction); + + return getPriorityOpResponse( + context, + context.sendRawTransaction(signed), + this._contextL2(), + ); + } + async signTransaction(tx: Transaction): Promise { + return this._contextL1().signTransaction(tx); + } + async sendRawTransaction(signedTx: string): Promise { + return this._contextL1().sendRawTransaction(signedTx); + } + /** + * Estimates the amount of gas required for a request execute transaction. + * + * @param transaction The transaction details. + * @param transaction.contractAddress The L2 contract to be called. + * @param transaction.calldata The input of the L2 transaction. + * @param [transaction.l2GasLimit] Maximum amount of L2 gas that transaction can consume during execution on L2. + * @param [transaction.mintValue] The amount of base token that needs to be minted on non-ETH-based L2. + * @param [transaction.l2Value] `msg.value` of L2 transaction. + * @param [transaction.factoryDeps] An array of L2 bytecodes that will be marked as known on L2. + * @param [transaction.operatorTip] (currently not used) If the ETH value passed with the transaction is not + * explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top + * of the base cost of the transaction. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.refundRecipient] The address on L2 that will receive the refund for the transaction. + * If the transaction fails, it will also be the address to receive `l2Value`. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + */ + async estimateGasRequestExecute(transaction: { + contractAddress: Address; + calldata: string; + l2GasLimit?: web3Types.Numbers; + mintValue?: web3Types.Numbers; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + operatorTip?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise { + const { method, overrides } = await this.getRequestExecuteContractMethod(transaction); + + delete overrides.gasPrice; + delete overrides.maxFeePerGas; + delete overrides.maxPriorityFeePerGas; + + return method.estimateGas(overrides as PayableTxOptions); + } + + /** + * Returns the parameters for the approval token transaction based on the request execute transaction. + * Existing allowance for the bridge is not checked; allowance is calculated solely based on the specified transaction. + * + * @param transaction The request execute transaction on which approval parameters are calculated. + */ + async getRequestExecuteAllowanceParams(transaction: { + contractAddress: Address; + calldata: string; + l2GasLimit?: web3Types.Numbers; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + operatorTip?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }): Promise<{ token: Address; allowance: web3Types.Numbers }> { + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const isETHBaseToken = isAddressEq( + await bridgehub.methods.baseToken(chainId).call(), + ETH_ADDRESS_IN_CONTRACTS, + ); + + if (isETHBaseToken) { + throw new Error( + "ETH token can't be approved! The address of the token does not exist on L1.", + ); + } + + const { ...tx } = transaction; + tx.l2Value ??= 0n; + tx.operatorTip ??= 0n; + tx.factoryDeps ??= []; + tx.overrides ??= {}; + tx.gasPerPubdataByte ??= REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + tx.refundRecipient ??= this.getAddress(); + tx.l2GasLimit ??= await this._contextL2().estimateL1ToL2Execute(transaction); + + const { l2Value, l2GasLimit, operatorTip, overrides, gasPerPubdataByte } = tx; + + await insertGasPrice(this._contextL1(), overrides); + const gasPriceForEstimation = overrides.maxFeePerGas || overrides.gasPrice; + + const baseCost = await this.getBaseCost({ + gasPrice: gasPriceForEstimation!, + gasPerPubdataByte, + gasLimit: l2GasLimit, + }); + + return { + token: await this.getBaseToken(), + allowance: baseCost + BigInt(operatorTip) + BigInt(l2Value), + }; + } + async getRequestExecuteContractMethod(transaction: { + contractAddress: Address; + calldata: string; + l2GasLimit?: web3Types.Numbers; + mintValue?: web3Types.Numbers; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + operatorTip?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }) { + const bridgehub = await this.getBridgehubContract(); + const chainId = await this._contextL2().eth.getChainId(); + const isETHBaseToken = isAddressEq( + await bridgehub.methods.baseToken(chainId).call(), + ETH_ADDRESS_IN_CONTRACTS, + ); + + const { ...tx } = transaction; + tx.l2Value ??= 0; + tx.mintValue ??= 0; + tx.operatorTip ??= 0; + tx.factoryDeps ??= []; + tx.overrides ??= {}; + tx.overrides.from ??= this.getAddress(); + tx.gasPerPubdataByte ??= REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; + tx.refundRecipient ??= this.getAddress(); + tx.l2GasLimit ??= await this._contextL2().estimateL1ToL2Execute(transaction); + + const { + contractAddress, + mintValue, + l2Value, + calldata, + l2GasLimit, + factoryDeps, + operatorTip, + overrides, + gasPerPubdataByte, + refundRecipient, + } = tx; + + await insertGasPrice(this._contextL1(), overrides); + const gasPriceForEstimation = overrides.maxFeePerGas || overrides.gasPrice; + + const baseCost = await this.getBaseCost({ + gasPrice: gasPriceForEstimation!, + gasPerPubdataByte, + gasLimit: l2GasLimit, + }); + + const l2Costs = baseCost + BigInt(operatorTip) + BigInt(l2Value); + let providedValue = isETHBaseToken ? overrides.value : mintValue; + if (providedValue === undefined || providedValue === null || BigInt(providedValue) === 0n) { + providedValue = l2Costs; + if (isETHBaseToken) overrides.value = providedValue; + } + await checkBaseCost(baseCost, providedValue); + + const method = bridgehub.methods.requestL2TransactionDirect({ + chainId, + mintValue: providedValue, + l2Contract: contractAddress, + l2Value: l2Value, + l2Calldata: calldata, + l2GasLimit: l2GasLimit, + l2GasPerPubdataByteLimit: REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, + factoryDeps: factoryDeps, + refundRecipient: refundRecipient, + }); + return { method, overrides }; + } + /** + * Returns a populated request execute transaction. + * + * @param transaction The transaction details. + * @param transaction.contractAddress The L2 contract to be called. + * @param transaction.calldata The input of the L2 transaction. + * @param [transaction.l2GasLimit] Maximum amount of L2 gas that transaction can consume during execution on L2. + * @param [transaction.mintValue] The amount of base token that needs to be minted on non-ETH-based L2. + * @param [transaction.l2Value] `msg.value` of L2 transaction. + * @param [transaction.factoryDeps] An array of L2 bytecodes that will be marked as known on L2. + * @param [transaction.operatorTip] (currently not used) If the ETH value passed with the transaction is not + * explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of the + * base cost of the transaction. + * @param [transaction.gasPerPubdataByte] The L2 gas price for each published L1 calldata byte. + * @param [transaction.refundRecipient] The address on L2 that will receive the refund for the transaction. + * If the transaction fails, it will also be the address to receive `l2Value`. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L1 `gasLimit`, `gasPrice`, `value`, etc. + */ + async getRequestExecuteTx(transaction: { + contractAddress: Address; + calldata: string; + l2GasLimit?: web3Types.Numbers; + mintValue?: web3Types.Numbers; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + operatorTip?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + }) { + const { method, overrides } = await this.getRequestExecuteContractMethod(transaction); + return method.populateTransaction(overrides as PayableTxOptions); + } + + async populateTransaction(tx: Transaction): Promise { + tx.from = this.getAddress(); + + if ( + (!tx.type || (tx.type && toHex(tx.type) !== toHex(EIP712_TX_TYPE))) && + !(tx as Eip712TxData).customData + ) { + return this._contextL1().populateTransaction(tx); + } + + const populated = (await this._contextL1().populateTransaction(tx)) as Eip712TxData; + populated.type = EIP712_TX_TYPE; + populated.value ??= 0; + populated.data ??= '0x'; + + return populated; + } + // @ts-ignore + public getAddress(): string { + throw new Error('Must be implemented by the derived class!'); + } +} + +export class AdapterL2 implements TxSender { + /** + * Returns a context (provider + Signer) instance for connecting to an L2 network. + */ + _contextL2(): Web3ZkSyncL2 { + throw new Error('Must be implemented by the derived class!'); + } + async _eip712Signer(): Promise { + throw new Error('Must be implemented by the derived class!'); + } + + /** + * Returns the balance of the account. + * + * @param [token] The token address to query balance for. Defaults to the native token. + * @param [blockTag='committed'] The block tag to get the balance at. + */ + async getBalance( + token?: Address, + blockTag: web3Types.BlockNumberOrTag = 'committed', + ): Promise { + if (token) { + const contract = new (this._contextL2().eth.Contract)(IERC20ABI, token); + return contract.methods.balanceOf(this.getAddress()).call(); + } + + return await this._contextL2().eth.getBalance(this.getAddress(), blockTag); + } + + /** + * Returns all token balances of the account. + */ + async getAllBalances(): Promise { + return this._contextL2().getAllAccountBalances(this.getAddress()); + } + + /** + * Returns the deployment nonce of the account. + */ + async getDeploymentNonce(): Promise { + const contract = new Web3.Contract(INonceHolderABI, NONCE_HOLDER_ADDRESS); + contract.setProvider(this._contextL2().provider); + return contract.methods.getDeploymentNonce(this.getAddress()).call(); + } + + /** + * Returns L2 bridge contracts. + */ + async getL2BridgeContracts(): Promise<{ + erc20: Web3.Contract; + weth: Web3.Contract; + shared: Web3.Contract; + }> { + const addresses = await this._contextL2().getDefaultBridgeAddresses(); + const erc20 = new Web3.Contract(IL2BridgeABI, addresses.erc20L2); + const weth = new Web3.Contract(IL2BridgeABI, addresses.wethL2); + const shared = new Web3.Contract(IL2BridgeABI, addresses.sharedL2); + + erc20.setProvider(this._contextL2().provider); + weth.setProvider(this._contextL2().provider); + shared.setProvider(this._contextL2().provider); + + return { + erc20, + weth, + shared, + }; + } + + /** + * Initiates the withdrawal process which withdraws ETH or any ERC20 token + * from the associated account on L2 network to the target account on L1 network. + * + * @param transaction Withdrawal transaction request. + * @param transaction.token The address of the token. Defaults to ETH. + * @param transaction.amount The amount of the token to withdraw. + * @param [transaction.to] The address of the recipient on L1. + * @param [transaction.bridgeAddress] The address of the bridge contract to be used. + * @param [transaction.paymasterParams] Paymaster parameters. + * @param [transaction.overrides] Transaction's overrides which may be used to pass L2 `gasLimit`, `gasPrice`, `value`, etc. + * @returns A Promise resolving to a withdrawal transaction response. + */ + async withdraw(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + bridgeAddress?: Address; + paymasterParams?: PaymasterParams; + overrides?: TransactionOverrides; + }) { + const tx = await this._contextL2().getWithdrawTx({ + ...transaction, + from: this.getAddress(), + }); + const populated = await this.populateTransaction(tx as Transaction); + const signed = await this.signTransaction(populated as Transaction); + return getPriorityOpResponse( + this._contextL2(), + this.sendRawTransaction(signed), + this._contextL2(), + ); + } + + async signTransaction(tx: Transaction): Promise { + return this._contextL2().signTransaction(tx); + } + async sendRawTransaction(signedTx: string): Promise { + return this._contextL2().sendRawTransaction(signedTx); + } + + /** + * Transfer ETH or any ERC20 token within the same interface. + * + * @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. + * @returns A Promise resolving to a transfer transaction response. + */ + async transfer(transaction: { + to: Address; + amount: web3Types.Numbers; + token?: Address; + paymasterParams?: PaymasterParams; + overrides?: TransactionOverrides; + }) { + return this._contextL2().getTransferTx({ + from: this.getAddress(), + ...transaction, + }); + } + // @ts-ignore + public getAddress(): string { + throw new Error('Must be implemented by the derived class!'); + } + + async populateTransaction(tx: Transaction): Promise { + tx.from = this.getAddress(); + if ( + (!tx.type || (tx.type && toHex(tx.type) !== toHex(EIP712_TX_TYPE))) && + !(tx as Eip712TxData).customData + ) { + return this._contextL2().populateTransaction(tx); + } + + const populated = (await this._contextL2().populateTransaction(tx)) as Eip712TxData; + populated.type = EIP712_TX_TYPE; + populated.value ??= 0; + populated.data ??= '0x'; + + return populated; + } +} + +// This method checks if the overrides contain a gasPrice (or maxFeePerGas), +// if not it will insert the maxFeePerGas +async function insertGasPrice( + l1Provider: Web3.Web3, + overrides: TransactionOverrides, +): Promise { + if (!overrides.gasPrice && !overrides.maxFeePerGas) { + const l1FeeData = await l1Provider.eth.calculateFeeData(); + // Sometimes baseFeePerGas is not available, so we use gasPrice instead. + const baseFee = BigInt( + l1FeeData.maxFeePerGas! ? getBaseCostFromFeeData(l1FeeData) : l1FeeData.gasPrice!, + ); + if (!baseFee) { + throw new Error('Failed to calculate base fee!'); + } + + // ethers.js by default uses multiplication by 2, but since the price for the L2 part + // will depend on the L1 part, doubling base fee is typically too much. + overrides.maxFeePerGas = + (baseFee * 3n) / 2n + (BigInt(l1FeeData.maxPriorityFeePerGas!) ?? 0n); + overrides.maxPriorityFeePerGas = + l1FeeData.maxPriorityFeePerGas && toHex(l1FeeData.maxPriorityFeePerGas); + } +} + +function getBaseCostFromFeeData(feeData: web3Types.FeeData): bigint { + const maxFeePerGas = feeData.maxFeePerGas!; + const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas!; + + return (BigInt(maxFeePerGas) - BigInt(maxPriorityFeePerGas)) / 2n; +} diff --git a/src/constants.ts b/src/constants.ts index f475531..bdbc5c6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,8 +1,181 @@ -export const ETH_ADDRESS = '0x0000000000000000000000000000000000000000'; +import type * as web3Types from 'web3-types'; + export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; -export const BOOTLOADER_FORMAL_ADDRESS = '0x0000000000000000000000000000000000008001'; -export const CONTRACT_DEPLOYER_ADDRESS = '0x0000000000000000000000000000000000008006'; -export const L1_MESSENGER_ADDRESS = '0x0000000000000000000000000000000000008008'; -export const L2_ETH_TOKEN_ADDRESS = '0x000000000000000000000000000000000000800a'; -export const NONCE_HOLDER_ADDRESS = '0x0000000000000000000000000000000000008003'; -export const L1_TO_L2_ALIAS_OFFSET = '0x1111000000000000000000000000000000001111'; + +/** + * The address of the L1 `ETH` token. + * @constant + */ +export const ETH_ADDRESS: web3Types.Address = '0x0000000000000000000000000000000000000000'; + +/** + * The address of the L1 `ETH` token. + * @constant + */ +export const LEGACY_ETH_ADDRESS: web3Types.Address = '0x0000000000000000000000000000000000000000'; + +/** + * In the contracts the zero address can not be used, use one instead + * @constant + */ +export const ETH_ADDRESS_IN_CONTRACTS: web3Types.Address = + '0x0000000000000000000000000000000000000001'; + +/** + * The formal address for the `Bootloader`. + * @constant + */ +export const BOOTLOADER_FORMAL_ADDRESS: web3Types.Address = + '0x0000000000000000000000000000000000008001'; + +/** + * The address of the Contract deployer. + * @constant + */ +export const CONTRACT_DEPLOYER_ADDRESS: web3Types.Address = + '0x0000000000000000000000000000000000008006'; + +/** + * The address of the L1 messenger. + * @constant + */ +export const L1_MESSENGER_ADDRESS: web3Types.Address = '0x0000000000000000000000000000000000008008'; + +/** + * The address of the L2 `ETH` token. + * @constant + * @deprecated In favor of {@link L2_BASE_TOKEN_ADDRESS}. + */ +export const L2_ETH_TOKEN_ADDRESS: web3Types.Address = '0x000000000000000000000000000000000000800a'; + +/** + * The address of the base token. + * @constant + */ +export const L2_BASE_TOKEN_ADDRESS = '0x000000000000000000000000000000000000800a'; + +/** + * The address of the Nonce holder. + * @constant + */ +export const NONCE_HOLDER_ADDRESS: web3Types.Address = '0x0000000000000000000000000000000000008003'; + +/** + * Used for applying and undoing aliases on addresses during bridging from L1 to L2. + * @constant + */ +export const L1_TO_L2_ALIAS_OFFSET: web3Types.Address = + '0x1111000000000000000000000000000000001111'; + +/** + * The EIP1271 magic value used for signature validation in smart contracts. + * This predefined constant serves as a standardized indicator to signal successful + * signature validation by the contract. + * + * @constant + */ +export const EIP1271_MAGIC_VALUE = '0x1626ba7e'; + +/** + * Represents an EIP712 transaction type. + * + * @constant + */ +export const EIP712_TX_TYPE = 0x71; + +/** + * Represents a priority transaction operation on L2. + * + * @constant + */ +export const PRIORITY_OPERATION_L2_TX_TYPE = 0xff; + +/** + * The maximum bytecode length in bytes that can be deployed. + * + * @constant + */ +export const MAX_BYTECODE_LEN_BYTES: number = ((1 << 16) - 1) * 32; + +/** + * Numerator used in scaling the gas limit to ensure acceptance of `L1->L2` transactions. + * + * This constant is part of a coefficient calculation to adjust the gas limit to account for variations + * in the SDK estimation, ensuring the transaction will be accepted. + * + * @constant + */ +export const L1_FEE_ESTIMATION_COEF_NUMERATOR = 12; + +/** + * Denominator used in scaling the gas limit to ensure acceptance of `L1->L2` transactions. + * + * This constant is part of a coefficient calculation to adjust the gas limit to account for variations + * in the SDK estimation, ensuring the transaction will be accepted. + * + * @constant + */ +export const L1_FEE_ESTIMATION_COEF_DENOMINATOR = 10; + +/** + * Gas limit used for displaying the error messages when the + * users do not have enough fee when depositing ERC20 token from L1 to L2. + * + * @constant + */ +export const L1_RECOMMENDED_MIN_ERC20_DEPOSIT_GAS_LIMIT = 400_000; + +/** + * Gas limit used for displaying the error messages when the + * users do not have enough fee when depositing `ETH` token from L1 to L2. + * + * @constant + */ +export const L1_RECOMMENDED_MIN_ETH_DEPOSIT_GAS_LIMIT = 200_000; + +/** + * Default gas per pubdata byte for L2 transactions. + * This value is utilized when inserting a default value for type 2 + * and EIP712 type transactions. + * + * @constant + */ +// It is a realistic value, but it is large enough to fill into any batch regardless of the pubdata price. +export const DEFAULT_GAS_PER_PUBDATA_LIMIT = 50_000; + +/** + * The `L1->L2` transactions are required to have the following gas per pubdata byte. + * + * @constant + */ +export const REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT = 800; + +/** + * All typed data conforming to the EIP712 standard within zkSync Era. + */ +export const EIP712_TYPES = { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + ], + Transaction: [ + { name: 'txType', type: 'uint256' }, + { name: 'from', type: 'uint256' }, + { name: 'to', type: 'uint256' }, + { name: 'gasLimit', type: 'uint256' }, + { name: 'gasPerPubdataByteLimit', type: 'uint256' }, + { name: 'maxFeePerGas', type: 'uint256' }, + { name: 'maxPriorityFeePerGas', type: 'uint256' }, + { name: 'paymaster', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'value', type: 'uint256' }, + { name: 'data', type: 'bytes' }, + { name: 'factoryDeps', type: 'bytes32[]' }, + { name: 'paymasterInput', type: 'bytes' }, + ], +}; + +export const MAX_INTEGER = BigInt( + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', +); diff --git a/src/contracts/ERC20Token.ts b/src/contracts/ERC20Token.ts deleted file mode 100644 index 98bdf44..0000000 --- a/src/contracts/ERC20Token.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* -This file is part of web3.js. - -web3.js is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -web3.js is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with web3.js. If not, see . -*/ - -export const ERC20TokenAbi = [ - { - inputs: [{ internalType: 'uint256', name: 'initialSupply', type: 'uint256' }], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, - { indexed: true, internalType: 'address', name: 'spender', type: 'address' }, - { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' }, - ], - name: 'Approval', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'from', type: 'address' }, - { indexed: true, internalType: 'address', name: 'to', type: 'address' }, - { indexed: false, internalType: 'uint256', name: 'value', type: 'uint256' }, - ], - name: 'Transfer', - type: 'event', - }, - { - inputs: [ - { internalType: 'address', name: 'owner', type: 'address' }, - { internalType: 'address', name: 'spender', type: 'address' }, - ], - name: 'allowance', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'spender', type: 'address' }, - { internalType: 'uint256', name: 'amount', type: 'uint256' }, - ], - name: 'approve', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [{ internalType: 'address', name: 'account', type: 'address' }], - name: 'balanceOf', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'decimals', - outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'spender', type: 'address' }, - { internalType: 'uint256', name: 'subtractedValue', type: 'uint256' }, - ], - name: 'decreaseAllowance', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'spender', type: 'address' }, - { internalType: 'uint256', name: 'addedValue', type: 'uint256' }, - ], - name: 'increaseAllowance', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [], - name: 'name', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'symbol', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'totalSupply', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'amount', type: 'uint256' }, - ], - name: 'transfer', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'amount', type: 'uint256' }, - ], - name: 'transferFrom', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const; diff --git a/src/contracts/ERC721Token.ts b/src/contracts/ERC721Token.ts deleted file mode 100644 index 973db8e..0000000 --- a/src/contracts/ERC721Token.ts +++ /dev/null @@ -1,173 +0,0 @@ -/* -This file is part of web3.js. - -web3.js is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -web3.js is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with web3.js. If not, see . -*/ - -export const ERC721TokenAbi = [ - { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, - { indexed: true, internalType: 'address', name: 'approved', type: 'address' }, - { indexed: true, internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'Approval', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'owner', type: 'address' }, - { indexed: true, internalType: 'address', name: 'operator', type: 'address' }, - { indexed: false, internalType: 'bool', name: 'approved', type: 'bool' }, - ], - name: 'ApprovalForAll', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'from', type: 'address' }, - { indexed: true, internalType: 'address', name: 'to', type: 'address' }, - { indexed: true, internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'Transfer', - type: 'event', - }, - { - inputs: [ - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'approve', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'player', type: 'address' }, - { internalType: 'string', name: 'tokenURI', type: 'string' }, - ], - name: 'awardItem', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], - name: 'balanceOf', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'getApproved', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'owner', type: 'address' }, - { internalType: 'address', name: 'operator', type: 'address' }, - ], - name: 'isApprovedForAll', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'name', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'ownerOf', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'bytes', name: 'data', type: 'bytes' }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'operator', type: 'address' }, - { internalType: 'bool', name: 'approved', type: 'bool' }, - ], - name: 'setApprovalForAll', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], - name: 'supportsInterface', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'symbol', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], - name: 'tokenURI', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - ], - name: 'transferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const; diff --git a/src/contracts/IBridgehub.ts b/src/contracts/IBridgehub.ts new file mode 100644 index 0000000..d1d1468 --- /dev/null +++ b/src/contracts/IBridgehub.ts @@ -0,0 +1,631 @@ +export const IBridgehubABI = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newAdmin', + type: 'address', + }, + ], + name: 'NewAdmin', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'stateTransitionManager', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'chainGovernance', + type: 'address', + }, + ], + name: 'NewChain', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldPendingAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newPendingAdmin', + type: 'address', + }, + ], + name: 'NewPendingAdmin', + type: 'event', + }, + { + inputs: [], + name: 'acceptAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_stateTransitionManager', + type: 'address', + }, + ], + name: 'addStateTransitionManager', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_token', + type: 'address', + }, + ], + name: 'addToken', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + ], + name: 'baseToken', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_stateTransitionManager', + type: 'address', + }, + { + internalType: 'address', + name: '_baseToken', + type: 'address', + }, + { + internalType: 'uint256', + name: '_salt', + type: 'uint256', + }, + { + internalType: 'address', + name: '_admin', + type: 'address', + }, + { + internalType: 'bytes', + name: '_initData', + type: 'bytes', + }, + ], + name: 'createNewChain', + outputs: [ + { + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + ], + name: 'getStateTransition', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_gasPrice', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasPerPubdataByteLimit', + type: 'uint256', + }, + ], + name: 'l2TransactionBaseCost', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + { + internalType: 'enum TxStatus', + name: '_status', + type: 'uint8', + }, + ], + name: 'proveL1ToL2TransactionStatus', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_index', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint8', + name: 'l2ShardId', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isService', + type: 'bool', + }, + { + internalType: 'uint16', + name: 'txNumberInBatch', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'key', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'value', + type: 'bytes32', + }, + ], + internalType: 'struct L2Log', + name: '_log', + type: 'tuple', + }, + { + internalType: 'bytes32[]', + name: '_proof', + type: 'bytes32[]', + }, + ], + name: 'proveL2LogInclusion', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_index', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint16', + name: 'txNumberInBatch', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + internalType: 'struct L2Message', + name: '_message', + type: 'tuple', + }, + { + internalType: 'bytes32[]', + name: '_proof', + type: 'bytes32[]', + }, + ], + name: 'proveL2MessageInclusion', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_stateTransitionManager', + type: 'address', + }, + ], + name: 'removeStateTransitionManager', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'mintValue', + type: 'uint256', + }, + { + internalType: 'address', + name: 'l2Contract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'l2Value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'l2Calldata', + type: 'bytes', + }, + { + internalType: 'uint256', + name: 'l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'l2GasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'bytes[]', + name: 'factoryDeps', + type: 'bytes[]', + }, + { + internalType: 'address', + name: 'refundRecipient', + type: 'address', + }, + ], + internalType: 'struct L2TransactionRequestDirect', + name: '_request', + type: 'tuple', + }, + ], + name: 'requestL2TransactionDirect', + outputs: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'mintValue', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'l2Value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'l2GasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'address', + name: 'refundRecipient', + type: 'address', + }, + { + internalType: 'address', + name: 'secondBridgeAddress', + type: 'address', + }, + { + internalType: 'uint256', + name: 'secondBridgeValue', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'secondBridgeCalldata', + type: 'bytes', + }, + ], + internalType: 'struct L2TransactionRequestTwoBridgesOuter', + name: '_request', + type: 'tuple', + }, + ], + name: 'requestL2TransactionTwoBridges', + outputs: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_newPendingAdmin', + type: 'address', + }, + ], + name: 'setPendingAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_sharedBridge', + type: 'address', + }, + ], + name: 'setSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'sharedBridge', + outputs: [ + { + internalType: 'contract IL1SharedBridge', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + ], + name: 'stateTransitionManager', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_stateTransitionManager', + type: 'address', + }, + ], + name: 'stateTransitionManagerIsRegistered', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_baseToken', + type: 'address', + }, + ], + name: 'tokenIsRegistered', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/src/contracts/IContractDeployer.ts b/src/contracts/IContractDeployer.ts new file mode 100644 index 0000000..37e4041 --- /dev/null +++ b/src/contracts/IContractDeployer.ts @@ -0,0 +1,410 @@ +export const IContractDeployerABI = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'accountAddress', + type: 'address', + }, + { + indexed: false, + internalType: 'enum IContractDeployer.AccountNonceOrdering', + name: 'nonceOrdering', + type: 'uint8', + }, + ], + name: 'AccountNonceOrderingUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'accountAddress', + type: 'address', + }, + { + indexed: false, + internalType: 'enum IContractDeployer.AccountAbstractionVersion', + name: 'aaVersion', + type: 'uint8', + }, + ], + name: 'AccountVersionUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'deployerAddress', + type: 'address', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'bytecodeHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'contractAddress', + type: 'address', + }, + ], + name: 'ContractDeployed', + type: 'event', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '_salt', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: '_input', + type: 'bytes', + }, + ], + name: 'create', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '_salt', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: '_input', + type: 'bytes', + }, + ], + name: 'create2', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '_salt', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: '_input', + type: 'bytes', + }, + { + internalType: 'enum IContractDeployer.AccountAbstractionVersion', + name: '_aaVersion', + type: 'uint8', + }, + ], + name: 'create2Account', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: '_input', + type: 'bytes', + }, + { + internalType: 'enum IContractDeployer.AccountAbstractionVersion', + name: '_aaVersion', + type: 'uint8', + }, + ], + name: 'createAccount', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'extendedAccountVersion', + outputs: [ + { + internalType: 'enum IContractDeployer.AccountAbstractionVersion', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'newAddress', + type: 'address', + }, + { + internalType: 'bool', + name: 'callConstructor', + type: 'bool', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'input', + type: 'bytes', + }, + ], + internalType: 'struct ContractDeployer.ForceDeployment', + name: '_deployment', + type: 'tuple', + }, + { + internalType: 'address', + name: '_sender', + type: 'address', + }, + ], + name: 'forceDeployOnAddress', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'newAddress', + type: 'address', + }, + { + internalType: 'bool', + name: 'callConstructor', + type: 'bool', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'input', + type: 'bytes', + }, + ], + internalType: 'struct ContractDeployer.ForceDeployment[]', + name: '_deployments', + type: 'tuple[]', + }, + ], + name: 'forceDeployOnAddresses', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'getAccountInfo', + outputs: [ + { + components: [ + { + internalType: 'enum IContractDeployer.AccountAbstractionVersion', + name: 'supportedAAVersion', + type: 'uint8', + }, + { + internalType: 'enum IContractDeployer.AccountNonceOrdering', + name: 'nonceOrdering', + type: 'uint8', + }, + ], + internalType: 'struct IContractDeployer.AccountInfo', + name: 'info', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_sender', + type: 'address', + }, + { + internalType: 'uint256', + name: '_senderNonce', + type: 'uint256', + }, + ], + name: 'getNewAddressCreate', + outputs: [ + { + internalType: 'address', + name: 'newAddress', + type: 'address', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_sender', + type: 'address', + }, + { + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_salt', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: '_input', + type: 'bytes', + }, + ], + name: 'getNewAddressCreate2', + outputs: [ + { + internalType: 'address', + name: 'newAddress', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'enum IContractDeployer.AccountAbstractionVersion', + name: '_version', + type: 'uint8', + }, + ], + name: 'updateAccountVersion', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'enum IContractDeployer.AccountNonceOrdering', + name: '_nonceOrdering', + type: 'uint8', + }, + ], + name: 'updateNonceOrdering', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/IERC1271.ts b/src/contracts/IERC1271.ts new file mode 100644 index 0000000..2028356 --- /dev/null +++ b/src/contracts/IERC1271.ts @@ -0,0 +1,43 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ + +export const IERC1271ABI = [ + { + inputs: [ + { + internalType: 'bytes32', + name: 'hash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + name: 'isValidSignature', + outputs: [ + { + internalType: 'bytes4', + name: 'magicValue', + type: 'bytes4', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/src/contracts/IERC20.ts b/src/contracts/IERC20.ts new file mode 100644 index 0000000..03239cf --- /dev/null +++ b/src/contracts/IERC20.ts @@ -0,0 +1,239 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ + +export const IERC20ABI = [ + { + constant: true, + inputs: [], + name: 'name', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_spender', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'totalSupply', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_from', + type: 'address', + }, + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'decimals', + outputs: [ + { + name: '', + type: 'uint8', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + name: 'balance', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'symbol', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'transfer', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + { + name: '_spender', + type: 'address', + }, + ], + name: 'allowance', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + payable: true, + stateMutability: 'payable', + type: 'fallback', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'spender', + type: 'address', + }, + { + indexed: false, + name: 'value', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'from', + type: 'address', + }, + { + indexed: true, + name: 'to', + type: 'address', + }, + { + indexed: false, + name: 'value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, +] as const; diff --git a/src/contracts/IEthToken.ts b/src/contracts/IEthToken.ts new file mode 100644 index 0000000..4820993 --- /dev/null +++ b/src/contracts/IEthToken.ts @@ -0,0 +1,245 @@ +export const Abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Mint', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: '_l2Sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: '_l1Receiver', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'Withdrawal', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: '_l2Sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: '_l1Receiver', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes', + name: '_additionalData', + type: 'bytes', + }, + ], + name: 'WithdrawalWithMessage', + type: 'event', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'decimals', + outputs: [ + { + internalType: 'uint8', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_account', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_from', + type: 'address', + }, + { + internalType: 'address', + name: '_to', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'transferFromTo', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_l1Receiver', + type: 'address', + }, + ], + name: 'withdraw', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_l1Receiver', + type: 'address', + }, + { + internalType: 'bytes', + name: '_additionalData', + type: 'bytes', + }, + ], + name: 'withdrawWithMessage', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, +] as const; diff --git a/src/contracts/IL1Bridge.ts b/src/contracts/IL1Bridge.ts new file mode 100644 index 0000000..3e24678 --- /dev/null +++ b/src/contracts/IL1Bridge.ts @@ -0,0 +1,512 @@ +export const IL1BridgeABI = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'txDataHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'l2DepositTxHash', + type: 'bytes32', + }, + ], + name: 'BridgehubDepositFinalized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'txDataHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'BridgehubDepositInitiatedSharedBridge', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ClaimedFailedDepositSharedBridge', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'l2DepositTxHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'DepositInitiatedSharedBridge', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'WithdrawalFinalizedSharedBridge', + type: 'event', + }, + { + inputs: [], + name: 'bridgehub', + outputs: [ + { + internalType: 'contract IBridgehub', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_txDataHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_txHash', + type: 'bytes32', + }, + ], + name: 'bridgehubConfirmL2Transaction', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_prevMsgSender', + type: 'address', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, + ], + name: 'bridgehubDeposit', + outputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'magicValue', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'l2Contract', + type: 'address', + }, + { + internalType: 'bytes', + name: 'l2Calldata', + type: 'bytes', + }, + { + internalType: 'bytes[]', + name: 'factoryDeps', + type: 'bytes[]', + }, + { + internalType: 'bytes32', + name: 'txDataHash', + type: 'bytes32', + }, + ], + internalType: 'struct L2TransactionRequestTwoBridgesInner', + name: 'request', + type: 'tuple', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_prevMsgSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'bridgehubDepositBaseToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_depositSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'claimFailedDeposit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_l2Receiver', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_mintValue', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasPerPubdataByte', + type: 'uint256', + }, + { + internalType: 'address', + name: '_refundRecipient', + type: 'address', + }, + ], + name: 'deposit', + outputs: [ + { + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + ], + name: 'depositHappened', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'finalizeWithdrawal', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + ], + name: 'isWithdrawalFinalizedShared', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + ], + name: 'l2BridgeAddress', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/src/contracts/IL1ERC20Bridge.ts b/src/contracts/IL1ERC20Bridge.ts new file mode 100644 index 0000000..ee84bc5 --- /dev/null +++ b/src/contracts/IL1ERC20Bridge.ts @@ -0,0 +1,377 @@ +export const IL1BridgeABI = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ClaimedFailedDeposit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'l2DepositTxHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'DepositInitiated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'WithdrawalFinalized', + type: 'event', + }, + { + inputs: [ + { + internalType: 'address', + name: '_depositSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'claimFailedDeposit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_l2Receiver', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasPerPubdataByte', + type: 'uint256', + }, + ], + name: 'deposit', + outputs: [ + { + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_l2Receiver', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasPerPubdataByte', + type: 'uint256', + }, + { + internalType: 'address', + name: '_refundRecipient', + type: 'address', + }, + ], + name: 'deposit', + outputs: [ + { + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_account', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'bytes32', + name: '_depositL2TxHash', + type: 'bytes32', + }, + ], + name: 'depositAmount', + outputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'finalizeWithdrawal', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + ], + name: 'isWithdrawalFinalized', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'l2Bridge', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + ], + name: 'l2TokenAddress', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'l2TokenBeacon', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'sharedBridge', + outputs: [ + { + internalType: 'contract IL1SharedBridge', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'transferTokenToSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/IL1Messenger.ts b/src/contracts/IL1Messenger.ts new file mode 100644 index 0000000..47627a4 --- /dev/null +++ b/src/contracts/IL1Messenger.ts @@ -0,0 +1,146 @@ +export const IL1MessengerABI = [ + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + ], + name: 'BytecodeL1PublicationRequested', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: '_sender', + type: 'address', + }, + { + indexed: true, + internalType: 'bytes32', + name: '_hash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + ], + name: 'L1MessageSent', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + internalType: 'uint8', + name: 'l2ShardId', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isService', + type: 'bool', + }, + { + internalType: 'uint16', + name: 'txNumberInBlock', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'key', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'value', + type: 'bytes32', + }, + ], + indexed: false, + internalType: 'struct L2ToL1Log', + name: '_l2log', + type: 'tuple', + }, + ], + name: 'L2ToL1LogSent', + type: 'event', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '_bytecodeHash', + type: 'bytes32', + }, + ], + name: 'requestBytecodeL1Publication', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bool', + name: '_isService', + type: 'bool', + }, + { + internalType: 'bytes32', + name: '_key', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_value', + type: 'bytes32', + }, + ], + name: 'sendL2ToL1Log', + outputs: [ + { + internalType: 'uint256', + name: 'logIdInMerkleTree', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + ], + name: 'sendToL1', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/IL1SharedBridge.ts b/src/contracts/IL1SharedBridge.ts new file mode 100644 index 0000000..17d7b60 --- /dev/null +++ b/src/contracts/IL1SharedBridge.ts @@ -0,0 +1,692 @@ +export const Abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'BridgehubDepositBaseTokenInitiated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'txDataHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'l2DepositTxHash', + type: 'bytes32', + }, + ], + name: 'BridgehubDepositFinalized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'txDataHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'BridgehubDepositInitiated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'ClaimedFailedDepositSharedBridge', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'l2DepositTxHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'LegacyDepositInitiated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'chainId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'WithdrawalFinalizedSharedBridge', + type: 'event', + }, + { + inputs: [], + name: 'bridgehub', + outputs: [ + { + internalType: 'contract IBridgehub', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_txDataHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_txHash', + type: 'bytes32', + }, + ], + name: 'bridgehubConfirmL2Transaction', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_prevMsgSender', + type: 'address', + }, + { + internalType: 'uint256', + name: '_l2Value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, + ], + name: 'bridgehubDeposit', + outputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'magicValue', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'l2Contract', + type: 'address', + }, + { + internalType: 'bytes', + name: 'l2Calldata', + type: 'bytes', + }, + { + internalType: 'bytes[]', + name: 'factoryDeps', + type: 'bytes[]', + }, + { + internalType: 'bytes32', + name: 'txDataHash', + type: 'bytes32', + }, + ], + internalType: 'struct L2TransactionRequestTwoBridgesInner', + name: 'request', + type: 'tuple', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_prevMsgSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'bridgehubDepositBaseToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'address', + name: '_depositSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'claimFailedDeposit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_depositSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'claimFailedDepositLegacyErc20Bridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + ], + name: 'depositHappened', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_msgSender', + type: 'address', + }, + { + internalType: 'address', + name: '_l2Receiver', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2TxGasPerPubdataByte', + type: 'uint256', + }, + { + internalType: 'address', + name: '_refundRecipient', + type: 'address', + }, + ], + name: 'depositLegacyErc20Bridge', + outputs: [ + { + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'finalizeWithdrawal', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'finalizeWithdrawalLegacyErc20Bridge', + outputs: [ + { + internalType: 'address', + name: 'l1Receiver', + type: 'address', + }, + { + internalType: 'address', + name: 'l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + ], + name: 'isWithdrawalFinalized', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'l1WethAddress', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + ], + name: 'l2BridgeAddress', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'legacyBridge', + outputs: [ + { + internalType: 'contract IL1ERC20Bridge', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + ], + name: 'receiveEth', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_eraFirstPostUpgradeBatch', + type: 'uint256', + }, + ], + name: 'setEraFirstPostUpgradeBatch', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/L2Bridge.ts b/src/contracts/IL2Bridge.ts similarity index 98% rename from src/contracts/L2Bridge.ts rename to src/contracts/IL2Bridge.ts index 31e9f8f..51e177e 100644 --- a/src/contracts/L2Bridge.ts +++ b/src/contracts/IL2Bridge.ts @@ -1,5 +1,5 @@ // https://docs.zksync.io/build/sdks/go/types/types.html#l2bridgecontracts -export const L2BridgeAbi = [ +export const IL2BridgeABI = [ { inputs: [ { diff --git a/src/contracts/INonceHolder.ts b/src/contracts/INonceHolder.ts new file mode 100644 index 0000000..bd4f685 --- /dev/null +++ b/src/contracts/INonceHolder.ts @@ -0,0 +1,219 @@ +export const INonceHolderABI = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'accountAddress', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'key', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'ValueSetUnderNonce', + type: 'event', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'getDeploymentNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'getMinNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'getRawNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_key', + type: 'uint256', + }, + ], + name: 'getValueUnderNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_value', + type: 'uint256', + }, + ], + name: 'increaseMinNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'incrementDeploymentNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_expectedNonce', + type: 'uint256', + }, + ], + name: 'incrementMinNonceIfEquals', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + { + internalType: 'uint256', + name: '_nonce', + type: 'uint256', + }, + ], + name: 'isNonceUsed', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_key', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_value', + type: 'uint256', + }, + ], + name: 'setValueUnderNonce', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + { + internalType: 'uint256', + name: '_key', + type: 'uint256', + }, + { + internalType: 'bool', + name: '_shouldBeUsed', + type: 'bool', + }, + ], + name: 'validateNonceUsage', + outputs: [], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/src/contracts/IPaymasterFlow.ts b/src/contracts/IPaymasterFlow.ts new file mode 100644 index 0000000..5c1f3a3 --- /dev/null +++ b/src/contracts/IPaymasterFlow.ts @@ -0,0 +1,38 @@ +export const Abi = [ + { + inputs: [ + { + internalType: 'address', + name: '_token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_minAllowance', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_innerInput', + type: 'bytes', + }, + ], + name: 'approvalBased', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'input', + type: 'bytes', + }, + ], + name: 'general', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/ITestnetERC20Token.ts b/src/contracts/ITestnetERC20Token.ts new file mode 100644 index 0000000..c1e590d --- /dev/null +++ b/src/contracts/ITestnetERC20Token.ts @@ -0,0 +1,39 @@ +export const Abi = [ + { + inputs: [], + name: 'decimals', + outputs: [ + { + internalType: 'uint8', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_to', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'mint', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/IZkSync.ts b/src/contracts/IZkSync.ts new file mode 100644 index 0000000..bff7c89 --- /dev/null +++ b/src/contracts/IZkSync.ts @@ -0,0 +1,1622 @@ +export const Abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'batchNumber', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + name: 'BlockCommit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'batchNumber', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + name: 'BlockExecution', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'totalBatchesCommitted', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'totalBatchesVerified', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'totalBatchesExecuted', + type: 'uint256', + }, + ], + name: 'BlocksRevert', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'previousLastVerifiedBatch', + type: 'uint256', + }, + { + indexed: true, + internalType: 'uint256', + name: 'currentLastVerifiedBatch', + type: 'uint256', + }, + ], + name: 'BlocksVerification', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'EthWithdrawalFinalized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + { + internalType: 'enum Diamond.Action', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct Diamond.FacetCut[]', + name: 'facetCuts', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'initAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: 'initCalldata', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct Diamond.DiamondCutData', + name: 'diamondCut', + type: 'tuple', + }, + ], + name: 'ExecuteUpgrade', + type: 'event', + }, + { + anonymous: false, + inputs: [], + name: 'Freeze', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bool', + name: 'isPorterAvailable', + type: 'bool', + }, + ], + name: 'IsPorterAvailableStatusUpdate', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newAdmin', + type: 'address', + }, + ], + name: 'NewAdmin', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldGovernor', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newGovernor', + type: 'address', + }, + ], + name: 'NewGovernor', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldPendingAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newPendingAdmin', + type: 'address', + }, + ], + name: 'NewPendingAdmin', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldPendingGovernor', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newPendingGovernor', + type: 'address', + }, + ], + name: 'NewPendingGovernor', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'txId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'uint64', + name: 'expirationTimestamp', + type: 'uint64', + }, + { + components: [ + { + internalType: 'uint256', + name: 'txType', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'from', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'to', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'gasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'gasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'maxFeePerGas', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'maxPriorityFeePerGas', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'paymaster', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256[4]', + name: 'reserved', + type: 'uint256[4]', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + { + internalType: 'uint256[]', + name: 'factoryDeps', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'paymasterInput', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'reservedDynamic', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct IMailbox.L2CanonicalTransaction', + name: 'transaction', + type: 'tuple', + }, + { + indexed: false, + internalType: 'bytes[]', + name: 'factoryDeps', + type: 'bytes[]', + }, + ], + name: 'NewPriorityRequest', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'oldPriorityTxMaxGasLimit', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'newPriorityTxMaxGasLimit', + type: 'uint256', + }, + ], + name: 'NewPriorityTxMaxGasLimit', + type: 'event', + }, + { + anonymous: false, + inputs: [], + name: 'Unfreeze', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'validatorAddress', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'isActive', + type: 'bool', + }, + ], + name: 'ValidatorStatusUpdate', + type: 'event', + }, + { + inputs: [], + name: 'acceptAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'acceptGovernor', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo', + name: '_lastCommittedBatchData', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'timestamp', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'newStateRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'bootloaderHeapInitialContentsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'eventsQueueStateHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: 'systemLogs', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'totalL2ToL1Pubdata', + type: 'bytes', + }, + ], + internalType: 'struct IExecutor.CommitBatchInfo[]', + name: '_newBatchesData', + type: 'tuple[]', + }, + ], + name: 'commitBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo[]', + name: '_batchesData', + type: 'tuple[]', + }, + ], + name: 'executeBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + { + internalType: 'enum Diamond.Action', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct Diamond.FacetCut[]', + name: 'facetCuts', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'initAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: 'initCalldata', + type: 'bytes', + }, + ], + internalType: 'struct Diamond.DiamondCutData', + name: '_diamondCut', + type: 'tuple', + }, + ], + name: 'executeUpgrade', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'facetAddress', + outputs: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'facetAddresses', + outputs: [ + { + internalType: 'address[]', + name: 'facets', + type: 'address[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_facet', + type: 'address', + }, + ], + name: 'facetFunctionSelectors', + outputs: [ + { + internalType: 'bytes4[]', + name: '', + type: 'bytes4[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'facets', + outputs: [ + { + components: [ + { + internalType: 'address', + name: 'addr', + type: 'address', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct IGetters.Facet[]', + name: '', + type: 'tuple[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'finalizeEthWithdrawal', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'freezeDiamond', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'getFirstUnprocessedPriorityTx', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getGovernor', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2BootloaderBytecodeHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2DefaultAccountBytecodeHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2SystemContractsUpgradeBatchNumber', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2SystemContractsUpgradeTxHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getName', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPendingGovernor', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPriorityQueueSize', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPriorityTxMaxGasLimit', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getProtocolVersion', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalBatchesCommitted', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalBatchesExecuted', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalBatchesVerified', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalPriorityTxs', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVerifier', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVerifierParams', + outputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'recursionNodeLevelVkHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'recursionLeafLevelVkHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'recursionCircuitsSetVksHash', + type: 'bytes32', + }, + ], + internalType: 'struct VerifierParams', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'isDiamondStorageFrozen', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + ], + name: 'isEthWithdrawalFinalized', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_facet', + type: 'address', + }, + ], + name: 'isFacetFreezable', + outputs: [ + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'isFunctionFreezable', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'isValidator', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + ], + name: 'l2LogsRootHash', + outputs: [ + { + internalType: 'bytes32', + name: 'hash', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_gasPrice', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasPerPubdataByteLimit', + type: 'uint256', + }, + ], + name: 'l2TransactionBaseCost', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'priorityQueueFrontOperation', + outputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'expirationTimestamp', + type: 'uint64', + }, + { + internalType: 'uint192', + name: 'layer2Tip', + type: 'uint192', + }, + ], + internalType: 'struct PriorityOperation', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo', + name: '_prevBatch', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo[]', + name: '_committedBatches', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256[]', + name: 'recursiveAggregationInput', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'serializedProof', + type: 'uint256[]', + }, + ], + internalType: 'struct IExecutor.ProofInput', + name: '_proof', + type: 'tuple', + }, + ], + name: 'proveBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + { + internalType: 'enum TxStatus', + name: '_status', + type: 'uint8', + }, + ], + name: 'proveL1ToL2TransactionStatus', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_index', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint8', + name: 'l2ShardId', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isService', + type: 'bool', + }, + { + internalType: 'uint16', + name: 'txNumberInBatch', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'key', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'value', + type: 'bytes32', + }, + ], + internalType: 'struct L2Log', + name: '_log', + type: 'tuple', + }, + { + internalType: 'bytes32[]', + name: '_proof', + type: 'bytes32[]', + }, + ], + name: 'proveL2LogInclusion', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_index', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint16', + name: 'txNumberInBatch', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + internalType: 'struct L2Message', + name: '_message', + type: 'tuple', + }, + { + internalType: 'bytes32[]', + name: '_proof', + type: 'bytes32[]', + }, + ], + name: 'proveL2MessageInclusion', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_contractL2', + type: 'address', + }, + { + internalType: 'uint256', + name: '_l2Value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_calldata', + type: 'bytes', + }, + { + internalType: 'uint256', + name: '_l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'bytes[]', + name: '_factoryDeps', + type: 'bytes[]', + }, + { + internalType: 'address', + name: '_refundRecipient', + type: 'address', + }, + ], + name: 'requestL2Transaction', + outputs: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_newLastBatch', + type: 'uint256', + }, + ], + name: 'revertBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_newPendingAdmin', + type: 'address', + }, + ], + name: 'setPendingAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_newPendingGovernor', + type: 'address', + }, + ], + name: 'setPendingGovernor', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bool', + name: '_zkPorterIsAvailable', + type: 'bool', + }, + ], + name: 'setPorterAvailability', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_newPriorityTxMaxGasLimit', + type: 'uint256', + }, + ], + name: 'setPriorityTxMaxGasLimit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_validator', + type: 'address', + }, + { + internalType: 'bool', + name: '_active', + type: 'bool', + }, + ], + name: 'setValidator', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + ], + name: 'storedBatchHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'unfreezeDiamond', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/contracts/IZkSyncStateTransition.ts b/src/contracts/IZkSyncStateTransition.ts new file mode 100644 index 0000000..71f4d09 --- /dev/null +++ b/src/contracts/IZkSyncStateTransition.ts @@ -0,0 +1,2403 @@ +export const IZkSyncABI = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'batchNumber', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + name: 'BlockCommit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'batchNumber', + type: 'uint256', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + name: 'BlockExecution', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'totalBatchesCommitted', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'totalBatchesVerified', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'totalBatchesExecuted', + type: 'uint256', + }, + ], + name: 'BlocksRevert', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'previousLastVerifiedBatch', + type: 'uint256', + }, + { + indexed: true, + internalType: 'uint256', + name: 'currentLastVerifiedBatch', + type: 'uint256', + }, + ], + name: 'BlocksVerification', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'EthWithdrawalFinalized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + { + internalType: 'enum Diamond.Action', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct Diamond.FacetCut[]', + name: 'facetCuts', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'initAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: 'initCalldata', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct Diamond.DiamondCutData', + name: 'diamondCut', + type: 'tuple', + }, + ], + name: 'ExecuteUpgrade', + type: 'event', + }, + { + anonymous: false, + inputs: [], + name: 'Freeze', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bool', + name: 'isPorterAvailable', + type: 'bool', + }, + ], + name: 'IsPorterAvailableStatusUpdate', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newAdmin', + type: 'address', + }, + ], + name: 'NewAdmin', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint128', + name: 'oldNominator', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint128', + name: 'oldDenominator', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint128', + name: 'newNominator', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint128', + name: 'newDenominator', + type: 'uint128', + }, + ], + name: 'NewBaseTokenMultiplier', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + internalType: 'enum PubdataPricingMode', + name: 'pubdataPricingMode', + type: 'uint8', + }, + { + internalType: 'uint32', + name: 'batchOverheadL1Gas', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'maxPubdataPerBatch', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'maxL2GasPerBatch', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'priorityTxMaxPubdata', + type: 'uint32', + }, + { + internalType: 'uint64', + name: 'minimalL2GasPrice', + type: 'uint64', + }, + ], + indexed: false, + internalType: 'struct FeeParams', + name: 'oldFeeParams', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum PubdataPricingMode', + name: 'pubdataPricingMode', + type: 'uint8', + }, + { + internalType: 'uint32', + name: 'batchOverheadL1Gas', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'maxPubdataPerBatch', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'maxL2GasPerBatch', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'priorityTxMaxPubdata', + type: 'uint32', + }, + { + internalType: 'uint64', + name: 'minimalL2GasPrice', + type: 'uint64', + }, + ], + indexed: false, + internalType: 'struct FeeParams', + name: 'newFeeParams', + type: 'tuple', + }, + ], + name: 'NewFeeParams', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'oldPendingAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newPendingAdmin', + type: 'address', + }, + ], + name: 'NewPendingAdmin', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'txId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'uint64', + name: 'expirationTimestamp', + type: 'uint64', + }, + { + components: [ + { + internalType: 'uint256', + name: 'txType', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'from', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'to', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'gasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'gasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'maxFeePerGas', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'maxPriorityFeePerGas', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'paymaster', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256[4]', + name: 'reserved', + type: 'uint256[4]', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + { + internalType: 'uint256[]', + name: 'factoryDeps', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'paymasterInput', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'reservedDynamic', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct L2CanonicalTransaction', + name: 'transaction', + type: 'tuple', + }, + { + indexed: false, + internalType: 'bytes[]', + name: 'factoryDeps', + type: 'bytes[]', + }, + ], + name: 'NewPriorityRequest', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'oldPriorityTxMaxGasLimit', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'newPriorityTxMaxGasLimit', + type: 'uint256', + }, + ], + name: 'NewPriorityTxMaxGasLimit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'oldTransactionFilterer', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'newTransactionFilterer', + type: 'address', + }, + ], + name: 'NewTransactionFilterer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + { + internalType: 'enum Diamond.Action', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct Diamond.FacetCut[]', + name: 'facetCuts', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'initAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: 'initCalldata', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct Diamond.DiamondCutData', + name: 'diamondCut', + type: 'tuple', + }, + { + indexed: true, + internalType: 'uint256', + name: 'proposalId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes32', + name: 'proposalSalt', + type: 'bytes32', + }, + ], + name: 'ProposeTransparentUpgrade', + type: 'event', + }, + { + anonymous: false, + inputs: [], + name: 'Unfreeze', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'validatorAddress', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'isActive', + type: 'bool', + }, + ], + name: 'ValidatorStatusUpdate', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'enum PubdataPricingMode', + name: 'validiumMode', + type: 'uint8', + }, + ], + name: 'ValidiumModeStatusUpdate', + type: 'event', + }, + { + inputs: [], + name: 'acceptAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'baseTokenGasPriceMultiplierDenominator', + outputs: [ + { + internalType: 'uint128', + name: '', + type: 'uint128', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'baseTokenGasPriceMultiplierNominator', + outputs: [ + { + internalType: 'uint128', + name: '', + type: 'uint128', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'address', + name: 'contractL2', + type: 'address', + }, + { + internalType: 'uint256', + name: 'mintValue', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'l2Value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'l2Calldata', + type: 'bytes', + }, + { + internalType: 'uint256', + name: 'l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'l2GasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'bytes[]', + name: 'factoryDeps', + type: 'bytes[]', + }, + { + internalType: 'address', + name: 'refundRecipient', + type: 'address', + }, + ], + internalType: 'struct BridgehubL2TransactionRequest', + name: '_request', + type: 'tuple', + }, + ], + name: 'bridgehubRequestL2Transaction', + outputs: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'enum PubdataPricingMode', + name: 'pubdataPricingMode', + type: 'uint8', + }, + { + internalType: 'uint32', + name: 'batchOverheadL1Gas', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'maxPubdataPerBatch', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'maxL2GasPerBatch', + type: 'uint32', + }, + { + internalType: 'uint32', + name: 'priorityTxMaxPubdata', + type: 'uint32', + }, + { + internalType: 'uint64', + name: 'minimalL2GasPrice', + type: 'uint64', + }, + ], + internalType: 'struct FeeParams', + name: '_newFeeParams', + type: 'tuple', + }, + ], + name: 'changeFeeParams', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo', + name: '_lastCommittedBatchData', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'timestamp', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'newStateRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'bootloaderHeapInitialContentsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'eventsQueueStateHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: 'systemLogs', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'pubdataCommitments', + type: 'bytes', + }, + ], + internalType: 'struct IExecutor.CommitBatchInfo[]', + name: '_newBatchesData', + type: 'tuple[]', + }, + ], + name: 'commitBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo', + name: '_lastCommittedBatchData', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'timestamp', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'newStateRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'bootloaderHeapInitialContentsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'eventsQueueStateHash', + type: 'bytes32', + }, + { + internalType: 'bytes', + name: 'systemLogs', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'pubdataCommitments', + type: 'bytes', + }, + ], + internalType: 'struct IExecutor.CommitBatchInfo[]', + name: '_newBatchesData', + type: 'tuple[]', + }, + ], + name: 'commitBatchesSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo[]', + name: '_batchesData', + type: 'tuple[]', + }, + ], + name: 'executeBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo[]', + name: '_batchesData', + type: 'tuple[]', + }, + ], + name: 'executeBatchesSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + { + internalType: 'enum Diamond.Action', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct Diamond.FacetCut[]', + name: 'facetCuts', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'initAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: 'initCalldata', + type: 'bytes', + }, + ], + internalType: 'struct Diamond.DiamondCutData', + name: '_diamondCut', + type: 'tuple', + }, + ], + name: 'executeUpgrade', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'facetAddress', + outputs: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'facetAddresses', + outputs: [ + { + internalType: 'address[]', + name: 'facets', + type: 'address[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_facet', + type: 'address', + }, + ], + name: 'facetFunctionSelectors', + outputs: [ + { + internalType: 'bytes4[]', + name: '', + type: 'bytes4[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'facets', + outputs: [ + { + components: [ + { + internalType: 'address', + name: 'addr', + type: 'address', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct IGetters.Facet[]', + name: '', + type: 'tuple[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes', + name: '_message', + type: 'bytes', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + ], + name: 'finalizeEthWithdrawal', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'freezeDiamond', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'getAdmin', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getBaseToken', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getBaseTokenBridge', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getBridgehub', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getFirstUnprocessedPriorityTx', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2BootloaderBytecodeHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2DefaultAccountBytecodeHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2SystemContractsUpgradeBatchNumber', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL2SystemContractsUpgradeTxHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getName', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPendingAdmin', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPriorityQueueSize', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPriorityTxMaxGasLimit', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getProtocolVersion', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPubdataPricingMode', + outputs: [ + { + internalType: 'enum PubdataPricingMode', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getStateTransitionManager', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalBatchesCommitted', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalBatchesExecuted', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalBatchesVerified', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalPriorityTxs', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVerifier', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getVerifierParams', + outputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'recursionNodeLevelVkHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'recursionLeafLevelVkHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'recursionCircuitsSetVksHash', + type: 'bytes32', + }, + ], + internalType: 'struct VerifierParams', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'isDiamondStorageFrozen', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + ], + name: 'isEthWithdrawalFinalized', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_facet', + type: 'address', + }, + ], + name: 'isFacetFreezable', + outputs: [ + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_selector', + type: 'bytes4', + }, + ], + name: 'isFunctionFreezable', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_address', + type: 'address', + }, + ], + name: 'isValidator', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + ], + name: 'l2LogsRootHash', + outputs: [ + { + internalType: 'bytes32', + name: 'merkleRoot', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_gasPrice', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasPerPubdataByteLimit', + type: 'uint256', + }, + ], + name: 'l2TransactionBaseCost', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'priorityQueueFrontOperation', + outputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'expirationTimestamp', + type: 'uint64', + }, + { + internalType: 'uint192', + name: 'layer2Tip', + type: 'uint192', + }, + ], + internalType: 'struct PriorityOperation', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo', + name: '_prevBatch', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo[]', + name: '_committedBatches', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256[]', + name: 'recursiveAggregationInput', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'serializedProof', + type: 'uint256[]', + }, + ], + internalType: 'struct IExecutor.ProofInput', + name: '_proof', + type: 'tuple', + }, + ], + name: 'proveBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo', + name: '_prevBatch', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint64', + name: 'batchNumber', + type: 'uint64', + }, + { + internalType: 'bytes32', + name: 'batchHash', + type: 'bytes32', + }, + { + internalType: 'uint64', + name: 'indexRepeatedStorageChanges', + type: 'uint64', + }, + { + internalType: 'uint256', + name: 'numberOfLayer1Txs', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'priorityOperationsHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'l2LogsTreeRoot', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'timestamp', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + ], + internalType: 'struct IExecutor.StoredBatchInfo[]', + name: '_committedBatches', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256[]', + name: 'recursiveAggregationInput', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'serializedProof', + type: 'uint256[]', + }, + ], + internalType: 'struct IExecutor.ProofInput', + name: '_proof', + type: 'tuple', + }, + ], + name: 'proveBatchesSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '_l2TxHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '_l2BatchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2MessageIndex', + type: 'uint256', + }, + { + internalType: 'uint16', + name: '_l2TxNumberInBatch', + type: 'uint16', + }, + { + internalType: 'bytes32[]', + name: '_merkleProof', + type: 'bytes32[]', + }, + { + internalType: 'enum TxStatus', + name: '_status', + type: 'uint8', + }, + ], + name: 'proveL1ToL2TransactionStatus', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_index', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint8', + name: 'l2ShardId', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isService', + type: 'bool', + }, + { + internalType: 'uint16', + name: 'txNumberInBatch', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'key', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'value', + type: 'bytes32', + }, + ], + internalType: 'struct L2Log', + name: '_log', + type: 'tuple', + }, + { + internalType: 'bytes32[]', + name: '_proof', + type: 'bytes32[]', + }, + ], + name: 'proveL2LogInclusion', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_index', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint16', + name: 'txNumberInBatch', + type: 'uint16', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + internalType: 'struct L2Message', + name: '_message', + type: 'tuple', + }, + { + internalType: 'bytes32[]', + name: '_proof', + type: 'bytes32[]', + }, + ], + name: 'proveL2MessageInclusion', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_contractL2', + type: 'address', + }, + { + internalType: 'uint256', + name: '_l2Value', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_calldata', + type: 'bytes', + }, + { + internalType: 'uint256', + name: '_l2GasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_l2GasPerPubdataByteLimit', + type: 'uint256', + }, + { + internalType: 'bytes[]', + name: '_factoryDeps', + type: 'bytes[]', + }, + { + internalType: 'address', + name: '_refundRecipient', + type: 'address', + }, + ], + name: 'requestL2Transaction', + outputs: [ + { + internalType: 'bytes32', + name: 'canonicalTxHash', + type: 'bytes32', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_newLastBatch', + type: 'uint256', + }, + ], + name: 'revertBatches', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_chainId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_newLastBatch', + type: 'uint256', + }, + ], + name: 'revertBatchesSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_newPendingAdmin', + type: 'address', + }, + ], + name: 'setPendingAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bool', + name: '_zkPorterIsAvailable', + type: 'bool', + }, + ], + name: 'setPorterAvailability', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_newPriorityTxMaxGasLimit', + type: 'uint256', + }, + ], + name: 'setPriorityTxMaxGasLimit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint128', + name: '_nominator', + type: 'uint128', + }, + { + internalType: 'uint128', + name: '_denominator', + type: 'uint128', + }, + ], + name: 'setTokenMultiplier', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_transactionFilterer', + type: 'address', + }, + ], + name: 'setTransactionFilterer', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_validator', + type: 'address', + }, + { + internalType: 'bool', + name: '_active', + type: 'bool', + }, + ], + name: 'setValidator', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'enum PubdataPricingMode', + name: '_validiumMode', + type: 'uint8', + }, + ], + name: 'setValidiumMode', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_batchNumber', + type: 'uint256', + }, + ], + name: 'storedBatchHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'transferEthToSharedBridge', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'unfreezeDiamond', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_protocolVersion', + type: 'uint256', + }, + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'facet', + type: 'address', + }, + { + internalType: 'enum Diamond.Action', + name: 'action', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'isFreezable', + type: 'bool', + }, + { + internalType: 'bytes4[]', + name: 'selectors', + type: 'bytes4[]', + }, + ], + internalType: 'struct Diamond.FacetCut[]', + name: 'facetCuts', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'initAddress', + type: 'address', + }, + { + internalType: 'bytes', + name: 'initCalldata', + type: 'bytes', + }, + ], + internalType: 'struct Diamond.DiamondCutData', + name: '_cutData', + type: 'tuple', + }, + ], + name: 'upgradeChainFromVersion', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/index.ts b/src/index.ts index 4098ae4..31a1263 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,7 @@ export { ZkSyncPlugin } from './plugin'; -export * from './types'; -export * from './constants'; +export { Web3ZkSyncL1 } from './web3zksync-l1'; +export { Web3ZkSyncL2 } from './web3zksync-l2'; +export { ZKSyncWallet } from './zksync-wallet'; +export * as utils from './utils'; +export * as types from './types'; +export * as constants from './constants'; diff --git a/src/paymaster-utils.ts b/src/paymaster-utils.ts new file mode 100644 index 0000000..0d11837 --- /dev/null +++ b/src/paymaster-utils.ts @@ -0,0 +1,81 @@ +import { Contract } from 'web3-eth-contract'; + +import type { + Address, + ApprovalBasedPaymasterInput, + GeneralPaymasterInput, + PaymasterInput, + PaymasterParams, +} from './types'; +import { Abi } from './contracts/IPaymasterFlow'; + +/** + * The ABI for the `IPaymasterFlow` interface, which is utilized + * for encoding input parameters for paymaster flows. + * @constant + */ +export const PAYMASTER_FLOW_ABI = new Contract(Abi); + +/** + * Returns encoded input for an approval-based paymaster. + * + * @param paymasterInput The input data for the paymaster. + */ +export function getApprovalBasedPaymasterInput(paymasterInput: ApprovalBasedPaymasterInput) { + return PAYMASTER_FLOW_ABI.methods + .approvalBased( + paymasterInput.token, + paymasterInput.minimalAllowance, + paymasterInput.innerInput, + ) + .encodeABI(); +} + +/** + * Returns encoded input for a general-based paymaster. + * + * @param paymasterInput The input data for the paymaster. + */ +export function getGeneralPaymasterInput(paymasterInput: GeneralPaymasterInput) { + return PAYMASTER_FLOW_ABI.methods.general(paymasterInput.innerInput).encodeABI(); +} + +/** + * Returns a correctly-formed {@link PaymasterParams|paymasterParams} object for common paymaster flows. + * + * @param paymasterAddress The non-zero paymaster address. + * @param paymasterInput The input data for the paymaster. + * + * @example Create general-based parameters. + * + * const paymasterAddress = "0x0a67078A35745947A37A552174aFe724D8180c25"; + * const paymasterParams = utils.getPaymasterParams(paymasterAddress, { + * type: "General", + * innerInput: new Uint8Array(), + * }); + * + * @example Create approval-based parameters. + * + * const result = utils.getPaymasterParams("0x0a67078A35745947A37A552174aFe724D8180c25", { + * type: "ApprovalBased", + * token: "0x65C899B5fb8Eb9ae4da51D67E1fc417c7CB7e964", + * minimalAllowance: BigInt(1), + * innerInput: new Uint8Array(), + * }); + */ +export function getPaymasterParams( + paymasterAddress: Address, + paymasterInput: PaymasterInput, +): PaymasterParams { + if (paymasterInput.type === 'General') { + return { + paymaster: paymasterAddress, + paymasterInput: getGeneralPaymasterInput(paymasterInput), + }; + } else { + return { + paymaster: paymasterAddress, + paymasterInput: getApprovalBasedPaymasterInput(paymasterInput), + }; + } +} diff --git a/src/plugin.ts b/src/plugin.ts index 606d44f..a90a3e3 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,30 +1,235 @@ -import type { Address } from 'web3'; -import { Web3PluginBase, Contract } from 'web3'; -import type { Web3RequestManager } from 'web3-core'; -import { ERC20TokenAbi } from './contracts/ERC20Token'; +import type { Web3Context, Web3ContextInitOptions, Web3RequestManager } from 'web3-core'; +import type * as web3Types from 'web3-types'; +import type { Address } from 'web3-types'; +import { Contract } from 'web3-eth-contract'; +import { Web3PluginBase } from 'web3-core'; + +import { TransactionFactory } from 'web3-eth-accounts'; +import { IERC20ABI } from './contracts/IERC20'; import { RpcMethods } from './rpc.methods'; -import { ETH_ADDRESS, ZERO_ADDRESS } from './constants'; -import { L2BridgeAbi } from './contracts/L2Bridge'; +import * as constants from './constants'; +import { IL2BridgeABI } from './contracts/IL2Bridge'; +import { IZkSyncABI } from './contracts/IZkSyncStateTransition'; +import { IBridgehubABI } from './contracts/IBridgehub'; +import { IContractDeployerABI } from './contracts/IContractDeployer'; +import { IL1MessengerABI } from './contracts/IL1Messenger'; +import { IERC1271ABI } from './contracts/IERC1271'; +import { IL1BridgeABI } from './contracts/IL1ERC20Bridge'; +import { INonceHolderABI } from './contracts/INonceHolder'; +import { EIP712Transaction } from './Eip712'; +import { ZKSyncWallet } from './zksync-wallet'; +import { Web3ZkSyncL2 } from './web3zksync-l2'; +import { Web3ZkSyncL1 } from './web3zksync-l1'; +import type { ContractsAddresses } from './types'; + +interface ZKSyncWalletConstructor { + new (privateKey: string): ZKSyncWallet; +} + +export type ZKSyncContractsCollection = { + Generic: { + /** + * The web3.js Contract instance for the `IERC20` interface, which is utilized for interacting with ERC20 tokens. + */ + IERC20Contract: Contract; + /** + * The web3.js Contract instance for the `IERC1271` interface, which is utilized for signature validation by contracts. + */ + IERC1271Contract: Contract; + }; + L1: { + /** + * The web3.js Contract instance for the `ZkSync` interface. + */ + ZkSyncMainContract: Contract; + /** + * The ABI of the `Bridgehub` interface. + */ + BridgehubContract: Contract; + /** + * The web3.js Contract instance for the `IL1Bridge` interface, which is utilized for transferring ERC20 tokens from L1 to L2. + */ + L1BridgeContract: Contract; + }; + L2: { + /** + * The web3.js Contract instance for the `IContractDeployer` interface, which is utilized for deploying smart contracts. + */ + ContractDeployerContract: Contract; + /** + * The web3.js Contract instance for the `IL1Messenger` interface, which is utilized for sending messages from the L2 to L1. + */ + L1MessengerContract: Contract; + /** + * The web3.js Contract instance for the `IL2Bridge` interface, which is utilized for transferring ERC20 tokens from L2 to L1. + */ + L2BridgeContract: Contract; + + /** + * The web3.js Contract instance for the `INonceHolder` interface, which is utilized for managing deployment nonces. + */ + NonceHolderContract: Contract; + }; +}; export class ZkSyncPlugin extends Web3PluginBase { + public L1: Web3ZkSyncL1 | undefined; + public L2: Web3ZkSyncL2; public pluginNamespace = 'zkSync'; - public erc20BridgeL1: string; - public erc20BridgeL2: string; - public wethBridgeL1: string; - public wethBridgeL2: string; public _rpc?: RpcMethods; - public _l2BridgeContracts: Record>; - public _erc20Contracts: Record>; + public _l2BridgeContracts: Record>; + public _erc20Contracts: Record>; + + private contracts: ZKSyncContractsCollection | undefined; + public get Contracts(): Promise { + if (this.contracts) { + return Promise.resolve(this.contracts); + } + return this.initContracts(); + } + + contractsAddresses: Promise; + public get ContractsAddresses(): Promise { + if (this.contractsAddresses) { + return Promise.resolve(this.contractsAddresses); + } + return this.initContractsAddresses(); + } - constructor() { - super(); + // the wallet type in this class is different from the parent class. So, they should have different names. + ZkWallet: ZKSyncWalletConstructor; + + /** + * Constructor + * @param providerOrContextL2 - The provider or context for the L2 network + */ + constructor( + providerOrContextL2: + | string + | web3Types.SupportedProviders + | Web3ContextInitOptions + | Web3ZkSyncL2, + ) { + super( + providerOrContextL2 as + | string + | web3Types.SupportedProviders + | Web3ContextInitOptions, + ); + if (providerOrContextL2 instanceof Web3ZkSyncL2) { + this.L2 = providerOrContextL2; + } else { + this.L2 = new Web3ZkSyncL2(providerOrContextL2); + } + // @ts-ignore-next-line + TransactionFactory.registerTransactionType(constants.EIP712_TX_TYPE, EIP712Transaction); - this.erc20BridgeL1 = ''; - this.erc20BridgeL2 = ''; - this.wethBridgeL1 = ''; - this.wethBridgeL2 = ''; this._l2BridgeContracts = {}; this._erc20Contracts = {}; + + this.contractsAddresses = this.initContractsAddresses(); + + const self = this; + class ZKSyncWalletWithFullContext extends ZKSyncWallet { + constructor(privateKey: string) { + super(privateKey, self.L2, self.L1); + } + } + + this.ZkWallet = ZKSyncWalletWithFullContext; + this.initWallet(); + } + + public async initContracts() { + if (!this.L1 || !this.L2) { + throw new Error( + 'Contracts cannot be initialized because a Web3 instance is not yet linked to ZkSync plugin', + ); + } + + const { + mainContract, + bridgehubContractAddress, + // l1Erc20DefaultBridge, + // l2Erc20DefaultBridge, + // l1WethBridge, + // l2WethBridge, + l1SharedDefaultBridge, + l2SharedDefaultBridge, + } = await this.contractsAddresses; + + const contractsCollection: ZKSyncContractsCollection = { + Generic: { + IERC20Contract: new Contract(IERC20ABI), + IERC1271Contract: new Contract(IERC1271ABI), + }, + L1: { + ZkSyncMainContract: new Contract(IZkSyncABI, mainContract, this.L1), + BridgehubContract: new Contract(IBridgehubABI, bridgehubContractAddress, this.L1), + L1BridgeContract: new Contract(IL1BridgeABI, l1SharedDefaultBridge, this.L1), + }, + L2: { + ContractDeployerContract: new Contract( + IContractDeployerABI, + constants.CONTRACT_DEPLOYER_ADDRESS, + this.L2, + ), + L1MessengerContract: new Contract( + IL1MessengerABI, + constants.L1_MESSENGER_ADDRESS, + this.L2, + ), + NonceHolderContract: new Contract( + INonceHolderABI, + constants.NONCE_HOLDER_ADDRESS, + this.L2, + ), + L2BridgeContract: new Contract(IL2BridgeABI, l2SharedDefaultBridge, this.L2), + }, + }; + + this.contracts = contractsCollection; + return contractsCollection; + } + + /** + * Try to fill the contract addresses + * @returns True if the contract addresses were successfully filled, false otherwise + */ + public async initContractsAddresses() { + const [mainContract, bridgehubContractAddress, bridgeContracts] = await Promise.all([ + this.rpc.getMainContract(), + this.rpc.getBridgehubContractAddress(), + this.rpc.getBridgeContracts(), + ]); + this.contractsAddresses = Promise.resolve({ + mainContract, + bridgehubContractAddress, + ...bridgeContracts, + }); + + return this.contractsAddresses; + } + + public link(parentContext: Web3Context): void { + super.link(parentContext); + + this.L1 = new Web3ZkSyncL1(parentContext); + + this.initContracts(); + + this.initWallet(); + } + + private initWallet() { + const self = this; + class ZKSyncWalletWithFullContext extends ZKSyncWallet { + constructor(privateKey: string) { + super(privateKey, self.L2, self.L1); + } + } + + this.ZkWallet = ZKSyncWalletWithFullContext; } /** @@ -33,20 +238,47 @@ export class ZkSyncPlugin extends Web3PluginBase { get rpc(): RpcMethods { if (!this._rpc) { this._rpc = new RpcMethods( - this.requestManager as unknown as Web3RequestManager, + this.L2.requestManager as unknown as Web3RequestManager, ); } return this._rpc; } + /** + * Update the providers + * @param contextL1 - The context for the L1 network + * @param contextL2 - The context for the L2 network + * + * @remarks This method is used to update the providers for the L1 and L2 networks. + * It is very important to call it if one of the providers is changed to a different network. + * For example, if the L1 or L2 providers were changed from testnet to mainnet, this method should be called. + */ + public updateProviders( + contextL1: + | Web3ZkSyncL1 + | web3Types.SupportedProviders + | Web3ContextInitOptions + | string, + contextL2: + | Web3ZkSyncL2 + | web3Types.SupportedProviders + | Web3ContextInitOptions + | string, + ) { + this.L1 = contextL1 instanceof Web3ZkSyncL1 ? contextL1 : new Web3ZkSyncL1(contextL1); + this.L2 = contextL2 instanceof Web3ZkSyncL2 ? contextL2 : new Web3ZkSyncL2(contextL2); + this.initContractsAddresses(); + this.initContracts(); + } + /** * Get L2 bridge contract instance * @param address - Contract address */ - getL2BridgeContract(address: Address): Contract { + getL2BridgeContract(address: Address): Contract { if (!this._l2BridgeContracts[address]) { - this._l2BridgeContracts[address] = new Contract(L2BridgeAbi, address); - this._l2BridgeContracts[address].link(this); + this._l2BridgeContracts[address] = new Contract(IL2BridgeABI, address); + this._l2BridgeContracts[address].link(this.L2); } return this._l2BridgeContracts[address]; } @@ -55,52 +287,28 @@ export class ZkSyncPlugin extends Web3PluginBase { * Get the ERC20 contract instance * @param address - Contract address */ - erc20(address: string): Contract { + erc20(address: string): Contract { if (!this._erc20Contracts[address]) { - this._erc20Contracts[address] = new Contract(ERC20TokenAbi, address); - this._erc20Contracts[address].link(this); + this._erc20Contracts[address] = new Contract(IERC20ABI, address); + this._erc20Contracts[address].link(this.L2); } return this._erc20Contracts[address]; } - /** - * Get the default bridge addresses - */ - async getDefaultBridgeAddresses(): Promise<{ - erc20L1: Address; - erc20L2: Address; - wethL1: Address; - wethL2: Address; - }> { - if (!this.erc20BridgeL1) { - const addresses = await this.rpc.getBridgeContracts(); - this.erc20BridgeL1 = addresses.l1Erc20DefaultBridge; - this.erc20BridgeL2 = addresses.l2Erc20DefaultBridge; - this.wethBridgeL1 = addresses.l1WethBridge; - this.wethBridgeL2 = addresses.l2WethBridge; - } - return { - erc20L1: this.erc20BridgeL1, - erc20L2: this.erc20BridgeL2, - wethL1: this.wethBridgeL1, - wethL2: this.wethBridgeL2, - }; - } - /** * Get the L1 address of a token * @param token - The address of the token */ async getL1Address(token: Address): Promise
{ - if (token == ETH_ADDRESS) { - return ETH_ADDRESS; + if (token == constants.ETH_ADDRESS) { + return constants.ETH_ADDRESS; } else { - const bridgeAddresses = await this.getDefaultBridgeAddresses(); - if (bridgeAddresses.wethL2 !== ZERO_ADDRESS) { + const bridgeAddresses = await this.L2.getDefaultBridgeAddresses(); + if (bridgeAddresses.wethL2 !== constants.ZERO_ADDRESS) { const l2Bridge = this.getL2BridgeContract(bridgeAddresses.wethL2); try { const l1Token = await l2Bridge.methods.l1TokenAddress(token).call(); - if (l1Token !== ZERO_ADDRESS) { + if (l1Token !== constants.ZERO_ADDRESS) { return l1Token; } } catch (e) { @@ -120,15 +328,15 @@ export class ZkSyncPlugin extends Web3PluginBase { * @param token - The address of the token */ async getL2Address(token: Address): Promise { - if (token == ETH_ADDRESS) { - return ETH_ADDRESS; + if (token == constants.ETH_ADDRESS) { + return constants.ETH_ADDRESS; } else { - const bridgeAddresses = await this.getDefaultBridgeAddresses(); - if (bridgeAddresses.wethL2 !== ZERO_ADDRESS) { + const bridgeAddresses = await this.L2.getDefaultBridgeAddresses(); + if (bridgeAddresses.wethL2 !== constants.ZERO_ADDRESS) { const l2Bridge = this.getL2BridgeContract(bridgeAddresses.wethL2); try { const l2WethToken = await l2Bridge.methods.l2TokenAddress(token).call(); - if (l2WethToken !== ZERO_ADDRESS) { + if (l2WethToken !== constants.ZERO_ADDRESS) { return l2WethToken; } } catch (e) { @@ -146,7 +354,7 @@ export class ZkSyncPlugin extends Web3PluginBase { // Module Augmentation declare module 'web3' { - interface Web3Context { + interface Web3 { zkSync: ZkSyncPlugin; } } diff --git a/src/rpc.methods.ts b/src/rpc.methods.ts index 46f2c7d..1868b28 100644 --- a/src/rpc.methods.ts +++ b/src/rpc.methods.ts @@ -1,6 +1,6 @@ import type { Web3RequestManager } from 'web3-core'; -import { format, toNumber } from 'web3-utils'; -import type { Address, Bytes, HexString32Bytes, Numbers, TransactionWithSenderAPI } from 'web3'; +import * as web3Utils from 'web3-utils'; +import type * as web3Types from 'web3-types'; import { DEFAULT_RETURN_FORMAT } from 'web3'; import type { DataFormat } from 'web3-types/src/data_format_types'; import type { @@ -9,10 +9,12 @@ import type { BridgeAddresses, EstimateFee, L2ToL1Proof, - Proof, + StorageProof, RawBlockTransaction, TransactionDetails, WalletBalances, + TransactionRequest, + Address, } from './types'; import { AddressSchema, @@ -32,6 +34,7 @@ import { // The ZkSync methods described here https://docs.zksync.io/build/api.html +// TODO: Think about inheritance from Web3Eth export class RpcMethods { requestManager: Web3RequestManager; @@ -52,7 +55,11 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async l1ChainId(returnFormat: DataFormat = DEFAULT_RETURN_FORMAT): Promise { - return format(IntSchema, await this._send('zks_L1ChainId', []), returnFormat) as bigint; + return web3Utils.format( + IntSchema, + await this._send('zks_L1ChainId', []), + returnFormat, + ) as bigint; } /** @@ -63,7 +70,11 @@ export class RpcMethods { public async getL1BatchNumber( returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format(IntSchema, await this._send('zks_L1BatchNumber', []), returnFormat) as bigint; + return web3Utils.format( + IntSchema, + await this._send('zks_L1BatchNumber', []), + returnFormat, + ) as bigint; } /** @@ -73,13 +84,13 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getL1BatchDetails( - number: Numbers, + number: web3Types.Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format( + return web3Utils.format( BatchDetailsSchema, await this._send('zks_getL1BatchDetails', [ - typeof number === 'number' ? number : Number(toNumber(number)), + typeof number === 'number' ? number : Number(web3Utils.toNumber(number)), ]), returnFormat, ) as BatchDetails; @@ -96,13 +107,13 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getBlockDetails( - number: Numbers, + number: web3Types.Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format( + return web3Utils.format( BlockDetailsSchema, await this._send('zks_getBlockDetails', [ - typeof number === 'number' ? number : Number(toNumber(number)), + typeof number === 'number' ? number : Number(web3Utils.toNumber(number)), ]), returnFormat, ) as BlockDetails; @@ -115,10 +126,10 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getTransactionDetails( - txHash: Bytes, + txHash: web3Types.Bytes, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format( + return web3Utils.format( TransactionDetailsSchema, await this._send('zks_getTransactionDetails', [txHash]), returnFormat, @@ -132,10 +143,10 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getBytecodeByHash( - bytecodeHash: Bytes, + bytecodeHash: web3Types.Bytes, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format( + return web3Utils.format( BytesSchema, await this._send('zks_getBytecodeByHash', [bytecodeHash]), returnFormat, @@ -149,15 +160,19 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getRawBlockTransactions( - number: Numbers, + number: web3Types.Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { const result = await this._send('zks_getRawBlockTransactions', [ - typeof number === 'number' ? number : Number(toNumber(number)), + typeof number === 'number' ? number : Number(web3Utils.toNumber(number)), ]); if (Array.isArray(result)) { return result.map(tx => { - return format(RawBlockTransactionSchema, tx, returnFormat) as RawBlockTransaction; + return web3Utils.format( + RawBlockTransactionSchema, + tx, + returnFormat, + ) as RawBlockTransaction; }); } return []; @@ -170,16 +185,40 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async estimateFee( - transaction: Partial, + transaction: Partial, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format( + return web3Utils.format( EstimateFeeSchema, await this._send('zks_estimateFee', [transaction]), returnFormat, ) as EstimateFee; } + /** + * Returns the L1 base token address. + */ + async getBaseTokenL1Address(): Promise { + const baseTokenL1Address = (await this._send( + 'zks_getBaseTokenL1Address', + [], + )) as web3Types.Address; + + return web3Utils.toChecksumAddress(baseTokenL1Address); + } + + /** + * 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(): Promise { + // 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', [])) as web3Types.Address | null; + } + /** * Returns an estimate of the gas required for a L1 to L2 transaction. * @@ -187,14 +226,14 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async estimateGasL1ToL2( - transaction: Partial, + transaction: Partial, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise { - return format( + ): Promise { + return web3Utils.format( UintSchema, await this._send('zks_estimateGasL1ToL2', [transaction]), returnFormat, - ) as Numbers; + ) as web3Types.Numbers; } /** @@ -204,7 +243,7 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getAllAccountBalances( - address: Address, + address: web3Types.Address, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { const res = (await this._send('zks_getAllAccountBalances', [address])) as WalletBalances; @@ -212,11 +251,11 @@ export class RpcMethods { return {}; } for (let i = 0; i < Object.keys(res).length; i++) { - res[Object.keys(res)[i]] = format( + res[Object.keys(res)[i]] = web3Utils.format( UintSchema, res[Object.keys(res)[i]], returnFormat, - ) as Numbers; + ) as web3Types.Numbers; } return res; @@ -229,12 +268,12 @@ export class RpcMethods { */ public async getMainContract( returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise
{ - return format( + ): Promise { + return web3Utils.format( AddressSchema, await this._send('zks_getMainContract', []), returnFormat, - ) as Address; + ) as web3Types.Address; } /** @@ -245,16 +284,16 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getL1BatchBlockRange( - number: Numbers, + number: web3Types.Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise { - return format( + ): Promise { + return web3Utils.format( BytesArraySchema, await this._send('zks_getL1BatchBlockRange', [ - typeof number === 'number' ? number : Number(toNumber(number)), + typeof number === 'number' ? number : Number(web3Utils.toNumber(number)), ]), returnFormat, - ) as Bytes[]; + ) as web3Types.Bytes[]; } /** @@ -267,20 +306,22 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getProof( - address: Address, + address: web3Types.Address, keys: string[], - l1BatchNumber: Numbers, + l1BatchNumber: web3Types.Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise { + ): Promise { const res = (await this._send('zks_getProof', [ address, keys, - typeof l1BatchNumber === 'number' ? l1BatchNumber : Number(toNumber(l1BatchNumber)), - ])) as Proof; - const result = format(ProofSchema, res, returnFormat) as Proof; + typeof l1BatchNumber === 'number' + ? l1BatchNumber + : Number(web3Utils.toNumber(l1BatchNumber)), + ])) as StorageProof; + const result = web3Utils.format(ProofSchema, res, returnFormat) as StorageProof; result.storageProof = []; for (let i = 0; i < res.storageProof.length; i++) { - result.storageProof[i] = format( + result.storageProof[i] = web3Utils.format( { type: 'object', properties: ProofSchema.properties.storageProof.properties, @@ -293,21 +334,6 @@ export class RpcMethods { return result; } - /** - * Returns the address of the testnet paymaster: the paymaster that is available on testnets and enables paying fees in ERC-20 compatible tokens. - * - * @param returnFormat - The format of the return value. - */ - public async getTestnetPaymaster( - returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, - ): Promise
{ - return format( - AddressSchema, - await this._send('zks_getTestnetPaymaster', []), - returnFormat, - ) as Address; - } - /** * Given a transaction hash, and an index of the L2 to L1 log produced within the transaction, it returns the proof for the corresponding L2 to L1 log. * @@ -318,19 +344,19 @@ export class RpcMethods { * @param returnFormat - The format of the return value. */ public async getL2ToL1LogProof( - txHash: HexString32Bytes, - l2ToL1LogIndex?: Numbers, + txHash: web3Types.HexString32Bytes, + l2ToL1LogIndex?: web3Types.Numbers, returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - const params: [HexString32Bytes, number?] = [txHash]; + const params: [web3Types.HexString32Bytes, number?] = [txHash]; if (l2ToL1LogIndex) { params.push( typeof l2ToL1LogIndex === 'number' ? l2ToL1LogIndex - : Number(toNumber(l2ToL1LogIndex)), + : Number(web3Utils.toNumber(l2ToL1LogIndex)), ); } - return format( + return web3Utils.format( L2ToL1ProofSchema, await this._send('zks_getL2ToL1LogProof', params), returnFormat, @@ -345,10 +371,25 @@ export class RpcMethods { public async getBridgeContracts( returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, ): Promise { - return format( + return web3Utils.format( BridgeAddressesSchema, await this._send('zks_getBridgeContracts', []), returnFormat, ) as BridgeAddresses; } + + /** + * Retrieves the bridge hub contract address + * + * @param returnFormat - The format of the return value. + */ + public async getBridgehubContractAddress( + returnFormat: DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise
{ + return web3Utils.format( + AddressSchema, + await this._send('zks_getBridgehubContract', []), + returnFormat, + ) as Address; + } } diff --git a/src/schemas.ts b/src/schemas.ts index 118624d..8e2c4de 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -1,3 +1,5 @@ +import { transactionReceiptSchema } from 'web3-eth'; + export const AddressSchema = { format: 'address' }; export const IntSchema = { format: 'int' }; export const UintSchema = { format: 'uint' }; @@ -171,6 +173,8 @@ export const BridgeAddressesSchema = { l2Erc20DefaultBridge: { format: 'address' }, l1WethBridge: { format: 'address' }, l2WethBridge: { format: 'address' }, + l2SharedDefaultBridge: { format: 'address' }, + l1SharedDefaultBridge: { format: 'address' }, }, }; @@ -214,3 +218,42 @@ export const EstimateFeeSchema = { gas_per_pubdata_limit: { format: 'uint' }, }, }; + +export const ZKTransactionReceiptSchema = { + type: 'object', + properties: { + ...transactionReceiptSchema.properties, + l1BatchNumber: { format: 'uint' }, + l1BatchTxIndex: { format: 'uint' }, + logs: { + type: 'array', + items: { + type: 'object', + properties: { + ...transactionReceiptSchema.properties.logs.items.properties, + l1BatchNumber: { format: 'string' }, + }, + }, + }, + l2ToL1Logs: { + type: 'array', + + items: { + type: 'object', + properties: { + blockNumber: { format: 'uint' }, + blockHash: { format: 'string' }, + l1BatchNumber: { format: 'string' }, + transactionIndex: { format: 'uint' }, + shardId: { format: 'uint' }, + isService: { format: 'string' }, + sender: { format: 'address' }, + key: { format: 'string' }, + value: { format: 'bytes' }, + transactionHash: { format: 'string' }, + logIndex: { format: 'string' }, + }, + }, + }, + }, +}; diff --git a/src/types.ts b/src/types.ts index 2ea8086..10f5dcf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,643 @@ -import type { Address, Bytes, HexString, Numbers } from 'web3'; +// import { FMT_BYTES, FMT_NUMBER, TransactionReceipt, Web3Eth } from 'web3'; +import type { FeeMarketEIP1559TxData } from 'web3-eth-accounts'; +import type { + Bytes, + HexString, + Numbers, + Transaction, + EIP1193Provider, + TransactionWithSenderAPI, + TransactionReceipt, +} from 'web3-types'; +import type { RpcMethods } from './rpc.methods'; + +export type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; + +export type { Bytes, HexString, Numbers } from 'web3-types'; +export interface TransactionOverrides extends Omit {} + +export const ZeroAddress: Address = '0x0000000000000000000000000000000000000000'; +export const ZeroHash: string = + '0x0000000000000000000000000000000000000000000000000000000000000000'; + +/** 0x-prefixed, hex encoded, ethereum account address. */ +export type Address = string; + +/** 0x-prefixed, hex encoded, ECDSA signature. */ +export type Signature = string; + +/** Ethereum network. */ +export enum Network { + Mainnet = 1, + Ropsten = 3, + Rinkeby = 4, + Goerli = 5, + Sepolia = 6, + Localhost = 9, + EraTestNode = 10, +} + +/** Enumerated list of priority queue types. */ +export enum PriorityQueueType { + Deque = 0, + HeapBuffer = 1, + Heap = 2, +} + +/** Enumerated list of priority operation tree types. */ +export enum PriorityOpTree { + Full = 0, + Rollup = 1, +} + +/** Enumerated list of transaction status types. */ +export enum TransactionStatus { + /** Transaction not found. */ + NotFound = 'not-found', + /** Transaction is processing. */ + Processing = 'processing', + /** Transaction has been committed. */ + Committed = 'committed', + /** Transaction has been finalized. */ + Finalized = 'finalized', +} + +/** Type defining a paymaster by its address and the bytestream input. */ +export type PaymasterParams = { + /** The address of the paymaster. */ + paymaster: Address; + /** The bytestream input for the paymaster. */ + paymasterInput: Bytes; +}; + +/** Contains EIP712 transaction metadata. */ +export type Eip712Meta = { + /** The maximum amount of gas the user is willing to pay for a single byte of pubdata. */ + gasPerPubdata?: Numbers; + /** An array of bytes containing the bytecode of the contract being deployed and any related contracts it can deploy. */ + factoryDeps?: Bytes[]; + /** Custom signature used for cases where the signer's account is not an EOA. */ + customSignature?: Bytes; + /** Parameters for configuring the custom paymaster for the transaction. */ + paymasterParams?: PaymasterParams; +}; + +/** + * Specifies a specific block. This can be represented by: + * - A numeric value (number, bigint, or hexadecimal string) representing the block height, where the genesis block is block 0. + * A negative value indicates the block number should be deducted from the most recent block. + * - A block hash as a string, specifying a specific block by its block hash. + * This allows potentially orphaned blocks to be specified without ambiguity, but many backends do not support this for some operations. + * - Constants representing special blocks such as 'committed', 'finalized', 'latest', 'earliest', or 'pending'. + */ +export type BlockTag = + | Numbers + | string // block hash + | 'committed' + | 'finalized' + | 'latest' + | 'earliest' + | 'pending'; + +/** Pipe-delimited choice of deployment types. */ +export type DeploymentType = 'create' | 'createAccount' | 'create2' | 'create2Account'; + +/** Bridged token. */ +export interface Token { + l1Address: Address; + l2Address: Address; + name: string; + symbol: string; + decimals: number; +} + +/** Represents the transaction fee parameters. */ +export interface Fee { + /** The maximum amount of gas allowed for the transaction. */ + gasLimit: bigint; + /** The maximum amount of gas the user is willing to pay for a single byte of pubdata. */ + gasPerPubdataLimit: bigint; + /** The EIP1559 tip per gas. */ + maxPriorityFeePerGas: bigint; + /** The EIP1559 fee cap per gas. */ + maxFeePerGas: bigint; +} + +/** Represents a message proof. */ +export interface MessageProof { + id: number; + proof: string[]; + root: string; +} + +export interface zkSyncTxData extends FeeMarketEIP1559TxData { + /** The batch number on the L1 network. */ + readonly l1BatchNumber: null | number; + /** The transaction index within the batch on the L1 network. */ + readonly l1BatchTxIndex: null | number; +} + +// /** +// * A `TransactionResponse` is an extension of {@link TransactionResponse} with additional features for +// * interacting with zkSync Era. +// */ +// export class TransactionResponse extends FeeMarketEIP1559Transaction { +// private web3Eth: Web3Eth; + +// /** The batch number on the L1 network. */ +// readonly l1BatchNumber: null | number; +// /** The transaction index within the batch on the L1 network. */ +// readonly l1BatchTxIndex: null | number; + +// constructor(txData: zkSyncTxData, provider: EIP1193Provider, opts?: TxOptions) { +// super(txData, opts); +// this.web3Eth = new Web3Eth(provider); +// this.l1BatchNumber = txData.l1BatchNumber; +// this.l1BatchTxIndex = txData.l1BatchTxIndex; + +// // copied from old base ethers.TransactionResponse! +// // this.blockNumber = tx.blockNumber != null ? tx.blockNumber : null; +// // this.blockHash = tx.blockHash != null ? tx.blockHash : null; + +// // this.hash = tx.hash; +// // this.index = tx.index; + +// // this.type = tx.type; + +// // this.from = tx.from; +// // this.to = tx.to || null; + +// // this.gasLimit = tx.gasLimit; +// // this.nonce = tx.nonce; +// // this.data = tx.data; +// // this.value = tx.value; + +// // this.gasPrice = tx.gasPrice; +// // this.maxPriorityFeePerGas = tx.maxPriorityFeePerGas != null ? tx.maxPriorityFeePerGas : null; +// // this.maxFeePerGas = tx.maxFeePerGas != null ? tx.maxFeePerGas : null; +// // this.maxFeePerBlobGas = tx.maxFeePerBlobGas != null ? tx.maxFeePerBlobGas : null; + +// // this.chainId = tx.chainId; +// // this.signature = tx.signature; + +// // this.accessList = tx.accessList != null ? tx.accessList : null; +// // this.blobVersionedHashes = tx.blobVersionedHashes != null ? tx.blobVersionedHashes : null; + +// // this.#startBlock = -1; +// } + +// /** +// * Waits for this transaction to be mined and have a specified number of confirmation blocks. +// * Resolves once the transaction has `confirmations` blocks including it. +// * If `confirmations` is 0 and the transaction has not been mined, it resolves to `null`. +// * Otherwise, it waits until enough confirmations have completed. +// * +// * @param confirmations The number of confirmation blocks. Defaults to 1. +// * @returns A promise that resolves to the transaction receipt. +// */ +// async wait(confirmations?: number): Promise { +// // eslint-disable-next-line no-constant-condition +// while (true) { +// // // it needs to be replaced with something like: + +// // // const receipt = await this.web3Eth.getTransactionReceipt(this.hash); +// // // eth.setConfig({ transactionConfirmationBlocks: waitConfirmations }); +// // // watchTransactionForConfirmations(this.web3Eth, , receipt, this.hash, 'hex'); + +// // // Or far better: to be replaced with a wait for confirmations on the PromiEvent of the sent transaction + +// // const receipt = (await super.wait(confirmations)) as TransactionReceipt; + +// // if (receipt && receipt.blockNumber) { +// // return receipt; +// // } +// await sleep(500); +// } +// } + +// async getTransaction() { +// return await this.web3Eth.getTransaction(this.data, { +// number: FMT_NUMBER.BIGINT, +// bytes: FMT_BYTES.HEX, +// }); +// } + +// // replaceableTransaction(startBlock: number): TransactionResponse { +// // return new TransactionResponse(super.replaceableTransaction(startBlock), this.provider); +// // } + +// // async getBlock(): Promise { +// // return await base.getBlock(this.hash); +// // } + +// // /** Waits for transaction to be finalized. */ +// // async waitFinalize(): Promise { +// // // eslint-disable-next-line no-constant-condition +// // while (true) { +// // const receipt = await this.wait(); +// // if (receipt && receipt.blockNumber) { +// // const block = await this.provider.getBlock('finalized'); +// // if (receipt.blockNumber <= block!.number) { +// // return (await this.provider.getTransactionReceipt(receipt.hash)) as TransactionReceipt; +// // } +// // } else { +// // await sleep(500); +// // } +// // } +// // } + +// override toJSON(): any { +// const { l1BatchNumber, l1BatchTxIndex } = this; + +// return { +// ...super.toJSON(), +// l1BatchNumber, +// l1BatchTxIndex, +// }; +// } +// } + +// /** +// * A `TransactionReceipt` is an extension of {@link ethers.TransactionReceipt} with additional features for +// * interacting with zkSync Era. +// */ +// export class TransactionReceipt extends FeeMarketEIP1559Transaction { +// private web3Eth: Web3Eth; + +// /** The batch number on the L1 network. */ +// readonly l1BatchNumber: null | number; +// /** The transaction index within the batch on the L1 network. */ +// readonly l1BatchTxIndex: null | number; +// /** The logs of L2 to L1 messages. */ +// readonly l2ToL1Logs: L2ToL1Log[]; +// /** All logs included in the transaction receipt. */ +// readonly _logs: ReadonlyArray; + +// constructor(params: any, provider: EIP1193Provider) { +// super(params); +// this.web3Eth = new Web3Eth(provider); +// this.l1BatchNumber = params.l1BatchNumber; +// this.l1BatchTxIndex = params.l1BatchTxIndex; +// this.l2ToL1Logs = params.l2ToL1Logs; +// this._logs = Object.freeze( +// params.logs.map((log: Log) => { +// return new Log(log, provider); +// }), +// ); +// } + +// // override get logs(): ReadonlyArray { +// // return this._logs; +// // } + +// // override getBlock(): Promise { +// // return super.getBlock(this.hash()) as Promise; +// // } + +// // override getTransaction(): Promise { +// // return super.getTransaction() as Promise; +// // } + +// override toJSON(): any { +// const { l1BatchNumber, l1BatchTxIndex, l2ToL1Logs } = this; +// return { +// ...super.toJSON(), +// l1BatchNumber, +// l1BatchTxIndex, +// l2ToL1Logs, +// }; +// } +// } + +// /** A `Block` is an extension of {@link ethers.Block} with additional features for interacting with zkSync Era. */ +// export class Block extends ethers.Block { +// /** The batch number on L1. */ +// readonly l1BatchNumber: null | number; +// /** The timestamp of the batch on L1. */ +// readonly l1BatchTimestamp: null | number; + +// constructor(params: any, provider: ethers.Provider) { +// super(params, provider); +// this.l1BatchNumber = params.l1BatchNumber; +// this.l1BatchTimestamp = params.l1BatchTxIndex; +// } + +// override toJSON(): any { +// const { l1BatchNumber, l1BatchTimestamp: l1BatchTxIndex } = this; +// return { +// ...super.toJSON(), +// l1BatchNumber, +// l1BatchTxIndex, +// }; +// } + +// override get prefetchedTransactions(): TransactionResponse[] { +// return super.prefetchedTransactions as TransactionResponse[]; +// } + +// override getTransaction(indexOrHash: number | string): Promise { +// return super.getTransaction(indexOrHash) as Promise; +// } +// } + +// /** A `LogParams` is an extension of {@link ethers.LogParams} with additional features for interacting with zkSync Era. */ +// export interface LogParams extends ethers.LogParams { +// /** The batch number on L1. */ +// readonly l1BatchNumber: null | number; +// } + +// /** A `Log` is an extension of {@link ethers.Log} with additional features for interacting with zkSync Era. */ +// export class Log extends ethers.Log { +// /** The batch number on L1. */ +// readonly l1BatchNumber: null | number; + +// constructor(params: LogParams, provider: ethers.Provider) { +// super(params, provider); +// this.l1BatchNumber = params.l1BatchNumber; +// } + +// override toJSON(): any { +// const { l1BatchNumber } = this; +// return { +// ...super.toJSON(), +// l1BatchNumber, +// }; +// } + +// override async getBlock(): Promise { +// return (await super.getBlock()) as Block; +// } + +// override async getTransaction(): Promise { +// return (await super.getTransaction()) as TransactionResponse; +// } + +// override async getTransactionReceipt(): Promise { +// return (await super.getTransactionReceipt()) as TransactionReceipt; +// } +// } + +// /** +// * A `Transaction` is an extension of {@link ethers.Transaction} with additional features for interacting +// * with zkSync Era. +// */ +// export class Transaction extends ethers.Transaction { +// /** The custom data for EIP712 transaction metadata. */ +// customData?: null | Eip712Meta; +// // super.#type is private and there is no way to override which enforced to +// // introduce following variable +// #type?: null | number; +// #from?: null | string; + +// override get type(): number | null { +// return this.#type === EIP712_TX_TYPE ? this.#type : super.type; +// } + +// override set type(value: number | string | null) { +// switch (value) { +// case EIP712_TX_TYPE: +// case 'eip-712': +// this.#type = EIP712_TX_TYPE; +// break; +// default: +// super.type = value; +// } +// } + +// static override from(tx: string | TransactionLike): Transaction { +// if (typeof tx === 'string') { +// const payload = ethers.getBytes(tx); +// if (payload[0] !== EIP712_TX_TYPE) { +// return Transaction.from(ethers.Transaction.from(tx)); +// } else { +// return Transaction.from(parseEip712(payload)); +// } +// } else { +// const result = new Transaction(); +// if (tx.type === EIP712_TX_TYPE) { +// result.type = EIP712_TX_TYPE; +// result.customData = tx.customData; +// result.from = tx.from!; +// } +// if (tx.type !== null && tx.type !== undefined) result.type = tx.type; +// if (tx.to) result.to = tx.to; +// if (tx.nonce) result.nonce = tx.nonce; +// if (tx.gasLimit) result.gasLimit = tx.gasLimit; +// if (tx.gasPrice) result.gasPrice = tx.gasPrice; +// if (tx.maxPriorityFeePerGas) result.maxPriorityFeePerGas = tx.maxPriorityFeePerGas; +// if (tx.maxFeePerGas) result.maxFeePerGas = tx.maxFeePerGas; +// if (tx.data) result.data = tx.data; +// if (tx.value) result.value = tx.value; +// if (tx.chainId) result.chainId = tx.chainId; +// if (tx.signature) result.signature = EthersSignature.from(tx.signature); +// result.accessList = null; + +// if (tx.from) { +// assertArgument(result.isSigned(), 'unsigned transaction cannot define from', 'tx', tx); +// assertArgument(isAddressEq(result.from, tx.from), 'from mismatch', 'tx', tx); +// } + +// if (tx.hash) { +// assertArgument(result.isSigned(), 'unsigned transaction cannot define hash', 'tx', tx); +// assertArgument(result.hash === tx.hash, 'hash mismatch', 'tx', tx); +// } + +// return result; +// } +// } + +// override get serialized(): string { +// if (!this.customData && this.#type !== EIP712_TX_TYPE) { +// return super.serialized; +// } +// return serializeEip712(this, this.signature!); +// } + +// override get unsignedSerialized(): string { +// if (!this.customData && this.type !== EIP712_TX_TYPE) { +// return super.unsignedSerialized; +// } +// return serializeEip712(this); +// } + +// override toJSON(): any { +// const { customData } = this; +// return { +// ...super.toJSON(), +// type: !this.#type ? this.type : this.#type, +// customData, +// }; +// } + +// override get typeName(): string | null { +// return this.#type === EIP712_TX_TYPE ? 'zksync' : super.typeName; +// } + +// override isSigned(): this is Transaction & { +// type: number; +// typeName: string; +// from: string; +// signature: Signature; +// } { +// return this.#type === EIP712_TX_TYPE +// ? this.customData?.customSignature !== null +// : super.isSigned(); +// } + +// override get hash(): string | null { +// if (this.#type === EIP712_TX_TYPE) { +// return this.customData?.customSignature !== null ? eip712TxHash(this) : null; +// } else { +// return super.hash; +// } +// } + +// override get from(): string | null { +// return this.#type === EIP712_TX_TYPE ? this.#from! : super.from; +// } +// override set from(value: string | null) { +// this.#from = value; +// } +// } + +/** + * Represents a L2 to L1 transaction log. + */ +export interface L2ToL1Log { + blockNumber: number; + blockHash: string; + l1BatchNumber: number; + transactionIndex: number; + shardId: number; + isService: boolean; + sender: string; + key: string; + value: string; + transactionHash: string; + logIndex: number; +} + +/** + * A `TransactionRequest` is an extension of {@link ethers.TransactionRequest} with additional features for interacting + * with zkSync Era. + */ +export declare type TransactionRequest = DeepWriteable< + TransactionWithSenderAPI & { + /** The custom data for EIP712 transaction metadata. */ + customData?: null | Eip712Meta; + type?: TransactionWithSenderAPI['type'] & Numbers; + } +>; + +/** + * Interface representation of priority op response that extends {@link ethers.TransactionResponse} and adds a function + * that waits to commit a L1 transaction, including when given on optional confirmation number. + */ +export interface PriorityL1OpResponse { + /** + * Waits for the L1 transaction to be committed, including waiting for the specified number of confirmations. + * @param confirmation The number of confirmations to wait for. Defaults to 1. + * @returns A promise that resolves to the transaction receipt once committed. + */ + waitL1Commit(confirmation?: number): Promise; + wait(confirmation?: number): Promise; + waitFinalize(confirmation?: number): Promise; +} +export interface PriorityL2OpResponse { + wait(confirmation?: number): Promise; + waitFinalize(confirmation?: number): Promise; +} +export type PriorityOpResponse = PriorityL1OpResponse | PriorityL2OpResponse; +/** A map containing accounts and their balances. */ +export type BalancesMap = { [key: string]: bigint }; + +/** Represents deployment information. */ +export interface DeploymentInfo { + /** The account responsible for deployment. */ + sender: Address; + /** The hash of the contract/account bytecode. */ + bytecodeHash: string; + /** The deployed address of the contract/address. */ + deployedAddress: Address; +} + +/** + * Represents the input data structure for an approval-based paymaster. + */ +export interface ApprovalBasedPaymasterInput { + /** The type of the paymaster input. */ + type: 'ApprovalBased'; + /** The address of the token to be approved. */ + token: Address; + /** The minimum allowance required for the token approval. */ + minimalAllowance: Numbers; + /** The additional input data. */ + innerInput: Bytes; +} + +/** + * Represents the input data structure for a general paymaster. + */ +export interface GeneralPaymasterInput { + /** The type of the paymaster input. */ + type: 'General'; + /** The additional input data. */ + innerInput: Bytes; +} + +/** + * Represents an Ethereum signature consisting of the components `v`, `r`, and `s`. + */ +export interface EthereumSignature { + /** The recovery id. */ + v: number; + /** The "r" value of the signature. */ + r: Bytes; + /** The "s" value of the signature. */ + s: Bytes; +} + +/** + * Represents the input data structure for a paymaster. + * It can be either approval-based or general. + */ +export type PaymasterInput = ApprovalBasedPaymasterInput | GeneralPaymasterInput; + +/** Enumerated list of account abstraction versions. */ +export enum AccountAbstractionVersion { + /** Used for contracts that are not accounts */ + None = 0, + /** Used for contracts that are accounts */ + Version1 = 1, +} + +/** + * Enumerated list of account nonce ordering formats. + */ +export enum AccountNonceOrdering { + /** + * Nonces should be ordered in the same way as in externally owned accounts (EOAs). + * This means, for instance, that the operator will always wait for a transaction with nonce `X` + * before processing a transaction with nonce `X+1`. + */ + Sequential = 0, + /** Nonces can be ordered in arbitrary order. */ + Arbitrary = 1, +} + +/** + * Interface representing contract account information containing details on the supported account abstraction version + * and nonce ordering format. + */ +export interface ContractAccountInfo { + /** The supported account abstraction version. */ + supportedAAVersion: AccountAbstractionVersion; + /** The nonce ordering format. */ + nonceOrdering: AccountNonceOrdering; +} + +/** Contains batch information. */ export interface BatchDetails { number: number; timestamp: number; @@ -17,6 +655,7 @@ export interface BatchDetails { l2FairGasPrice: number; } +/** Contains batch information. */ export interface BlockDetails { number: bigint; timestamp: bigint; @@ -33,6 +672,7 @@ export interface BlockDetails { executedAt?: Date; } +/** Contains transaction details information. */ export interface TransactionDetails { isL1Originated: boolean; status: string; @@ -44,6 +684,23 @@ export interface TransactionDetails { ethExecuteTxHash?: string; } +/** Represents the full deposit fee containing fees for both L1 and L2 transactions. */ +export interface FullDepositFee { + /** The maximum fee per gas for L1 transaction. */ + maxFeePerGas?: bigint; + /** The maximum priority fee per gas for L1 transaction. */ + maxPriorityFeePerGas?: bigint; + /** The gas price for L2 transaction. */ + gasPrice?: bigint; + /** The base cost of the deposit transaction on L2. */ + baseCost: bigint; + /** The gas limit for L1 transaction. */ + l1GasLimit: bigint; + /** The gas limit for L2 transaction. */ + l2GasLimit: bigint; +} + +/** Represents a raw block transaction. */ export interface RawBlockTransaction { common_data: { L2: { @@ -77,6 +734,69 @@ export interface RawBlockTransaction { raw_bytes: string; } +/** Contains parameters for finalizing the withdrawal transaction. */ +export interface FinalizeWithdrawalParams { + l1BatchNumber: number | null; + l2MessageIndex: number; + l2TxNumberInBlock: number | null; + message: any; + sender: string; + proof: string[]; +} + +/** Represents storage proof */ +export interface StorageProof { + address: Address; + storageProof: { + index: Numbers; + key: HexString; + value: HexString; + proof: HexString[]; + }[]; +} + +/** + * Signs various types of payloads, optionally using a some kind of secret. + * + * @param payload The payload that needs to be sign already populated transaction to sign. + * @param [secret] The secret used for signing the `payload`. + * @param [provider] The provider is used to fetch data from the network if it is required for signing. + * @returns A promise that resolves to the serialized signature in hexadecimal format. + */ +export type PayloadSigner = ( + payload: Bytes, + secret?: any, + provider?: null | EIP1193Provider, +) => Promise; + +// /** +// * Populates missing fields in a transaction with default values. +// * +// * @param transaction The transaction that needs to be populated. +// * @param [secret] The secret used for populating the transaction. +// * @param [provider] The provider is used to fetch data from the network if it is required for signing. +// * @returns A promise that resolves to the populated transaction. +// */ +// export type TransactionBuilder = ( +// transaction: TransactionRequest, +// secret?: any, +// provider?: null | EIP1193Provider, +// ) => Promise; + +// /** +// * Encapsulates the required input parameters for creating a signer for `SmartAccount`. +// */ +// export interface SmartAccountSigner { +// /** Address to which the `SmartAccount` is bound. */ +// address: string; +// /** Secret in any form that can be used for signing different payloads. */ +// secret: any; +// /** Custom method for signing different payloads. */ +// payloadSigner?: PayloadSigner; +// /** Custom method for populating transaction requests. */ +// transactionBuilder?: TransactionBuilder; +// } + export interface WalletBalances { [key: Address]: Numbers; } @@ -93,6 +813,13 @@ export interface BridgeAddresses { l2Erc20DefaultBridge: Address; l1WethBridge: Address; l2WethBridge: Address; + l1SharedDefaultBridge: Address; + l2SharedDefaultBridge: Address; +} + +export interface ContractsAddresses extends BridgeAddresses { + mainContract: string; + bridgehubContractAddress: string; } export interface L2ToL1Proof { @@ -101,19 +828,92 @@ export interface L2ToL1Proof { root: HexString; } -export interface Proof { - address: Address; - storageProof: { - index: Numbers; - key: HexString; - value: HexString; - proof: HexString[]; - }[]; -} - export interface EstimateFee { gas_limit: Numbers; gas_per_pubdata_limit: Numbers; max_fee_per_gas: Numbers; max_priority_fee_per_gas: Numbers; } + +export interface TypedDataDomain { + /** + * The human-readable name of the signing domain. + */ + name?: null | string; + + /** + * The major version of the signing domain. + */ + version?: null | string; + + /** + * The chain ID of the signing domain. + */ + chainId?: null | Numbers; + + /** + * The the address of the contract that will verify the signature. + */ + verifyingContract?: null | string; + + /** + * A salt used for purposes decided by the specific domain. + */ + salt?: null | Bytes; +} + +/** + * A specific field of a structured [[link-eip-712]] type. + */ +export interface TypedDataField { + /** + * The field name. + */ + name: string; + + /** + * The type of the field. + */ + type: string; +} + +export type Eip712TxData = Omit & { + /** The custom data for EIP712 transaction metadata. */ + customData?: null | Eip712Meta; + from?: Address; + hash?: string; + signature?: string; + /** + * The transaction's gas price. To be used if maxPriorityFeePerGas and maxFeePerGas were not provided + */ + gasPrice?: Numbers | Uint8Array | null; +}; + +export type Eip712SignedInput = FeeMarketEIP1559TxData & { + customData?: null | Eip712Meta; + data: Bytes; + value: Numbers; + nonce: Numbers; + gasLimit: Numbers; + maxFeePerGas: Numbers; + maxPriorityFeePerGas: Numbers; + from?: Address; + txType: Numbers; + gasPerPubdataByteLimit?: Numbers; + paymaster: Address; + factoryDeps: Bytes[]; + paymasterInput: Bytes; + [key: string]: unknown; +}; + +export type ZKTransactionReceipt = TransactionReceipt & { + l1BatchNumber: Numbers; + l1BatchTxIndex: Numbers; + l2ToL1Logs: L2ToL1Log[]; + logs: TransactionReceipt['logs'] & { + l1BatchNumber: Numbers; + }; +}; + +export interface OverridesReadOnly extends Omit {} +export type Overrides = DeepWriteable; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..bcd8b5e --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,1153 @@ +import { sha256 } from 'ethereum-cryptography/sha256.js'; +import * as web3 from 'web3'; +import * as web3Utils from 'web3-utils'; +import * as web3Accounts from 'web3-eth-accounts'; +import * as web3Types from 'web3-types'; +import * as web3Abi from 'web3-eth-abi'; +import type { + AbiEventFragment, + BlockNumberOrTag, + Bytes, + LogsInput, + TransactionHash, + TransactionReceipt, +} from 'web3-types'; +import { toUint8Array } from 'web3-eth-accounts'; +import type { Web3Eth } from 'web3-eth'; +import { ALL_EVENTS_ABI, decodeEventABI } from 'web3-eth'; +import { keccak256, toBigInt } from 'web3-utils'; +import { encodeEventSignature, jsonInterfaceMethodToString } from 'web3-eth-abi'; +import { PriorityOpTree, PriorityQueueType } from './types'; +import type { + DeploymentInfo, + EthereumSignature, + PriorityL2OpResponse, + PriorityOpResponse, +} from './types'; +import { IZkSyncABI } from './contracts/IZkSyncStateTransition'; +import { IBridgehubABI } from './contracts/IBridgehub'; +import { IContractDeployerABI } from './contracts/IContractDeployer'; +import { IL1MessengerABI } from './contracts/IL1Messenger'; +import { IERC20ABI } from './contracts/IERC20'; +import { IERC1271ABI } from './contracts/IERC1271'; +import { IL1BridgeABI } from './contracts/IL1ERC20Bridge'; +import { IL2BridgeABI } from './contracts/IL2Bridge'; +import { INonceHolderABI } from './contracts/INonceHolder'; +import { + LEGACY_ETH_ADDRESS, + L2_BASE_TOKEN_ADDRESS, + ETH_ADDRESS_IN_CONTRACTS, + L1_MESSENGER_ADDRESS, + CONTRACT_DEPLOYER_ADDRESS, + MAX_BYTECODE_LEN_BYTES, + L1_TO_L2_ALIAS_OFFSET, + EIP1271_MAGIC_VALUE, + L1_FEE_ESTIMATION_COEF_NUMERATOR, + L1_FEE_ESTIMATION_COEF_DENOMINATOR, + // EIP712_TX_TYPE, + // DEFAULT_GAS_PER_PUBDATA_LIMIT, +} from './constants'; + +import type { Web3ZkSyncL2 } from './web3zksync-l2'; +import { Web3ZkSyncL1 } from './web3zksync-l1'; + +export * from './Eip712'; // to be used instead of the one at zksync-ethers: Provider from ./provider + +/** + * The web3.js Contract instance for the `ZkSync` interface. + * @constant + */ +export const ZkSyncMainContract = new web3.Contract(IZkSyncABI); + +/** + * The ABI of the `Bridgehub` interface. + * @constant + */ +export const BridgehubContract = new web3.Contract(IBridgehubABI); + +/** + * The web3.js Contract instance for the `IContractDeployer` interface, which is utilized for deploying smart contracts. + * @constant + */ +export const ContractDeployerContract = new web3.Contract(IContractDeployerABI); + +/** + * The web3.js Contract instance for the `IL1Messenger` interface, which is utilized for sending messages from the L2 to L1. + * @constant + */ +export const L1MessengerContract = new web3.Contract(IL1MessengerABI); + +/** + * The web3.js Contract instance for the `IERC20` interface, which is utilized for interacting with ERC20 tokens. + * @constant + */ +export const IERC20Contract = new web3.Contract(IERC20ABI); + +/** + * The web3.js Contract instance for the `IERC1271` interface, which is utilized for signature validation by contracts. + * @constant + */ +export const IERC1271Contract = new web3.Contract(IERC1271ABI); + +/** + * The web3.js Contract instance for the `IL1Bridge` interface, which is utilized for transferring ERC20 tokens from L1 to L2. + * @constant + */ +export const L1BridgeContract = new web3.Contract(IL1BridgeABI); + +/** + * The web3.js Contract instance for the `IL2Bridge` interface, which is utilized for transferring ERC20 tokens from L2 to L1. + * @constant + */ +export const L2BridgeContract = new web3.Contract(IL2BridgeABI); + +/** + * The web3.js Contract instance for the `INonceHolder` interface, which is utilized for managing deployment nonces. + * @constant + */ +export const NonceHolderContract = new web3.Contract(INonceHolderABI); + +/** + * ------------------------------------------------------------ + * consider adding the next few functions to web3.js: + * */ + +export const evenHex = (hex: string) => { + return hex.length % 2 === 0 ? hex : `0x0${hex.slice(2)}`; +}; +export const toBytes = (number: web3Types.Numbers | Uint8Array) => { + const hex = + typeof number === 'number' || typeof number === 'bigint' + ? web3Utils.numberToHex(number) + : web3Utils.bytesToHex(number); + + return web3Utils.hexToBytes(evenHex(hex)); +}; + +export function concat(bytes: web3Types.Bytes[]): string { + return '0x' + bytes.map(d => web3Utils.toHex(d).substring(2)).join(''); +} + +export function contractFunctionId(value: string): string { + return web3Utils.keccak256(web3Utils.utf8ToBytes(value)); +} + +function recoverSignerAddress( + messageOrData: string | web3Types.Eip712TypedData, + signature: SignatureLike, +) { + let message; + if (typeof messageOrData !== 'string') { + message = web3Abi.getEncodedEip712Data(messageOrData); + } else { + message = messageOrData; + } + + const signatureObject = + typeof signature === 'string' + ? new SignatureObject(signature) + : new SignatureObject( + toUint8Array(signature.r), + toUint8Array(signature.s), + signature.v, + ); + + return web3Accounts.recover(web3Utils.keccak256(message), signatureObject.serialized, true); +} + +export class SignatureObject { + public r: Uint8Array; + public s: Uint8Array; + public v: bigint; + + constructor(r: Uint8Array, s: Uint8Array, v: web3Types.Numbers); + constructor(signature: string | SignatureLike); + constructor( + rOrSignature: string | Uint8Array | SignatureLike, + s?: Uint8Array, + v?: web3Types.Numbers, + ) { + if (typeof rOrSignature === 'string') { + const bytes: Uint8Array = web3Utils.hexToBytes(rOrSignature); + + if (bytes.length === 64) { + const r = bytes.slice(0, 32); + const s = bytes.slice(32, 64); + const v = BigInt(s[0] & 0x80 ? 28 : 27); + s[0] &= 0x7f; + this.r = r; + this.s = s; + this.v = v; + } else if (bytes.length === 65) { + const r = bytes.slice(0, 32); + const s = bytes.slice(32, 64); + const v = BigInt(SignatureObject.getNormalizedV(bytes[64])); + this.r = r; + this.s = s; + this.v = v; + } else { + throw new Error('Invalid signature length'); + } + } else if ( + (rOrSignature as EthereumSignature).r && + (rOrSignature as EthereumSignature).s && + (rOrSignature as EthereumSignature).v + ) { + const ethereumSignature = rOrSignature as EthereumSignature; + this.r = web3Utils.bytesToUint8Array(ethereumSignature.r); + this.s = web3Utils.bytesToUint8Array(ethereumSignature.s); + this.v = BigInt(ethereumSignature.v); + } else { + const signature = { r: rOrSignature as Uint8Array, s: s, v: v }; + // Initialize with individual parameters + this.r = signature.r!; + this.s = signature.s!; + this.v = BigInt(signature.v!); + } + } + + static getNormalizedV(v: number): 27 | 28 { + if (v === 0 || v === 27) { + return 27; + } + if (v === 1 || v === 28) { + return 28; + } + + // Otherwise, EIP-155 v means odd is 27 and even is 28 + return v & 1 ? 27 : 28; + } + + concat(datas: ReadonlyArray): string { + return '0x' + datas.map(d => web3Utils.toHex(d).substring(2)).join(''); + } + + get yParity(): 0 | 1 { + return this.v === 27n ? 0 : 1; + } + + public get serialized(): string { + return this.concat([this.r, this.s, this.yParity ? '0x1c' : '0x1b']); + } + + public toString() { + return `${this.r}${this.s.slice(2)}${web3Utils.toHex(this.v).slice(2)}`; + } +} + +export type SignatureLike = SignatureObject | EthereumSignature | string; + +/** + * eip-191 message prefix + */ +export const MessagePrefix: string = '\x19Ethereum Signed Message:\n'; + +/** + * Has the message according to eip-191 + * @param message - message to hash + * @returns hash of the message + */ +export function hashMessage(message: Uint8Array | string): string { + if (typeof message === 'string') { + message = web3Accounts.toUint8Array(message); + } + return web3Utils.keccak256( + concat([ + web3Accounts.toUint8Array(MessagePrefix), + web3Accounts.toUint8Array(String(message.length)), + message, + ]), + ); +} + +/** + * ------------------------------------------------------------ + * End of the function section that would be added to web3.js + */ + +/** + * Returns true if token represents ETH on L1 or L2. + * + * @param token The token address. + * + * @example + * + * const isL1ETH = utils.isETH(utils.ETH_ADDRESS); // true + * const isL2ETH = utils.isETH(utils.ETH_ADDRESS_IN_CONTRACTS); // true + */ +export function isETH(token: web3.Address) { + return ( + isAddressEq(token, LEGACY_ETH_ADDRESS) || + isAddressEq(token, L2_BASE_TOKEN_ADDRESS) || + isAddressEq(token, ETH_ADDRESS_IN_CONTRACTS) + ); +} + +/** + * Pauses execution for a specified number of milliseconds. + * + * @param millis The number of milliseconds to pause execution. + * + * @example + * + * await sleep(1_000); + */ +export function sleep(millis: number): Promise { + return new Promise(resolve => setTimeout(resolve, millis)); +} + +/** + * Returns the default settings for L1 transactions. + */ +export function layer1TxDefaults(): { + queueType: PriorityQueueType.Deque; + opTree: PriorityOpTree.Full; +} { + return { + queueType: PriorityQueueType.Deque, + opTree: PriorityOpTree.Full, + }; +} + +/** + * Returns a `keccak` encoded message with a given sender address and block number from the L1 messenger contract. + * + * @param sender The sender of the message on L2. + * @param msg The encoded message. + * @param txNumberInBlock The index of the transaction in the block. + * @returns The hashed `L2->L1` message. + * + * @example + * + * const withdrawETHMessage = "0x6c0960f936615cf349d7f6344891b1e7ca7c72883f5dc04900000000000000000000000000000000000000000000000000000001a13b8600"; + * const withdrawETHMessageHash = utils.getHashedL2ToL1Msg("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", withdrawETHMessage, 0); + * // withdrawETHMessageHash = "0xd8c80ecb64619e343f57c3b133c6c6d8dd0572dd3488f1ca3276c5b7fd3a938d" + */ +export function getHashedL2ToL1Msg( + sender: web3.Address, + msg: web3Types.Bytes, + txNumberInBlock: number, +): string { + const encodedMsg = new Uint8Array([ + 0, // l2ShardId + 1, // isService + ...web3Utils.hexToBytes(web3Utils.padLeft(web3Utils.toHex(txNumberInBlock), 2 * 2)), + ...web3Utils.hexToBytes(L1_MESSENGER_ADDRESS), + ...web3Utils.hexToBytes(web3Utils.padLeft(sender, 32 * 2)), + ...web3Utils.hexToBytes(web3Utils.keccak256(msg)), + ]); + + return web3Utils.keccak256(encodedMsg); +} + +/** + * Returns a log containing details of all deployed contracts related to a transaction receipt. + * + * @param receipt The transaction receipt containing deployment information. + * + * @example + * + * + */ +export function getDeployedContracts(receipt: web3Types.TransactionReceipt): DeploymentInfo[] { + const addressBytesLen = 40; + return ( + receipt.logs + .filter( + log => + log.topics && + log.topics[0] === + contractFunctionId('ContractDeployed(address,bytes32,address)') && + log.address && + isAddressEq(log.address, CONTRACT_DEPLOYER_ADDRESS), + ) + // Take the last topic (deployed contract address as U256) and extract address from it (U160). + .map(log => { + if (!log.topics) throw new Error('No topics in log'); + const sender = `0x${log.topics[1].slice(log.topics[1].length - addressBytesLen)}`; + const bytecodeHash = log.topics[2]; + const address = `0x${log.topics[3].slice(log.topics[3].length - addressBytesLen)}`; + return { + sender: web3Utils.toChecksumAddress(sender), + bytecodeHash: web3Utils.toHex(bytecodeHash), + deployedAddress: web3Utils.toChecksumAddress(address), + }; + }) + ); +} + +/** + * Generates a future-proof contract address using a salt plus bytecode, allowing the determination of an address before deployment. + * + * @param sender The sender's address. + * @param bytecodeHash The hash of the bytecode, typically the output from `zkSolc`. + * @param salt A randomization element used to create the contract address. + * @param input The ABI-encoded constructor arguments, if any. + * + * @remarks The implementation of `create2Address` in zkSync Era may differ slightly from Ethereum. + * + * @example + * + * const address = utils.create2Address("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", "0x010001cb6a6e8d5f6829522f19fa9568660e0a9cd53b2e8be4deb0a679452e41", "0x01", "0x01"); + * // address = "0x29bac3E5E8FFE7415F97C956BFA106D70316ad50" + */ +export function create2Address( + sender: web3Types.Address, + bytecodeHash: web3Types.Bytes, + salt: web3Types.Bytes, + input: web3Types.Bytes = '', +): string { + const prefix = web3Utils.keccak256(web3Utils.utf8ToBytes('zksyncCreate2')); + const inputHash = web3Utils.keccak256(input); + const addressBytes = web3Utils + .keccak256( + concat([prefix, web3Utils.padLeft(sender, 32 * 2), salt, bytecodeHash, inputHash]), + ) + .slice(26); + return web3Utils.toChecksumAddress(addressBytes); +} + +/** + * Generates a contract address from the deployer's account and nonce. + * + * @param sender The address of the deployer's account. + * @param senderNonce The nonce of the deployer's account. + * + * @example + * + * const address = utils.createAddress("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", 1); + * // address = "0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021" + */ +export function createAddress(sender: web3.Address, senderNonce: web3Types.Numbers): string { + const prefix = web3Utils.keccak256(web3Utils.utf8ToBytes('zksyncCreate')); + const addressBytes = web3Utils + .keccak256( + concat([ + prefix, + web3Utils.padLeft(sender, 32 * 2), + web3Utils.padLeft(web3Utils.toHex(senderNonce), 32 * 2), + ]), + ) + .slice(26); + + return web3Utils.toChecksumAddress(addressBytes); +} + +/** + * Checks if the transaction's base cost is greater than the provided value, which covers the transaction's cost. + * + * @param baseCost The base cost of the transaction. + * @param value The value covering the transaction's cost. + * @throws {Error} The base cost must be greater than the provided value. + * + * @example + * + * const baseCost = 100; + * const value = 99; + * try { + * await utils.checkBaseCost(baseCost, value); + * } catch (e) { + * // e.message = `The base cost of performing the priority operation is higher than the provided value parameter for the transaction: baseCost: ${baseCost}, provided value: ${value}`, + * } + */ +export async function checkBaseCost( + baseCost: web3Types.Numbers, + value: web3Types.Numbers | Promise, +): Promise { + if (baseCost > (await value)) { + throw new Error( + 'The base cost of performing the priority operation is higher than the provided value parameter ' + + `for the transaction: baseCost: ${baseCost}, provided value: ${value}!`, + ); + } +} + +/** + * Returns the hash of the given bytecode. + * + * @param bytecode The bytecode to hash. + * + * @example + * + * const bytecode = + * "0x000200000000000200010000000103550000006001100270000000130010019d0000008001000039000000400010043f0000000101200190000000290000c13d0000000001000031000000040110008c000000420000413d0000000101000367000000000101043b000000e001100270000000150210009c000000310000613d000000160110009c000000420000c13d0000000001000416000000000110004c000000420000c13d000000040100008a00000000011000310000001702000041000000200310008c000000000300001900000000030240190000001701100197000000000410004c000000000200a019000000170110009c00000000010300190000000001026019000000000110004c000000420000c13d00000004010000390000000101100367000000000101043b000000000010041b0000000001000019000000490001042e0000000001000416000000000110004c000000420000c13d0000002001000039000001000010044300000120000004430000001401000041000000490001042e0000000001000416000000000110004c000000420000c13d000000040100008a00000000011000310000001702000041000000000310004c000000000300001900000000030240190000001701100197000000000410004c000000000200a019000000170110009c00000000010300190000000001026019000000000110004c000000440000613d00000000010000190000004a00010430000000000100041a000000800010043f0000001801000041000000490001042e0000004800000432000000490001042e0000004a00010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000006d4ce63c0000000000000000000000000000000000000000000000000000000060fe47b18000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000080000000000000000000000000000000000000000000000000000000000000000000000000000000009c8c8fa789967eb514f3ec9def748480945cc9b10fcbd1a19597d924eb201083"; + * const hashedBytecode = utils.hashBytecode(bytecode); + * /* + * hashedBytecode = new Uint8Array([ + * 1, 0, 0, 27, 57, 231, 154, 55, 0, 164, 201, 96, 244, 120, 23, 112, 54, 34, 224, 133, + * 160, 122, 88, 164, 112, 80, 0, 134, 48, 138, 74, 16, + * ]), + * ); + * *\/ + */ +export function hashBytecode(bytecode: web3Types.Bytes): Uint8Array { + // For getting the consistent length we first convert the bytecode to UInt8Array + const bytecodeAsArray = web3Utils.bytesToUint8Array(bytecode); + + if (bytecodeAsArray.length % 32 !== 0) { + throw new Error('The bytecode length in bytes must be divisible by 32!'); + } + + if (bytecodeAsArray.length > MAX_BYTECODE_LEN_BYTES) { + throw new Error(`Bytecode can not be longer than ${MAX_BYTECODE_LEN_BYTES} bytes!`); + } + + const hashStr = web3Utils.toHex(sha256(Buffer.from(bytecodeAsArray))); + const hash = web3Utils.bytesToUint8Array(hashStr); + + // Note that the length of the bytecode + // should be provided in 32-byte words. + const bytecodeLengthInWords = bytecodeAsArray.length / 32; + if (bytecodeLengthInWords % 2 === 0) { + throw new Error('Bytecode length in 32-byte words must be odd!'); + } + + // The bytecode should always take the first 2 bytes of the bytecode hash, + // so we pad it from the left in case the length is smaller than 2 bytes. + const bytecodeLengthPadded = web3Utils.bytesToUint8Array( + web3Utils.padLeft(bytecodeLengthInWords, 2 * 2), + ); + + const codeHashVersion = new Uint8Array([1, 0]); + hash.set(codeHashVersion, 0); + hash.set(bytecodeLengthPadded, 2); + + return hash; +} + +/** + * Returns the hash of the L2 priority operation from a given transaction receipt and L2 address. + * + * @param txReceipt The receipt of the L1 transaction. + * @param zkSyncAddress The address of the zkSync Era main contract. + * + * @example + */ + +let ZkSyncABIEvents: Array | null = null; + +const getZkSyncEvents = () => { + if (ZkSyncABIEvents === null) { + ZkSyncABIEvents = IZkSyncABI.filter(e => e.type === 'event').map(e => ({ + ...e, + signature: encodeEventSignature(jsonInterfaceMethodToString(e)), + })); + } + return ZkSyncABIEvents; +}; + +export function getL2HashFromPriorityOp( + txReceipt: web3Types.TransactionReceipt, + zkSyncAddress: web3.Address, +): string { + let txHash: string | null = null; + for (const log of txReceipt.logs) { + if (!isAddressEq(log.address as string, zkSyncAddress)) { + continue; + } + + try { + const decoded = decodeEventABI(ALL_EVENTS_ABI, log as LogsInput, getZkSyncEvents()); + if (decoded && decoded.returnValues && decoded.returnValues.txHash !== null) { + txHash = decoded.returnValues.txHash ? String(decoded.returnValues.txHash) : null; + } + } catch { + // skip + } + } + if (!txHash) { + throw new Error('Failed to parse tx logs!'); + } + + return txHash; +} + +const ADDRESS_MODULO = 2n ** 160n; + +/** + * Converts the address that submitted a transaction to the inbox on L1 to the `msg.sender` viewed on L2. + * Returns the `msg.sender` of the `L1->L2` transaction as the address of the contract that initiated the transaction. + * + * All available cases: + * - During a normal transaction, if contract `A` calls contract `B`, the `msg.sender` is `A`. + * - During `L1->L2` communication, if an EOA `X` calls contract `B`, the `msg.sender` is `X`. + * - During `L1->L2` communication, if a contract `A` calls contract `B`, the `msg.sender` is `applyL1ToL2Alias(A)`. + * + * @param address The address of the contract. + * @returns The transformed address representing the `msg.sender` on L2. + * + * @see + * {@link undoL1ToL2Alias}. + * + * @example + * + * const l1ContractAddress = "0x702942B8205E5dEdCD3374E5f4419843adA76Eeb"; + * const l2ContractAddress = utils.applyL1ToL2Alias(l1ContractAddress); + * // l2ContractAddress = "0x813A42B8205E5DedCd3374e5f4419843ADa77FFC" + * + */ +export function applyL1ToL2Alias(address: string): string { + return web3Utils.padLeft( + web3Utils.toHex((BigInt(address) + BigInt(L1_TO_L2_ALIAS_OFFSET)) % ADDRESS_MODULO), + 20 * 2, + ); +} + +/** + * Converts and returns the `msg.sender` viewed on L2 to the address that submitted a transaction to the inbox on L1. + * + * @param address The sender address viewed on L2. + * + * @see + * {@link applyL1ToL2Alias}. + * + * @example + * + * const l2ContractAddress = "0x813A42B8205E5DedCd3374e5f4419843ADa77FFC"; + * const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress); + * // const l1ContractAddress = "0x702942B8205E5dEdCD3374E5f4419843adA76Eeb" + */ +export function undoL1ToL2Alias(address: string): string { + let result = BigInt(address) - BigInt(L1_TO_L2_ALIAS_OFFSET); + if (result < 0n) { + result += ADDRESS_MODULO; + } + return web3Utils.padLeft(web3Utils.toHex(result), 20 * 2); +} + +/** + * Returns the data needed for correct initialization of an L1 token counterpart on L2. + * + * @param l1TokenAddress The token address on L1. + * @param provider The client that is able to work with contracts on a read-write basis. + * @returns The encoded bytes which contains token name, symbol and decimals. + */ +export async function getERC20DefaultBridgeData( + l1TokenAddress: string, + context: web3.Web3, // or maybe use RpcMethods? +): Promise { + if (isAddressEq(l1TokenAddress, LEGACY_ETH_ADDRESS)) { + l1TokenAddress = ETH_ADDRESS_IN_CONTRACTS; + } + const token = new context.eth.Contract(IERC20ABI, l1TokenAddress); + const name = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) + ? 'Ether' + : await token.methods.name().call(); + const symbol = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) + ? 'ETH' + : await token.methods.symbol().call(); + const decimals = isAddressEq(l1TokenAddress, ETH_ADDRESS_IN_CONTRACTS) + ? 18 + : await token.methods.decimals().call(); + + return web3Abi.encodeParameters( + ['string', 'string', 'uint256'], + [name, symbol, Number(decimals)], + ); +} + +/** + * Returns the calldata sent by an L1 ERC20 bridge to its L2 counterpart during token bridging. + * + * @param l1TokenAddress The token address on L1. + * @param l1Sender The sender address on L1. + * @param l2Receiver The recipient address on L2. + * @param amount The gas fee for the number of tokens to bridge. + * @param bridgeData Additional bridge data. + * + * @example + * + * + */ +export async function getERC20BridgeCalldata( + l1TokenAddress: string, + l1Sender: string, + l2Receiver: string, + amount: web3Types.Numbers, + bridgeData: web3Types.Bytes, +): Promise { + return L2BridgeContract.methods + .finalizeDeposit(l1Sender, l2Receiver, l1TokenAddress, amount, bridgeData) + .encodeABI(); +} + +/** + * Validates signatures from non-contract account addresses (EOA: Externally Owned Account). + * Provides similar functionality to `new Web3().eth.accounts.recover(message, v, r, s)` but returns `true` + * if the validation process succeeds, otherwise returns `false`. + * + * Called from {@link isSignatureCorrect} for non-contract account addresses. + * + * @param address The address which signs the `msgHash`. + * @param message The message which its hash had been signed early. + * @param signature The Ethers signature. + * + * @example + * + * import * as web3Accounts from 'web3-eth-accounts'; + * + * const wallet = web3Accounts.create(); + * const ADDRESS = wallet.address; + * const PRIVATE_KEY = wallet.privateKey; + * + * const message = "Hello, world!"; + * + * const signature = web3Accounts.sign(message, PRIVATE_KEY).signature; + * const isValidSignature = await utils.isECDSASignatureCorrect(ADDRESS, message, signature); + * // isValidSignature = true + */ +function isECDSASignatureCorrect( + address: string, + message: string | web3Types.Eip712TypedData, + signature: SignatureLike, +): boolean { + try { + return isAddressEq(address, recoverSignerAddress(message, signature)); + } catch { + // In case ECDSA signature verification has thrown an error, + // we simply consider the signature as incorrect. + return false; + } +} + +/** + * Called from {@link isSignatureCorrect} for contract account addresses. + * The function returns `true` if the validation process results + * in the {@link EIP1271_MAGIC_VALUE}. + * + * @param context The web3 context. + * @param address The sender address. + * @param msgHash The hash of the message. + * @param signature The Ethers signature. + * + * @see + * {@link isMessageSignatureCorrect} and {@link isTypedDataSignatureCorrect} to validate signatures. + * + * @example + * + */ +async function isEIP1271SignatureCorrect( + context: web3.Web3Context, // or maybe use RpcMethods? + address: string, + msgHash: string, + signature: SignatureLike, +): Promise { + const accountContract = new web3.Contract(IERC1271ABI, address, context); + + // This line may throw an exception if the contract does not implement the EIP1271 correctly. + // But it may also throw an exception in case the internet connection is lost. + // It is the caller's responsibility to handle the exception. + const result = await accountContract.methods.isValidSignature(msgHash, signature).call(); + + return result === EIP1271_MAGIC_VALUE; +} + +/** + * Called from {@link isMessageSignatureCorrect} and {@link isTypedDataSignatureCorrect}. + * Returns whether the account abstraction signature is correct. + * Signature can be created using EIP1271 or ECDSA. + * + * @param context The web3 context. + * @param address The sender address. + * @param message The message which its hash had been signed early. + * @param signature The Ethers signature. + */ +async function isSignatureCorrect( + context: web3.Web3Context, // or maybe use RpcMethods? + address: string, + message: string | web3Types.Eip712TypedData, + signature: SignatureLike, +): Promise { + let isContractAccount; + if (context.provider) { + const code = await web3.eth.getCode( + context, + address, + undefined, + web3Types.DEFAULT_RETURN_FORMAT, + ); + isContractAccount = web3Utils.bytesToUint8Array(code).length !== 0; + } + + if (!isContractAccount) { + return isECDSASignatureCorrect(address, message, signature); + } else { + const msgHash = web3Accounts.hashMessage( + typeof message === 'string' + ? message + : web3Utils.bytesToHex(message as unknown as string), + ); + return await isEIP1271SignatureCorrect(context, address, msgHash, signature); + } +} + +/** + * Returns whether the account abstraction message signature is correct. + * Signature can be created using EIP1271 or ECDSA. + * + * @param provider The provider. + * @param address The sender address. + * @param message The hash of the message. + * @param signature The Ethers signature. + * + * @example + * + * import { Web3 } from 'web3'; + * import * as web3Accounts from 'web3-eth-accounts'; + * + * const wallet = web3Accounts.create(); + * const ADDRESS = wallet.address; + * const PRIVATE_KEY = wallet.privateKey; + * const context = new Web3('some-rpc-url'); + * + * const message = "Hello, world!"; + * const signature = await new Wallet(PRIVATE_KEY).signMessage(message); + * const isValidSignature = await utils.isMessageSignatureCorrect( + * web3, + * ADDRESS, + * message, + * signature, + * ); + * // isValidSignature = true + */ +export async function isMessageSignatureCorrect( + context: web3.Web3Context, // or maybe use RpcMethods? + address: string, + message: Uint8Array | string, + signature: SignatureLike, +): Promise { + return await isSignatureCorrect(context, address, web3Utils.toHex(message), signature); +} + +/** + * Returns whether the account abstraction EIP712 signature is correct. + * + * @param context The web3 context. + * @param address The sender address. + * @param domain The domain data. + * @param types A map of records pointing from field name to field type. + * @param value A single record value. + * @param signature The Ethers signature. + * + * @example + * + * import { Wallet, utils, constants, Provider, EIP712Signer } from "web3-plugin-zksync"; + * + * const ADDRESS = ""; + * const PRIVATE_KEY = ""; + * const context = Provider.getDefaultProvider(types.Network.Sepolia); + * + * const tx: types.TransactionRequest = { + * type: 113, + * chainId: 270, + * from: ADDRESS, + * to: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", + * value: BigInt(7_000_000), + * }; + * + * const eip712Signer = new EIP712Signer( + * new Wallet(PRIVATE_KEY), // or web3Accounts.privateKeyToAccount(PRIVATE_KEY), + * Number((await context.getNetwork()).chainId) + * ); + * + * const signature = await eip712Signer.sign(tx); + * + * const isValidSignature = await utils.isTypedDataSignatureCorrect(context, ADDRESS, await eip712Signer.getDomain(), constants.EIP712_TYPES, 'Transaction', EIP712Signer.getSignInput(tx), signature); + * // isValidSignature = true + */ +export async function isTypedDataSignatureCorrect( + context: web3.Web3Context, // or maybe use RpcMethods? + address: string, + domain: web3Types.Eip712TypedData['domain'], + types: web3Types.Eip712TypedData['types'], + value: Record, + signature: SignatureLike, +): Promise { + const typedDataStruct: web3Types.Eip712TypedData = { + domain, + types, + primaryType: 'Transaction', + message: value, + }; + + return isSignatureCorrect(context, address, typedDataStruct, signature); +} + +/** + * 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 providerL2 The zkSync provider for the L2 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. + * + * @see + * {@link https://docs.zksync.io/build/developer-reference/bridging-asset.html#default-bridges Default bridges documentation}. + * + * @example + * + * + */ +export async function estimateDefaultBridgeDepositL2Gas( + providerL1: web3.Web3, + providerL2: Web3ZkSyncL2, + token: web3.Address, + amount: web3Types.Numbers, + to: web3.Address, + from?: web3.Address, + gasPerPubdataByte?: web3Types.Numbers, +): Promise { + // 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 ??= web3Accounts.create().address; + if (await providerL2.isBaseToken(token)) { + return await providerL2.estimateL1ToL2Execute({ + contractAddress: to, + gasPerPubdataByte: gasPerPubdataByte, + caller: from, + calldata: '0x', + l2Value: amount, + }); + } else { + const bridgeAddresses = await providerL2.getDefaultBridgeAddresses(); + + const value = 0; + const l1BridgeAddress = bridgeAddresses.sharedL1; + const l2BridgeAddress = bridgeAddresses.sharedL2; + const bridgeData = await getERC20DefaultBridgeData(token, providerL1); + + return estimateCustomBridgeDepositL2Gas( + providerL2, + l1BridgeAddress, + l2BridgeAddress, + isAddressEq(token, LEGACY_ETH_ADDRESS) ? ETH_ADDRESS_IN_CONTRACTS : token, + amount, + to, + bridgeData, + from, + gasPerPubdataByte, + value, + ); + } +} + +/** + * Scales the provided gas limit using a coefficient to ensure acceptance of L1->L2 transactions. + * + * This function adjusts the gas limit by multiplying it with a coefficient calculated from the + * `L1_FEE_ESTIMATION_COEF_NUMERATOR` and `L1_FEE_ESTIMATION_COEF_DENOMINATOR` constants. + * + * @param gasLimit - The gas limit to be scaled. + * + * @example + * + * const scaledGasLimit = utils.scaleGasLimit(10_000); + * // scaledGasLimit = 12_000 + */ +export function scaleGasLimit(gasLimit: bigint): bigint { + return ( + (gasLimit * BigInt(L1_FEE_ESTIMATION_COEF_NUMERATOR)) / + BigInt(L1_FEE_ESTIMATION_COEF_DENOMINATOR) + ); +} + +/** + * Returns an estimation of the L2 gas required for token bridging via the custom ERC20 bridge. + * + * @param providerL2 The zkSync provider for the L2 network. + * @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. + * + * @see + * {@link https://docs.zksync.io/build/developer-reference/bridging-asset.html#custom-bridges-on-l1-and-l2 Custom bridges documentation}. + * + * @example + * + * + */ +export async function estimateCustomBridgeDepositL2Gas( + providerL2: Web3ZkSyncL2, + l1BridgeAddress: web3.Address, + l2BridgeAddress: web3.Address, + token: web3.Address, + amount: web3Types.Numbers, + to: web3.Address, + bridgeData: web3Types.Bytes, + from: web3.Address, + gasPerPubdataByte?: web3Types.Numbers, + l2Value?: web3Types.Numbers, +): Promise { + const calldata = await getERC20BridgeCalldata(token, from, to, amount, bridgeData); + return providerL2.estimateL1ToL2Execute({ + caller: applyL1ToL2Alias(l1BridgeAddress), + contractAddress: l2BridgeAddress, + gasPerPubdataByte: gasPerPubdataByte, + calldata: calldata, + l2Value: l2Value, + }); +} + +/** + * Creates a JSON string from an object, including support for serializing bigint types. + * + * @param object The object to serialize to JSON. + */ +export function toJSON(object: any): string { + return JSON.stringify( + object, + (_, value) => { + if (typeof value === 'bigint') { + return value.toString(); // Convert BigInt to string + } + return value; + }, + 2, + ); +} + +/** + * Compares stringified addresses, taking into account the fact that + * addresses might be represented in different casing. + * + * @param a - The first address to compare. + * @param b - The second address to compare. + * @returns A boolean indicating whether the addresses are equal. + */ +export function isAddressEq(a: web3.Address, b: web3.Address): boolean { + return a.toLowerCase() === b.toLowerCase(); +} + +export async function waitTxReceipt(web3Eth: Web3Eth, txHash: string): Promise { + while (true) { + try { + const receipt = await web3Eth.getTransactionReceipt(txHash); + if (receipt && receipt.blockNumber) { + return receipt; + } + } catch {} + await sleep(500); + } +} +export async function waitTxByHashConfirmation( + web3Eth: Web3Eth, + txHash: TransactionHash, + waitConfirmations = 1, +): Promise { + const receipt = await waitTxReceipt(web3Eth, txHash); + while (true) { + const blockNumber = await web3Eth.getBlockNumber(); + if (toBigInt(blockNumber) - toBigInt(receipt.blockNumber) + 1n >= waitConfirmations) { + return receipt; + } + await sleep(500); + } +} + +export const getPriorityOpResponse = ( + context: Web3ZkSyncL1 | Web3ZkSyncL2, + l1TxPromise: Promise, + contextL2?: Web3ZkSyncL2, +): PriorityOpResponse => { + if (context instanceof Web3ZkSyncL1) { + return getPriorityOpL1Response(context, l1TxPromise, contextL2); + } else { + return getPriorityOpL2Response(context, l1TxPromise); + } +}; + +export const getPriorityOpL1Response = ( + context: Web3ZkSyncL1, + l1TxPromise: Promise, + contextL2?: Web3ZkSyncL2, +): PriorityOpResponse => { + return { + waitL1Commit: async () => { + const hash = await l1TxPromise; + return waitTxByHashConfirmation(context.eth, hash, 1); + }, + wait: async () => { + const hash = await l1TxPromise; + return waitTxByHashConfirmation(context.eth, hash, 1); + }, + waitFinalize: async () => { + const hash = await l1TxPromise; + const receipt = await waitTxReceipt(context.eth, hash); + const l2TxHash = await (contextL2 as Web3ZkSyncL2).getL2TransactionFromPriorityOp( + receipt, + ); + + if (!contextL2) { + return { + transactionHash: l2TxHash, + } as TransactionReceipt; + } + + return await waitTxByHashConfirmationFinalized(contextL2.eth, l2TxHash, 1); + }, + }; +}; + +export const getPriorityOpL2Response = ( + context: Web3ZkSyncL2, + txPromise: Promise, +): PriorityL2OpResponse => { + return { + wait: async () => { + const hash = await txPromise; + return waitTxByHashConfirmation(context.eth, hash, 1); + }, + waitFinalize: async () => { + const hash = await txPromise; + + return await waitTxByHashConfirmationFinalized(context.eth, hash, 1, 'finalized'); + }, + }; +}; + +export async function waitTxByHashConfirmationFinalized( + web3Eth: Web3Eth, + txHash: TransactionHash, + waitConfirmations = 1, + blogTag?: BlockNumberOrTag, +): Promise { + const receipt = await waitTxReceipt(web3Eth, txHash); + while (true) { + const block = await web3Eth.getBlock(blogTag ?? 'latest'); + if (toBigInt(block.number) - toBigInt(receipt.blockNumber) + 1n >= waitConfirmations) { + return receipt; + } + await sleep(500); + // 3298012n // block.number + // 3303874n + } +} + +/** + * A simple hashing function which operates on UTF-8 strings to compute an 32-byte identifier. + * This simply computes the UTF-8 bytes and computes the [[keccak256]]. + * @param value + */ +export function id(value: string): string { + return keccak256(value); +} + +export function dataSlice(data: Bytes, start?: number, end?: number): string { + const bytes = toBytes(data); + if (end != null && end > bytes.length) { + throw new Error('cannot slice beyond data bounds'); + } + return web3Utils.toHex( + bytes.slice(start == null ? 0 : start, end == null ? bytes.length : end), + ); +} diff --git a/src/web3zksync-l1.ts b/src/web3zksync-l1.ts new file mode 100644 index 0000000..57f8516 --- /dev/null +++ b/src/web3zksync-l1.ts @@ -0,0 +1,6 @@ +import { Web3ZkSync } from './web3zksync'; + +// Equivalent to both L1Provider and L1Signer in sksync-ethers +export class Web3ZkSyncL1 extends Web3ZkSync { + // TODO: Add and possibly move some of the methods from `Web3ZkSync` class. +} diff --git a/src/web3zksync-l2.ts b/src/web3zksync-l2.ts new file mode 100644 index 0000000..d80f39f --- /dev/null +++ b/src/web3zksync-l2.ts @@ -0,0 +1,397 @@ +// import type { Web3ContextInitOptions } from 'web3-core'; +// import { Web3Eth } from 'web3-eth'; +// import * as web3Utils from 'web3-utils'; +// import type { Address, HexString } from 'web3'; + +import type { Block } from 'web3'; +import { DEFAULT_RETURN_FORMAT } from 'web3-types'; +import type { + BlockNumberOrTag, + Bytes, + DataFormat, + Numbers, + Transaction, + TransactionReceipt, +} from 'web3-types'; +import { format, toHex } from 'web3-utils'; +import { ethRpcMethods } from 'web3-rpc-methods'; +import { isNullish } from 'web3-validator'; +import { getL2HashFromPriorityOp, isAddressEq, isETH, sleep } from './utils'; +import { Network as ZkSyncNetwork, TransactionStatus } from './types'; +import type { Address, TransactionOverrides, PaymasterParams, ZKTransactionReceipt } from './types'; +import { Web3ZkSync } from './web3zksync'; +import { ZKTransactionReceiptSchema } from './schemas'; +import { Abi as IEthTokenAbi } from './contracts/IEthToken'; +import { + BOOTLOADER_FORMAL_ADDRESS, + EIP712_TX_TYPE, + ETH_ADDRESS_IN_CONTRACTS, + L2_BASE_TOKEN_ADDRESS, + LEGACY_ETH_ADDRESS, +} from './constants'; +import { IL2BridgeABI } from './contracts/IL2Bridge'; +import { IERC20ABI } from './contracts/IERC20'; + +// Equivalent to both Provider and Signer in zksync-ethers +export class Web3ZkSyncL2 extends Web3ZkSync { + // protected _contractAddresses: { + // mainContract?: Address; + // erc20BridgeL1?: Address; + // erc20BridgeL2?: Address; + // wethBridgeL1?: Address; + // wethBridgeL2?: Address; + // }; + + // override contractAddresses(): { + // mainContract?: Address; + // erc20BridgeL1?: Address; + // erc20BridgeL2?: Address; + // wethBridgeL1?: Address; + // wethBridgeL2?: Address; + // } { + // 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?: ethers.FetchRequest | string, network?: Networkish, options?: any) { + // if (!url) { + // url = 'http://localhost:3050'; + // } + + // const isLocalNetwork = + // typeof url === 'string' + // ? url.includes('localhost') || url.includes('127.0.0.1') + // : url.url.includes('localhost') || url.url.includes('127.0.0.1'); + + // const optionsWithDisabledCache = isLocalNetwork ? { ...options, cacheTimeout: -1 } : options; + + // super(url, network, optionsWithDisabledCache); + // typeof url === 'string' + // ? (this.#connect = new FetchRequest(url)) + // : (this.#connect = url.clone()); + // this.pollingInterval = 500; + // this._contractAddresses = {}; + // } + + // override async _send( + // payload: JsonRpcPayload | Array, + // ): Promise> { + // const request = this._getConnection(); + // request.body = JSON.stringify(payload); + // request.setHeader('content-type', 'application/json'); + + // const response = await request.send(); + // response.assertOk(); + + // let resp = response.bodyJson; + // if (!Array.isArray(resp)) { + // resp = [resp]; + // } + + // return resp; + // } + + async getZKTransactionReceipt( + transactionHash: Bytes, + returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat, + ) { + const transactionHashFormatted = format( + { format: 'bytes32' }, + transactionHash, + DEFAULT_RETURN_FORMAT, + ); + const response = await ethRpcMethods.getTransactionReceipt( + this.requestManager, + transactionHashFormatted, + ); + + return isNullish(response) + ? response + : format( + ZKTransactionReceiptSchema, + response as unknown as ZKTransactionReceipt, + returnFormat ?? this.defaultReturnFormat, + ); + } + + async _getPriorityOpConfirmationL2ToL1Log(txHash: string, index = 0) { + const hash = toHex(txHash); + const receipt = await this.getZKTransactionReceipt(hash); + if (!receipt) { + throw new Error('Transaction is not mined!'); + } + const messages = Array.from(receipt.l2ToL1Logs.entries()).filter(([, log]) => + isAddressEq(log.sender, 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: string, index = 0) { + const { l2ToL1LogIndex, l2ToL1Log, l1BatchTxId } = + await this._getPriorityOpConfirmationL2ToL1Log(txHash, index); + const proof = await this._rpc.getL2ToL1LogProof(txHash, l2ToL1LogIndex); + return { + l1BatchNumber: l2ToL1Log.l1BatchNumber, + l2MessageIndex: proof.id, + l2TxNumberInBlock: l1BatchTxId, + proof: proof.proof, + }; + } + + /** + * Returns a L2 transaction response from L1 transaction response. + * + * @param context + * @param txHash + */ + async getL2TransactionFromPriorityOp(receipt: TransactionReceipt) { + const l2Hash = getL2HashFromPriorityOp(receipt, await this.getMainContractAddress()); + + let status = null; + do { + status = await this.getTransactionStatus(l2Hash); + await sleep(this.transactionPollingInterval); + } while (status === TransactionStatus.NotFound); + + return l2Hash; + } + + /** + * 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: string): Promise { + let tx; + try { + tx = await this.eth.getTransaction(txHash); + } catch {} + + if (!tx) { + return TransactionStatus.NotFound; + } + if (!tx.blockNumber) { + return TransactionStatus.Processing; + } + const verifiedBlock = (await this.eth.getBlock('finalized')) as Block; + if (tx.blockNumber <= verifiedBlock.number) { + return TransactionStatus.Finalized; + } + return TransactionStatus.Committed; + } + /** + * Returns the populated 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 getWithdrawTx(transaction: { + token: Address; + amount: Numbers; + from?: Address; + to?: Address; + bridgeAddress?: Address; + paymasterParams?: PaymasterParams; + overrides?: TransactionOverrides; + }) { + const { ...tx } = transaction; + const isEthBasedChain = await this.isEthBasedChain(); + + // In case of Ether on non Ether based chain it should get l2 Ether address, + // and in case of base token it should use L2_BASE_TOKEN_ADDRESS + if (isAddressEq(tx.token, LEGACY_ETH_ADDRESS) && !isEthBasedChain) { + tx.token = await this.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + } else if (await this.isBaseToken(tx.token)) { + tx.token = L2_BASE_TOKEN_ADDRESS; + } + + if ( + (tx.to === null || tx.to === undefined) && + (tx.from === null || tx.from === undefined) + ) { + throw new Error('Withdrawal target address is undefined!'); + } + + tx.to ??= tx.from; + tx.overrides ??= {} as TransactionOverrides; + tx.overrides.from ??= tx.from as Address; + + if (isETH(tx.token)) { + if (!tx.overrides?.value) { + tx.overrides.value = toHex(tx.amount); + } + const passedValue = BigInt(tx.overrides?.value ?? 0); + + 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 = new this.eth.Contract(IEthTokenAbi, L2_BASE_TOKEN_ADDRESS); + const populatedTx = ethL2Token.methods + .withdraw(tx.to) + // @ts-ignore + .populateTransaction(tx.overrides); + if (tx.paymasterParams) { + return { + ...populatedTx, + customData: { + paymasterParams: tx.paymasterParams, + }, + }; + } + return populatedTx; + } + + if (!tx.bridgeAddress) { + const bridgeAddresses = await this.getDefaultBridgeAddresses(); + tx.bridgeAddress = bridgeAddresses.sharedL2; + } + const bridge = new this.eth.Contract(IL2BridgeABI, tx.bridgeAddress); + + const populatedTx = bridge.methods + .withdraw(tx.to, tx.token, tx.amount) + // @ts-ignore + .populateTransaction(tx.overrides); + + if (tx.paymasterParams) { + return { + ...populatedTx, + customData: { + paymasterParams: tx.paymasterParams, + }, + }; + } + return populatedTx; + } + + /** + * 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: { + to: Address; + amount: Numbers; + from?: Address; + token?: Address; + paymasterParams?: PaymasterParams; + overrides?: TransactionOverrides; + }) { + const { ...tx } = transaction; + const isEthBasedChain = await this.isEthBasedChain(); + + // In case of Ether on non Ether based chain it should get l2 Ether address, + // and in case of base token it should use L2_BASE_TOKEN_ADDRESS + if (tx.token && isAddressEq(tx.token, LEGACY_ETH_ADDRESS) && !isEthBasedChain) { + tx.token = await this.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + } else if (!tx.token || (await this.isBaseToken(tx.token))) { + tx.token = L2_BASE_TOKEN_ADDRESS; + } + + tx.overrides ??= {} as TransactionOverrides; + tx.overrides.from ??= tx.from as Address; + + if (isETH(tx.token)) { + if (tx.paymasterParams) { + return { + ...tx.overrides, + type: EIP712_TX_TYPE, + to: tx.to, + value: tx.amount, + customData: { + paymasterParams: tx.paymasterParams, + } as Transaction, + }; + } + + return { + ...tx.overrides, + to: tx.to, + value: tx.amount, + } as Transaction; + } else { + const token = new this.eth.Contract(IERC20ABI, tx.token); + const populatedTx = token.methods + .transfer(tx.to, tx.amount) + // @ts-ignore + .populateTransaction(tx.overrides); + + if (tx.paymasterParams) { + return { + ...populatedTx, + customData: { + paymasterParams: tx.paymasterParams, + }, + }; + } + return populatedTx as Transaction; + } + } + + /** + * Creates a new `Provider` from provided URL or network name. + * + * @param zksyncNetwork The type of zkSync network. + * + * @example + * + * import { initWithDefaultProvider, types } from "web3-plugin-zksync"; + * + * const provider = ZkSyncNetwork.initWithDefaultProvider(types.Network.Sepolia); + */ + static initWithDefaultProvider( + zksyncNetwork: ZkSyncNetwork = ZkSyncNetwork.Localhost, + ): Web3ZkSyncL2 { + switch (zksyncNetwork) { + case ZkSyncNetwork.Localhost: + return new Web3ZkSyncL2('http://localhost:3050'); + case ZkSyncNetwork.Sepolia: + return new Web3ZkSyncL2('https://sepolia.era.zksync.dev'); + case ZkSyncNetwork.Mainnet: + return new Web3ZkSyncL2('https://mainnet.era.zksync.io'); + case ZkSyncNetwork.EraTestNode: + return new Web3ZkSyncL2('http://localhost:8011'); + default: + return new Web3ZkSyncL2('http://localhost:3050'); + } + } + + getBalance(address: Address, blockNumber: BlockNumberOrTag = this.defaultBlock) { + return this.eth.getBalance(address, blockNumber); + } +} diff --git a/src/web3zksync.ts b/src/web3zksync.ts new file mode 100644 index 0000000..7c0b471 --- /dev/null +++ b/src/web3zksync.ts @@ -0,0 +1,630 @@ +import type { Web3ContextInitOptions } from 'web3-core'; +import * as web3Utils from 'web3-utils'; +import type * as web3Types from 'web3-types'; +import * as web3Accounts from 'web3-eth-accounts'; +import { DEFAULT_RETURN_FORMAT } from 'web3'; +import { estimateGas, getGasPrice, transactionBuilder, transactionSchema } from 'web3-eth'; +import * as Web3 from 'web3'; +import type { Transaction } from 'web3-types'; +import { toHex } from 'web3-utils'; +import { ethRpcMethods } from 'web3-rpc-methods'; +import type { + BatchDetails, + BlockDetails, + BridgeAddresses, + EstimateFee, + L2ToL1Proof, + StorageProof, + RawBlockTransaction, + TransactionDetails, + WalletBalances, + TransactionRequest, + Address, + TransactionOverrides, + Eip712TxData, + Eip712Meta, +} from './types'; +import { + DEFAULT_GAS_PER_PUBDATA_LIMIT, + EIP712_TX_TYPE, + ETH_ADDRESS_IN_CONTRACTS, + L2_BASE_TOKEN_ADDRESS, + LEGACY_ETH_ADDRESS, + REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT, +} from './constants'; +import { EIP712, type EIP712Signer, isAddressEq } from './utils'; +import { RpcMethods } from './rpc.methods'; +import { IL2BridgeABI } from './contracts/IL2Bridge'; +import { IERC20ABI } from './contracts/IERC20'; + +/** + * The base class for interacting with zkSync Era. + * It extends the `Web3Eth` class and provides additional methods for interacting with zkSync Era. + * It is the base class for the `Web3ZkSyncL1` and `Web3ZkSyncL2`. + */ +// Note: Code logic here is similar to JsonRpcApiProvider class in zksync-ethers +export class Web3ZkSync extends Web3.Web3 { + protected _rpc: RpcMethods; + + protected _contractAddresses: { + bridgehubContract?: web3Types.Address; + mainContract?: web3Types.Address; + erc20BridgeL1?: web3Types.Address; + erc20BridgeL2?: web3Types.Address; + wethBridgeL1?: web3Types.Address; + wethBridgeL2?: web3Types.Address; + sharedBridgeL1?: web3Types.Address; + sharedBridgeL2?: web3Types.Address; + baseToken?: web3Types.Address; + }; + + protected contractAddresses(): { + bridgehubContract?: web3Types.Address; + mainContract?: web3Types.Address; + erc20BridgeL1?: web3Types.Address; + erc20BridgeL2?: web3Types.Address; + wethBridgeL1?: web3Types.Address; + wethBridgeL2?: web3Types.Address; + sharedBridgeL1?: web3Types.Address; + sharedBridgeL2?: web3Types.Address; + baseToken?: web3Types.Address; + } { + return this._contractAddresses; + } + + constructor( + providerOrContext?: web3Types.SupportedProviders | Web3ContextInitOptions | string, + ) { + // @ts-ignore + super(providerOrContext as web3Types.SupportedProviders); + // @ts-ignore + this._rpc = new RpcMethods(this.requestManager); + + this._contractAddresses = {}; + } + + /** + * Returns the chain id of the underlying L1. + * + * @param returnFormat - The format of the return value. + */ + public async l1ChainId( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.l1ChainId(returnFormat); + } + async _eip712Signer(): Promise { + throw new Error('Must be implemented by the derived class!'); + } + /** + * Returns the latest L1 batch number. + * + * @param returnFormat - The format of the return value. + */ + public async getL1BatchNumber( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getL1BatchNumber(returnFormat); + } + + /** + * Returns data pertaining to a given batch. + * + * @param number - The layer 1 batch number. + * @param returnFormat - The format of the return value. + */ + public async getL1BatchDetails( + number: web3Types.Numbers, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getL1BatchDetails(number, returnFormat); + } + + /** + * Returns additional zkSync-specific information about the L2 block. + * + * committed: The batch is closed and the state transition it creates exists on layer 1. + * proven: The batch proof has been created, submitted, and accepted on layer 1. + * executed: The batch state transition has been executed on L1; meaning the root state has been updated. + * + * @param number - The number of the block. + * @param returnFormat - The format of the return value. + */ + public async getBlockDetails( + number: web3Types.Numbers, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getBlockDetails(number, returnFormat); + } + + /** + * Returns data from a specific transaction given by the transaction hash. + * + * @param txHash - Transaction hash as string. + * @param returnFormat - The format of the return value. + */ + public async getTransactionDetails( + txHash: web3Types.Bytes, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getTransactionDetails(txHash, returnFormat); + } + + /** + * Returns bytecode of a transaction given by its hash. + * + * @param bytecodeHash - Bytecode hash as string. + * @param returnFormat - The format of the return value. + */ + public async getBytecodeByHash( + bytecodeHash: web3Types.Bytes, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getBytecodeByHash(bytecodeHash, returnFormat); + } + + /** + * Returns data of transactions in a block. + * + * @param number - Block number. + * @param returnFormat - The format of the return value. + */ + public async getRawBlockTransactions( + number: web3Types.Numbers, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getRawBlockTransactions(number, returnFormat); + } + + /** + * Returns the fee for the transaction. + * + * @param transaction - Transaction object. + * @param returnFormat - The format of the return value. + */ + public async estimateFee( + transaction: Partial, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.estimateFee(transaction, returnFormat); + } + + /** + * Returns the L1 base token address. + */ + async getBaseTokenContractAddress(): Promise { + if (!this.contractAddresses().baseToken) { + this.contractAddresses().baseToken = await this._rpc.getBaseTokenL1Address(); + } + return this.contractAddresses().baseToken!; + } + + /** + * Returns whether the chain is ETH-based. + */ + async isEthBasedChain(): Promise { + return isAddressEq(await this.getBaseTokenContractAddress(), ETH_ADDRESS_IN_CONTRACTS); + } + + /** + * Returns whether the `token` is the base token. + */ + async isBaseToken(token: web3Types.Address): Promise { + return ( + isAddressEq(token, await this.getBaseTokenContractAddress()) || + isAddressEq(token, 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(): Promise { + // 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 this._rpc.getTestnetPaymasterAddress(); + } + + /** + * 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(): Promise<{ + erc20L1: string; + erc20L2: string; + wethL1: string; + wethL2: string; + sharedL1: string; + sharedL2: string; + }> { + if (!this.contractAddresses().erc20BridgeL1) { + const addresses = await this._rpc.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!, + }; + } + + /** + * Returns an estimate of the gas required for a L1 to L2 transaction. + * + * @param transaction - Transaction object. + * @param returnFormat - The format of the return value. + */ + public async estimateGasL1ToL2( + transaction: Partial, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.estimateGasL1ToL2(transaction, returnFormat); + } + + /** + * Returns all balances for confirmed tokens given by an account address. + * + * @param address - The account address. + * @param returnFormat - The format of the return value. + */ + public async getAllAccountBalances( + address: web3Types.Address, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getAllAccountBalances(address, returnFormat); + } + + /** + * Returns the address of the zkSync Era contract. + * + * @param returnFormat - The format of the return value. + */ + public async getMainContract( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getMainContract(returnFormat); + } + + /** + * Returns the range of blocks contained within a batch given by batch number. + * The range is given by beginning/end block numbers in hexadecimal. + * + * @param number The layer 1 batch number. + * @param returnFormat - The format of the return value. + */ + public async getL1BatchBlockRange( + number: web3Types.Numbers, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getL1BatchBlockRange(number, returnFormat); + } + + /** + * Returns Merkle proofs for one or more storage values at the specified account along with a Merkle proof of their authenticity. This allows to verify that the values have not been tampered with. + * More details: https://docs.zksync.io/build/api.html#zks-getproof + * + * @param address - The account to fetch storage values and proofs for. + * @param keys - Vector of storage keys in the account. + * @param l1BatchNumber - Number of the L1 batch specifying the point in time at which the requested values are returned. + * @param returnFormat - The format of the return value. + */ + // @ts-ignore + public async getProof( + address: web3Types.Address, + keys: string[], + l1BatchNumber: web3Types.Numbers, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getProof(address, keys, l1BatchNumber, returnFormat); + } + + /** + * Given a transaction hash, and an index of the L2 to L1 log produced within the transaction, it returns the proof for the corresponding L2 to L1 log. + * + * The index of the log that can be obtained from the transaction receipt (it includes a list of every log produced by the transaction) + * + * @param txHash - Hash of the L2 transaction the L2 to L1 log was produced within. + * @param l2ToL1LogIndex - The index of the L2 to L1 log in the transaction (optional). + * @param returnFormat - The format of the return value. + */ + public async getL2ToL1LogProof( + txHash: web3Types.HexString32Bytes, + l2ToL1LogIndex?: web3Types.Numbers, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getL2ToL1LogProof(txHash, l2ToL1LogIndex, returnFormat); + } + + /** + * Returns L1/L2 addresses of default bridges. + * + * @param returnFormat - The format of the return value. + */ + public async getBridgeContracts( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + return this._rpc.getBridgeContracts(returnFormat); + } + + /** + * Returns the address of the BridgeHub contract. + * + * @param returnFormat - The format of the return value. + */ + public async getBridgehubContractAddress( + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise
{ + if (!this.contractAddresses().bridgehubContract) { + this.contractAddresses().bridgehubContract = + await this._rpc.getBridgehubContractAddress(returnFormat); + } + return this.contractAddresses().bridgehubContract!; + } + + /** + * 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 current L2 gas value. + * @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`. + */ + // TODO (EVM-3): support refundRecipient for fee estimation + async estimateL1ToL2Execute( + transaction: { + contractAddress: web3Types.Address; + calldata: string; + caller?: web3Types.Address; + l2Value?: web3Types.Numbers; + factoryDeps?: web3Types.Bytes[]; + gasPerPubdataByte?: web3Types.Numbers; + overrides?: TransactionOverrides; + }, + returnFormat: web3Types.DataFormat = DEFAULT_RETURN_FORMAT, + ): Promise { + transaction.gasPerPubdataByte ??= 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 ??= web3Accounts.create().address; + + const customData = { + gasPerPubdata: transaction.gasPerPubdataByte, + }; + if (transaction.factoryDeps) { + Object.assign(customData, { factoryDeps: transaction.factoryDeps }); + } + + return this.estimateGasL1ToL2( + { + from: transaction.caller, + data: transaction.calldata, + to: transaction.contractAddress, + value: transaction.l2Value ? web3Utils.toHex(transaction.l2Value) : undefined, + customData, + }, + returnFormat, + ); + } + + /** + * 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. + */ + async l2TokenAddress(token: Address): Promise { + if (isAddressEq(token, LEGACY_ETH_ADDRESS)) { + token = ETH_ADDRESS_IN_CONTRACTS; + } + + const baseToken = await this.getBaseTokenContractAddress(); + if (isAddressEq(token, baseToken)) { + return L2_BASE_TOKEN_ADDRESS; + } + + const bridgeAddresses = await this.getDefaultBridgeAddresses(); + + const contract = new Web3.Contract(IL2BridgeABI, bridgeAddresses.sharedL2); + contract.setProvider(this.provider); + return await contract.methods.l2TokenAddress(token).call(); + } + + /** + * 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(): Promise
{ + if (!this.contractAddresses().mainContract) { + this.contractAddresses().mainContract = await this._rpc.getMainContract(); + } + return this.contractAddresses().mainContract!; + } + // /** + // * 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. + // */ + // override getRpcTransaction(tx: TransactionRequest): JsonRpcTransactionRequest { + // const result: any = super.getRpcTransaction(tx); + // if (!tx.customData) { + // return result; + // } + // result.type = ethers.toBeHex(EIP712_TX_TYPE); + // result.eip712Meta = { + // gasPerPubdata: ethers.toBeHex(tx.customData.gasPerPubdata ?? 0), + // } as any; + // if (tx.customData.factoryDeps) { + // result.eip712Meta.factoryDeps = tx.customData.factoryDeps.map((dep: ethers.BytesLike) => + // // TODO (SMA-1605): we arraify instead of hexlifying because server expects Vec. + // // We should change deserialization there. + // Array.from(ethers.getBytes(dep)), + // ); + // } + // if (tx.customData.paymasterParams) { + // result.eip712Meta.paymasterParams = { + // paymaster: ethers.hexlify(tx.customData.paymasterParams.paymaster), + // paymasterInput: Array.from(ethers.getBytes(tx.customData.paymasterParams.paymasterInput)), + // }; + // } + // return result; + // } + async getTokenBalance(token: Address, walletAddress: Address): Promise { + const erc20 = new this.eth.Contract(IERC20ABI, token); + + return await erc20.methods.balanceOf(walletAddress).call(); + } + + fillCustomData(data: Eip712Meta): Eip712Meta { + const customData = { ...data }; + customData.gasPerPubdata ??= DEFAULT_GAS_PER_PUBDATA_LIMIT; + customData.factoryDeps ??= []; + return customData; + } + + private async populateTransactionAndGasPrice(transaction: Transaction): Promise { + transaction.nonce = + transaction.nonce ?? (await this.eth.getTransactionCount(transaction.from!, 'pending')); + + const populated = await transactionBuilder({ + transaction, + web3Context: this, + }); + + const formatted = web3Utils.format(transactionSchema, populated); + + delete formatted.input; + delete formatted.chain; + delete formatted.hardfork; + delete formatted.networkId; + if ( + formatted.accessList && + Array.isArray(formatted.accessList) && + formatted.accessList.length === 0 + ) { + delete formatted.accessList; + } + + formatted.gasLimit = + formatted.gasLimit ?? + (await estimateGas(this, formatted as Transaction, 'latest', DEFAULT_RETURN_FORMAT)); + + if (formatted.type === 0n) { + formatted.gasPrice = + formatted.gasPrice ?? (await getGasPrice(this, DEFAULT_RETURN_FORMAT)); + return formatted; + } + if (formatted.type === 2n && formatted.gasPrice) { + formatted.maxFeePerGas = formatted.maxFeePerGas ?? formatted.gasPrice; + formatted.maxPriorityFeePerGas = formatted.maxPriorityFeePerGas ?? formatted.gasPrice; + return formatted; + } + if (formatted.maxPriorityFeePerGas && formatted.maxFeePerGas) { + return formatted; + } + + const gasFees = await this.eth.calculateFeeData(); + if (gasFees.maxFeePerGas && gasFees.maxPriorityFeePerGas) { + formatted.maxFeePerGas = + formatted.maxFeePerGas ?? web3Utils.toBigInt(gasFees.maxFeePerGas); + formatted.maxPriorityFeePerGas = + formatted.maxPriorityFeePerGas ?? + (web3Utils.toBigInt(formatted.maxFeePerGas) > + web3Utils.toBigInt(gasFees.maxPriorityFeePerGas) + ? formatted.maxFeePerGas + : gasFees.maxPriorityFeePerGas); + } else { + formatted.maxFeePerGas = formatted.maxFeePerGas ?? formatted.gasPrice; + formatted.maxPriorityFeePerGas = formatted.maxPriorityFeePerGas ?? formatted.gasPrice; + } + + return formatted; + } + + async populateTransaction(transaction: Transaction) { + if ( + (!transaction.type || + (transaction.type && toHex(transaction.type) !== toHex(EIP712_TX_TYPE))) && + !(transaction as Eip712TxData).customData + ) { + return this.populateTransactionAndGasPrice(transaction); + } + const populated = (await this.populateTransactionAndGasPrice(transaction)) as Eip712TxData; + populated.type = BigInt(EIP712_TX_TYPE); + populated.value ??= 0; + populated.data ??= '0x'; + + populated.customData = this.fillCustomData( + (transaction as Eip712TxData).customData as Eip712Meta, + ); + return populated; + } + + async signTransaction(tx: Transaction): Promise { + if (tx.type && toHex(tx.type) === toHex(EIP712_TX_TYPE)) { + const data = EIP712.getSignInput(tx as Eip712TxData); + // @ts-ignore + delete data.txType; + data.type = EIP712_TX_TYPE; + const signer = await this._eip712Signer(); + data.chainId = signer.getDomain().chainId; + data.customData = { + customSignature: signer.sign(data)?.serialized, + }; + return EIP712.serialize(data); + } + + const account = this.eth.accounts.wallet.get(tx.from!); + + if (!account) { + throw new Error('Account not found'); + } + + const res = await this.eth.accounts.signTransaction(tx, account?.privateKey); + return res.rawTransaction; + } + async sendRawTransaction(signedTx: string) { + // tx.chainId = await this._contextL2().eth.getChainId(); + // const { gasPrice, ...other } = await this._contextL2().eth.calculateFeeData(); + // const data = { ...tx, ...other }; + // @ts-ignore + // return this._contextL2().eth.sendTransaction(data); + + // + // const populated = await this.populateTransaction(tx); + + // const signedTx = await this.signTransaction(populated); + + // data.nonce = await this._contextL2().eth.getTransactionCount(this.getAddress(), 'pending'); + // const { gasPrice, ...other } = await this._contextL2().eth.calculateFeeData(); + // const dataWithGas = { ...data, ...other, gasLimit: 5000000n }; + // + // dataWithGas.customData = { + // customSignature: signer.sign(dataWithGas)?.serialized, + // }; + // + // const serialized = EIP712.serialize(dataWithGas); + return ethRpcMethods.sendRawTransaction(this.requestManager, signedTx); + } +} diff --git a/src/zksync-wallet.ts b/src/zksync-wallet.ts new file mode 100644 index 0000000..4c781a3 --- /dev/null +++ b/src/zksync-wallet.ts @@ -0,0 +1,219 @@ +import type { Web3Account } from 'web3-eth-accounts'; +import { privateKeyToAccount, create as createAccount } from 'web3-eth-accounts'; +import type * as web3Types from 'web3-types'; +import type { Transaction } from 'web3-types'; +import type { Web3ZkSyncL2 } from './web3zksync-l2'; +import type { Web3ZkSyncL1 } from './web3zksync-l1'; +import * as utils from './utils'; +import { AdapterL1, AdapterL2 } from './adapters'; +import type { Address, Eip712TxData, PaymasterParams, TransactionOverrides } from './types'; +import type { EIP712Signer } from './utils'; +import { getPriorityOpResponse, isAddressEq } from './utils'; + +class Adapters extends AdapterL1 { + adapterL2: AdapterL2; + constructor() { + super(); + this.adapterL2 = new AdapterL2(); + this.adapterL2.getAddress = this.getAddress.bind(this); + this.adapterL2._contextL2 = this._contextL2.bind(this); + this.adapterL2._eip712Signer = this._eip712Signer; + } + getBalance(token?: Address, blockTag: web3Types.BlockNumberOrTag = 'committed') { + return this.adapterL2.getBalance(token, blockTag); + } + getAllBalances() { + return this.adapterL2.getAllBalances(); + } + + async populateTransaction( + tx: web3Types.Transaction, + ): Promise { + return this.adapterL2.populateTransaction(tx); + } + + getDeploymentNonce() { + return this.adapterL2.getDeploymentNonce(); + } + getL2BridgeContracts() { + return this.adapterL2.getL2BridgeContracts(); + } + protected async _eip712Signer(): Promise { + throw new Error('Must be implemented by the derived class!'); + } + + withdraw(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + bridgeAddress?: Address; + paymasterParams?: PaymasterParams; + overrides?: TransactionOverrides; + }) { + return this.adapterL2.withdraw(transaction); + } + async transfer(transaction: { + to: Address; + amount: web3Types.Numbers; + token?: Address; + paymasterParams?: PaymasterParams; + overrides?: TransactionOverrides; + }) { + return this.signAndSend(await this.adapterL2.transfer(transaction), this._contextL2()); + } +} + +export class ZKSyncWallet extends Adapters { + provider?: Web3ZkSyncL2; + providerL1?: Web3ZkSyncL1; + protected eip712!: utils.EIP712Signer; + public account: Web3Account; + + /** + * + * @param privateKey The private key of the account. + * @param providerL2 The provider instance for connecting to a L2 network. + * @param providerL1 The provider instance for connecting to a L1 network. + * + * @example + * + * import { Wallet, Provider, types } from "zksync-ethers"; + * import { ethers } from "ethers"; + * + * const PRIVATE_KEY = ""; + * + * const provider = Provider.getDefaultProvider(types.Network.Sepolia); + * const ethProvider = ethers.getDefaultProvider("sepolia"); + * const wallet = new Wallet(PRIVATE_KEY, provider, ethProvider); + */ + constructor(privateKey: string, providerL2?: Web3ZkSyncL2, providerL1?: Web3ZkSyncL1) { + super(); + + this.account = privateKeyToAccount(privateKey); + if (providerL2) { + this.connect(providerL2); + } + if (providerL1) { + this.connectToL1(providerL1); + } + } + public connect(provider: Web3ZkSyncL2) { + if (!provider.eth.accounts.wallet.get(this.account.address)) { + provider.eth.accounts.wallet.add( + provider.eth.accounts.privateKeyToAccount(this.account.privateKey), + ); + } + this.provider = provider; + this.provider._eip712Signer = this._eip712Signer.bind(this); + return this; + } + public connectToL1(provider: Web3ZkSyncL1) { + if (!provider.eth.accounts.wallet.get(this.account.address)) { + provider.eth.accounts.wallet.add( + provider.eth.accounts.privateKeyToAccount(this.account.privateKey), + ); + } + this.providerL1 = provider; + return this; + } + + protected async _eip712Signer(): Promise { + if (!this.eip712) { + this.eip712 = new utils.EIP712Signer( + this.account, + Number(await this.provider!.eth.getChainId()), + ); + } + + return this.eip712!; + } + protected _contextL1() { + return this.providerL1!; + } + protected _contextL2() { + return this.provider!; + } + + getBalanceL1(token?: Address, blockTag?: web3Types.BlockNumberOrTag) { + return super.getBalanceL1(token, blockTag); + } + getBalance(token?: Address, blockTag: web3Types.BlockNumberOrTag = 'committed') { + return super.getBalance(token, blockTag); + } + getAddress(): any { + return this.account.address; + } + deposit(transaction: { + token: Address; + amount: web3Types.Numbers; + to?: Address; + operatorTip?: web3Types.Numbers; + bridgeAddress?: Address; + approveERC20?: boolean; + approveBaseERC20?: boolean; + l2GasLimit?: web3Types.Numbers; + gasPerPubdataByte?: web3Types.Numbers; + refundRecipient?: Address; + overrides?: TransactionOverrides; + approveOverrides?: TransactionOverrides; + approveBaseOverrides?: TransactionOverrides; + customBridgeData?: web3Types.Bytes; + }) { + return super.deposit(transaction); + } + getNonce(blockNumber?: web3Types.BlockNumberOrTag) { + return this.provider?.eth.getTransactionCount(this.account.address, blockNumber); + } + static createRandom(provider?: Web3ZkSyncL2, providerL1?: Web3ZkSyncL1) { + const acc = createAccount(); + return new ZKSyncWallet(acc.privateKey, provider, providerL1); + } + async signTransaction(transaction: web3Types.Transaction): Promise { + const populated = (await this.populateTransaction(transaction)) as Transaction; + if (!isAddressEq(populated.from!, this.getAddress())) { + throw new Error('Transaction from mismatch'); + } + return this._contextL2().signTransaction(populated); + } + sendRawTransaction(signedTx: string) { + return this._contextL2().sendRawTransaction(signedTx); + } + + /** + * Designed for users who prefer a simplified approach by providing only the necessary data to create a valid transaction. + * The only required fields are `transaction.to` and either `transaction.data` or `transaction.value` (or both, if the method is payable). + * Any other fields that are not set will be prepared by this method. + * + * @param tx The transaction request that needs to be populated. + * + * @example + * + * import { Wallet, Provider, types, utils } from "zksync-ethers"; + * import { ethers } from "ethers"; + * + * const PRIVATE_KEY = ""; + * + * const provider = Provider.getDefaultProvider(types.Network.Sepolia); + * const ethProvider = ethers.getDefaultProvider("sepolia"); + * const wallet = new Wallet(PRIVATE_KEY, provider, ethProvider); + * + * const populatedTx = await wallet.populateTransaction({ + * type: utils.EIP712_TX_TYPE, + * to: RECEIVER, + * value: 7_000_000_000n, + * }); + */ + async populateTransaction(tx: web3Types.Transaction) { + tx.from = tx.from ?? this.getAddress(); + return this._contextL2().populateTransaction(tx); + } + + async getBridgehubContractAddress() { + return this._contextL2().getBridgehubContractAddress(); + } + + async sendTransaction(transaction: web3Types.Transaction) { + const signed = await this.signTransaction(transaction); + return getPriorityOpResponse(this._contextL2(), this.sendRawTransaction(signed)); + } +} diff --git a/test/fixtures.ts b/test/fixtures.ts index f29387c..f6f15f7 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -1,5 +1,5 @@ import type { Address, HexString } from 'web3'; -import type { Proof } from '../src/types'; +import type { StorageProof } from '../src/types'; export const getRawBlockTransactionsData = { input: 251491, @@ -75,6 +75,7 @@ export const getRawBlockTransactionsData = { calldata: '0xc9807539000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fd69e45d6f51e482ac4f8f2e14f2155200005d8b0600010203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000038c58633c000000000000000000000000000000000000000000000000000000038c58633c000000000000000000000000000000000000000000000000000000038c58633c000000000000000000000000000000000000000000000000000000038c58633c00000000000000000000000000000000000000000000000000000000000000002dd5b728d0f7335e173e9df0ec28a1b98e49f549043f6a34a111ba1f7057e1e01f36286dabf8ba11ba7791babe75ef8ce49c39c8ef6a4c6cd4e43bcc193d2d12e00000000000000000000000000000000000000000000000000000000000000020138cc5cd56da9e72c4b82131e4ff8324e7730a1ce77184a04be7d5a17650d5318e78a4684390b7c536507e840880ebb34e0dc6808d7302ccfcb9026e77457d0', value: BigInt(0), + factoryDeps: [], }, received_timestamp_ms: 1706539720746, raw_bytes: @@ -118,8 +119,10 @@ export const getBlockDetailsData = { export const getBridgeContractsData = { output: { l1Erc20DefaultBridge: '0x2ae09702f77a4940621572fbcdae2382d44a2cba', + l1SharedDefaultBridge: '0x3e8b2fe58675126ed30d0d12dea2a9bda72d18ae', l1WethBridge: '0x0000000000000000000000000000000000000000', l2Erc20DefaultBridge: '0x681a1afdc2e06776816386500d2d461a6c96cb45', + l2SharedDefaultBridge: '0x681a1afdc2e06776816386500d2d461a6c96cb45', l2WethBridge: '0x0000000000000000000000000000000000000000', }, }; @@ -142,7 +145,10 @@ export const getL2ToL1LogProofData = { root: '0x920c63cb0066a08da45f0a9bf934517141bd72d8e5a51421a94b517bf49a0d39', }, }; -export const getProofData: { input: [Address, [HexString], number]; output: Proof } = { +export const getProofData: { + input: [Address, [HexString], number]; + output: StorageProof; +} = { input: [ '0x0000000000000000000000000000000000008003', ['0x8b65c0cf1012ea9f393197eb24619fd814379b298b238285649e14f936a5eb12'], diff --git a/test/index.test.ts b/test/index.test.ts deleted file mode 100644 index c23ce91..0000000 --- a/test/index.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { core } from 'web3'; -import { ZkSyncPlugin } from '../src'; - -describe('ZkSyncPlugin tests', () => { - it('should register ZkSync plugin on Web3Context instance', () => { - const web3Context = new core.Web3Context('http://127.0.0.1:8545'); - web3Context.registerPlugin(new ZkSyncPlugin()); - expect(web3Context.zkSync).toBeDefined(); - }); -}); diff --git a/test/integration/_wallet.test.ts b/test/integration/_wallet.test.ts new file mode 100644 index 0000000..35c14ce --- /dev/null +++ b/test/integration/_wallet.test.ts @@ -0,0 +1,52 @@ +import * as web3Accounts from 'web3-eth-accounts'; +import { TransactionFactory } from 'web3-eth-accounts'; +import { Network as ZkSyncNetwork } from '../../src/types'; +import { Web3ZkSyncL2, Web3ZkSyncL1, ZKSyncWallet } from '../../src'; +import { EIP712_TX_TYPE, ETH_ADDRESS } from '../../src/constants'; +import * as utils from '../../src/utils'; + +// TODO: This test needs to setup local dev nodes for L1 and L2 +// and also needs to have a private key with funds in the L1 +// to be able to run the test. +// Additionally, the test needs to be fixed as `wallet.deposit` throws an internal exception. + +jest.setTimeout(50000); +describe('wallet', () => { + // @ts-ignore + TransactionFactory.registerTransactionType(EIP712_TX_TYPE, utils.EIP712Transaction); + const l1Provider = new Web3ZkSyncL1( + 'https://eth-sepolia.g.alchemy.com/v2/VCOFgnRGJF_vdAY2ZjgSksL6-6pYvRkz', + ); + const l2Provider = Web3ZkSyncL2.initWithDefaultProvider(ZkSyncNetwork.Sepolia); + const PRIVATE_KEY = (process.env.PRIVATE_KEY as string) || web3Accounts.create().privateKey; + const wallet = new ZKSyncWallet(PRIVATE_KEY, l2Provider, l1Provider); + console.log('acc-test', String(process.env.PRIVATE_KEY).slice(0, 10)); + it('should deposit', async () => { + const tx = await wallet.deposit({ + token: ETH_ADDRESS, + to: wallet.getAddress(), + amount: 10000_000000_000000n, + refundRecipient: wallet.getAddress(), + }); + const receipt = await tx.wait(); + + console.log(`Tx: ${receipt.transactionHash}`); + + expect(receipt.status).toBe(1n); + expect(receipt.transactionHash).toBeDefined(); + }); + + it('should withdraw eth', async () => { + const tx = await wallet.withdraw({ + token: ETH_ADDRESS, + to: wallet.getAddress(), + amount: 10n, + }); + const receipt = await tx.wait(); + + console.log(`Tx: ${receipt.transactionHash}`); + + expect(receipt.status).toBe(1n); + expect(receipt.transactionHash).toBeDefined(); + }); +}); diff --git a/test/mainnet.test.ts b/test/integration/mainnet.test.ts similarity index 83% rename from test/mainnet.test.ts rename to test/integration/mainnet.test.ts index 96907f2..276ec58 100644 --- a/test/mainnet.test.ts +++ b/test/integration/mainnet.test.ts @@ -1,5 +1,5 @@ import { Web3 } from 'web3'; -import { ZkSyncPlugin } from '../src'; +import { ZkSyncPlugin } from '../../src'; const EXAMPLE_ERC20_TOKEN = { address: '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', @@ -12,8 +12,8 @@ describe('ZkSyncPlugin rpc mainnet tests', () => { let web3: Web3; beforeAll(() => { - web3 = new Web3('https://mainnet.era.zksync.io'); - web3.registerPlugin(new ZkSyncPlugin()); + web3 = new Web3(); + web3.registerPlugin(new ZkSyncPlugin('https://mainnet.era.zksync.io')); }); it('should get L1 token address', async () => { diff --git a/test/rpc.mainnet.test.ts b/test/integration/rpc.mainnet.test.ts similarity index 79% rename from test/rpc.mainnet.test.ts rename to test/integration/rpc.mainnet.test.ts index 65ad652..d0bfbbb 100644 --- a/test/rpc.mainnet.test.ts +++ b/test/integration/rpc.mainnet.test.ts @@ -1,22 +1,23 @@ import { Web3 } from 'web3'; -import { ZkSyncPlugin } from '../src'; +import { ethRpcMethods } from 'web3-rpc-methods'; +import { ZkSyncPlugin } from '../../src'; import { estimateData, getL1BatchDetailsData, getL2ToL1LogProofData, getProofData, -} from './fixtures'; +} from '../fixtures'; describe('ZkSyncPlugin rpc mainnet tests', () => { let web3: Web3; beforeAll(() => { - web3 = new Web3('https://mainnet.era.zksync.io'); - web3.registerPlugin(new ZkSyncPlugin()); + web3 = new Web3(); + web3.registerPlugin(new ZkSyncPlugin('https://mainnet.era.zksync.io')); }); it('should get bridge addresses', async () => { - const res = await web3.zkSync.getDefaultBridgeAddresses(); + const res = await web3.zkSync.L2.getDefaultBridgeAddresses(); expect(res.erc20L1).toBe('0x57891966931eb4bb6fb81430e6ce0a03aabde063'); expect(res.erc20L2).toBe('0x11f943b2c77b743ab90f4a0ae7d5a4e7fca3e102'); @@ -54,4 +55,11 @@ describe('ZkSyncPlugin rpc mainnet tests', () => { const blockDetails = await web3.zkSync.rpc.getBlockDetails(latestBatchDetails.number); expect(blockDetails.number).toBeDefined(); }); + it.skip('transactionReceipt', async () => { + const res = await ethRpcMethods.getTransactionReceipt( + web3.requestManager, + '0x8cee929f16b4738a8ae6cf4bd4f43d5be0e5f028e4db45a0bde6203ef242c30e', + ); + console.log('res', res); + }); }); diff --git a/test/rpc.test.ts b/test/integration/rpc.test.ts similarity index 78% rename from test/rpc.test.ts rename to test/integration/rpc.test.ts index bb08fe7..e456d86 100644 --- a/test/rpc.test.ts +++ b/test/integration/rpc.test.ts @@ -1,18 +1,18 @@ import { Web3 } from 'web3'; -import { ZkSyncPlugin } from '../src'; +import { ZkSyncPlugin } from '../../src'; import { getBlockDetailsData, getBridgeContractsData, getRawBlockTransactionsData, getTransactionDetailsData, -} from './fixtures'; +} from '../fixtures'; describe('ZkSyncPlugin rpc tests', () => { let web3: Web3; beforeAll(() => { - web3 = new Web3('https://sepolia.era.zksync.dev'); - web3.registerPlugin(new ZkSyncPlugin()); + web3 = new Web3(); + web3.registerPlugin(new ZkSyncPlugin('https://sepolia.era.zksync.dev')); }); it('l1ChainId', async () => { @@ -51,10 +51,10 @@ describe('ZkSyncPlugin rpc tests', () => { const res = await web3.zkSync.rpc.getL1BatchBlockRange(1); expect(res).toEqual(['0x1', '0x2']); }); - it('getTestnetPaymaster', async () => { - const res = await web3.zkSync.rpc.getTestnetPaymaster(); - expect(res).toBe('0x3cb2b87d10ac01736a65688f3e0fb1b070b3eea3'); - }); + // it('getTestnetPaymaster', async () => { + // const res = await web3.zkSync.rpc.getTestnetPaymaster(); + // expect(res).toBe('0x3cb2b87d10ac01736a65688f3e0fb1b070b3eea3'); + // }); it('getBridgeContracts', async () => { const res = await web3.zkSync.rpc.getBridgeContracts(); expect(res).toEqual(getBridgeContractsData.output); @@ -67,4 +67,9 @@ describe('ZkSyncPlugin rpc tests', () => { }); expect(res).toBeGreaterThan(BigInt(0)); }); + + it('getBridgeHubContract', async () => { + const res = await web3.zkSync.rpc.getBridgehubContractAddress(); + expect(res).toEqual('0x35a54c8c757806eb6820629bc82d90e056394c92'); // @todo: set bridge hub contract address + }); }); diff --git a/test/integration/utils.test.ts b/test/integration/utils.test.ts new file mode 100644 index 0000000..a42dfb7 --- /dev/null +++ b/test/integration/utils.test.ts @@ -0,0 +1,39 @@ +import * as web3Accounts from 'web3-eth-accounts'; +import { Web3 } from 'web3'; +import * as constants from '../../src/constants'; +import * as utils from '../../src/utils'; + +describe('utils', () => { + describe('#isTypedDataSignatureCorrect()', () => { + it('should return true if correct', async () => { + const PRIVATE_KEY = + '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'; + const ADDRESS = '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'; + const tx = { + type: 113, + chainId: 270, + from: ADDRESS, + to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + value: 7_000_000n, + }; + const eip712Signer = new utils.EIP712Signer( + web3Accounts.privateKeyToAccount(PRIVATE_KEY), + 270, + ); + const signature = eip712Signer.sign(tx) as utils.SignatureObject; + const web3 = new Web3('https://sepolia.era.zksync.dev'); + expect(signature.serialized).toBe( + '0x5ea12f3d54a1624d7e7f5161dbf6ab746c3335e643b2966264e740cf8e10e9b64b0251fb79d9a5b11730387085a0d58f105926f72e20242ecb274639991939ca1b', + ); + const isValidSignature = await utils.isTypedDataSignatureCorrect( + web3, + ADDRESS, + eip712Signer.getDomain(), + constants.EIP712_TYPES, + utils.EIP712.getSignInput(tx), + signature.serialized, + ); + expect(isValidSignature).toBe(true); + }); + }); +}); diff --git a/test/integration/wallet.test.ts b/test/integration/wallet.test.ts new file mode 100644 index 0000000..b154a15 --- /dev/null +++ b/test/integration/wallet.test.ts @@ -0,0 +1,1777 @@ +import * as ethAccounts from 'web3-eth-accounts'; +import * as web3Utils from 'web3-utils'; +import { toBigInt } from 'web3-utils'; +import type { Transaction } from 'web3-types'; +import type { Address } from 'web3'; +import { privateKeyToAccount } from 'web3-eth-accounts'; +import { utils, Web3ZkSyncL2, ZKSyncWallet, Web3ZkSyncL1 } from '../../src'; +import { + IS_ETH_BASED, + ADDRESS1, + PRIVATE_KEY1, + ADDRESS2, + DAI_L1, + USDC_L1, + APPROVAL_TOKEN, + PAYMASTER, + deepEqualExcluding, +} from '../utils'; +import type { Eip712TxData } from '../../src/types'; +import { Network as ZkSyncNetwork } from '../../src/types'; +import { + ETH_ADDRESS, + ETH_ADDRESS_IN_CONTRACTS, + L2_BASE_TOKEN_ADDRESS, + LEGACY_ETH_ADDRESS, + DEFAULT_GAS_PER_PUBDATA_LIMIT, + EIP712_TX_TYPE, +} from '../../src/constants'; +import { IERC20ABI } from '../../src/contracts/IERC20'; +import { getPaymasterParams } from '../../src/paymaster-utils'; + +jest.setTimeout(5 * 60000); + +describe('Wallet', () => { + const provider = Web3ZkSyncL2.initWithDefaultProvider(ZkSyncNetwork.Sepolia); + const ethProvider = new Web3ZkSyncL1( + 'https://eth-sepolia.g.alchemy.com/v2/VCOFgnRGJF_vdAY2ZjgSksL6-6pYvRkz', + ); + const PRIVATE_KEY = (process.env.PRIVATE_KEY as string) || PRIVATE_KEY1; + const wallet = new ZKSyncWallet(PRIVATE_KEY, provider, ethProvider); + const walletAddress = privateKeyToAccount(PRIVATE_KEY).address; + describe('#constructor()', () => { + it('`Wallet(privateKey, provider)` should return a `Wallet` with L2 provider', async () => { + const wallet = new ZKSyncWallet(PRIVATE_KEY, provider); + + expect(wallet.account.privateKey).toEqual(PRIVATE_KEY); + expect(wallet.provider).toEqual(provider); + }); + + it('`Wallet(privateKey, provider, ethProvider)` should return a `Wallet` with L1 and L2 provider', async () => { + const wallet = new ZKSyncWallet(PRIVATE_KEY, provider, ethProvider); + + expect(wallet.account.privateKey).toEqual(PRIVATE_KEY); + expect(wallet.provider).toEqual(provider); + expect(wallet.providerL1).toEqual(ethProvider); + }); + }); + + describe('#getMainContract()', () => { + it('should return the main contract', async () => { + const result = await wallet.getMainContract(); + expect(result).not.toBeNull(); + }); + }); + + describe('#getBridgehubContract()', () => { + it('should return the bridgehub contract', async () => { + const result = await wallet.getBridgehubContractAddress(); + expect(result).not.toBeNull(); + }); + }); + + describe('#getL1BridgeContracts()', () => { + it('should return the L1 bridge contracts', async () => { + const result = await wallet.getL1BridgeContracts(); + expect(result).not.toBeNull(); + }); + }); + + describe('#isETHBasedChain()', () => { + it('should return whether the chain is ETH-based or not', async () => { + const result = await wallet.isETHBasedChain(); + expect(result).toEqual(IS_ETH_BASED); + }); + }); + + describe('#getBaseToken()', () => { + it('should return base token', async () => { + const result = await wallet.getBaseToken(); + IS_ETH_BASED + ? expect(result).toEqual(ETH_ADDRESS_IN_CONTRACTS) + : expect(result).not.toBeNull(); + }); + }); + + describe('#getBalanceL1()', () => { + it('should return a L1 balance', async () => { + const result = await wallet.getBalanceL1(); + expect(result > 0n).toEqual(true); + }); + }); + + describe('#getAllowanceL1()', () => { + it('should return an allowance of L1 token', async () => { + const result = await wallet.getAllowanceL1(DAI_L1); + expect(result >= 0n).toEqual(true); + }); + }); + + describe('#l2TokenAddress()', () => { + it('should return the L2 base address', async () => { + const baseToken = await provider.getBaseTokenContractAddress(); + const result = await wallet.l2TokenAddress(baseToken); + expect(result).toEqual(L2_BASE_TOKEN_ADDRESS); + }); + + it('should return the L2 ETH address', async () => { + if (!IS_ETH_BASED) { + const result = await wallet.l2TokenAddress(LEGACY_ETH_ADDRESS); + expect(result).not.toBeNull(); + } + }); + + it('should return the L2 DAI address', async () => { + const result = await wallet.l2TokenAddress(DAI_L1); + expect(result).not.toBeNull(); + }); + }); + + describe('#approveERC20()', () => { + it('should approve a L1 token', async () => { + const result = await wallet.approveERC20(DAI_L1, 5); + expect(result).not.toBeNull(); + }); + + it('should throw an error when approving ETH token', async () => { + try { + await wallet.approveERC20(LEGACY_ETH_ADDRESS, 5); + } catch (e) { + expect((e as Error).message).toEqual( + "ETH token can't be approved! The address of the token does not exist on L1.", + ); + } + }); + }); + + describe('#getBaseCost()', () => { + it('should return a base cost of L1 transaction', async () => { + const result = await wallet.getBaseCost({ gasLimit: 100_000 }); + expect(result).not.toBeNull(); + }); + }); + + describe('#getBalance()', () => { + it('should return a `Wallet` balance', async () => { + const result = await wallet.getBalance(); + expect(result > 0n).toEqual(true); + }); + }); + + describe('#getAllBalances()', () => { + it('should return the all balances', async () => { + const result = await wallet.getAllBalances(); + const expected = IS_ETH_BASED ? 2 : 3; + expect(Object.keys(result)).toHaveLength(expected); + }); + }); + + describe('#getL2BridgeContracts()', () => { + it('should return a L2 bridge contracts', async () => { + const result = await wallet.getL2BridgeContracts(); + expect(result).not.toBeNull(); + }); + }); + + describe('#getAddress()', () => { + it('should return the `Wallet` address', async () => { + const result = wallet.getAddress(); + expect(result).toEqual(walletAddress); + }); + }); + + // describe('#ethWallet()', () => { + // it('should return a L1 `Wallet`', async () => { + // const wallet = new ZKSyncWallet(PRIVATE_KEY, provider, ethProvider); + // const ethWallet = wallet.ethWallet(); + // expect(ethWallet.signingKey.privateKey).toEqual(PRIVATE_KEY); + // expect(ethWallet.provider).toEqual(ethProvider); + // }); + // + // it('should throw an error when L1 `Provider` is not specified in constructor', async () => { + // const wallet = new Wallet(PRIVATE_KEY, provider); + // try { + // wallet.ethWallet(); + // } catch (e) { + // expect((e as Error).message).toEqual( + // 'L1 provider is missing! Specify an L1 provider using `Wallet.connectToL1()`.', + // ); + // } + // }); + // }); + + describe('#connect()', () => { + it('should return a `Wallet` with provided `provider` as L2 provider', async () => { + const w = new ZKSyncWallet(PRIVATE_KEY); + w.connect(provider); + expect(w.account.privateKey).toEqual(PRIVATE_KEY); + expect(w.provider).toEqual(provider); + }); + }); + + describe('#connectL1()', () => { + it('should return a `Wallet` with provided `provider` as L1 provider', async () => { + const w = new ZKSyncWallet(PRIVATE_KEY); + w.connectToL1(ethProvider); + expect(w.account.privateKey).toEqual(PRIVATE_KEY); + expect(w.providerL1).toEqual(ethProvider); + }); + }); + + describe('#getDeploymentNonce()', () => { + it('should return a deployment nonce', async () => { + const result = await wallet.getDeploymentNonce(); + expect(result).not.toBeNull(); + }); + }); + + describe('#populateTransaction()', () => { + it('should return a populated transaction', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000_000n, + type: BigInt(EIP712_TX_TYPE), + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + gasLimit: 154_379n, + chainId: 300n, + data: '0x', + customData: { gasPerPubdata: 50_000, factoryDeps: [] }, + gasPrice: 100_000_000n, + }; + + // @ts-ignore + const result = await wallet.populateTransaction({ + type: web3Utils.toHex(EIP712_TX_TYPE), + to: ADDRESS2, + value: web3Utils.toHex(7_000_000_000), + }); + deepEqualExcluding(result, tx, [ + 'gasLimit', + 'gasPrice', + 'maxFeePerGas', + 'maxPriorityFeePerGas', + ]); + expect(toBigInt(result.gasLimit) > 0n).toEqual(true); + expect( + toBigInt(result.maxPriorityFeePerGas) > 0n || + toBigInt(result.maxFeePerGas) > 0n || + toBigInt(result.gasPrice) > 0n, + ).toEqual(true); + }); + it('should return a populated transaction with default values if are omitted', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: 2n, + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + chainId: 300n, + maxFeePerGas: 1_200_000_000n, + maxPriorityFeePerGas: 1_000_000_000n, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + }); + deepEqualExcluding(result, tx, ['gasLimit', 'maxFeePerGas', 'maxPriorityFeePerGas']); + expect(toBigInt(result.gasLimit) > 0n).toEqual(true); + expect(toBigInt(result.maxFeePerGas) > 0n).toEqual(true); + expect(toBigInt(result.maxPriorityFeePerGas) > 0n).toEqual(true); + }); + it('should return populated transaction when `maxFeePerGas` and `maxPriorityFeePerGas` and `customData` are provided', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: BigInt(EIP712_TX_TYPE), + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + data: '0x', + chainId: 300n, + maxFeePerGas: 3_500_000_000n, + maxPriorityFeePerGas: 2_000_000_000n, + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + factoryDeps: [], + }, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + maxFeePerGas: web3Utils.toHex(3_500_000_000n), + maxPriorityFeePerGas: web3Utils.toHex(2_000_000_000n), + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + factoryDeps: [], + }, + } as Eip712TxData); + deepEqualExcluding(tx, result, ['gasLimit']); + expect(toBigInt(result.gasLimit) > 0n).toEqual(true); + }); + + it('should return populated transaction when `maxPriorityFeePerGas` and `customData` are provided', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: BigInt(EIP712_TX_TYPE), + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + data: '0x', + chainId: 300n, + maxPriorityFeePerGas: 2_000_000_000n, + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + factoryDeps: [], + }, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + maxPriorityFeePerGas: web3Utils.toHex(2_000_000_000n), + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + }, + } as Eip712TxData); + deepEqualExcluding(result, tx, ['gasLimit', 'maxFeePerGas']); + expect(toBigInt(result.gasLimit) > 0n).toEqual(true); + }); + + it('should return populated transaction when `maxFeePerGas` and `customData` are provided', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: BigInt(EIP712_TX_TYPE), + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + data: '0x', + chainId: 300n, + maxFeePerGas: 3_500_000_000n, + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + factoryDeps: [], + }, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + maxFeePerGas: web3Utils.toHex(3_500_000_000n), + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + }, + } as Eip712TxData); + deepEqualExcluding(tx, result, ['gasLimit']); + expect(toBigInt(result.gasLimit) > 0n).toEqual(true); + }); + + it('should return populated EIP1559 transaction when `maxFeePerGas` and `maxPriorityFeePerGas` are provided', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: 2n, + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + chainId: 300n, + maxFeePerGas: 3_500_000_000n, + maxPriorityFeePerGas: 2_000_000_000n, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + maxFeePerGas: web3Utils.toHex(3_500_000_000n), + maxPriorityFeePerGas: web3Utils.toHex(2_000_000_000n), + }); + deepEqualExcluding(tx, result, ['gasLimit']); + expect(toBigInt(result.gasLimit!) > 0n).toEqual(true); + }); + + it('should return populated EIP1559 transaction with `maxFeePerGas` and `maxPriorityFeePerGas` same as provided `gasPrice`', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: 2, + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + chainId: 300n, + maxFeePerGas: 3_500_000_000n, + maxPriorityFeePerGas: 3_500_000_000n, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + gasPrice: web3Utils.toHex(3_500_000_000n), + }); + deepEqualExcluding(result, tx, ['gasLimit', 'type', 'gasPrice']); + expect(toBigInt(result.gasLimit!) > 0n).toEqual(true); + }); + + it('should return populated legacy transaction when `type = 0`', async () => { + const tx = { + to: ADDRESS2, + value: 7_000_000n, + type: 0n, + from: wallet.getAddress(), + nonce: await wallet.getNonce('pending'), + chainId: 300n, + gasPrice: 100_000_000n, + }; + // @ts-ignore + const result = await wallet.populateTransaction({ + type: web3Utils.toHex(0), + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + }); + deepEqualExcluding(tx, result, ['gasLimit', 'gasPrice']); + expect(toBigInt(result.gasLimit!) > 0n).toEqual(true); + expect(toBigInt(result.gasPrice!) > 0n).toEqual(true); + }); + }); + + describe('#sendTransaction()', () => { + it('should send already populated transaction with provided `maxFeePerGas` and `maxPriorityFeePerGas` and `customData` fields', async () => { + const populatedTx = (await wallet.populateTransaction({ + to: ADDRESS2 as Address, + value: web3Utils.toHex(7_000_000), + maxFeePerGas: web3Utils.toHex(3_500_000_000n), + maxPriorityFeePerGas: web3Utils.toHex(2_000_000_000n), + // @ts-ignore + customData: { + gasPerPubdata: DEFAULT_GAS_PER_PUBDATA_LIMIT, + }, + })) as unknown as Transaction; + const tx = await wallet.sendTransaction(populatedTx); + const result = await tx.wait(); + expect(result).not.toBeNull(); + }); + + it('should send EIP1559 transaction when `maxFeePerGas` and `maxPriorityFeePerGas` are provided', async () => { + const tx = await wallet.sendTransaction({ + to: ADDRESS2, + value: 7_000_000, + maxFeePerGas: 3_500_000_000n, + maxPriorityFeePerGas: 2_000_000_000n, + }); + const result = await tx.wait(); + expect(result).not.toBeNull(); + expect(result.type).toEqual(2n); + }); + + it('should send already populated EIP1559 transaction with `maxFeePerGas` and `maxPriorityFeePerGas`', async () => { + const populatedTx = await wallet.populateTransaction({ + to: ADDRESS2, + value: web3Utils.toHex(7_000_000), + maxFeePerGas: web3Utils.toHex(3_500_000_000n), + maxPriorityFeePerGas: web3Utils.toHex(2_000_000_000n), + }); + + const tx = await wallet.sendTransaction(populatedTx as Transaction); + const result = await tx.wait(); + expect(result).not.toBeNull(); + expect(result.type).toEqual(2n); + }); + + it('should send EIP1559 transaction with `maxFeePerGas` and `maxPriorityFeePerGas` same as provided `gasPrice`', async () => { + const tx = await wallet.sendTransaction({ + to: ADDRESS2, + value: 7_000_000, + gasPrice: 3_500_000_000n, + }); + const result = await tx.wait(); + expect(result).not.toBeNull(); + expect(result.type).toEqual(0n); + }); + + it('should send legacy transaction when `type = 0`', async () => { + const tx = await wallet.sendTransaction({ + type: 0, + to: ADDRESS2, + value: 7_000_000, + }); + const result = await tx.wait(); + expect(result).not.toBeNull(); + expect(result.type).toEqual(0n); + }); + }); + + // describe('#fromMnemonic()', () => { + // it('should return a `Wallet` with the `provider` as L1 provider and a private key that is built from the `mnemonic` passphrase', async () => { + // const wallet = Web3ZkSyncL2.fromMnemonic(MNEMONIC1, ethProvider); + // expect(wallet.signingKey.privateKey).toEqual(PRIVATE_KEY); + // expect(wallet.providerL1).toEqual(ethProvider); + // }); + // }); + + // describe('#fromEncryptedJson()', () => { + // it('should return a `Wallet` from encrypted `json` file using provided `password`', async () => { + // const wallet = await Wallet.fromEncryptedJson( + // fs.readFileSync('tests/files/wallet.json', 'utf8'), + // 'password', + // ); + // expect(wallet.signingKey.privateKey).toEqual(PRIVATE_KEY); + // }) + // }); + + // describe('#fromEncryptedJsonSync()', () => { + // it('should return a `Wallet` from encrypted `json` file using provided `password`', async () => { + // const wallet = Wallet.fromEncryptedJsonSync( + // fs.readFileSync('tests/files/wallet.json', 'utf8'), + // 'password', + // ); + // expect(wallet.signingKey.privateKey).toEqual(PRIVATE_KEY); + // }) + // }); + + describe('#createRandom()', () => { + it('should return a random `Wallet` with L2 provider', async () => { + const wallet = ZKSyncWallet.createRandom(provider); + expect(wallet.account.privateKey).not.toBeNull(); + expect(wallet.provider).toEqual(provider); + }); + }); + + describe('#getDepositTx()', () => { + if (IS_ETH_BASED) { + it('should return ETH deposit transaction', async () => { + const tx = { + contractAddress: wallet.getAddress(), + calldata: '0x', + l2Value: 7_000_000, + l2GasLimit: 415_035n, + mintValue: 111_540_663_250_000n, + token: ETH_ADDRESS_IN_CONTRACTS, + to: wallet.getAddress(), + amount: 7_000_000, + refundRecipient: wallet.getAddress(), + operatorTip: 0, + overrides: { + from: wallet.getAddress(), + maxFeePerGas: 1_000_000_001n, + maxPriorityFeePerGas: 1_000_000_000n, + value: 111_540_663_250_000n, + }, + gasPerPubdataByte: 800, + }; + const result = await wallet.getDepositTx({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount: 7_000_000, + refundRecipient: wallet.getAddress(), + }); + deepEqualExcluding(result, tx, ['l2GasLimit', 'mintValue', 'overrides']); + expect(result.l2GasLimit > 0n).toEqual(true); + expect(result.mintValue > 0n).toEqual(true); + expect(utils.isAddressEq(result.overrides.from, wallet.getAddress())).toEqual(true); + expect(result.overrides.maxFeePerGas > 0n).toEqual(true); + expect(result.overrides.maxPriorityFeePerGas > 0n).toEqual(true); + expect(result.overrides.value > 0n).toEqual(true); + }); + + it('should return a deposit transaction with `tx.to == Wallet.getAddress()` when `tx.to` is not specified', async () => { + const tx = { + contractAddress: wallet.getAddress(), + calldata: '0x', + l2Value: 7_000_000, + l2GasLimit: 415_035n, + mintValue: 111_540_663_250_000n, + token: ETH_ADDRESS_IN_CONTRACTS, + amount: 7_000_000, + refundRecipient: wallet.getAddress(), + operatorTip: 0, + overrides: { + from: wallet.getAddress(), + maxFeePerGas: 1_000_000_001n, + maxPriorityFeePerGas: 1_000_000_000n, + value: 111_540_663_250_000n, + }, + gasPerPubdataByte: 800, + }; + const result = await wallet.getDepositTx({ + token: LEGACY_ETH_ADDRESS, + amount: 7_000_000, + refundRecipient: wallet.getAddress(), + }); + deepEqualExcluding(tx, result, ['l2GasLimit', 'mintValue', 'overrides']); + expect(result.l2GasLimit > 0n).toEqual(true); + expect(result.mintValue > 0n).toEqual(true); + expect(utils.isAddressEq(result.overrides.from, wallet.getAddress())).toEqual(true); + expect(result.overrides.maxFeePerGas > 0n).toEqual(true); + expect(result.overrides.maxPriorityFeePerGas > 0n).toEqual(true); + expect(result.overrides.value > 0n).toEqual(true); + }); + + it('should return USDC deposit transaction', async () => { + const transaction = { + maxFeePerGas: 1_000_000_001n, + maxPriorityFeePerGas: 1_000_000_000n, + value: 105_100_275_000_000n, + from: wallet.getAddress(), + to: await provider.getBridgehubContractAddress(), + }; + const result = await wallet.getDepositTx({ + token: USDC_L1, + to: wallet.getAddress(), + amount: 5, + refundRecipient: wallet.getAddress(), + }); + const tx = (await wallet.populateTransaction( + result.tx.populateTransaction(result.overrides), + )) as Transaction; + + expect(tx.from!.toLowerCase()).toBe(transaction.from.toLowerCase()); + expect(tx.to!.toLowerCase()).toBe(transaction.to.toLowerCase()); + expect(tx.maxFeePerGas).toBeGreaterThan(0n); + expect(tx.maxFeePerGas).toBeGreaterThan(0n); + expect(tx.maxPriorityFeePerGas).toBeGreaterThan(0n); + expect(tx.value).toBeGreaterThan(0n); + }); + } else { + it('should return ETH deposit transaction', async () => { + const tx = { + from: ADDRESS1, + to: (await provider.getBridgehubContractAddress()).toLowerCase(), + value: 7_000_000n, + data: '0x24fd57fb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000010e0000000000000000000000000000000000000000000000000000bf1aaa17ee7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062e3d000000000000000000000000000000000000000000000000000000000000032000000000000000000000000036615cf349d7f6344891b1e7ca7c72883f5dc049000000000000000000000000842deab39809094bf5e4b77a7f97ae308adc5e5500000000000000000000000000000000000000000000000000000000006acfc0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036615cf349d7f6344891b1e7ca7c72883f5dc049', + maxFeePerGas: 1_000_000_001n, + maxPriorityFeePerGas: 1_000_000_000n, + }; + const result = await wallet.getDepositTx({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount: 7_000_000, + refundRecipient: wallet.getAddress(), + }); + result.to = result.to.toLowerCase(); + const { data: a, maxFeePerGas: b, maxPriorityFeePerGas: c, ...otherTx } = tx; + const { + data: d, + maxFeePerGas: e, + maxPriorityFeePerGas: f, + ...otherResult + } = result; + + expect(otherResult).toEqual(otherTx); + expect(toBigInt(result.maxPriorityFeePerGas) > 0n).toEqual(true); + expect(toBigInt(result.maxFeePerGas) > 0n).toEqual(true); + }); + + it('should return a deposit transaction with `tx.to == Wallet.getAddress()` when `tx.to` is not specified', async () => { + const tx = { + from: ADDRESS1, + to: (await provider.getBridgehubContractAddress()).toLowerCase(), + value: 7_000_000n, + maxFeePerGas: 1_000_000_001n, + maxPriorityFeePerGas: 1000_000_000n, + }; + const result = await wallet.getDepositTx({ + token: LEGACY_ETH_ADDRESS, + amount: 7_000_000, + refundRecipient: wallet.getAddress(), + }); + result.to = result.to.toLowerCase(); + deepEqualExcluding(tx, result, ['data', 'maxFeePerGas', 'maxPriorityFeePerGas']); + expect(toBigInt(result.maxPriorityFeePerGas) > 0n).toEqual(true); + expect(toBigInt(result.maxFeePerGas) > 0n).toEqual(true); + }); + + it('should return DAI deposit transaction', async () => { + const tx = { + maxFeePerGas: 1_000_000_001n, + maxPriorityFeePerGas: 1_000_000_000n, + value: 0n, + from: ADDRESS1, + to: (await provider.getBridgehubContractAddress()).toLowerCase(), + }; + const result = await wallet.getDepositTx({ + token: DAI_L1, + to: wallet.getAddress(), + amount: 5, + refundRecipient: wallet.getAddress(), + }); + result.to = result.to.toLowerCase(); + deepEqualExcluding(tx, result, ['data', 'maxFeePerGas', 'maxPriorityFeePerGas']); + expect(toBigInt(result.maxPriorityFeePerGas) > 0n).toEqual(true); + expect(toBigInt(result.maxFeePerGas) > 0n).toEqual(true); + }); + } + }); + + describe('#estimateGasDeposit()', () => { + if (IS_ETH_BASED) { + it('should return a gas estimation for the ETH deposit transaction', async () => { + const result = await wallet.estimateGasDeposit({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount: 5, + refundRecipient: wallet.getAddress(), + }); + expect(result > 0n).toEqual(true); + }); + + it('should return a gas estimation for the USDC deposit transaction', async () => { + const result = await wallet.estimateGasDeposit({ + token: USDC_L1, + to: wallet.getAddress(), + amount: 5, + refundRecipient: wallet.getAddress(), + }); + expect(result > 0n).toEqual(true); + }); + } else { + it('should throw an error for insufficient allowance when estimating gas for ETH deposit transaction', async () => { + try { + await wallet.estimateGasDeposit({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount: 5, + refundRecipient: wallet.getAddress(), + }); + } catch (e: any) { + expect(e.reason).toMatch('ERC20: insufficient allowance'); + } + }); + + it('should return gas estimation for ETH deposit transaction', async () => { + const token = LEGACY_ETH_ADDRESS; + const amount = 5; + const approveParams = await wallet.getDepositAllowanceParams(token, amount); + + await wallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + + const result = await wallet.estimateGasDeposit({ + token: token, + to: wallet.getAddress(), + amount: amount, + refundRecipient: wallet.getAddress(), + }); + expect(result > 0n).toEqual(true); + }); + + it('should return gas estimation for base token deposit transaction', async () => { + const token = await wallet.getBaseToken(); + const amount = 5; + const approveParams = await wallet.getDepositAllowanceParams(token, amount); + + await wallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + + const result = await wallet.estimateGasDeposit({ + token: token, + to: wallet.getAddress(), + amount: amount, + refundRecipient: wallet.getAddress(), + }); + expect(result > 0n).toEqual(true); + }); + + it('should return gas estimation for DAI deposit transaction', async () => { + const token = DAI_L1; + const amount = 5; + const approveParams = await wallet.getDepositAllowanceParams(token, amount); + + await wallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + await wallet.approveERC20(approveParams[1].token, approveParams[1].allowance); + + const result = await wallet.estimateGasDeposit({ + token: token, + to: wallet.getAddress(), + amount: amount, + refundRecipient: wallet.getAddress(), + }); + expect(result > 0n).toEqual(true); + }); + } + }); + + describe('#deposit()', () => { + if (IS_ETH_BASED) { + it('should deposit ETH to L2 network', async () => { + const amount = 7_000_000_000; + const l2BalanceBeforeDeposit = await wallet.getBalance(); + const l1BalanceBeforeDeposit = await wallet.getBalanceL1(); + const tx = await wallet.deposit({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount, + refundRecipient: wallet.getAddress(), + }); + console.log('tx', tx); + const result = await tx.wait(); + console.log('result', result); + const l2BalanceAfterDeposit = await wallet.getBalance(); + const l1BalanceAfterDeposit = await wallet.getBalanceL1(); + expect(result).not.toBeNull(); + expect(l2BalanceAfterDeposit - l2BalanceBeforeDeposit >= 0).toBe(true); + expect(l1BalanceBeforeDeposit - l1BalanceAfterDeposit >= 0).toBe(true); + }); + + it('should deposit USDC to L2 network', async () => { + const amount = 5; + const l2USDC = await provider.l2TokenAddress(USDC_L1); + const l2BalanceBeforeDeposit = await wallet.getBalance(l2USDC); + const l1BalanceBeforeDeposit = await wallet.getBalanceL1(USDC_L1); + const tx = await wallet.deposit({ + token: USDC_L1, + to: wallet.getAddress(), + amount: amount, + approveERC20: true, + refundRecipient: wallet.getAddress(), + }); + const result = await tx.wait(); + const l2BalanceAfterDeposit = await wallet.getBalance(l2USDC); + const l1BalanceAfterDeposit = await wallet.getBalanceL1(USDC_L1); + expect(result).not.toBeNull(); + expect(l2BalanceAfterDeposit - l2BalanceBeforeDeposit >= 0).toEqual(true); + expect(l1BalanceBeforeDeposit - l1BalanceAfterDeposit >= 0).toEqual(true); + }); + + it('should deposit USDC to the L2 network with approve transaction for allowance', async () => { + const amount = 7; + const l2USDC = await provider.l2TokenAddress(USDC_L1); + const l2BalanceBeforeDeposit = await wallet.getBalance(l2USDC); + const l1BalanceBeforeDeposit = await wallet.getBalanceL1(USDC_L1); + const tx = await wallet.deposit({ + token: USDC_L1, + to: wallet.getAddress(), + amount, + approveERC20: true, + refundRecipient: wallet.getAddress(), + }); + const result = await tx.wait(); + await tx.waitFinalize(); + const l2BalanceAfterDeposit = await wallet.getBalance(l2USDC); + const l1BalanceAfterDeposit = await wallet.getBalanceL1(USDC_L1); + expect(result).not.toBeNull(); + expect(l2BalanceAfterDeposit - l2BalanceBeforeDeposit >= amount).toEqual(true); + expect(l1BalanceBeforeDeposit - l1BalanceAfterDeposit >= amount).toEqual(true); + }); + } else { + it('should deposit ETH to L2 network', async () => { + const amount = 7_000_000_000; + const l2EthAddress = await wallet.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + const l2BalanceBeforeDeposit = await wallet.getBalance(l2EthAddress); + const l1BalanceBeforeDeposit = await wallet.getBalanceL1(); + const tx = await wallet.deposit({ + token: ETH_ADDRESS, + to: wallet.getAddress(), + amount: amount, + approveBaseERC20: true, + refundRecipient: wallet.getAddress(), + }); + const result = await tx.wait(); + const l2BalanceAfterDeposit = await wallet.getBalance(l2EthAddress); + const l1BalanceAfterDeposit = await wallet.getBalanceL1(); + expect(result).not.toBeNull(); + expect(l2BalanceAfterDeposit - l2BalanceBeforeDeposit >= amount).toEqual(true); + expect(l1BalanceBeforeDeposit - l1BalanceAfterDeposit >= amount).toEqual(true); + }); + + it('should deposit base token to L2 network', async () => { + const amount = 5; + const baseTokenL1 = await wallet.getBaseToken(); + const l2BalanceBeforeDeposit = await wallet.getBalance(); + const l1BalanceBeforeDeposit = await wallet.getBalanceL1(baseTokenL1); + const tx = await wallet.deposit({ + token: baseTokenL1, + to: wallet.getAddress(), + amount: amount, + approveERC20: true, + refundRecipient: wallet.getAddress(), + }); + const result = await tx.wait(); + const l2BalanceAfterDeposit = await wallet.getBalance(); + const l1BalanceAfterDeposit = await wallet.getBalanceL1(baseTokenL1); + expect(result).not.toBeNull(); + expect(l2BalanceAfterDeposit - l2BalanceBeforeDeposit >= amount).toEqual(true); + expect(l1BalanceBeforeDeposit - l1BalanceAfterDeposit >= 0n).toEqual(true); + }); + + it('should deposit DAI to L2 network', async () => { + const amount = 5; + const l2DAI = await provider.l2TokenAddress(DAI_L1); + const l2BalanceBeforeDeposit = await wallet.getBalance(l2DAI); + const l1BalanceBeforeDeposit = await wallet.getBalanceL1(DAI_L1); + const tx = await wallet.deposit({ + token: DAI_L1, + to: wallet.getAddress(), + amount: amount, + approveERC20: true, + approveBaseERC20: true, + refundRecipient: wallet.getAddress(), + }); + const result = await tx.wait(); + const l2BalanceAfterDeposit = await wallet.getBalance(l2DAI); + const l1BalanceAfterDeposit = await wallet.getBalanceL1(DAI_L1); + expect(result).not.toBeNull(); + expect(l2BalanceAfterDeposit - l2BalanceBeforeDeposit >= amount).toEqual(true); + expect(l1BalanceBeforeDeposit - l1BalanceAfterDeposit >= amount).toEqual(true); + }); + } + }); + + describe('#claimFailedDeposit()', () => { + if (IS_ETH_BASED) { + it('should claim failed deposit', async () => { + const response = await wallet.deposit({ + token: USDC_L1, + to: wallet.getAddress(), + amount: 5, + approveERC20: true, + refundRecipient: wallet.getAddress(), + l2GasLimit: 300_000, // make it fail because of low gas + }); + const receipt = await response.wait(); + expect(receipt.transactionHash).toBeDefined(); + // console.log('rr', rr); + // try { + // await response.waitFinalize(); + // } catch (error) { + // const hash = '0x229f99f63a6fd5e90154546797c56348e8f6260808bf63769ea22e842d09750f'; + // const blockNumber = (await wallet.provider!.eth.getTransaction(hash)).blockNumber!; + // // Now wait for block number to be executed. + // let blockDetails: BlockDetails; + // do { + // // still not executed. + // await utils.sleep(500); + // blockDetails = await wallet.provider!.getBlockDetails(blockNumber); + // } while (!blockDetails || !blockDetails.executeTxHash); + // const result = await wallet.claimFailedDeposit(hash); + // expect(result?.blockHash).not.toBeNull(); + // } + }); + + it('should throw an error when trying to claim successful deposit', async () => { + const response = await wallet.deposit({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount: 7_000_000_000, + refundRecipient: wallet.getAddress(), + }); + const tx = await response.waitFinalize(); + try { + await wallet.claimFailedDeposit(tx.transactionHash); + } catch (e) { + expect((e as Error).message).toEqual('Cannot claim successful deposit!'); + } + }); + } else { + it('should throw an error when trying to claim successful deposit', async () => { + const response = await wallet.deposit({ + token: await wallet.getBaseToken(), + to: wallet.getAddress(), + amount: 5, + approveERC20: true, + refundRecipient: wallet.getAddress(), + }); + const tx = await response.waitFinalize(); + try { + await wallet.claimFailedDeposit(tx.transactionHash); + } catch (e) { + expect((e as Error).message).toEqual('Cannot claim successful deposit!'); + } + }); + } + }); + + describe('#getFullRequiredDepositFee()', () => { + if (IS_ETH_BASED) { + it('should return a fee for ETH token deposit', async () => { + const result = await wallet.getFullRequiredDepositFee({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + }); + expect(result.baseCost > 0n).toEqual(true); + expect(result.l1GasLimit > 0n).toEqual(true); + expect(result.l2GasLimit > 0n).toEqual(true); + expect(result.maxPriorityFeePerGas! > 0n).toEqual(true); + expect(result.maxFeePerGas! > 0n).toEqual(true); + }); + + it('should throw an error when there is not enough allowance to cover the deposit', async () => { + try { + await wallet.getFullRequiredDepositFee({ + token: USDC_L1, + to: wallet.getAddress(), + }); + } catch (e) { + expect((e as Error).message).toEqual( + 'Not enough allowance to cover the deposit!', + ); + } + }); + + it('should return a fee for DAI token deposit', async () => { + await wallet.approveERC20(USDC_L1, 5); + + const result = await wallet.getFullRequiredDepositFee({ + token: USDC_L1, + to: wallet.getAddress(), + }); + expect(result.baseCost > 0n).toEqual(true); + expect(result.l1GasLimit > 0n).toEqual(true); + expect(result.l2GasLimit > 0n).toEqual(true); + expect(result.maxPriorityFeePerGas! > 0n).toEqual(true); + expect(result.maxFeePerGas! > 0n).toEqual(true); + }); + + it('should throw Not enough balance for deposit!', async () => { + //Not enough allowance to cover the deposit! + expect(async () => { + const randomWallet = new ZKSyncWallet( + wallet.providerL1!.eth.accounts.create().privateKey, + provider, + ethProvider, + ); + + await randomWallet.getFullRequiredDepositFee({ + token: USDC_L1, + to: wallet.getAddress(), + }); + }).rejects.toThrow('Not enough balance for deposit!'); + }); + } else { + it('should throw an error when there is not enough base token allowance to cover the deposit', async () => { + try { + await new ZKSyncWallet( + ethAccounts.create().privateKey, + provider, + ethProvider, + ).getFullRequiredDepositFee({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + }); + } catch (e) { + expect((e as Error).message).toEqual( + 'Not enough base token allowance to cover the deposit!', + ); + } + }); + + it('should return fee for ETH token deposit', async () => { + const token = LEGACY_ETH_ADDRESS; + const approveParams = await wallet.getDepositAllowanceParams(token, 1); + + await wallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + + const result = await wallet.getFullRequiredDepositFee({ + token: token, + to: wallet.getAddress(), + }); + expect(result.baseCost > 0n).toEqual(true); + expect(result.l1GasLimit > 0n).toEqual(true); + expect(result.l2GasLimit > 0n).toEqual(true); + expect(result.maxPriorityFeePerGas! > 0n).toEqual(true); + expect(result.maxFeePerGas! > 0n).toEqual(true); + }); + + it('should return fee for base token deposit', async () => { + const token = await wallet.getBaseToken(); + const approveParams = await wallet.getDepositAllowanceParams(token, 1); + + await wallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + + const result = await wallet.getFullRequiredDepositFee({ + token: token, + to: wallet.getAddress(), + }); + expect(result).not.toBeNull(); + }); + + it('should return fee for DAI token deposit', async () => { + const token = DAI_L1; + const approveParams = await wallet.getDepositAllowanceParams(token, 1); + + await wallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + await wallet.approveERC20(approveParams[1].token, approveParams[1].allowance); + + const result = await wallet.getFullRequiredDepositFee({ + token: token, + to: wallet.getAddress(), + }); + expect(result.baseCost > 0n).toEqual(true); + expect(result.l1GasLimit > 0n).toEqual(true); + expect(result.l2GasLimit > 0n).toEqual(true); + expect(result.maxPriorityFeePerGas! > 0n).toEqual(true); + expect(result.maxFeePerGas! > 0n).toEqual(true); + }); + + it('should throw an error when there is not enough token allowance to cover the deposit', async () => { + const token = DAI_L1; + const randomWallet = new ZKSyncWallet( + ethAccounts.create().privateKey, + provider, + ethProvider, + ); + + // mint base token to random wallet + const baseToken = new ethProvider.eth.Contract( + IERC20ABI, + await wallet.getBaseToken(), + ); + + await baseToken.methods + .mint(randomWallet.getAddress(), web3Utils.toWei('0.5', 'ether')) + .send({ from: wallet.getAddress() }); + + // transfer ETH to random wallet so that base token approval tx can be performed + await ethProvider.eth.sendTransaction({ + from: wallet.getAddress(), + to: randomWallet.getAddress(), + value: web3Utils.toWei('0.1', 'ether'), + }); + + const approveParams = await randomWallet.getDepositAllowanceParams(token, 1); + // only approve base token + await randomWallet.approveERC20(approveParams[0].token, approveParams[0].allowance); + try { + await randomWallet.getFullRequiredDepositFee({ + token: token, + to: wallet.getAddress(), + }); + } catch (e) { + expect((e as Error).message).toEqual( + 'Not enough token allowance to cover the deposit!', + ); + } + }); + } + }); + + describe('#withdraw()', () => { + if (IS_ETH_BASED) { + it('should withdraw ETH to the L1 network', async () => { + const amount = 7n; + // const l2BalanceBeforeWithdrawal = await wallet.getBalance(); + const withdrawTx = await wallet.withdraw({ + token: LEGACY_ETH_ADDRESS, + to: wallet.getAddress(), + amount: amount, + }); + const receipt = await withdrawTx.wait(); + expect(receipt.transactionHash).toBeDefined(); + // works but in sepolia we need to wait few hours to finalize block + // const txHash = '0xd638355396984ae5f94660ad5b803890f952e036e846b5ea23213d8335e8aef6'; + // expect(await wallet.isWithdrawalFinalized(txHash)).toEqual(false); + // + // const result = await wallet.finalizeWithdrawal(txHash); + // // const l2BalanceAfterWithdrawal = await wallet.getBalance(); + // expect(result).not.toBeNull(); + // expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal >= amount).toEqual( + // true, + // ); + }); + + it.skip('should withdraw ETH to the L1 network using paymaster to cover fee', async () => { + const amount = 7n; + const minimalAllowance = 1n; + // const paymasterBalanceBeforeWithdrawal = await provider.eth.getBalance(PAYMASTER); + // const paymasterTokenBalanceBeforeWithdrawal = await provider.getTokenBalance( + // APPROVAL_TOKEN, + // PAYMASTER, + // ); + // const l2BalanceBeforeWithdrawal = await wallet.getBalance(); + // const l2ApprovalTokenBalanceBeforeWithdrawal = + // await wallet.getBalance(APPROVAL_TOKEN); + + const tx = await wallet.withdraw({ + token: ETH_ADDRESS_IN_CONTRACTS, + to: wallet.getAddress(), + amount: amount, + paymasterParams: getPaymasterParams(PAYMASTER, { + type: 'ApprovalBased', + token: APPROVAL_TOKEN, + minimalAllowance, + innerInput: new Uint8Array(), + }), + }); + const receipt = await tx.wait(); + + expect(receipt).toBeDefined(); + // const withdrawTx = await tx.waitFinalize(); + // expect(await wallet.isWithdrawalFinalized(withdrawTx.transactionHash)).toEqual( + // false, + // ); + // + // const result = await wallet.finalizeWithdrawal(withdrawTx.transactionHash); + // + // const paymasterBalanceAfterWithdrawal = await provider.eth.getBalance(PAYMASTER); + // const paymasterTokenBalanceAfterWithdrawal = await provider.getTokenBalance( + // APPROVAL_TOKEN, + // PAYMASTER, + // ); + // const l2BalanceAfterWithdrawal = await wallet.getBalance(); + // const l2ApprovalTokenBalanceAfterWithdrawal = + // await wallet.getBalance(APPROVAL_TOKEN); + // + // expect( + // paymasterBalanceBeforeWithdrawal - paymasterBalanceAfterWithdrawal >= 0n, + // ).toEqual(true); + // expect( + // paymasterTokenBalanceAfterWithdrawal - paymasterTokenBalanceBeforeWithdrawal, + // ).toEqual(minimalAllowance); + // + // expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal).toEqual(amount); + // expect( + // l2ApprovalTokenBalanceAfterWithdrawal === + // l2ApprovalTokenBalanceBeforeWithdrawal - minimalAllowance, + // ).toEqual(true); + // + // expect(result).not.toBeNull(); + }); + } else { + it('should withdraw ETH to the L1 network', async () => { + const amount = 7_000_000_000n; + const token = await wallet.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + const l2BalanceBeforeWithdrawal = await wallet.getBalance(token); + const tx = await wallet.withdraw({ + token: token, + to: wallet.getAddress(), + amount: amount, + }); + const withdrawTx = await tx.waitFinalize(); + expect(await wallet.isWithdrawalFinalized(withdrawTx.transactionHash)).toEqual( + false, + ); + + const result = await wallet.finalizeWithdrawal(withdrawTx.transactionHash); + const l2BalanceAfterWithdrawal = await wallet.getBalance(token); + expect(result).not.toBeNull(); + expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal >= amount).toEqual( + true, + ); + }); + + it('should withdraw base token to the L1 network', async () => { + const amount = 7_000_000_000n; + const baseToken = await wallet.getBaseToken(); + const l2BalanceBeforeWithdrawal = await wallet.getBalance(); + const tx = await wallet.withdraw({ + token: baseToken, + to: wallet.getAddress(), + amount: amount, + }); + const withdrawTx = await tx.waitFinalize(); + expect(await wallet.isWithdrawalFinalized(withdrawTx.transactionHash)).toEqual( + false, + ); + + const result = await wallet.finalizeWithdrawal(withdrawTx.transactionHash); + const l2BalanceAfterWithdrawal = await wallet.getBalance(); + expect(result).not.toBeNull(); + expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal >= amount).toEqual( + true, + ); + }); + + it('should withdraw ETH to the L1 network using paymaster to cover fee', async () => { + const amount = 7_000_000_000n; + const minimalAllowance = 1n; + + const token = await wallet.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + const paymasterBalanceBeforeWithdrawal = await provider.eth.getBalance(PAYMASTER); + const paymasterTokenBalanceBeforeWithdrawal = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const l2BalanceBeforeWithdrawal = await wallet.getBalance(token); + const l2ApprovalTokenBalanceBeforeWithdrawal = + await wallet.getBalance(APPROVAL_TOKEN); + + const tx = await wallet.withdraw({ + token: token, + to: wallet.getAddress(), + amount: amount, + paymasterParams: getPaymasterParams(PAYMASTER, { + type: 'ApprovalBased', + token: APPROVAL_TOKEN, + minimalAllowance: minimalAllowance, + innerInput: new Uint8Array(), + }), + }); + const withdrawTx = await tx.waitFinalize(); + expect(await wallet.isWithdrawalFinalized(withdrawTx.transactionHash)).toEqual( + false, + ); + + const result = await wallet.finalizeWithdrawal(withdrawTx.transactionHash); + + const paymasterBalanceAfterWithdrawal = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceAfterWithdrawal = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const l2BalanceAfterWithdrawal = await wallet.getBalance(token); + const l2ApprovalTokenBalanceAfterWithdrawal = + await wallet.getBalance(APPROVAL_TOKEN); + + expect( + paymasterBalanceBeforeWithdrawal - paymasterBalanceAfterWithdrawal >= 0n, + ).toEqual(true); + expect( + paymasterTokenBalanceAfterWithdrawal - paymasterTokenBalanceBeforeWithdrawal, + ).toEqual(minimalAllowance); + + expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal).toEqual(amount); + expect( + l2ApprovalTokenBalanceAfterWithdrawal === + l2ApprovalTokenBalanceBeforeWithdrawal - minimalAllowance, + ).toEqual(true); + + expect(result).not.toBeNull(); + }); + } + + it('should withdraw USDC to the L1 network', async () => { + const amount = 5n; + const l2USDC = await provider.l2TokenAddress(USDC_L1); + const l2BalanceBeforeWithdrawal = await wallet.getBalance(l2USDC); + const l1BalanceBeforeWithdrawal = await wallet.getBalanceL1(USDC_L1); + expect(l2BalanceBeforeWithdrawal).toBeGreaterThan(0n); + expect(l1BalanceBeforeWithdrawal).toBeDefined(); + const tx = await wallet.withdraw({ + token: l2USDC, + to: wallet.getAddress(), + amount: amount, + }); + const receipt = await tx.wait(); + expect(receipt.transactionHash).toBeDefined(); + // const withdrawTx = await tx.waitFinalize(); + // expect(await wallet.isWithdrawalFinalized(withdrawTx.transactionHash)).toEqual(false); + // + // const result = await wallet.finalizeWithdrawal(withdrawTx.transactionHash); + // const l2BalanceAfterWithdrawal = await wallet.getBalance(l2USDC); + // const l1BalanceAfterWithdrawal = await wallet.getBalanceL1(DAI_L1); + + // expect(result).not.toBeNull(); + // expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal).toEqual(amount); + // expect(l1BalanceAfterWithdrawal - l1BalanceBeforeWithdrawal).toEqual(amount); + }); + + it.skip('should withdraw USDC to the L1 network using paymaster to cover fee', async () => { + const amount = 5n; + const minimalAllowance = 1n; + const l2USDC = await provider.l2TokenAddress(USDC_L1); + + const paymasterBalanceBeforeWithdrawal = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceBeforeWithdrawal = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const l2BalanceBeforeWithdrawal = await wallet.getBalance(l2USDC); + const l1BalanceBeforeWithdrawal = await wallet.getBalanceL1(USDC_L1); + const l2ApprovalTokenBalanceBeforeWithdrawal = await wallet.getBalance(APPROVAL_TOKEN); + + const tx = await wallet.withdraw({ + token: l2USDC, + to: wallet.getAddress(), + amount: amount, + paymasterParams: getPaymasterParams(PAYMASTER, { + type: 'ApprovalBased', + token: APPROVAL_TOKEN, + minimalAllowance: minimalAllowance, + innerInput: new Uint8Array(), + }), + }); + const withdrawTx = await tx.waitFinalize(); + expect(await wallet.isWithdrawalFinalized(withdrawTx.transactionHash)).toEqual(false); + + const result = await wallet.finalizeWithdrawal(withdrawTx.transactionHash); + + const paymasterBalanceAfterWithdrawal = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceAfterWithdrawal = await provider.getBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const l2BalanceAfterWithdrawal = await wallet.getBalance(l2USDC); + const l1BalanceAfterWithdrawal = await wallet.getBalanceL1(USDC_L1); + const l2ApprovalTokenBalanceAfterWithdrawal = await wallet.getBalance(APPROVAL_TOKEN); + + expect( + paymasterBalanceBeforeWithdrawal - paymasterBalanceAfterWithdrawal >= 0n, + ).toEqual(true); + expect( + paymasterTokenBalanceAfterWithdrawal - paymasterTokenBalanceBeforeWithdrawal, + ).toEqual(minimalAllowance); + expect( + l2ApprovalTokenBalanceAfterWithdrawal === + l2ApprovalTokenBalanceBeforeWithdrawal - minimalAllowance, + ).toEqual(true); + + expect(result).not.toBeNull(); + expect(l2BalanceBeforeWithdrawal - l2BalanceAfterWithdrawal).toEqual(amount); + expect(l1BalanceAfterWithdrawal - l1BalanceBeforeWithdrawal).toEqual(amount); + }); + }); + + describe('#getRequestExecuteTx()', () => { + const amount = 7_000_000_000; + if (IS_ETH_BASED) { + it('should return request execute transaction', async () => { + const result = await wallet.getRequestExecuteTx({ + contractAddress: await provider.getBridgehubContractAddress(), + calldata: '0x', + l2Value: amount, + }); + expect(result).not.toBeNull(); + }); + } else { + it('should return request execute transaction', async () => { + const result = await wallet.getRequestExecuteTx({ + contractAddress: wallet.getAddress(), + calldata: '0x', + l2Value: amount, + overrides: { nonce: 0 }, + }); + expect(result).not.toBeNull(); + }); + } + }); + + describe('#estimateGasRequestExecute()', () => { + if (IS_ETH_BASED) { + it('should return gas estimation for request execute transaction', async () => { + const result = await wallet.estimateGasRequestExecute({ + contractAddress: await provider.getBridgehubContractAddress(), + calldata: '0x', + l2Value: 7_000_000_000, + }); + expect(result > 0n).toEqual(true); + }); + } else { + it('should return gas estimation for request execute transaction', async () => { + const tx = { + contractAddress: wallet.getAddress(), + calldata: '0x', + l2Value: 7_000_000_000, + overrides: { value: 0 }, + }; + + const approveParams = await wallet.getRequestExecuteAllowanceParams(tx); + await wallet.approveERC20(approveParams.token, approveParams.allowance); + + const result = await wallet.estimateGasRequestExecute(tx); + expect(result > 0n).toEqual(true); + }); + } + }); + + describe('#requestExecute()', () => { + if (IS_ETH_BASED) { + it('should request transaction execution on L2 network', async () => { + const amount = 7_000_000_000; + const l2BalanceBeforeExecution = await wallet.getBalance(); + const l1BalanceBeforeExecution = await wallet.getBalanceL1(); + const tx = await wallet.requestExecute({ + contractAddress: await provider.getBridgehubContractAddress(), + calldata: '0x', + l2Value: amount, + l2GasLimit: 900_000, + }); + const result = await tx.waitFinalize(); + const l2BalanceAfterExecution = await wallet.getBalance(); + const l1BalanceAfterExecution = await wallet.getBalanceL1(); + expect(result).not.toBeNull(); + expect(l2BalanceAfterExecution - l2BalanceBeforeExecution >= amount).toEqual(true); + expect(l1BalanceBeforeExecution - l1BalanceAfterExecution >= amount).toEqual(true); + }); + } else { + it('should request transaction execution on L2 network', async () => { + const amount = 7_000_000_000; + const request = { + contractAddress: wallet.getAddress(), + calldata: '0x', + l2Value: amount, + l2GasLimit: 1_319_957n, + operatorTip: 0, + gasPerPubdataByte: 800, + refundRecipient: wallet.getAddress(), + overrides: { + maxFeePerGas: 1_000_000_010n, + maxPriorityFeePerGas: 1_000_000_000n, + gasLimit: 238_654n, + value: 0, + }, + }; + + const approveParams = await wallet.getRequestExecuteAllowanceParams(request); + await wallet.approveERC20(approveParams.token, approveParams.allowance); + + const l2BalanceBeforeExecution = await wallet.getBalance(); + const l1BalanceBeforeExecution = await wallet.getBalanceL1(); + + const tx = await wallet.requestExecute(request); + const result = await tx.wait(); + const l2BalanceAfterExecution = await wallet.getBalance(); + const l1BalanceAfterExecution = await wallet.getBalanceL1(); + expect(result).not.toBeNull(); + expect(l2BalanceAfterExecution - l2BalanceBeforeExecution >= amount).toEqual(true); + expect(l1BalanceBeforeExecution - l1BalanceAfterExecution >= amount).toEqual(true); + }); + } + }); + + describe('#transfer()', () => { + it('should transfer ETH or base token depending on chain type', async () => { + const amount = 7n; + const balanceBeforeTransfer = await provider.getBalance(ADDRESS1); + const result = await wallet.transfer({ + token: await wallet.getBaseToken(), + to: ADDRESS1, + amount: amount, + }); + await result.wait(); + const balanceAfterTransfer = await provider.getBalance(ADDRESS1); + expect(result).not.toBeNull(); + expect(balanceAfterTransfer - balanceBeforeTransfer).toEqual(amount); + }); + + it.skip('should transfer ETH or base token depending on chain using paymaster to cover fee', async () => { + const amount = 7_000_000_000n; + const minimalAllowance = 1n; + + const paymasterBalanceBeforeTransfer = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceBeforeTransfer = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const senderBalanceBeforeTransfer = await wallet.getBalance(); + const senderApprovalTokenBalanceBeforeTransfer = + await wallet.getBalance(APPROVAL_TOKEN); + const receiverBalanceBeforeTransfer = await provider.getBalance(ADDRESS2, 'latest'); + + const result = await wallet.transfer({ + to: ADDRESS2, + amount: amount, + paymasterParams: getPaymasterParams(PAYMASTER, { + type: 'ApprovalBased', + token: APPROVAL_TOKEN, + minimalAllowance: minimalAllowance, + innerInput: new Uint8Array(), + }), + }); + + const paymasterBalanceAfterTransfer = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceAfterTransfer = await provider.getBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const senderBalanceAfterTransfer = await wallet.getBalance(); + const senderApprovalTokenBalanceAfterTransfer = await wallet.getBalance(APPROVAL_TOKEN); + const receiverBalanceAfterTransfer = await provider.getBalance(ADDRESS2); + + expect(paymasterBalanceBeforeTransfer - paymasterBalanceAfterTransfer >= 0n).toEqual( + true, + ); + expect( + paymasterTokenBalanceAfterTransfer - paymasterTokenBalanceBeforeTransfer, + ).toEqual(minimalAllowance); + + expect(senderBalanceBeforeTransfer - senderBalanceAfterTransfer).toEqual(amount); + expect( + senderApprovalTokenBalanceAfterTransfer === + senderApprovalTokenBalanceBeforeTransfer - minimalAllowance, + ).toEqual(true); + + expect(result).not.toBeNull(); + expect(receiverBalanceAfterTransfer - receiverBalanceBeforeTransfer).toEqual(amount); + }); + + if (!IS_ETH_BASED) { + it('should transfer ETH on non eth based chain', async () => { + const amount = 7_000_000_000n; + const token = await wallet.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + const balanceBeforeTransfer = await provider.getTokenBalance(token, ADDRESS2); + const result = await wallet.transfer({ + token: LEGACY_ETH_ADDRESS, + to: ADDRESS2, + amount: amount, + }); + const balanceAfterTransfer = await provider.getTokenBalance(token, ADDRESS2); + expect(result).not.toBeNull(); + expect(balanceAfterTransfer - balanceBeforeTransfer).toEqual(amount); + }); + + it('should transfer ETH using paymaster to cover fee', async () => { + const amount = 7_000_000_000n; + const minimalAllowance = 1n; + + const token = await wallet.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS); + const paymasterBalanceBeforeTransfer = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceBeforeTransfer = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const senderBalanceBeforeTransfer = await wallet.getBalance(token); + const senderApprovalTokenBalanceBeforeTransfer = + await wallet.getBalance(APPROVAL_TOKEN); + const receiverBalanceBeforeTransfer = await provider.getTokenBalance( + token, + ADDRESS2, + ); + + const result = await wallet.transfer({ + token: token, + to: ADDRESS2, + amount: amount, + paymasterParams: getPaymasterParams(PAYMASTER, { + type: 'ApprovalBased', + token: APPROVAL_TOKEN, + minimalAllowance: minimalAllowance, + innerInput: new Uint8Array(), + }), + }); + + const paymasterBalanceAfterTransfer = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceAfterTransfer = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const senderBalanceAfterTransfer = await wallet.getBalance(token); + const senderApprovalTokenBalanceAfterTransfer = + await wallet.getBalance(APPROVAL_TOKEN); + const receiverBalanceAfterTransfer = await provider.getTokenBalance( + token, + ADDRESS2, + ); + + expect( + paymasterBalanceBeforeTransfer - paymasterBalanceAfterTransfer >= 0n, + ).toEqual(true); + expect( + paymasterTokenBalanceAfterTransfer - paymasterTokenBalanceBeforeTransfer, + ).toEqual(minimalAllowance); + + expect(senderBalanceBeforeTransfer - senderBalanceAfterTransfer).toEqual(amount); + expect( + senderApprovalTokenBalanceAfterTransfer === + senderApprovalTokenBalanceBeforeTransfer - minimalAllowance, + ).toEqual(true); + + expect(result).not.toBeNull(); + expect(receiverBalanceAfterTransfer - receiverBalanceBeforeTransfer).toEqual( + amount, + ); + }); + } + + it('should transfer USDC', async () => { + const amount = 5n; + const l2USDC = await provider.l2TokenAddress(USDC_L1); + const balanceBeforeTransfer = await provider.getTokenBalance(l2USDC, ADDRESS2); + const result = await wallet.transfer({ + token: l2USDC, + to: ADDRESS2, + amount: amount, + }); + const receipt = await result.wait(); + console.log('receipt', receipt); + const balanceAfterTransfer = await provider.getTokenBalance(l2USDC, ADDRESS2); + expect(result).not.toBeNull(); + expect(balanceAfterTransfer - balanceBeforeTransfer).toEqual(amount); + }); + + it.skip('should transfer DAI using paymaster to cover fee', async () => { + const amount = 5n; + const minimalAllowance = 1n; + const l2DAI = await provider.l2TokenAddress(DAI_L1); + + const paymasterBalanceBeforeTransfer = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceBeforeTransfer = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const senderBalanceBeforeTransfer = await wallet.getBalance(l2DAI); + const senderApprovalTokenBalanceBeforeTransfer = + await wallet.getBalance(APPROVAL_TOKEN); + const receiverBalanceBeforeTransfer = await provider.getTokenBalance(l2DAI, ADDRESS2); + + const result = await wallet.transfer({ + token: l2DAI, + to: ADDRESS2, + amount: amount, + paymasterParams: getPaymasterParams(PAYMASTER, { + type: 'ApprovalBased', + token: APPROVAL_TOKEN, + minimalAllowance: minimalAllowance, + innerInput: new Uint8Array(), + }), + }); + + const paymasterBalanceAfterTransfer = await provider.getBalance(PAYMASTER); + const paymasterTokenBalanceAfterTransfer = await provider.getTokenBalance( + APPROVAL_TOKEN, + PAYMASTER, + ); + const senderBalanceAfterTransfer = await wallet.getBalance(l2DAI); + const senderApprovalTokenBalanceAfterTransfer = await wallet.getBalance(APPROVAL_TOKEN); + const receiverBalanceAfterTransfer = await provider.getTokenBalance(l2DAI, ADDRESS2); + + expect(paymasterBalanceBeforeTransfer - paymasterBalanceAfterTransfer >= 0n).toEqual( + true, + ); + expect( + paymasterTokenBalanceAfterTransfer - paymasterTokenBalanceBeforeTransfer, + ).toEqual(minimalAllowance); + + expect(senderBalanceBeforeTransfer - senderBalanceAfterTransfer).toEqual(amount); + expect( + senderApprovalTokenBalanceAfterTransfer === + senderApprovalTokenBalanceBeforeTransfer - minimalAllowance, + ).toEqual(true); + + expect(result).not.toBeNull(); + expect(receiverBalanceAfterTransfer - receiverBalanceBeforeTransfer).toEqual(amount); + }); + + if (!IS_ETH_BASED) { + it('should transfer base token', async () => { + const amount = 7_000_000_000n; + const balanceBeforeTransfer = await provider.getBalance(ADDRESS2); + const result = await wallet.transfer({ + token: await wallet.getBaseToken(), + to: ADDRESS2, + amount: amount, + }); + const balanceAfterTransfer = await provider.getBalance(ADDRESS2); + expect(result).not.toBeNull(); + expect(balanceAfterTransfer - balanceBeforeTransfer).toEqual(amount); + }); + } + }); + + describe('#signTransaction()', () => { + it('should return a signed type EIP1559 transaction', async () => { + const result = await wallet.signTransaction({ + type: 2, + to: ADDRESS2, + value: 7_000_000_000n, + }); + expect(result).not.toBeNull(); + }); + + it('should return a signed EIP712 transaction', async () => { + const result = await wallet.signTransaction({ + type: EIP712_TX_TYPE, + to: ADDRESS2, + value: 1, + }); + expect(result).not.toBeNull(); + }); + + it('should throw an error when `tx.from` is mismatched from private key', async () => { + try { + await wallet.signTransaction({ + type: EIP712_TX_TYPE, + from: ADDRESS2, + to: ADDRESS2, + value: 1, + }); + } catch (e) { + expect((e as Error).message).toMatch('Transaction from mismatch'); + } + }); + }); +}); diff --git a/test/integration/zksync.contracts.test.ts b/test/integration/zksync.contracts.test.ts new file mode 100644 index 0000000..e806357 --- /dev/null +++ b/test/integration/zksync.contracts.test.ts @@ -0,0 +1,40 @@ +import { Web3 } from 'web3'; +import { QuickNodeProvider, Network } from 'web3-rpc-providers'; +import type { ZKSyncContractsCollection } from 'src/plugin'; +import { ZkSyncPlugin } from '../../src'; + +describe('ZkSyncPlugin rpc tests', () => { + let web3: Web3; + let zkSync: ZkSyncPlugin; + let contracts: ZKSyncContractsCollection; + + beforeAll(async () => { + web3 = new Web3(new QuickNodeProvider(Network.ETH_SEPOLIA)); + zkSync = new ZkSyncPlugin('https://sepolia.era.zksync.dev'); + }); + + it('contracts only works after registering the plugin', async () => { + expect(zkSync.Contracts).rejects.toThrow(); + web3.registerPlugin(zkSync); + + // after registering the plugin, the Contracts property should be without throwing an error + contracts = await zkSync.Contracts; + expect(contracts).toBeDefined(); + }); + + it('should be able to call contracts methods', async () => { + expect(await contracts.L1.ZkSyncMainContract).toBeDefined(); + + // ZkSyncMainContract is a proxy contract at 0x9A6DE0f62Aa270A8bCB1e2610078650D539B1Ef9 + // that is a proxy to the actual contract at 0x550cf73F4b50aA0DF0257f2D07630D48fA00f73a + // TODO: Need to check how to either get the actual contract address or how to call the proxy with now issues + // current error when calling the proxy is: "ContractExecutionError: Error happened while trying to execute a function inside a smart contract" + // related web3.js issue: https://github.com/web3/web3.js/issues/7143 + contracts.L1.ZkSyncMainContract.options.address = + '0x550cf73F4b50aA0DF0257f2D07630D48fA00f73a'; + + const contractName = await contracts.L1.ZkSyncMainContract.methods.getName().call(); + + expect(contractName).toBe('MailboxFacet'); + }); +}); diff --git a/test/jest.config.js b/test/jest.config.js index 3abcbd9..6231bde 100644 --- a/test/jest.config.js +++ b/test/jest.config.js @@ -1,5 +1,5 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { - preset: "ts-jest", - testEnvironment: "node", + preset: 'ts-jest', + testEnvironment: 'node', }; diff --git a/test/unit/index.test.ts b/test/unit/index.test.ts new file mode 100644 index 0000000..267b6cc --- /dev/null +++ b/test/unit/index.test.ts @@ -0,0 +1,10 @@ +import { Web3 } from 'web3'; +import { ZkSyncPlugin } from '../../src'; + +describe('ZkSyncPlugin tests', () => { + it('should register ZkSync plugin on Web3Context instance', () => { + const web3 = new Web3('https://sepolia.era.zksync.dev'); + web3.registerPlugin(new ZkSyncPlugin('https://sepolia.era.zksync.dev')); + expect(web3.zkSync).toBeDefined(); + }); +}); diff --git a/test/unit/signer.test.ts b/test/unit/signer.test.ts new file mode 100644 index 0000000..fc28770 --- /dev/null +++ b/test/unit/signer.test.ts @@ -0,0 +1,75 @@ +// import '../custom-matchers'; +import { EIP712Signer } from '../../src/Eip712'; +import { ADDRESS1, ADDRESS2 } from '../utils'; +import { ZeroAddress } from '../../src/types'; +import { DEFAULT_GAS_PER_PUBDATA_LIMIT, EIP712_TX_TYPE } from '../../src/constants'; +import { EIP712 } from '../../src/Eip712'; + +describe('EIP712Signer', () => { + describe('#getSignInput()', () => { + it('should return a populated transaction', async () => { + const tx = { + txType: EIP712_TX_TYPE, + from: ADDRESS1, + to: ADDRESS2, + gasLimit: 21_000n, + gasPerPubdataByteLimit: DEFAULT_GAS_PER_PUBDATA_LIMIT, + maxFeePerGas: 250_000_000n, + maxPriorityFeePerGas: 250_000_000n, + paymaster: ZeroAddress, + nonce: 0, + value: 7_000_000n, + data: '0x', + factoryDeps: [], + paymasterInput: '0x', + }; + + const result = EIP712.getSignInput({ + type: EIP712_TX_TYPE, + to: ADDRESS2, + value: 7_000_000n, + from: ADDRESS1, + nonce: 0, + chainId: 270n, + gasPrice: 250_000_000n, + gasLimit: 21_000n, + customData: {}, + }); + expect(result).toEqual(tx); + }); + it('should return a populated transaction with default values', async () => { + const tx = { + txType: EIP712_TX_TYPE, + from: ADDRESS1, + to: ADDRESS2, + gasLimit: 0n, + gasPerPubdataByteLimit: DEFAULT_GAS_PER_PUBDATA_LIMIT, + maxFeePerGas: 0n, + maxPriorityFeePerGas: 0n, + paymaster: ZeroAddress, + nonce: 0, + value: 0n, + data: '0x', + factoryDeps: [], + paymasterInput: '0x', + }; + + const result = EIP712.getSignInput({ + type: EIP712_TX_TYPE, + to: ADDRESS2, + from: ADDRESS1, + }); + expect(result).toEqual(tx); + }); + }); + + describe('#getSignedDigest()', () => { + it('should throw an error when chain ID is not specified', async () => { + try { + EIP712Signer.getSignedDigest({}); + } catch (e) { + expect((e as Error).message).toEqual("Transaction chainId isn't set!"); + } + }); + }); +}); diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts new file mode 100644 index 0000000..61c4e84 --- /dev/null +++ b/test/unit/utils.test.ts @@ -0,0 +1,421 @@ +import { Web3 } from 'web3'; +import * as web3Accounts from 'web3-eth-accounts'; +import type * as web3Types from 'web3-types'; +import type { types } from '../../src'; +import { utils } from '../../src'; +import { ADDRESS1, ADDRESS3 } from '../utils'; +import * as constants from '../../src/constants'; +import { getL2HashFromPriorityOp } from '../../src/utils'; + +describe('utils', () => { + describe('#getHashedL2ToL1Msg()', () => { + it('should return a hashed L2 to L1 message', () => { + const withdrawETHMessage = + '0x6c0960f936615cf349d7f6344891b1e7ca7c72883f5dc04900000000000000000000000000000000000000000000000000000001a13b8600'; + const withdrawETHMessageHash = + '0x521bd25904766c83fe868d6a29cbffa011afd8a1754f6c9a52b053b693e42f18'; + const result = utils.getHashedL2ToL1Msg(ADDRESS1, withdrawETHMessage, 0); + expect(result).toBe(withdrawETHMessageHash); + }); + }); + + describe('#isETH()', () => { + it('should return true for legacy L1 ETH address', () => { + const result = utils.isETH(constants.LEGACY_ETH_ADDRESS); + expect(result).toBeTruthy(); + }); + + it('should return true for L1 ETH address', () => { + const result = utils.isETH(constants.ETH_ADDRESS_IN_CONTRACTS); + expect(result).toBeTruthy(); + }); + + it('should return true for L2 ETH address', () => { + const result = utils.isETH(constants.L2_BASE_TOKEN_ADDRESS); + expect(result).toBeTruthy(); + }); + }); + + describe('#createAddress()', () => { + it('should return a correct address', () => { + const address = utils.createAddress(ADDRESS1, 1); + expect(address).toBe('0x4B5DF730c2e6b28E17013A1485E5d9BC41Efe021'); + }); + }); + + describe('#create2Address()', () => { + it('should return a correct address', () => { + const address = utils.create2Address( + ADDRESS1, + '0x010001cb6a6e8d5f6829522f19fa9568660e0a9cd53b2e8be4deb0a679452e41', + '0x01', + '0x01', + ); + expect(address).toBe('0x29bac3E5E8FFE7415F97C956BFA106D70316ad50'); + }); + }); + + describe('#applyL1ToL2Alias()', () => { + it('should return the L2 contract address based on provided L1 contract address', () => { + const l1ContractAddress = '0x702942B8205E5dEdCD3374E5f4419843adA76Eeb'; + const l2ContractAddress = utils.applyL1ToL2Alias(l1ContractAddress); + expect(l2ContractAddress.toLowerCase()).toBe( + '0x813A42B8205E5DedCd3374e5f4419843ADa77FFC'.toLowerCase(), + ); + }); + + it('should return the L2 contract address by padding zero to left', () => { + const l1ContractAddress = '0xeeeeffffffffffffffffffffffffffffffffeeef'; + const l2ContractAddress = utils.applyL1ToL2Alias(l1ContractAddress); + expect(l2ContractAddress.toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000'.toLowerCase(), + ); + }); + }); + + describe('#undoL1ToL2Alias()', () => { + it('should return the L1 contract address based on provided L2 contract address', () => { + const l2ContractAddress = '0x813A42B8205E5DedCd3374e5f4419843ADa77FFC'; + const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress); + expect(l1ContractAddress.toLowerCase()).toBe( + '0x702942B8205E5dEdCD3374E5f4419843adA76Eeb'.toLowerCase(), + ); + }); + + it('should return the L1 contract address by padding zero to left', () => { + const l2ContractAddress = '0x1111000000000000000000000000000000001111'; + const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress); + expect(l1ContractAddress.toLowerCase()).toBe( + '0x0000000000000000000000000000000000000000'.toLowerCase(), + ); + }); + + it('should handle a case where L1_TO_L2_ALIAS_OFFSET is greater than the address', () => { + const l2ContractAddress = '0x100'; + const l1ContractAddress = utils.undoL1ToL2Alias(l2ContractAddress); + + expect(l1ContractAddress.toLowerCase()).toBe( + '0xeeeeffffffffffffffffffffffffffffffffefef'.toLowerCase(), + ); + }); + }); + + describe('#checkBaseCost()', () => { + it('should throw an error if the base cost bigger than value', async () => { + const baseCost = 100; + const value = 99; + try { + await utils.checkBaseCost(baseCost, value); + } catch (e) { + expect((e as Error).message).toBe( + `The base cost of performing the priority operation is higher than the provided value parameter for the transaction: baseCost: ${baseCost}, provided value: ${value}!`, + ); + } + }); + }); + + describe('#serializeEip712()', () => { + it('should throw an error when `tx.chainId` is not specified', () => { + try { + utils.EIP712.serialize({}); + } catch (e) { + expect((e as Error).message).toBe("Transaction chainId isn't set!"); + } + }); + + it('should throw an error when `tx.from` is not specified', () => { + try { + utils.EIP712.serialize({ chainId: 270 }); + } catch (e) { + expect((e as Error).message).toBe( + 'Explicitly providing `from` field is required for EIP712 transactions!', + ); + } + }); + + it('should throw an error when `tx.customData.customSignature` is empty string', () => { + try { + utils.EIP712.serialize({ + chainId: 270, + from: ADDRESS1, + customData: { + customSignature: '', + }, + }); + } catch (e) { + expect((e as Error).message).toBe('Empty signatures are not supported'); + } + }); + + it('should return a serialized transaction with populated default values', () => { + const tx = + '0x71ea8080808080808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + const result = utils.EIP712.serialize({ + chainId: 270, + from: ADDRESS1, + }); + expect(result).toBe(tx); + }); + + it('should return a serialized transaction with provided signature', async () => { + const tx = + '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + const result = utils.EIP712.serialize( + { + chainId: 270, + from: ADDRESS1, + to: ADDRESS3, + value: 1_000_000, + }, + '0x73a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aaf87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a', + ); + expect(result).toBe(tx); + }); + }); + + describe('#hashBytecode()', () => { + it('should return the hash of bytecode which length is not 2 bytes so padding needs to be performed', () => { + const bytecode = + '0x000200000000000200010000000103550000006001100270000000130010019d0000008001000039000000400010043f0000000101200190000000290000c13d0000000001000031000000040110008c000000420000413d0000000101000367000000000101043b000000e001100270000000150210009c000000310000613d000000160110009c000000420000c13d0000000001000416000000000110004c000000420000c13d000000040100008a00000000011000310000001702000041000000200310008c000000000300001900000000030240190000001701100197000000000410004c000000000200a019000000170110009c00000000010300190000000001026019000000000110004c000000420000c13d00000004010000390000000101100367000000000101043b000000000010041b0000000001000019000000490001042e0000000001000416000000000110004c000000420000c13d0000002001000039000001000010044300000120000004430000001401000041000000490001042e0000000001000416000000000110004c000000420000c13d000000040100008a00000000011000310000001702000041000000000310004c000000000300001900000000030240190000001701100197000000000410004c000000000200a019000000170110009c00000000010300190000000001026019000000000110004c000000440000613d00000000010000190000004a00010430000000000100041a000000800010043f0000001801000041000000490001042e0000004800000432000000490001042e0000004a00010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000006d4ce63c0000000000000000000000000000000000000000000000000000000060fe47b18000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000080000000000000000000000000000000000000000000000000000000000000000000000000000000009c8c8fa789967eb514f3ec9def748480945cc9b10fcbd1a19597d924eb201083'; + const hashedBytecode = utils.hashBytecode(bytecode); + expect(hashedBytecode).toEqual( + new Uint8Array([ + 1, 0, 0, 27, 57, 231, 154, 55, 0, 164, 201, 96, 244, 120, 23, 112, 54, 34, 224, + 133, 160, 122, 88, 164, 112, 80, 0, 134, 48, 138, 74, 16, + ]), + ); + }); + + it('should return the hash of bytecode which length is 2 bytes so padding does not need to be performed', () => { + const bytecode = + '0x0002000000000002000900000000000200010000000103550000006001100270000001980010019d0000008001000039000000400010043f0000000101200190000000340000c13d0000000001000031000000040110008c000003670000413d0000000101000367000000000101043b000000e0011002700000019d0210009c000001420000213d000001a50210009c000001720000213d000001a90210009c000001fd0000613d000001aa0210009c000002210000613d000001ab0110009c000003670000c13d0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000000310004c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d0000000201000039000000000101041a000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d00000000020000310000001f01200039000000200a00008a0000000004a1016f000000400100043d0000000003140019000000000443004b00000000040000190000000104004039000001990530009c000003c90000213d0000000104400190000003c90000c13d000000400030043f0000001f0320018f00000001040003670000000505200272000000520000613d000000000600001900000005076002100000000008710019000000000774034f000000000707043b00000000007804350000000106600039000000000756004b0000004a0000413d000000000630004c000000610000613d0000000505500210000000000454034f00000000055100190000000303300210000000000605043300000000063601cf000000000636022f000000000404043b0000010003300089000000000434022f00000000033401cf000000000363019f00000000003504350000019a03000041000000600420008c000000000400001900000000040340190000019a05200197000000000650004c000000000300a0190000019a0550009c000000000304c019000000000330004c000003670000c13d0000000034010434000001990540009c000003670000213d000000000221001900000000041400190000001f054000390000019a06000041000000000725004b000000000700001900000000070680190000019a055001970000019a08200197000000000985004b0000000006008019000000000585013f0000019a0550009c00000000050700190000000005066019000000000550004c000003670000c13d0000000005040433000001990650009c000003c90000213d0000003f065000390000000006a6016f000000400b00043d00000000066b00190000000007b6004b00000000070000190000000107004039000001990860009c000003c90000213d0000000107700190000003c90000c13d000000400060043f000000000c5b043600000020065000390000000007460019000000000727004b000003670000213d000000000750004c0000009e0000613d000000000700001900000020077000390000000008b70019000000000947001900000000090904330000000000980435000000000857004b000000970000413d00000000046b001900000000000404350000000003030433000001990430009c000003670000213d00000000031300190000001f043000390000019a05000041000000000624004b000000000600001900000000060580190000019a044001970000019a07200197000000000874004b0000000005008019000000000474013f0000019a0440009c00000000040600190000000004056019000000000440004c000003670000c13d0000000004030433000001990540009c000003c90000213d0000003f054000390000000005a5016f000000400800043d0000000005580019000000000685004b00000000060000190000000106004039000001990750009c000003c90000213d0000000106600190000003c90000c13d000000400050043f0000000005480436000800000005001d00000020054000390000000006350019000000000226004b000003670000213d00060000000c001d00090000000b001d00070000000a001d000000000240004c000000d50000613d000000000200001900000020022000390000000006820019000000000732001900000000070704330000000000760435000000000642004b000000ce0000413d0000000002580019000000000002043500000040011000390000000001010433000500000001001d000000ff0110008c0000000901000029000003670000213d0000000001010433000400000001001d000001990110009c000003c90000213d000100000008001d0000000301000039000300000001001d000000000101041a000000010210019000000001011002700000007f0310018f0000000001036019000200000001001d0000001f0110008c00000000010000190000000101002039000000010110018f000000000112004b0000021b0000c13d0000000201000029000000200110008c000001100000413d0000000301000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c0011002100000019b011001c70000801002000039065b06560000040f0000000102200190000003670000613d00000004030000290000001f023000390000000502200270000000200330008c0000000002004019000000000301043b00000002010000290000001f01100039000000050110027000000000011300190000000002230019000000000312004b000001100000813d000000000002041b0000000102200039000000000312004b0000010c0000413d00000004010000290000001f0110008c000004990000a13d0000000301000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c0011002100000019b011001c70000801002000039065b06560000040f000000010220019000000007020000290000000906000029000003670000613d000000040300002900000000032301700000002002000039000000000101043b000001300000613d0000002002000039000000000400001900000000056200190000000005050433000000000051041b000000200220003900000001011000390000002004400039000000000534004b000001280000413d0000000404000029000000000343004b0000013e0000813d00000004030000290000000303300210000000f80330018f000000010400008a000000000334022f000000000343013f000000090400002900000000024200190000000002020433000000000232016f000000000021041b0000000401000029000000010110021000000001011001bf000004a70000013d0000019e0210009c000001c60000213d000001a20210009c000002440000613d000001a30210009c000002700000613d000001a40110009c000003670000c13d0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000000310004c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d0000000405000039000000000405041a000000010640019000000001014002700000007f0210018f00000000010260190000001f0210008c00000000020000190000000102002039000000000224013f00000001022001900000021b0000c13d000000400200043d0000000003120436000000000660004c000003800000c13d000001000500008a000000000454016f0000000000430435000000000110004c000000200400003900000000040060190000038d0000013d000001a60210009c000002940000613d000001a70210009c000002e30000613d000001a80110009c000003670000c13d0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000004010000390000000101100367000000000101043b000900000001001d000001ac0110009c000003670000213d0000000001000411000700000001001d00000000001004350000000101000039000800000001001d000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b00000009020000290000000000200435000000200010043f00000024010000390000000101100367000000000101043b000600000001001d00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b000000000101041a00000006020000290000000003210019000000000113004b000000000100001900000001010040390000000101100190000003ae0000c13d00000007010000290000000902000029065b05ea0000040f000000400100043d000000080200002900000000002104350000019802000041000001980310009c00000000010280190000004001100210000001ad011001c70000065c0001042e0000019f0210009c000002ff0000613d000001a00210009c000003510000613d000001a10110009c000003670000c13d0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001020003670000000401200370000000000101043b000001ac0310009c000003670000213d0000002402200370000000000302043b000001ac0230009c000003670000213d00000000001004350000000101000039000000200010043f0000004002000039000900000002001d0000000001000019000800000003001d065b052b0000040f00000008020000290000000000200435000000200010043f00000000010000190000000902000029065b052b0000040f000000000101041a000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000000310004c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d0000000303000039000000000203041a000000010420019000000001012002700000007f0510018f000000000601001900000000060560190000001f0560008c00000000050000190000000105002039000000000552013f0000000105500190000003690000613d000001b70100004100000000001004350000002201000039000000040010043f000001b8010000410000065d000104300000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000202043b000001ac0320009c000003670000213d0000002401100370000000000301043b0000000001000411065b05ea0000040f0000000101000039000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000402043b000001ac0240009c000003670000213d0000002401100370000000000501043b000000000140004c000003a60000c13d000000400100043d0000004402100039000001b503000041000000000032043500000024021000390000001f030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b6011001c70000065d000104300000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000200310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000004010000390000000101100367000000000101043b000001ac0210009c000003670000213d0000000000100435000000200000043f00000040020000390000000001000019065b052b0000040f000000000101041a000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000600310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000402043b000001ac0240009c000003670000213d0000002402100370000000000202043b000900000002001d000001ac0220009c000003670000213d0000004401100370000000000101043b000700000001001d00000000004004350000000101000039000600000001001d000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039000800000004001d065b06560000040f0000000102200190000003670000613d000000000101043b0000000002000411000500000002001d0000000000200435000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f00000008030000290000000102200190000003670000613d000000000101043b000000000201041a000000010100008a000000000112004b0000041c0000c13d000000000103001900000009020000290000000703000029065b05570000040f000000400100043d000000060200002900000000002104350000019802000041000001980310009c00000000010280190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000000310004c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d0000000501000039000000000101041a000000ff0110018f000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000202043b000900000002001d000001ac0220009c000003670000213d0000002401100370000000000101043b000800000001001d0000000001000411000600000001001d00000000001004350000000101000039000700000001001d000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b00000009020000290000000000200435000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b000000000101041a0000000803000029000000000231004b0000040f0000813d000000400100043d0000006402100039000001af0300004100000000003204350000004402100039000001b0030000410000000000320435000000240210003900000025030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d000104300000000001000416000000000110004c000003670000c13d000000040100008a00000000011000310000019a02000041000000400310008c000000000300001900000000030240190000019a01100197000000000410004c000000000200a0190000019a0110009c00000000010300190000000001026019000000000110004c000003670000c13d00000001010003670000000402100370000000000202043b000001ac0320009c000003730000a13d00000000010000190000065d00010430000000800060043f000000000440004c000003b40000c13d000001000300008a000000000232016f000000a00020043f000000000160004c000000c001000039000000a001006039000003c30000013d0000002401100370000000000301043b0000000001000411065b05570000040f0000000101000039000000400200043d00000000001204350000019801000041000001980320009c00000000010240190000004001100210000001ad011001c70000065c0001042e0000000000500435000000000410004c00000000040000190000038d0000613d000001b30500004100000000040000190000000006430019000000000705041a000000000076043500000001055000390000002004400039000000000614004b000003860000413d0000003f01400039000000200300008a000000000331016f0000000001230019000000000331004b00000000040000190000000104004039000001990310009c000003c90000213d0000000103400190000003c90000c13d000000400010043f000900000001001d065b05410000040f000000090400002900000000014100490000019802000041000001980310009c0000000001028019000001980340009c000000000204401900000040022002100000006001100210000000000121019f0000065c0001042e0000000201000039000000000301041a0000000002530019000000000332004b000000000300001900000001030040390000000103300190000003de0000613d000001b70100004100000000001004350000001101000039000000040010043f000001b8010000410000065d000104300000000000300435000000a001000039000000000260004c000003cf0000613d000001bf0200004100000000040000190000000003040019000000000402041a000000a005300039000000000045043500000001022000390000002004300039000000000564004b000003ba0000413d000000c0013000390000001f01100039000000200200008a000000000121016f000001c002100041000001c10220009c000003cf0000813d000001b70100004100000000001004350000004101000039000000040010043f000001b8010000410000065d00010430000900000001001d000000400010043f0000008002000039065b05410000040f000000090400002900000000014100490000019802000041000001980310009c0000000001028019000001980340009c000000000204401900000040022002100000006001100210000000000121019f0000065c0001042e000800000005001d000000000021041b0000000000400435000000200000043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039000900000004001d065b06560000040f00000009060000290000000102200190000003670000613d000000000101043b000000000201041a00000008030000290000000002320019000000000021041b000000400100043d000000000031043500000198020000410000000003000414000001980430009c0000000003028019000001980410009c00000000010280190000004001100210000000c002300210000000000112019f0000019b011001c70000800d020000390000000303000039000001b4040000410000000005000019065b06510000040f0000000101200190000003670000613d000000400100043d000000010200003900000000002104350000019802000041000001980310009c00000000010280190000004001100210000001ad011001c70000065c0001042e000000000331004900000006010000290000000902000029065b05ea0000040f000000400100043d000000070200002900000000002104350000019802000041000001980310009c00000000010280190000004001100210000001ad011001c70000065c0001042e0000000701000029000000000112004b000004310000813d000000400100043d0000004402100039000001be03000041000000000032043500000024021000390000001d030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b6011001c70000065d00010430000400000002001d000000000130004c000004490000c13d000000400100043d0000006402100039000001bc0300004100000000003204350000004402100039000001bd030000410000000000320435000000240210003900000024030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d000104300000000501000029000001ac01100198000500000001001d000004620000c13d000000400100043d0000006402100039000001ba0300004100000000003204350000004402100039000001bb030000410000000000320435000000240210003900000022030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d00010430000000080100002900000000001004350000000601000029000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000003670000613d000000000101043b00000005020000290000000000200435000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f00000004030000290000000102200190000003670000613d00000007020000290000000002230049000000000101043b000000000021041b000000400100043d000000000021043500000198020000410000000003000414000001980430009c0000000003028019000001980410009c00000000010280190000004001100210000000c002300210000000000112019f0000019b011001c70000800d020000390000000303000039000001b90400004100000008050000290000000506000029065b06510000040f00000008030000290000000101200190000002d60000c13d000003670000013d0000000401000029000000000110004c00000000010000190000049f0000613d0000000601000029000000000101043300000004040000290000000302400210000000010300008a000000000223022f000000000232013f000000000121016f0000000102400210000000000121019f0000000302000029000000000012041b00000001010000290000000001010433000900000001001d000001990110009c000003c90000213d0000000401000039000600000001001d000000000101041a000000010210019000000001021002700000007f0320018f0000000002036019000400000002001d0000001f0220008c00000000020000190000000102002039000000000121013f00000001011001900000021b0000c13d0000000401000029000000200110008c000004dc0000413d0000000601000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c0011002100000019b011001c70000801002000039065b06560000040f0000000102200190000003670000613d00000009030000290000001f023000390000000502200270000000200330008c0000000002004019000000000301043b00000004010000290000001f01100039000000050110027000000000011300190000000002230019000000000312004b000004dc0000813d000000000002041b0000000102200039000000000312004b000004d80000413d00000009010000290000001f0110008c0000050e0000a13d0000000601000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c0011002100000019b011001c70000801002000039065b06560000040f000000010220019000000007020000290000000106000029000003670000613d000000090300002900000000032301700000002002000039000000000101043b000004fc0000613d0000002002000039000000000400001900000000056200190000000005050433000000000051041b000000200220003900000001011000390000002004400039000000000534004b000004f40000413d0000000904000029000000000343004b0000050a0000813d00000009030000290000000303300210000000f80330018f000000010400008a000000000334022f000000000343013f000000010400002900000000024200190000000002020433000000000232016f000000000021041b0000000101000039000000090200002900000001022002100000051b0000013d0000000901000029000000000110004c0000000001000019000005140000613d0000000801000029000000000101043300000009040000290000000302400210000000010300008a000000000223022f000000000232013f000000000221016f0000000101400210000000000112019f0000000602000029000000000012041b0000000501000039000000000201041a000001000300008a000000000232016f0000000503000029000000ff0330018f000000000232019f000000000021041b0000002001000039000001000010044300000120000004430000019c010000410000065c0001042e0000019803000041000001980410009c00000000010380190000004001100210000001980420009c00000000020380190000006002200210000000000112019f0000000002000414000001980420009c0000000002038019000000c002200210000000000112019f000001c2011001c70000801002000039065b06560000040f00000001022001900000053f0000613d000000000101043b000000000001042d00000000010000190000065d0001043000000020030000390000000004310436000000000302043300000000003404350000004001100039000000000430004c000005500000613d000000000400001900000000054100190000002004400039000000000624001900000000060604330000000000650435000000000534004b000005490000413d000000000231001900000000000204350000001f02300039000000200300008a000000000232016f0000000001210019000000000001042d0004000000000002000400000003001d000001ac01100198000005ab0000613d000001ac02200198000200000002001d000005c00000613d000300000001001d0000000000100435000000200000043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000005a90000613d000000000101043b000000000201041a0000000401000029000100000002001d000000000112004b000005d50000413d00000003010000290000000000100435000000200000043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000005a90000613d000000040200002900000001030000290000000002230049000000000101043b000000000021041b0000000201000029000000000010043500000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f0000000102200190000005a90000613d000000000101043b000000000201041a00000004030000290000000002320019000000000021041b000000400100043d000000000031043500000198020000410000000003000414000001980430009c0000000003028019000001980410009c00000000010280190000004001100210000000c002300210000000000112019f0000019b011001c70000800d020000390000000303000039000001b40400004100000003050000290000000206000029065b06510000040f0000000101200190000005a90000613d000000000001042d00000000010000190000065d00010430000000400100043d0000006402100039000001c70300004100000000003204350000004402100039000001c8030000410000000000320435000000240210003900000025030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d00010430000000400100043d0000006402100039000001c50300004100000000003204350000004402100039000001c6030000410000000000320435000000240210003900000023030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d00010430000000400100043d0000006402100039000001c30300004100000000003204350000004402100039000001c4030000410000000000320435000000240210003900000026030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d000104300003000000000002000001ac01100198000006270000613d000200000003001d000001ac02200198000300000002001d0000063c0000613d000100000001001d00000000001004350000000101000039000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f00000001022001900000000304000029000006250000613d000000000101043b0000000000400435000000200010043f00000198010000410000000002000414000001980320009c0000000001024019000000c001100210000001ae011001c70000801002000039065b06560000040f00000003060000290000000102200190000006250000613d000000000101043b0000000202000029000000000021041b000000400100043d000000000021043500000198020000410000000003000414000001980430009c0000000003028019000001980410009c00000000010280190000004001100210000000c002300210000000000112019f0000019b011001c70000800d020000390000000303000039000001b9040000410000000105000029065b06510000040f0000000101200190000006250000613d000000000001042d00000000010000190000065d00010430000000400100043d0000006402100039000001bc0300004100000000003204350000004402100039000001bd030000410000000000320435000000240210003900000024030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d00010430000000400100043d0000006402100039000001ba0300004100000000003204350000004402100039000001bb030000410000000000320435000000240210003900000022030000390000000000320435000001b10200004100000000002104350000000402100039000000200300003900000000003204350000019802000041000001980310009c00000000010280190000004001100210000001b2011001c70000065d0001043000000654002104210000000102000039000000000001042d0000000002000019000000000001042d00000659002104230000000102000039000000000001042d0000000002000019000000000001042d0000065b000004320000065c0001042e0000065d000104300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff000000000000000000000000000000000000000000000000ffffffffffffffff8000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000002000000000000000000000000000000002000000000000000000000000000000400000010000000000000000000000000000000000000000000000000000000000000000000000000040c10f1800000000000000000000000000000000000000000000000000000000a457c2d600000000000000000000000000000000000000000000000000000000a457c2d700000000000000000000000000000000000000000000000000000000a9059cbb00000000000000000000000000000000000000000000000000000000dd62ed3e0000000000000000000000000000000000000000000000000000000040c10f190000000000000000000000000000000000000000000000000000000070a082310000000000000000000000000000000000000000000000000000000095d89b410000000000000000000000000000000000000000000000000000000023b872dc0000000000000000000000000000000000000000000000000000000023b872dd00000000000000000000000000000000000000000000000000000000313ce56700000000000000000000000000000000000000000000000000000000395093510000000000000000000000000000000000000000000000000000000006fdde0300000000000000000000000000000000000000000000000000000000095ea7b30000000000000000000000000000000000000000000000000000000018160ddd000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000200000000000000000000000000200000000000000000000000000000000000040000000000000000000000000207a65726f00000000000000000000000000000000000000000000000000000045524332303a2064656372656173656420616c6c6f77616e63652062656c6f7708c379a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000000000000000000008a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19bddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef45524332303a206d696e7420746f20746865207a65726f20616464726573730000000000000000000000000000000000000000640000000000000000000000004e487b710000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000240000000000000000000000008c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925737300000000000000000000000000000000000000000000000000000000000045524332303a20617070726f766520746f20746865207a65726f206164647265726573730000000000000000000000000000000000000000000000000000000045524332303a20617070726f76652066726f6d20746865207a65726f2061646445524332303a20696e73756666696369656e7420616c6c6f77616e6365000000c2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85bffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff00000000000000800200000000000000000000000000000000000000000000000000000000000000616c616e6365000000000000000000000000000000000000000000000000000045524332303a207472616e7366657220616d6f756e7420657863656564732062657373000000000000000000000000000000000000000000000000000000000045524332303a207472616e7366657220746f20746865207a65726f2061646472647265737300000000000000000000000000000000000000000000000000000045524332303a207472616e736665722066726f6d20746865207a65726f206164000000000000000000000000000000000000000000000000000000000000000018469939d00da7016fd24775544e09a6a1ad29697146a060aa4a0baa144c2ede'; + const hashedBytecode = utils.hashBytecode(bytecode); + expect(hashedBytecode).toEqual( + new Uint8Array([ + 1, 0, 1, 203, 106, 110, 141, 95, 104, 41, 82, 47, 25, 250, 149, 104, 102, 14, + 10, 156, 213, 59, 46, 139, 228, 222, 176, 166, 121, 69, 46, 65, + ]), + ); + }); + + it('should throw an error when bytecode is not divisible by 32', () => { + try { + utils.hashBytecode('0x0002'); + } catch (e) { + expect((e as Error).message).toBe( + 'The bytecode length in bytes must be divisible by 32!', + ); + } + }); + + it('should throw an error when bytecode is has even number of 32-byte words', () => { + try { + utils.hashBytecode(`0x${'00020000000000020009000000000002'.repeat(2)}`); + } catch (e) { + expect((e as Error).message).toBe( + `Bytecode can not be longer than ${constants.MAX_BYTECODE_LEN_BYTES} bytes`, + ); + } + }); + }); + + describe('#parseEip712()', () => { + it('should parse a transaction with a signature', () => { + const tx: types.Eip712TxData = { + type: 113, + nonce: 0n, + maxPriorityFeePerGas: 0n, + maxFeePerGas: 0n, + gasLimit: 0n, + to: ADDRESS3, + value: 1_000_000n, + data: '0x', + chainId: 270n, + from: ADDRESS1, + customData: { + gasPerPubdata: 50_000n, + factoryDeps: [], + customSignature: '0x', + paymasterParams: undefined, + }, + hash: '0x9ed410ce33179ac1ff6b721060605afc72d64febfe0c08cacab5a246602131ee', + }; + + const serializedTx = + '0x71f87f8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408001a073a20167b8d23b610b058c05368174495adf7da3a4ed4a57eb6dbdeb1fafc24aa02f87530d663a0d061f69bb564d2c6fb46ae5ae776bbd4bd2a2a4478b9cd1b42a82010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + const result = utils.EIP712.fromSerializedTx(serializedTx); + expect(result).toEqual(tx); + }); + + it('should parse a transaction without a signature', () => { + const tx: types.Eip712TxData = { + type: 113, + nonce: 0n, + maxPriorityFeePerGas: 0n, + maxFeePerGas: 0n, + gasLimit: 0n, + to: ADDRESS3, + value: 0n, + data: '0x', + chainId: 270n, + from: ADDRESS1, + customData: { + gasPerPubdata: 50_000n, + factoryDeps: [], + customSignature: '0x', + paymasterParams: undefined, + }, + hash: '0x7d3aab3e3d06d6a702228d911c2a9afaccddd52514fb89dc9d0ff81a67bfff04', + }; + + const serializedTx = + '0x71f83e8080808094a61464658afeaf65cccaafd3a512b69a83b77618808082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0'; + + const result = utils.EIP712.fromSerializedTx(serializedTx); + expect(result).toEqual(tx); + }); + }); + describe('#isMessageSignatureCorrect()', () => { + it('should return true if signature made by a private key was correct', async () => { + const account = web3Accounts.create(); + const ADDRESS = account.address; + const message = 'Hello, world!'; + const signature = utils.EIP712.sign(message, account.privateKey).serialized; + const web3 = new Web3(); + const isValidSignature = await utils.isMessageSignatureCorrect( + web3, + ADDRESS, + message, + signature, + ); + expect(isValidSignature).toBe(true); + }); + }); + describe('#isMessageSignatureCorrect()', () => { + it('should return true if signature made by a private key was correct', async () => { + const receipt = { + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + blockHash: '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + blockNumber: '0x5f6994', + logsBloom: + '0x00040004800000000000020000000000000000001001000000020002000000020000000000000000000200800001000200040000000000000000000000200000000000020000000400000008000000000000000000000000000000081000020000000000000000400004000010000000000000000000000000040010000000000000000000000000000001000200000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000222000000000000000000000000000000000000000000000001000002000010001000000000000000000002080080020010000000000000000000000000', + gasUsed: '0x420e7', + cumulativeGasUsed: '0x13867f2', + transactionIndex: '0x97', + from: '0x81d3ce1a567389f6cb1178a68eb33aa6f081dc52', + to: '0x35a54c8c757806eb6820629bc82d90e056394c92', + type: '0x2', + effectiveGasPrice: '0x22b3acaea', + logs: [ + { + blockHash: + '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + address: '0x3e8b2fe58675126ed30d0d12dea2a9bda72d18ae', + logIndex: '0x11a', + data: '0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000006ff3c58a20e0', + removed: false, + topics: [ + '0x249bc8a55d0c4a0034b9aaa6be739bec2d4466e5d859bec9566a8553c405c838', + '0x000000000000000000000000000000000000000000000000000000000000012c', + '0x00000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc52', + ], + blockNumber: '0x5f6994', + transactionIndex: '0x97', + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + }, + { + blockHash: + '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + address: '0x53844f9577c2334e541aec7df7174ece5df1fcf0', + logIndex: '0x11b', + data: '0x0000000000000000000000000000000000000000000000000000000000000005', + removed: false, + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x00000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc52', + '0x0000000000000000000000003e8b2fe58675126ed30d0d12dea2a9bda72d18ae', + ], + blockNumber: '0x5f6994', + transactionIndex: '0x97', + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + }, + { + blockHash: + '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + address: '0x53844f9577c2334e541aec7df7174ece5df1fcf0', + logIndex: '0x11c', + data: '0x0000000000000000000000000000000000000000000000000000000000000000', + removed: false, + topics: [ + '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', + '0x00000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc52', + '0x0000000000000000000000003e8b2fe58675126ed30d0d12dea2a9bda72d18ae', + ], + blockNumber: '0x5f6994', + transactionIndex: '0x97', + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + }, + { + blockHash: + '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + address: '0x3e8b2fe58675126ed30d0d12dea2a9bda72d18ae', + logIndex: '0x11d', + data: '0x00000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc5200000000000000000000000053844f9577c2334e541aec7df7174ece5df1fcf00000000000000000000000000000000000000000000000000000000000000005', + removed: false, + topics: [ + '0x8768405a01370685449c74c293804d6c9cc216d170acdbdba50b33ed4144447f', + '0x000000000000000000000000000000000000000000000000000000000000012c', + '0x14ea5757930256307269b33bbf0794af5b5ad979c2e1fffcc9b0f7486416f2e5', + '0x00000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc52', + ], + blockNumber: '0x5f6994', + transactionIndex: '0x97', + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + }, + { + blockHash: + '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + address: '0x9a6de0f62aa270a8bcb1e2610078650d539b1ef9', + logIndex: '0x11e', + data: '0x000000000000000000000000000000000000000000000000000000000000b77d801ee033bc4d9df4e224f814b895fcd3b8d37648c563b4105c73d491a11cc64800000000000000000000000000000000000000000000000000000000668857c800000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000062000000000000000000000000000000000000000000000000000000000000000ff0000000000000000000000004f9c2fe58675126ed30d0d12dea2a9bda72d29bf000000000000000000000000681a1afdc2e06776816386500d2d461a6c96cb4500000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000015d76ea200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b77d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ff3c58a20e000000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc5200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000520000000000000000000000000000000000000000000000000000000000000054000000000000000000000000000000000000000000000000000000000000005600000000000000000000000000000000000000000000000000000000000000264cfe7af7c00000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc5200000000000000000000000081d3ce1a567389f6cb1178a68eb33aa6f081dc5200000000000000000000000053844f9577c2334e541aec7df7174ece5df1fcf0000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008444149204d6f636b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047444414900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + removed: false, + topics: [ + '0x4531cd5795773d7101c17bdeb9f5ab7f47d7056017506f937083be5d6e77a382', + ], + blockNumber: '0x5f6994', + transactionIndex: '0x97', + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + }, + { + blockHash: + '0x9df9bbbfcadf7afb7fecd1133c306a7d37a62c3fc9c8cae4b3480666d79c42cf', + address: '0x3e8b2fe58675126ed30d0d12dea2a9bda72d18ae', + logIndex: '0x11f', + data: '0x', + removed: false, + topics: [ + '0xe4def01b981193a97a9e81230d7b9f31812ceaf23f864a828a82c687911cb2df', + '0x000000000000000000000000000000000000000000000000000000000000012c', + '0x14ea5757930256307269b33bbf0794af5b5ad979c2e1fffcc9b0f7486416f2e5', + '0x801ee033bc4d9df4e224f814b895fcd3b8d37648c563b4105c73d491a11cc648', + ], + blockNumber: '0x5f6994', + transactionIndex: '0x97', + transactionHash: + '0xd5660cc02410f62bff04fefeefba6217ad31a670063af0c3fed5bd02a8fa9065', + }, + ], + status: '0x1', + } as web3Types.TransactionReceipt; + const addr = '0x9a6de0f62aa270a8bcb1e2610078650d539b1ef9'; + const hash = getL2HashFromPriorityOp(receipt, addr); + expect(hash).toBe('0x801ee033bc4d9df4e224f814b895fcd3b8d37648c563b4105c73d491a11cc648'); + }); + }); +}); diff --git a/test/unit/web3zksync-l2.as.provider.test.ts b/test/unit/web3zksync-l2.as.provider.test.ts new file mode 100644 index 0000000..b1c4e5f --- /dev/null +++ b/test/unit/web3zksync-l2.as.provider.test.ts @@ -0,0 +1,59 @@ +import type { Transaction } from 'web3-types'; +import { ethRpcMethods } from 'web3-rpc-methods'; + +import { Web3ZkSyncL2, Web3ZkSyncL1 } from '../../src'; +import { getPriorityOpResponse } from '../../src/utils'; +import type { PriorityL1OpResponse } from '../../src/types'; + +jest.mock('web3-rpc-methods'); + +describe('Web3ZkSyncL2 as a Provider', () => { + it('should correctly initialize and assign function properties in getPriorityOpResponse', async () => { + const web3ZkSyncL2 = new Web3ZkSyncL2('https://mainnet.era.zksync.io'); + const acc = web3ZkSyncL2.eth.accounts.privateKeyToAccount( + '0x1f953dc9b6437fb94fcafa5dabe3faa0c34315b954dd66f41bf53273339c6d26', + ); + web3ZkSyncL2.eth.accounts.wallet.add(acc); + // TODO: remove the commented lines after the test passes + // NOTE: this was an object of TransactionResponse in ethers + const l1Tx: Transaction = { + // hash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', + from: acc.address, + // blockHash: '0xabcd1234567890abcdef1234567890abcdef1234567890abcdef1234567890', + // blockNumber: 123456, + to: '0xabcdef1234567890abcdef1234567890abcdef12', + type: 0, + nonce: 42, + gasLimit: 2000000n, + // index: 3, + gasPrice: 1000000000n, + // data: '0x', + value: 0n, + chainId: 1337n, + // // not used in tested code, keep the test simple + // signature: null as unknown as Signature, + }; + + // mock ethRpcMethods.sendTransaction + jest.spyOn(ethRpcMethods, 'sendTransaction').mockResolvedValue(''); + // @ts-ignore + jest.spyOn(ethRpcMethods, 'signTransaction').mockResolvedValue({ + // @ts-ignore + raw: '0xabcd1234567890abcdef1234567890abcdef1234567890abcdef1234567890', + }); + + const signed = await web3ZkSyncL2.signTransaction(l1Tx); + + const txPromise = web3ZkSyncL2.sendRawTransaction(signed); + + const priorityOpResponse = await getPriorityOpResponse(new Web3ZkSyncL1(), txPromise); + // 'The waitL1Commit function should be properly initialized' + expect(typeof (priorityOpResponse as PriorityL1OpResponse).waitL1Commit).toEqual( + 'function', + ); + // 'The wait function should be properly initialized' + expect(typeof priorityOpResponse.wait).toBe('function'); + // 'The waitFinalize function should be properly initialized' + expect(typeof priorityOpResponse.waitFinalize).toEqual('function'); + }); +}); diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 0000000..2207e89 --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,26 @@ +export const ADDRESS1 = '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'; +export const ADDRESS3 = '0xa61464658AfeAf65CccaaFD3a512b69A83B77618'; + +export const PRIVATE_KEY1 = '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110'; +export const MNEMONIC1 = + 'stuff slice staff easily soup parent arm payment cotton trade scatter struggle'; +export const ADDRESS2 = '0x12b1d9d74d73b1c3a245b19c1c5501c653af1af9'; +export const PRIVATE_KEY2 = '0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3'; +export const DAI_L1 = '0x3e622317f8C93f7328350cF0B56d9eD4C620C5d6'; +export const USDC_L1 = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'; +export const APPROVAL_TOKEN = '0x927488F48ffbc32112F1fF721759649A89721F8F'; // Crown token +export const PAYMASTER = '0x6f72f0d7bDba2E2a923beC09fBEE64cD134680F2'; // Crown token paymaster + +export const IS_ETH_BASED = true; + +export function deepEqualExcluding( + obj1: Record, + expected: Record, + excludeFields: string[], +) { + for (const key in obj1) { + if (!excludeFields.includes(key)) { + expect(obj1[key]).toEqual(expected[key]); + } + } +} diff --git a/yarn.lock b/yarn.lock index 02d98d3..d7d3eec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -467,6 +467,11 @@ resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== +"@ethereumjs/rlp@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-5.0.2.tgz#c89bd82f2f3bec248ab2d517ae25f5bbc4aac842" + integrity sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA== + "@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" @@ -1140,6 +1145,13 @@ dependencies: "@noble/hashes" "1.3.1" +"@noble/curves@1.3.0", "@noble/curves@~1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" + integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== + dependencies: + "@noble/hashes" "1.3.3" + "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" @@ -1150,6 +1162,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== +"@noble/hashes@1.3.3", "@noble/hashes@~1.3.2": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" + integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== + "@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -1386,6 +1403,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== +"@scure/base@~1.1.4": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d" + integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g== + "@scure/bip32@1.1.5": version "1.1.5" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.5.tgz#d2ccae16dcc2e75bc1d75f5ef3c66a338d1ba300" @@ -1404,6 +1426,15 @@ "@noble/hashes" "~1.3.1" "@scure/base" "~1.1.0" +"@scure/bip32@1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.3.tgz#a9624991dc8767087c57999a5d79488f48eae6c8" + integrity sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ== + dependencies: + "@noble/curves" "~1.3.0" + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.4" + "@scure/bip39@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5" @@ -1420,6 +1451,14 @@ "@noble/hashes" "~1.3.0" "@scure/base" "~1.1.0" +"@scure/bip39@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.2.tgz#f3426813f4ced11a47489cbcf7294aa963966527" + integrity sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA== + dependencies: + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.4" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -2928,6 +2967,16 @@ ethereum-cryptography@^2.0.0: "@scure/bip32" "1.3.1" "@scure/bip39" "1.2.1" +ethereum-cryptography@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz#1352270ed3b339fe25af5ceeadcf1b9c8e30768a" + integrity sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA== + dependencies: + "@noble/curves" "1.3.0" + "@noble/hashes" "1.3.3" + "@scure/bip32" "1.3.3" + "@scure/bip39" "1.2.2" + ethereumjs-abi@^0.6.8: version "0.6.8" resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz#71bc152db099f70e62f108b7cdfca1b362c6fcae" @@ -2993,6 +3042,11 @@ ethjs-util@0.1.6, ethjs-util@^0.1.6: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -5741,316 +5795,288 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -web3-core@^4.3.0, web3-core@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.3.1.tgz#5c3b5b59f1e31537a64237caa5fd83f5ffd7b0f3" - integrity sha512-xa3w5n/ESxp5HIbrwsYBhpAPx2KI5LprjRFEtRwP0GpqqhTcCSMMYoyItRqQQ+k9YnB0PoFpWJfJI6Qn5x8YUQ== - dependencies: - web3-errors "^1.1.4" - web3-eth-iban "^4.0.7" - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-types "^1.3.1" - web3-utils "^4.0.7" - web3-validator "^2.0.3" +web3-core@4.5.1-dev.1436228.0, web3-core@4.5.1-dev.1436228.0+1436228: + version "4.5.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.5.1-dev.1436228.0.tgz#6b869109b0456d735a228b268fe1dae918228d3e" + integrity sha512-Ahmb16rwfyRoSAQiYOpMtzXxHPvfZKxfqD2lP77Ox6cJElCj5Y5Sm8La3OTn0PkTjUPmdzeilDpvAwT5zwk9Vg== + dependencies: + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-eth-accounts "4.1.3-dev.1436228.0+1436228" + web3-eth-iban "4.0.8-dev.1436228.0+1436228" + web3-providers-http "4.1.1-dev.1436228.0+1436228" + web3-providers-ws "4.0.8-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" optionalDependencies: - web3-providers-ipc "^4.0.7" + web3-providers-ipc "4.0.8-dev.1436228.0+1436228" -web3-core@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.3.2.tgz#f24b11d6a57dee527de8d42c89de2a439f0c4bed" - integrity sha512-uIMVd/j4BgOnwfpY8ZT+QKubOyM4xohEhFZXz9xB8wimXWMMlYVlIK/TbfHqFolS9uOerdSGhsMbcK9lETae8g== +web3-errors@1.2.1-dev.1436228.0+1436228: + version "1.2.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.2.1-dev.1436228.0.tgz#e865fa4b381daa4359542b621879ce25cc81bb97" + integrity sha512-Cruu3QYpvMMikAVovNG53go/0G7Wq3ZHGZqacAMCuZH41i9SHM0jJWC9CNPbtK1V8qKZyIX6IwyplwDH0iQTxA== dependencies: - web3-errors "^1.1.4" - web3-eth-accounts "^4.1.0" - web3-eth-iban "^4.0.7" - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-types "^1.3.1" - web3-utils "^4.1.0" - web3-validator "^2.0.3" - optionalDependencies: - web3-providers-ipc "^4.0.7" + web3-types "1.7.1-dev.1436228.0+1436228" -web3-errors@^1.1.3, web3-errors@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.1.4.tgz#5667a0a5f66fc936e101ef32032ccc1e8ca4d5a1" - integrity sha512-WahtszSqILez+83AxGecVroyZsMuuRT+KmQp4Si5P4Rnqbczno1k748PCrZTS1J4UCPmXMG2/Vt+0Bz2zwXkwQ== +web3-errors@^1.1.4, web3-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.2.0.tgz#441acfd7fd744c9beedf23f277f20759fae92433" + integrity sha512-58Kczou5zyjcm9LuSs5Hrm6VrG8t9p2J8X0yGArZrhKNPZL66gMGkOUpPx+EopE944Sk4yE+Q25hKv4H5BH+kA== dependencies: - web3-types "^1.3.1" + web3-types "^1.6.0" -web3-eth-abi@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.1.4.tgz#56ae7ebb1385db1a948e69fb35f4057bff6743af" - integrity sha512-YLOBVVxxxLYKXjaiwZjEWYEnkMmmrm0nswZsvzSsINy/UgbWbzfoiZU+zn4YNWIEhORhx1p37iS3u/dP6VyC2w== +web3-eth-abi@4.2.3-dev.1436228.0+1436228: + version "4.2.3-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.3-dev.1436228.0.tgz#6689269cacfad983bf2435e5edc9ba2d97f39fa4" + integrity sha512-4MfysOUh7YVLeuwyLkKe/PzecFOoKceqvGUlGlwhEQiUbg2jU6h3GZAsDOeLueiWcGQOqnZOR0N9IjeWHDQtjg== dependencies: abitype "0.7.1" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" -web3-eth-abi@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.0.tgz#398d415e7783442d06fb7939e40ce3de7a3f04e9" - integrity sha512-x7dUCmk6th+5N63s5kUusoNtsDJKUUQgl9+jECvGTBOTiyHe/V6aOY0120FUjaAGaapOnR7BImQdhqHv6yT2YQ== +web3-eth-abi@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.2.tgz#d7592e2cc113fd34da3fb4c933537ddf8639d9b2" + integrity sha512-akbGi642UtKG3k3JuLbhl9KuG7LM/cXo/by2WfdwfOptGZrzRsWJNWje1d2xfw1n9kkVG9SAMvPJl1uSyR3dfw== dependencies: abitype "0.7.1" - web3-errors "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" + web3-errors "^1.2.0" + web3-types "^1.6.0" + web3-utils "^4.3.0" + web3-validator "^2.0.6" -web3-eth-accounts@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.0.tgz#5b5e6c60d457e7b829ec590021fc87ada8585920" - integrity sha512-UFtAsOANsvihTQ6SSvOKguupmQkResyR9M9JNuOxYpKh7+3W+sTnbLXw2UbOSYIsKlc1mpqqW9bVr1SjqHDpUQ== +web3-eth-accounts@4.1.3-dev.1436228.0+1436228: + version "4.1.3-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.3-dev.1436228.0.tgz#588c37eab5e79fa78ca8a9184b82681f61a76182" + integrity sha512-Wg1SVTxKCDZ49iR4xswzmYbXRZ9GsaqTCzpAJn2WCcTMlEit8v0Cr/wzi9orZZqrTQFXLxOjlY8UETsOGiiBGg== dependencies: "@ethereumjs/rlp" "^4.0.1" crc-32 "^1.2.2" ethereum-cryptography "^2.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" -web3-eth-accounts@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.1.tgz#55225e5510b961e1cacb4eccc996544998e907fc" - integrity sha512-9JqhRi1YhO1hQOEmmBHgEGsME/B1FHMxpA/AK3vhpvQ8QeP6KbJW+cForTLfPpUbkmPxnRunG4PNNaETNlZfrA== +web3-eth-accounts@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.2.tgz#652d6e3daf4d6cb3fe67cec6a878e768f6e8b8e8" + integrity sha512-y0JynDeTDnclyuE9mShXLeEj+BCrPHxPHOyPCgTchUBQsALF9+0OhP7WiS3IqUuu0Hle5bjG2f5ddeiPtNEuLg== dependencies: "@ethereumjs/rlp" "^4.0.1" crc-32 "^1.2.2" ethereum-cryptography "^2.0.0" web3-errors "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" - -web3-eth-contract@^4.1.2: - version "4.1.3" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.1.3.tgz#15dd4c978eaf0d8f894b2c1f3e9f94edd29ff57c" - integrity sha512-F6e3eyetUDiNOb78EDVJtNOb0H1GPz3xAZH8edSfYdhaxI9tTutP2V3p++kh2ZJ/RrdE2+xil7H/nPLgHymBvg== - dependencies: - web3-core "^4.3.1" - web3-errors "^1.1.4" - web3-eth "^4.3.1" - web3-eth-abi "^4.1.4" - web3-types "^1.3.1" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth-contract@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.2.0.tgz#73f70b19217cd4854211c05846f0c841763b3b29" - integrity sha512-K7bUypsomTs8/Oa0Lgvq5plsSB5afgKJ79NMuXxvC5jfV+AxNrQyKcr5Vd5yEGNqrdQuIPduGQa8DpuY+rMe1g== - dependencies: - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth "^4.4.0" - web3-eth-abi "^4.2.0" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" - -web3-eth-ens@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.0.8.tgz#f4e0a018ce6cc69e37007ee92063867feb5994f0" - integrity sha512-nj0JfeD45BbzVJcVYpUJnSo8iwDcY9CQ7CZhhIVVOFjvpMAPw0zEwjTvZEIQyCW61OoDG9xcBzwxe2tZoYhMRw== + web3-types "^1.6.0" + web3-utils "^4.2.3" + web3-validator "^2.0.5" + +web3-eth-contract@4.5.1-dev.1436228.0, web3-eth-contract@4.5.1-dev.1436228.0+1436228: + version "4.5.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.5.1-dev.1436228.0.tgz#a659bf3ccb594901a2a2a7469fbf9577d056272a" + integrity sha512-eVG5u5NF2f1Fv9tyQLkfevyke2DJB0DYGN8hCuFzUd2B7zEEyQ+ywEsT15u0DXPoXIt23fp90Lw9DIpHlZiL4g== + dependencies: + "@ethereumjs/rlp" "^5.0.2" + web3-core "4.5.1-dev.1436228.0+1436228" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-eth "4.8.1-dev.1436228.0+1436228" + web3-eth-abi "4.2.3-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-eth-ens@4.4.1-dev.1436228.0+1436228: + version "4.4.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.4.1-dev.1436228.0.tgz#ecd42f19c2ea7916f96d16d88fea8a779e4efb24" + integrity sha512-MIw/1WtRMNPiPILNmHjihV0VNop27N8Yq0b20lXjQOv7FHy5boz4jnrg1Vv2qeUUqJ4SEO11Dey66a+gXph12A== dependencies: "@adraffy/ens-normalize" "^1.8.8" - web3-core "^4.3.0" - web3-errors "^1.1.3" - web3-eth "^4.3.1" - web3-eth-contract "^4.1.2" - web3-net "^4.0.7" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth-iban@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-4.0.7.tgz#ee504f845d7b6315f0be78fcf070ccd5d38e4aaf" - integrity sha512-8weKLa9KuKRzibC87vNLdkinpUE30gn0IGY027F8doeJdcPUfsa4IlBgNC4k4HLBembBB2CTU0Kr/HAOqMeYVQ== - dependencies: - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth-personal@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-4.0.8.tgz#b51628c560de550ca8b354fa784f9556aae6065c" - integrity sha512-sXeyLKJ7ddQdMxz1BZkAwImjqh7OmKxhXoBNF3isDmD4QDpMIwv/t237S3q4Z0sZQamPa/pHebJRWVuvP8jZdw== - dependencies: - web3-core "^4.3.0" - web3-eth "^4.3.1" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.3.1.tgz#cb4224356716da71e694091aa3fbf10bcb813fb5" - integrity sha512-zJir3GOXooHQT85JB8SrufE+Voo5TtXdjhf1D8IGXmxM8MrhI8AT+Pgt4siBTupJcu5hF17iGmTP/Nj2XnaibQ== + web3-core "4.5.1-dev.1436228.0+1436228" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-eth "4.8.1-dev.1436228.0+1436228" + web3-eth-contract "4.5.1-dev.1436228.0+1436228" + web3-net "4.1.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-eth-iban@4.0.8-dev.1436228.0+1436228: + version "4.0.8-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-4.0.8-dev.1436228.0.tgz#23e2520f8d692b079a5b4603516994847ece7fc9" + integrity sha512-Q5Vkj87ivRt33oMWdkzssJpeUANmTJdqjmQj0PBDpmCRU6ghP0SETB9NoQlfGkmoC56e1z+detQOH2E2dLCKbQ== + dependencies: + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-eth-personal@4.0.9-dev.1436228.0+1436228: + version "4.0.9-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-4.0.9-dev.1436228.0.tgz#8e74fa26a4656b1ac28c611ca947229d64d3cf69" + integrity sha512-yZlNIZ3j7a7MIZNB/ztEsEF1Ji9oI8zT0gAiygAnLvYmqOcLBQoRFnSaZ01+AKMl00BCD1xToOtGs8Kd9vpVqQ== + dependencies: + web3-core "4.5.1-dev.1436228.0+1436228" + web3-eth "4.8.1-dev.1436228.0+1436228" + web3-rpc-methods "1.3.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-eth@4.8.1-dev.1436228.0+1436228: + version "4.8.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.8.1-dev.1436228.0.tgz#74a587d6faca22f8cfc6a42edb5c82661bcec65e" + integrity sha512-T06ETt1gh7oxo7tZqtY+SpPfa8e5DzMMlYRT0wvYEZ7EKZtqMkrZVImuWDP0mtg+AqS+r1mf6DP+DJzIAXVRsA== dependencies: setimmediate "^1.0.5" - web3-core "^4.3.0" - web3-errors "^1.1.3" - web3-eth-abi "^4.1.4" - web3-eth-accounts "^4.1.0" - web3-net "^4.0.7" - web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.4.0.tgz#755c34a769109836d122a53b33814d63f9ec5a65" - integrity sha512-HswKdzF44wUrciRAtEJaml9O7rDYDxElHmFs+27WcO3nel2zku+n0xs4e2ZAehfrCZ+05/y7TgnOZnaDU8Fb/A== - dependencies: - setimmediate "^1.0.5" - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth-abi "^4.2.0" - web3-eth-accounts "^4.1.1" - web3-net "^4.0.7" - web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" - -web3-net@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.0.7.tgz#ed2c1bd700cf94be93a6dbd8bd8aa413d8681942" - integrity sha512-SzEaXFrBjY25iQGk5myaOfO9ZyfTwQEa4l4Ps4HDNVMibgZji3WPzpjq8zomVHMwi8bRp6VV7YS71eEsX7zLow== - dependencies: - web3-core "^4.3.0" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - -web3-providers-http@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.1.0.tgz#8d7afda67d1d8542ca85b30f60a3d1fe1993b561" - integrity sha512-6qRUGAhJfVQM41E5t+re5IHYmb5hSaLc02BE2MaRQsz2xKA6RjmHpOA5h/+ojJxEpI9NI2CrfDKOAgtJfoUJQg== + web3-core "4.5.1-dev.1436228.0+1436228" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-eth-abi "4.2.3-dev.1436228.0+1436228" + web3-eth-accounts "4.1.3-dev.1436228.0+1436228" + web3-net "4.1.1-dev.1436228.0+1436228" + web3-providers-ws "4.0.8-dev.1436228.0+1436228" + web3-rpc-methods "1.3.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-net@4.1.1-dev.1436228.0+1436228: + version "4.1.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.1.1-dev.1436228.0.tgz#19efe0c67d91ad3592a7549ffdd7be2b74becd16" + integrity sha512-RwZjG/9FIIeZybECh4VDAAE8eoDFxvbzzZrF0aKnQi5xjTB0S3MCcki7bUwomVWAZCU8Fjz62K7dFh0Rxtt98Q== + dependencies: + web3-core "4.5.1-dev.1436228.0+1436228" + web3-rpc-methods "1.3.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + +web3-providers-http@4.1.1-dev.1436228.0+1436228: + version "4.1.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.1.1-dev.1436228.0.tgz#22763c49ef9644b11d2d6420c02c30810a73e079" + integrity sha512-wGrnkvDvwWjgGho2ByIDOqfUgDkFqTB7lVy/wTT7aZnjie1NbW4f2UbkFfhcao2dIAGY2rRZI0Mk8uBfpFiJ4w== dependencies: cross-fetch "^4.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" -web3-providers-ipc@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-4.0.7.tgz#9ec4c8565053af005a5170ba80cddeb40ff3e3d3" - integrity sha512-YbNqY4zUvIaK2MHr1lQFE53/8t/ejHtJchrWn9zVbFMGXlTsOAbNoIoZWROrg1v+hCBvT2c9z8xt7e/+uz5p1g== +web3-providers-ipc@4.0.8-dev.1436228.0+1436228: + version "4.0.8-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-4.0.8-dev.1436228.0.tgz#b35d7bb95345e1625807b22c868c02a8e2abb540" + integrity sha512-pFiheejy19qi7061I6ppZSD5LB2O0++JQ8gZ+S4MWgWX1hdVxojTeiEgj7Cx+j1xuvN2vvLb1zZ/i35100RSrA== dependencies: - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" -web3-providers-ws@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-4.0.7.tgz#7a78a0dcf077e0e802da524fbb37d080b356c14b" - integrity sha512-n4Dal9/rQWjS7d6LjyEPM2R458V8blRm0eLJupDEJOOIBhGYlxw5/4FthZZ/cqB7y/sLVi7K09DdYx2MeRtU5w== +web3-providers-ws@4.0.8-dev.1436228.0+1436228: + version "4.0.8-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-4.0.8-dev.1436228.0.tgz#3c2609aa7f809fa318c15511dbc504085b2cf9b3" + integrity sha512-/9ECj/OMnMuSS9MCT6ep70iCfm7N9LX3JtBPHttZaalM3P1sVGzXFowW7jtF84ltZX0a6uqIjwsqmPZ/ID26VA== dependencies: "@types/ws" "8.5.3" isomorphic-ws "^5.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - ws "^8.8.1" - -web3-rpc-methods@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.1.3.tgz#4be8a85628d8b69846e2e0afa0ed71e3f6eaf163" - integrity sha512-XB6SsCZZPdZUMPIRqDxJkZFKMu0/Y+yaExAt+Z7RqmuM7xF55fJ/Qb84LQho8zarvUoYziy4jnIfs+SXImxQUw== - dependencies: - web3-core "^4.3.0" - web3-types "^1.3.0" - web3-validator "^2.0.3" - -web3-rpc-methods@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.1.4.tgz#0b478e38231d3f3260ac504307c6dc4059af6fda" - integrity sha512-LTFNg4LFaeU8K9ecuT8fHDp/LOXyxCneeZjCrRYIW1u82Ly52SrY55FIzMIISGoG/iT5Wh7UiHOB3CQsWLBmbQ== - dependencies: - web3-core "^4.3.2" - web3-types "^1.3.1" - web3-validator "^2.0.3" - -web3-types@^1.3.0, web3-types@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.3.1.tgz#cf6148ad46b68c5c89714613380b270d31e297be" - integrity sha512-8fXi7h/t95VKRtgU4sxprLPZpsTh3jYDfSghshIDBgUD/OoGe5S+syP24SUzBZYllZ/L+hMr2gdp/0bGJa8pYQ== + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + ws "^8.17.1" + +web3-rpc-methods@1.3.1-dev.1436228.0+1436228: + version "1.3.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.3.1-dev.1436228.0.tgz#8fb4c78429f1f18404888d8f22f4db1c443bb236" + integrity sha512-Y+sFdRNuE3wIQcCiH0TPU+aRugWk3v+FyF2VkFsFXfSnDYqJTnhm4MTDQhkfruG68xuw3pHo8J6YPTCiJQRdLg== + dependencies: + web3-core "4.5.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" + +web3-rpc-providers@1.0.0-dev.1436228.0+1436228: + version "1.0.0-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-rpc-providers/-/web3-rpc-providers-1.0.0-dev.1436228.0.tgz#ffc61375fa507fceb1cdeeb270f145a7d35e41f1" + integrity sha512-glQblyg9SMLv1SZTwdQ/0T1oU+dmmh/KbOXY5Jw6kG0BxeSle/pgBXj14ps9T+HnDtmI7SlTUAggXQk4ehNJ5w== + dependencies: + web3-providers-http "4.1.1-dev.1436228.0+1436228" + web3-providers-ws "4.0.8-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + +web3-types@1.7.1-dev.1436228.0, web3-types@1.7.1-dev.1436228.0+1436228: + version "1.7.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.7.1-dev.1436228.0.tgz#374f101087431b20823107c2f0c3b096fcdd1d69" + integrity sha512-Lxg6ZpLUC36i/idSQZ8GI/LRBhHyHOwZxcmrNELiAtWe7cQzU+zOGybtWV7XRNwOUUQaZ4NCqrIF6LuM6oJE9Q== + +web3-types@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.7.0.tgz#9945fa644af96b20b1db18564aff9ab8db00df59" + integrity sha512-nhXxDJ7a5FesRw9UG5SZdP/C/3Q2EzHGnB39hkAV+YGXDMgwxBXFWebQLfEzZzuArfHnvC0sQqkIHNwSKcVjdA== -web3-utils@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.0.7.tgz#7df497b7cdd06cdfe7d02036c45fecbe3382d137" - integrity sha512-sy8S6C2FIa5NNHc8DjND+Fx3S8KDAizuh5RbL1RX3h0PRbFgPfWzF5RfUns8gTt0mjJuOhs/IaDhrZfeTszG5A== +web3-utils@4.3.1-dev.1436228.0, web3-utils@4.3.1-dev.1436228.0+1436228: + version "4.3.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.3.1-dev.1436228.0.tgz#482bb705a54007222eccfe88f5a1203ff3d1d672" + integrity sha512-0knKVcFmAK8sVgjn26/pJIiCV1LrEzHiJNGOzg8DiR/pqFOF+K4lsQK6q+wJYfd5h4qpxwg93qouU8ZKxnC8YQ== dependencies: ethereum-cryptography "^2.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-validator "^2.0.3" + eventemitter3 "^5.0.1" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" -web3-utils@^4.1.0, web3-utils@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.1.1.tgz#88c0fe404abc3f038b7318f3a31141676cefeb63" - integrity sha512-5AOmLKH6QuwHunLCNdVFlPSDE+T88bJYRQP+HWYoKNbI4STALCYQiJvj7LXE+Ed6cPfqANaK/LwKNbMPLCPFwA== +web3-utils@^4.2.3, web3-utils@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.3.0.tgz#c18918f0d692f745d622d22172406f6102528860" + integrity sha512-fGG2IZr0XB1vEoWZiyJzoy28HpsIfZgz4mgPeQA9aj5rIx8z0o80qUPtIyrCYX/Bo2gYALlV5SWIJWxJNUQn9Q== dependencies: ethereum-cryptography "^2.0.0" - web3-errors "^1.1.4" - web3-types "^1.3.1" - web3-validator "^2.0.4" + eventemitter3 "^5.0.1" + web3-errors "^1.2.0" + web3-types "^1.6.0" + web3-validator "^2.0.6" -web3-validator@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.3.tgz#e5dcd4b4902612cff21b7f8817dd233393999d97" - integrity sha512-fJbAQh+9LSNWy+l5Ze6HABreml8fra98o5+vS073T35jUcLbRZ0IOjF/ZPJhJNbJDt+jP1vseZsc3z3uX9mxxQ== +web3-validator@2.0.7-dev.1436228.0+1436228: + version "2.0.7-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.7-dev.1436228.0.tgz#9b4b71501b8731225b9dccd450b080e351830b23" + integrity sha512-qY0dtddh08xbh4owLmelUC1USQtcpvbo8IAjpwo60VrjJ4xIvbgPUNsvd54e2TwiHpTRlwFEi5r3M7WVLLq6ug== dependencies: ethereum-cryptography "^2.0.0" util "^0.12.5" - web3-errors "^1.1.3" - web3-types "^1.3.0" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" zod "^3.21.4" -web3-validator@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.4.tgz#66f34c94f21a3c94d0dc2a2d30deb8a379825d38" - integrity sha512-qRxVePwdW+SByOmTpDZFWHIUAa7PswvxNszrOua6BoGqAhERo5oJZBN+EbWtK/+O+ApNxt5FR3nCPmiZldiOQA== +web3-validator@^2.0.5, web3-validator@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.6.tgz#a0cdaa39e1d1708ece5fae155b034e29d6a19248" + integrity sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg== dependencies: ethereum-cryptography "^2.0.0" util "^0.12.5" - web3-errors "^1.1.4" - web3-types "^1.3.1" + web3-errors "^1.2.0" + web3-types "^1.6.0" zod "^3.21.4" -web3@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/web3/-/web3-4.4.0.tgz#83e5906675608adf9a14841f257e441c9154a8c7" - integrity sha512-WcFA3RgE+cyM96MHLPs5Ec155nkYQuwhjk3JIhxcy6J1QrZK2zvKGzTQCCYHe3JhraduNTMbWy+EeNNGGji9+Q== - dependencies: - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth "^4.4.0" - web3-eth-abi "^4.2.0" - web3-eth-accounts "^4.1.1" - web3-eth-contract "^4.2.0" - web3-eth-ens "^4.0.8" - web3-eth-iban "^4.0.7" - web3-eth-personal "^4.0.8" - web3-net "^4.0.7" - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" +web3@4.10.1-dev.1436228.0: + version "4.10.1-dev.1436228.0" + resolved "https://registry.yarnpkg.com/web3/-/web3-4.10.1-dev.1436228.0.tgz#7e62eaa01c0ea29933a40483c57c04f66d7994a7" + integrity sha512-rn8CMhwTYHiFAccOww//0ZwXagMHZFWxktJvwt1kWBNT4oQ8ggSEi1z2rrkuPC+Oll4hWCPmRPnqcVSwuMnHvA== + dependencies: + web3-core "4.5.1-dev.1436228.0+1436228" + web3-errors "1.2.1-dev.1436228.0+1436228" + web3-eth "4.8.1-dev.1436228.0+1436228" + web3-eth-abi "4.2.3-dev.1436228.0+1436228" + web3-eth-accounts "4.1.3-dev.1436228.0+1436228" + web3-eth-contract "4.5.1-dev.1436228.0+1436228" + web3-eth-ens "4.4.1-dev.1436228.0+1436228" + web3-eth-iban "4.0.8-dev.1436228.0+1436228" + web3-eth-personal "4.0.9-dev.1436228.0+1436228" + web3-net "4.1.1-dev.1436228.0+1436228" + web3-providers-http "4.1.1-dev.1436228.0+1436228" + web3-providers-ws "4.0.8-dev.1436228.0+1436228" + web3-rpc-methods "1.3.1-dev.1436228.0+1436228" + web3-rpc-providers "1.0.0-dev.1436228.0+1436228" + web3-types "1.7.1-dev.1436228.0+1436228" + web3-utils "4.3.1-dev.1436228.0+1436228" + web3-validator "2.0.7-dev.1436228.0+1436228" webidl-conversions@^3.0.0: version "3.0.1" @@ -6131,10 +6157,10 @@ ws@^7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.8.1: - version "8.13.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" - integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== +ws@^8.17.1: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== y18n@^5.0.5: version "5.0.8"