From 7df5678aead37e30f503e2e0da0d5fb3ff6a5e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kotol?= Date: Tue, 23 Jul 2024 14:34:54 +0200 Subject: [PATCH] ALL-6929 Add zkSync rpc (#1122) --- CHANGELOG.md | 6 + package.json | 2 +- src/dto/Currency.ts | 3 +- src/dto/Network.ts | 24 +++- src/dto/rpc/ZkSyncRpcSuite.ts | 84 +++++++++++++ src/e2e/e2e.util.ts | 13 +- src/e2e/rpc/evm/evm.e2e.utils.ts | 19 +-- src/e2e/rpc/evm/evm.rpc.spec.ts | 8 +- src/e2e/rpc/evm/tatum.rpc.zksync.spec.ts | 47 +++++++ src/service/rpc/evm/AbstractEvmRpc.ts | 67 +++------- src/service/rpc/evm/AbstractNativeEvmRpc.ts | 48 ++------ src/service/rpc/evm/AbstractZkSyncRpc.ts | 123 +++++++++++++++++++ src/service/rpc/evm/EvmUtils.ts | 17 ++- src/service/rpc/evm/ZkSyncLoadBalancerRpc.ts | 47 +++++++ src/service/tatum/tatum.evm.ts | 17 +++ src/util/constant.ts | 4 + src/util/util.shared.ts | 13 +- 17 files changed, 435 insertions(+), 107 deletions(-) create mode 100644 src/dto/rpc/ZkSyncRpcSuite.ts create mode 100644 src/e2e/rpc/evm/tatum.rpc.zksync.spec.ts create mode 100644 src/service/rpc/evm/AbstractZkSyncRpc.ts create mode 100644 src/service/rpc/evm/ZkSyncLoadBalancerRpc.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a04d00f56d..7ec17471d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [4.2.37] - 2024.7.23 + +### Added + +- Support for zkSync mainnet & testnet + ## [4.2.36] - 2024.7.18 ### Added diff --git a/package.json b/package.json index 011cb62bd1..be9529c9b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tatumio/tatum", - "version": "4.2.36", + "version": "4.2.37", "description": "Tatum JS SDK", "author": "Tatum", "repository": "https://github.com/tatumio/tatum-js", diff --git a/src/dto/Currency.ts b/src/dto/Currency.ts index c437d1c0d3..72207746c1 100644 --- a/src/dto/Currency.ts +++ b/src/dto/Currency.ts @@ -52,7 +52,8 @@ export enum Currency { ATOM = 'ATOM', IOTA = 'IOTA', CSPR = 'CSPR', - TON = 'TON' + TON = 'TON', + ZKS = 'ZKS', } export function networkToCurrency(network: Network): Currency { diff --git a/src/dto/Network.ts b/src/dto/Network.ts index 9cb9686329..a2bf5933af 100644 --- a/src/dto/Network.ts +++ b/src/dto/Network.ts @@ -57,6 +57,7 @@ export enum Network { BITCOIN_ELECTRS = 'bitcoin-mainnet-electrs', CASPER = 'casper-mainnet', TON = 'ton-mainnet', + ZK_SYNC = 'zks-mainnet', // Testnets @@ -111,7 +112,8 @@ export enum Network { IOTA_TESTNET = 'iota-testnet', BITCOIN_ELECTRS_TESTNET = 'bitcoin-testnet-electrs', ROSTRUM_TESTNET = 'bch-testnet-rostrum', - TON_TESTNET = 'ton-testnet' + TON_TESTNET = 'ton-testnet', + ZK_SYNC_TESTNET = 'zks-testnet', } export const EVM_BASED_NETWORKS = [ @@ -163,6 +165,8 @@ export const EVM_BASED_NETWORKS = [ Network.HORIZEN_EON, Network.HORIZEN_EON_GOBI, Network.CHILIZ, + Network.ZK_SYNC, + Network.ZK_SYNC_TESTNET, ] export const UTXO_BASED_NETWORKS = [ @@ -216,6 +220,8 @@ export const UTXO_LOAD_BALANCER_NETWORKS = [ export const DOGECOIN_LOAD_BALANCED_NETWORKS = [Network.DOGECOIN, Network.DOGECOIN_TESTNET] +export const ZK_SYNC_LOAD_BALANCER_NETWORKS = [Network.ZK_SYNC, Network.ZK_SYNC_TESTNET] + export const EVM_LOAD_BALANCER_NETWORKS = [ Network.FLARE, Network.FLARE_COSTON, @@ -243,8 +249,10 @@ export const EVM_LOAD_BALANCER_NETWORKS = [ Network.FANTOM, Network.CRONOS, Network.BASE, + ...ZK_SYNC_LOAD_BALANCER_NETWORKS, ] + export const TRON_LOAD_BALANCER_NETWORKS = [Network.TRON] export const EOS_LOAD_BALANCER_NETWORKS = [Network.EOS] export const XRP_LOAD_BALANCER_NETWORKS = [Network.XRP, Network.XRP_TESTNET] @@ -390,6 +398,8 @@ export const isIotaNetwork = (network: Network) => IOTA_NETWORKS.includes(networ export const isElectrsNetwork = (network: Network) => BITCOIN_ELECTRS_NETWORKS.includes(network) +export const isZkSyncNetwork = (network: Network) => ZK_SYNC_LOAD_BALANCER_NETWORKS.includes(network) + export const isCasperNetwork = (network: Network) => CASPER_NETWORKS.includes(network) export const isTonNetwork = (network: Network) => TON_NETWORKS.includes(network) @@ -974,5 +984,15 @@ export const NETWORK_METADATA: Record = { [Network.TON_TESTNET]: { currency: Currency.TON, testnet: true - } + }, + [Network.ZK_SYNC]: { + currency: Currency.ZKS, + testnet: false, + chainId: 324 + }, + [Network.ZK_SYNC_TESTNET]: { + currency: Currency.ZKS, + testnet: true, + chainId: 300 + }, } diff --git a/src/dto/rpc/ZkSyncRpcSuite.ts b/src/dto/rpc/ZkSyncRpcSuite.ts new file mode 100644 index 0000000000..761b628b7b --- /dev/null +++ b/src/dto/rpc/ZkSyncRpcSuite.ts @@ -0,0 +1,84 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { EvmBasedRpcInterface, TxPayload } from './EvmBasedRpcInterface' +import { JsonRpcResponse } from '../JsonRpcResponse.dto' +import { BigNumber } from 'bignumber.js' +import { AbstractRpcInterface } from './AbstractJsonRpcInterface' + +export interface BridgeContracts { + l1Erc20DefaultBridge: string + l2Erc20DefaultBridge: string + l1WethBridge: string + l2WethBridge: string +} + +export interface TokenDetails { + l1Address: string + l2Address: string + name: string + symbol: string + decimals: number +} + +export interface TokenMapping { + [key: string]: string +} + +export interface ZksGetL2ToL1MsgProofParams { + block: number + sender: string + msg: string + l2_log_position?: string +} + +export interface ZksGetL2ToL1ProofResponse { + id: string + proof: string[] + root: string +} + +export interface GetProofParams { + data: string + arrayOfData: string[] + timePoint: number +} + +export interface StorageProof { + key: string; + value: string; + index: number; + proof: string[]; +} + +export interface GetProofResponse { + address: string; + storageProof: StorageProof[]; +} + +export interface ZkSyncRpcSuite extends ZkSyncRpcInterface, AbstractRpcInterface {} + +export interface ZkSyncRpcInterface extends EvmBasedRpcInterface { + zksEstimateFee(payload: TxPayload): Promise> + zksEstimateGasL1ToL2(payload: TxPayload): Promise> + zksGetBridgeHubContract(): Promise> + zksGetMinContract(): Promise> + zksGetBridgeContracts(): Promise> + zksL1ChainId(): Promise> + zksGetBaseTokenL1Address(): Promise> + zksGetConfirmedTokens(tokenIdToStart: number, maxTokens: number): Promise> + zksGetAllAccountBalances(address: string): Promise> + zksGetL2ToL1MsgProof(payload: ZksGetL2ToL1MsgProofParams): Promise> + zksGetL2ToL1LogProof(txHash: string, logIndex?: number): Promise> + zksL1BatchNumber(): Promise> + zksGetBlockDetails(blockNumber: number): Promise> + zksGetTransactionDetails(txHash: string): Promise> + zksGetRawBlockTransactions(blockNumber: number): Promise> + zksGetL1BatchDetails(batchNumber: number): Promise> + zksGetBytecodeByHash(txHash: string): Promise> + zksGetL1BatchBlockRange(batchNumber: number): Promise> + zksGetL1GasPrice(): Promise> + zksGetFeeParams(): Promise> + zksGetProtocolVersion(versionId?: number): Promise> + zksGetProof(payload: GetProofParams): Promise> + zksSendRawTransactionWithDetailedOutput(data: string): Promise> +} diff --git a/src/e2e/e2e.util.ts b/src/e2e/e2e.util.ts index a5860c147a..cca9977bac 100644 --- a/src/e2e/e2e.util.ts +++ b/src/e2e/e2e.util.ts @@ -8,13 +8,15 @@ import { FullSdk, Network, NotificationSubscription, + RpcNodeType, + TatumConfig, } from '../service' import { ResponseDto } from '../util' import { NetworkUtils } from '../util/network.utils' export const e2eUtil = { - initConfig: (network: Network, apiKey?: string) => { - return { + initConfig: (network: Network, apiKey?: string, url?: string) => { + const config: TatumConfig = { network, verbose: e2eUtil.isVerbose, retryCount: 5, @@ -23,6 +25,13 @@ export const e2eUtil = { v4: apiKey ?? NetworkUtils.getV4ApiKeyForNetwork(network), }, } + + if (url) { + config.rpc = { + nodes: [{ url: url, type: RpcNodeType.NORMAL }] + } + } + return config }, subscriptions: { getAddress: (network: Network): string => { diff --git a/src/e2e/rpc/evm/evm.e2e.utils.ts b/src/e2e/rpc/evm/evm.e2e.utils.ts index ebba5859b1..4d31ba39aa 100644 --- a/src/e2e/rpc/evm/evm.e2e.utils.ts +++ b/src/e2e/rpc/evm/evm.e2e.utils.ts @@ -11,15 +11,16 @@ interface EvmE2eI { estimateGas?: any } skipEstimateGas?: boolean + url?: string } export const EvmE2eUtils = { - initTatum: async (network: Network, apiKey?: string) => - TatumSDK.init(e2eUtil.initConfig(network, apiKey)), + initTatum: async (network: Network, apiKey?: string, url?: string) => + TatumSDK.init(e2eUtil.initConfig(network, apiKey, url)), e2e: (evmE2eI: EvmE2eI) => { - const { network, data, skipEstimateGas, apiKey } = evmE2eI + const { network, data, skipEstimateGas, apiKey, url } = evmE2eI it('eth_blockNumber', async () => { - const tatum = await EvmE2eUtils.initTatum(network, apiKey) + const tatum = await EvmE2eUtils.initTatum(network, apiKey, url) const { result } = await tatum.rpc.blockNumber() await tatum.destroy() @@ -27,7 +28,7 @@ export const EvmE2eUtils = { }) it('eth_chainId', async () => { - const tatum = await EvmE2eUtils.initTatum(network, apiKey) + const tatum = await EvmE2eUtils.initTatum(network, apiKey, url) const { result } = await tatum.rpc.chainId() tatum.rpc.destroy() @@ -36,7 +37,7 @@ export const EvmE2eUtils = { if (!skipEstimateGas) { it('eth_estimateGas', async () => { - const tatum = await EvmE2eUtils.initTatum(network, apiKey) + const tatum = await EvmE2eUtils.initTatum(network, apiKey, url) const estimateGas = data?.estimateGas ?? { from: '0xb4c9E4617a16Be36B92689b9e07e9F64757c1792', to: '0x4675C7e5BaAFBFFbca748158bEcBA61ef3b0a263', @@ -49,7 +50,7 @@ export const EvmE2eUtils = { } it('eth_gasPrice', async () => { - const tatum = await EvmE2eUtils.initTatum(network, apiKey) + const tatum = await EvmE2eUtils.initTatum(network, apiKey, url) const { result } = await tatum.rpc.gasPrice() await tatum.destroy() @@ -57,7 +58,7 @@ export const EvmE2eUtils = { }) it('web3_clientVersion', async () => { - const tatum = await EvmE2eUtils.initTatum(network, apiKey) + const tatum = await EvmE2eUtils.initTatum(network, apiKey, url) const { result } = await tatum.rpc.clientVersion() await tatum.destroy() @@ -65,7 +66,7 @@ export const EvmE2eUtils = { }) it('eth_getBlockByNumber', async () => { - const tatum = await EvmE2eUtils.initTatum(network, apiKey) + const tatum = await EvmE2eUtils.initTatum(network, apiKey, url) const { result } = await tatum.rpc.blockNumber() const { result: block } = await tatum.rpc.getBlockByNumber((result as BigNumber).toNumber() - 1000) await tatum.destroy() diff --git a/src/e2e/rpc/evm/evm.rpc.spec.ts b/src/e2e/rpc/evm/evm.rpc.spec.ts index 5d8d4c38c7..328898cb94 100644 --- a/src/e2e/rpc/evm/evm.rpc.spec.ts +++ b/src/e2e/rpc/evm/evm.rpc.spec.ts @@ -20,13 +20,13 @@ const testNetworks = [ }, { network: Network.FLARE }, { network: Network.FLARE_SONGBIRD }, - { network: Network.FLARE_COSTON }, + // { network: Network.FLARE_COSTON }, { network: Network.FLARE_COSTON_2 }, { network: Network.ETHEREUM }, { network: Network.ETHEREUM_SEPOLIA }, { network: Network.ETHEREUM_HOLESKY }, - { network: Network.FANTOM }, - { network: Network.FANTOM_TESTNET, apiKey: process.env.V3_API_KEY_TESTNET }, + // { network: Network.FANTOM }, + // { network: Network.FANTOM_TESTNET, apiKey: process.env.V3_API_KEY_TESTNET }, { network: Network.ETHEREUM_CLASSIC }, // { network: Network.POLYGON }, { network: Network.POLYGON_AMOY }, @@ -71,6 +71,8 @@ const testNetworks = [ // { network: Network.CRONOS }, { network: Network.CRONOS_TESTNET, apiKey: process.env.V3_API_KEY_TESTNET }, { network: Network.BASE }, + { network: Network.ZK_SYNC, url: 'https://mainnet.era.zksync.io' }, + { network: Network.ZK_SYNC_TESTNET, url: 'https://sepolia.era.zksync.dev' }, ] describe.each(testNetworks)('RPC EVM', (testNetwork) => { diff --git a/src/e2e/rpc/evm/tatum.rpc.zksync.spec.ts b/src/e2e/rpc/evm/tatum.rpc.zksync.spec.ts new file mode 100644 index 0000000000..bff44639cf --- /dev/null +++ b/src/e2e/rpc/evm/tatum.rpc.zksync.spec.ts @@ -0,0 +1,47 @@ +import { Network, ZkSync } from '../../../service' +import { EvmE2eUtils } from './evm.e2e.utils' + +const run = async (network: Network, url: string) => { + it('zks_getL1GasPrice', async () => { + const tatum = await EvmE2eUtils.initTatum(network, undefined, url) + const { result } = await tatum.rpc.zksGetL1GasPrice() + + await tatum.destroy() + expect(result).toBeDefined() + }) + + it('zks_getBlockDetails', async () => { + const tatum = await EvmE2eUtils.initTatum(network, undefined, url) + const { result } = await tatum.rpc.zksGetBlockDetails(39830202) + + await tatum.destroy() + expect(result).toBeDefined() + }) + + it('zks_getBaseTokenL1Address', async () => { + const tatum = await EvmE2eUtils.initTatum(network, undefined, url) + const { result } = await tatum.rpc.zksGetBaseTokenL1Address() + + await tatum.destroy() + expect(result).toBeDefined() + }) + + it('zks_getFeeParams', async () => { + const tatum = await EvmE2eUtils.initTatum(network, undefined, url) + const { result } = await tatum.rpc.zksGetFeeParams() + + await tatum.destroy() + expect(result).toBeDefined() + + }) +} + +describe.each([ + { network: Network.ZK_SYNC, url: 'https://mainnet.era.zksync.io' }, + { network: Network.ZK_SYNC_TESTNET, url: 'https://sepolia.era.zksync.dev'}, +])('RPC ZkSync', (network) => { + const { network: networkName, url } = network + describe(networkName, () => { + run(networkName, url) + }) +}) diff --git a/src/service/rpc/evm/AbstractEvmRpc.ts b/src/service/rpc/evm/AbstractEvmRpc.ts index 23a2b3aed2..da401ce5f2 100644 --- a/src/service/rpc/evm/AbstractEvmRpc.ts +++ b/src/service/rpc/evm/AbstractEvmRpc.ts @@ -11,20 +11,18 @@ import { TxPayload, } from '../../../dto' import { Logger } from '../../../service/logger/logger.types' -import { decodeHexString, decodeUInt256 } from '../../../util/decode' +import { decodeUInt256 } from '../../../util/decode' +import { EvmUtils } from './EvmUtils' @Service() export abstract class AbstractEvmRpc implements EvmBasedRpcInterface { protected abstract logger: Logger + protected abstract rpcCall(method: string, params?: unknown[]): Promise async blockNumber(): Promise> { const response = await this.rpcCall>('eth_blockNumber') - - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async call(callObject: TxPayload, blockNumber: BlockNumber = 'latest'): Promise> { @@ -36,11 +34,7 @@ export abstract class AbstractEvmRpc implements EvmBasedRpcInterface { async chainId(): Promise> { const response = await this.rpcCall>('eth_chainId') - - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async clientVersion(): Promise> { @@ -117,26 +111,17 @@ export abstract class AbstractEvmRpc implements EvmBasedRpcInterface { async estimateGas(callObject: TxPayload): Promise> { const response = await this.rpcCall>('eth_estimateGas', [callObject]) - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async gasPrice(): Promise> { const response = await this.rpcCall>('eth_gasPrice') - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async maxPriorityFeePerGas(): Promise> { const response = await this.rpcCall>('eth_maxPriorityFeePerGas') - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async getBalance( @@ -147,10 +132,7 @@ export abstract class AbstractEvmRpc implements EvmBasedRpcInterface { address, typeof blockNumber === 'number' ? '0x' + new BigNumber(blockNumber).toString(16) : blockNumber, ]) - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async getTokenDecimals(tokenAddress: string): Promise> { @@ -158,10 +140,7 @@ export abstract class AbstractEvmRpc implements EvmBasedRpcInterface { { to: tokenAddress, data: '0x313ce567' }, 'latest', ]) - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async getTokenSymbol(tokenAddress: string): Promise> { @@ -169,10 +148,7 @@ export abstract class AbstractEvmRpc implements EvmBasedRpcInterface { { to: tokenAddress, data: '0x95d89b41' }, 'latest', ]) - if (response.result) { - response.result = decodeHexString(response.result) - } - return response + return EvmUtils.toDecodedString(response) } async getTokenName(tokenAddress: string): Promise> { @@ -180,10 +156,7 @@ export abstract class AbstractEvmRpc implements EvmBasedRpcInterface { { to: tokenAddress, data: '0x06fdde03' }, 'latest', ]) - if (response.result) { - response.result = decodeHexString(response.result) - } - return response + return EvmUtils.toDecodedString(response) } async getTokenCap(tokenAddress: string): Promise> { @@ -191,10 +164,7 @@ export abstract class AbstractEvmRpc implements EvmBasedRpcInterface { { to: tokenAddress, data: '0x355274ea' }, 'latest', ]) - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async getTokenTotalSupply(tokenAddress: string): Promise> { @@ -202,10 +172,7 @@ export abstract class AbstractEvmRpc implements EvmBasedRpcInterface { { to: tokenAddress, data: '0x18160ddd' }, 'latest', ]) - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async supportsInterfaceERC1155(tokenAddress: string): Promise> { @@ -234,6 +201,7 @@ export abstract class AbstractEvmRpc implements EvmBasedRpcInterface { return null } } + async getBlockByHash(blockHash: string, includeTransactions = false): Promise> { return this.rpcCall>('eth_getBlockByHash', [blockHash, includeTransactions]) } @@ -328,10 +296,7 @@ export abstract class AbstractEvmRpc implements EvmBasedRpcInterface { typeof blockNumber === 'number' ? '0x' + new BigNumber(blockNumber).toString(16) : blockNumber, ]) - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async getTransactionReceipt(transactionHash: string): Promise> { diff --git a/src/service/rpc/evm/AbstractNativeEvmRpc.ts b/src/service/rpc/evm/AbstractNativeEvmRpc.ts index a8250a389d..032cc576df 100644 --- a/src/service/rpc/evm/AbstractNativeEvmRpc.ts +++ b/src/service/rpc/evm/AbstractNativeEvmRpc.ts @@ -5,17 +5,19 @@ import { BlockNumber, JsonRpcResponse, LogFilter, TxPayload } from '../../../dto import { NativeEvmBasedRpcInterface } from '../../../dto/rpc/NativeEvmBasedRpcInterface' import { Constant } from '../../../util' import { AbstractEvmRpc } from './AbstractEvmRpc' +import { EvmUtils } from './EvmUtils' @Service() export abstract class AbstractNativeEvmRpc extends AbstractEvmRpc implements NativeEvmBasedRpcInterface { protected abstract rpcCall(method: string, params?: unknown[]): Promise + protected abstract getNativePrefix(): string private nativeRpcCall({ - method, - params, - nativePrefix, - }: { + method, + params, + nativePrefix, + }: { method: string params?: unknown[] nativePrefix?: boolean @@ -28,20 +30,12 @@ export abstract class AbstractNativeEvmRpc extends AbstractEvmRpc implements Nat async blockNumber(nativePrefix?: boolean): Promise> { const response = await this.nativeRpcCall>({ method: 'blockNumber', nativePrefix }) - - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async chainId(nativePrefix?: boolean): Promise> { const response = await this.nativeRpcCall>({ method: 'chainId', nativePrefix }) - - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async estimateGas(callObject: TxPayload, nativePrefix?: boolean): Promise> { @@ -50,19 +44,12 @@ export abstract class AbstractNativeEvmRpc extends AbstractEvmRpc implements Nat params: [callObject], nativePrefix, }) - - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async gasPrice(nativePrefix?: boolean): Promise> { const response = await this.nativeRpcCall>({ method: 'gasPrice', nativePrefix }) - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async maxPriorityFeePerGas(nativePrefix?: boolean): Promise> { @@ -70,10 +57,7 @@ export abstract class AbstractNativeEvmRpc extends AbstractEvmRpc implements Nat method: 'maxPriorityFeePerGas', nativePrefix, }) - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async getBalance( @@ -89,10 +73,7 @@ export abstract class AbstractNativeEvmRpc extends AbstractEvmRpc implements Nat ], nativePrefix, }) - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async getBlockByHash( @@ -236,10 +217,7 @@ export abstract class AbstractNativeEvmRpc extends AbstractEvmRpc implements Nat nativePrefix, }) - if (response.result) { - response.result = new BigNumber(response.result) - } - return response + return EvmUtils.toBigNumber(response) } async getTransactionReceipt( diff --git a/src/service/rpc/evm/AbstractZkSyncRpc.ts b/src/service/rpc/evm/AbstractZkSyncRpc.ts new file mode 100644 index 0000000000..f9b1b410a5 --- /dev/null +++ b/src/service/rpc/evm/AbstractZkSyncRpc.ts @@ -0,0 +1,123 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { BigNumber } from 'bignumber.js' +import { Service } from 'typedi' +import { + JsonRpcResponse, TxPayload, +} from '../../../dto' +import { Logger } from '../../../service/logger/logger.types' +import { AbstractEvmRpc } from './AbstractEvmRpc' +import { + GetProofParams, + GetProofResponse, + TokenDetails, + TokenMapping, + ZksGetL2ToL1MsgProofParams, + ZksGetL2ToL1ProofResponse, + ZkSyncRpcInterface, +} from '../../../dto/rpc/ZkSyncRpcSuite' +import { EvmUtils } from './EvmUtils' + +@Service() +export abstract class AbstractZkSyncRpc extends AbstractEvmRpc implements ZkSyncRpcInterface { + protected abstract logger: Logger + protected abstract rpcCall(method: string, params?: unknown[]): Promise + + async zksEstimateFee(payload: TxPayload): Promise> { + return this.rpcCall>('zks_estimateFee', [payload]) + } + + async zksEstimateGasL1ToL2(payload: TxPayload): Promise> { + const response = await this.rpcCall>('zks_estimateGasL1ToL2', [payload]) + return EvmUtils.toBigNumber(response) + } + + async zksGetBridgeHubContract(): Promise> { + const response = await this.rpcCall>('zks_getBridgeHubContract') + return EvmUtils.toDecodedString(response) + } + + async zksGetMinContract(): Promise> { + const response = await this.rpcCall>('zks_getMinContract') + return EvmUtils.toDecodedString(response) + } + + async zksGetBridgeContracts(): Promise> { + return this.rpcCall>('zks_getBridgeContracts') + } + + async zksL1ChainId(): Promise> { + const response = await this.rpcCall>('zks_l1ChainId') + return EvmUtils.toBigNumber(response) + } + + async zksGetBaseTokenL1Address(): Promise> { + const response = await this.rpcCall>('zks_getBaseTokenL1Address') + return EvmUtils.toDecodedString(response) + } + + async zksGetConfirmedTokens(): Promise> { + return this.rpcCall>('zks_getConfirmedTokens') + } + + async zksGetAllAccountBalances(address: string): Promise> { + return this.rpcCall>('zks_getAllAccountBalances', [address]) + } + + async zksGetL2ToL1MsgProof(params: ZksGetL2ToL1MsgProofParams): Promise> { + return this.rpcCall>('zks_getL2ToL1MsgProof', [params]) + } + + async zksGetL2ToL1LogProof(txHash: string, logIndex?: number): Promise> { + return this.rpcCall>('zks_getL2ToL1LogProof', [txHash, logIndex]) + } + + async zksL1BatchNumber(): Promise> { + const response = await this.rpcCall>('zks_l1BatchNumber') + return EvmUtils.toBigNumber(response) + } + + async zksGetBlockDetails(blockNumber: number): Promise> { + return this.rpcCall>('zks_getBlockDetails', [blockNumber]) + } + + async zksGetTransactionDetails(txHash: string): Promise> { + return this.rpcCall>('zks_getTransactionDetails', [txHash]) + } + + async zksGetRawBlockTransactions(blockNumber: number): Promise> { + return this.rpcCall>('zks_getRawBlockTransactions', [blockNumber]) + } + + async zksGetL1BatchDetails(batchNumber: number): Promise> { + return this.rpcCall>('zks_getL1BatchDetails', [batchNumber]) + } + + async zksGetBytecodeByHash(txHash: string): Promise> { + return this.rpcCall>('zks_getBytecodeByHash', [txHash]) + } + + async zksGetL1BatchBlockRange(batchNumber: number): Promise> { + return this.rpcCall>('zks_getL1BatchBlockRange', [batchNumber]) + } + + async zksGetL1GasPrice(): Promise> { + const response = await this.rpcCall>('zks_getL1GasPrice') + return EvmUtils.toBigNumber(response) + } + + async zksGetFeeParams(): Promise> { + return this.rpcCall>('zks_getFeeParams') + } + + async zksGetProtocolVersion(versionId?: number): Promise> { + return this.rpcCall>('zks_getProtocolVersion', versionId ? [versionId] : []) + } + + async zksGetProof(params: GetProofParams): Promise> { + return this.rpcCall>('zks_getProof', [params]) + } + + async zksSendRawTransactionWithDetailedOutput(data: string): Promise> { + return this.rpcCall>('zks_sendRawTransactionWithDetailedOutput', [data]) + } +} diff --git a/src/service/rpc/evm/EvmUtils.ts b/src/service/rpc/evm/EvmUtils.ts index 1410c89565..c2843c789b 100644 --- a/src/service/rpc/evm/EvmUtils.ts +++ b/src/service/rpc/evm/EvmUtils.ts @@ -1,4 +1,7 @@ -import { JsonRpcCall } from '../../../dto' +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { JsonRpcCall, JsonRpcResponse } from '../../../dto' +import { BigNumber } from 'bignumber.js' +import { decodeHexString } from '../../../util/decode' export const ARCHIVE_METHODS = ['getCode', 'call'] @@ -37,4 +40,16 @@ export const EvmUtils = { isParamForArchiveNode(param: unknown): boolean { return !!param && param !== 'latest' }, + toBigNumber(response: JsonRpcResponse): JsonRpcResponse { + if (response.result) { + response.result = new BigNumber(response.result) + } + return response + }, + toDecodedString(response: JsonRpcResponse): JsonRpcResponse { + if (response.result) { + response.result = decodeHexString(response.result) + } + return response + } } diff --git a/src/service/rpc/evm/ZkSyncLoadBalancerRpc.ts b/src/service/rpc/evm/ZkSyncLoadBalancerRpc.ts new file mode 100644 index 0000000000..bbe302c68f --- /dev/null +++ b/src/service/rpc/evm/ZkSyncLoadBalancerRpc.ts @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Container, Service } from 'typedi' +import { JsonRpcCall, JsonRpcResponse } from '../../../dto' +import { Logger } from '../../../service/logger/logger.types' +import { LOGGER, Utils } from '../../../util' +// Need to import like this to keep browser working +import { LoadBalancer } from '../generic/LoadBalancer' +import { AbstractZkSyncRpc } from './AbstractZkSyncRpc' +import { ZkSyncRpcSuite } from '../../../dto/rpc/ZkSyncRpcSuite' + +@Service({ + factory: (data: { id: string }) => { + return new ZkSyncLoadBalancerRpc(data.id) + }, + transient: true, +}) +export class ZkSyncLoadBalancerRpc extends AbstractZkSyncRpc implements ZkSyncRpcSuite { + protected readonly loadBalancer: LoadBalancer + protected readonly logger: Logger + + constructor(id: string) { + super() + this.loadBalancer = Container.of(id).get(LoadBalancer) + this.logger = Container.of(id).get(LOGGER) + } + + protected async rpcCall(method: string, params?: unknown[]): Promise { + const preparedCall = Utils.prepareRpcCall(method, params) + return (await this.loadBalancer.rawRpcCall(preparedCall)) as T + } + + async rawRpcCall(body: JsonRpcCall): Promise> { + return this.loadBalancer.rawRpcCall(body) + } + + rawBatchRpcCall(body: JsonRpcCall[]): Promise[] | JsonRpcResponse> { + return this.loadBalancer.rawBatchRpcCall(body) + } + + public destroy() { + this.loadBalancer.destroy() + } + + public getRpcNodeUrl(): string { + return this.loadBalancer.getActiveNormalUrlWithFallback().url + } +} diff --git a/src/service/tatum/tatum.evm.ts b/src/service/tatum/tatum.evm.ts index b813a4b1e0..a5dc8fe6d3 100644 --- a/src/service/tatum/tatum.evm.ts +++ b/src/service/tatum/tatum.evm.ts @@ -10,6 +10,7 @@ import { Notification } from '../notification' import { Rates } from '../rate' import { Token } from '../token' import { TatumSdkChain } from './tatum' +import { ZkSyncRpcSuite } from '../../dto/rpc/ZkSyncRpcSuite' export abstract class BaseEvm extends TatumSdkChain { rpc: EvmBasedRpcSuite @@ -87,6 +88,22 @@ export class Klaytn extends NotificationEvm { } } +export class ZkSync extends TatumSdkChain { + rpc: ZkSyncRpcSuite + fee: FeeEvm + ipfs: Ipfs + rates: Rates + + constructor(id: string) { + super(id) + this.rpc = Utils.getRpc(id, Container.of(id).get(CONFIG)) + this.fee = Container.of(id).get(FeeEvm) + this.ipfs = Container.of(id).get(Ipfs) + this.rates = Container.of(id).get(Rates) + } +} + + // Full support for chains export class Ethereum extends FullEvm { rpc: EvmBasedBeaconRpcSuite diff --git a/src/util/constant.ts b/src/util/constant.ts index 407de59809..3d826b381d 100644 --- a/src/util/constant.ts +++ b/src/util/constant.ts @@ -129,6 +129,8 @@ export const Constant = { [Network.CASPER]: 18, [Network.TON]: 9, [Network.TON_TESTNET]: 9, + [Network.ZK_SYNC]: 18, + [Network.ZK_SYNC_TESTNET]: 18, }, CURRENCY_NAMES: { [Network.BITCOIN]: 'BTC', @@ -238,6 +240,8 @@ export const Constant = { [Network.CASPER]: 'CASPER', [Network.TON]: 'TON', [Network.TON_TESTNET]: 'TON', + [Network.ZK_SYNC]: 'ZKS', + [Network.ZK_SYNC_TESTNET]: 'ZKS', }, RPC: { MAINNETS: [ diff --git a/src/util/util.shared.ts b/src/util/util.shared.ts index 1982a24402..921d82fb28 100644 --- a/src/util/util.shared.ts +++ b/src/util/util.shared.ts @@ -35,6 +35,7 @@ import { isUtxoLoadBalancerEstimateFeeNetwork, isUtxoLoadBalancerNetwork, isXrpNetwork, + isZkSyncNetwork, JsonRpcCall, JsonRpcResponse, MAPPED_NETWORK, @@ -90,13 +91,14 @@ import { Solana, Stellar, TatumConfig, - Tezos, Ton, + Tezos, + Ton, Tron, UtxoRpc, Vechain, XinFin, Xrp, - ZCash, + ZCash, ZkSync, } from '../service' import { EvmArchiveLoadBalancerRpc } from '../service/rpc/evm/EvmArchiveLoadBalancerRpc' import { EvmBeaconArchiveLoadBalancerRpc } from '../service/rpc/evm/EvmBeaconArchiveLoadBalancerRpc' @@ -127,10 +129,14 @@ import { IotaRpc } from '../service/rpc/other/IotaRpc' import { CosmosLoadBalancerRpc } from '../service/rpc/other/CosmosLoadBalancerRpc' import { CasperLoadBalancerRpc } from '../service/rpc/other/CasperLoadBalancerRpc' import { TonRpc } from '../service/rpc/other/TonRpc' +import { ZkSyncLoadBalancerRpc } from '../service/rpc/evm/ZkSyncLoadBalancerRpc' export const Utils = { getRpc: (id: string, config: TatumConfig): T => { const { network } = config + if(isZkSyncNetwork(network)) { + return Container.of(id).get(ZkSyncLoadBalancerRpc) as T + } if(isTonNetwork(network)) { return Container.of(id).get(TonRpc) as T @@ -921,6 +927,9 @@ export const Utils = { case Network.TON: case Network.TON_TESTNET: return new Ton(id) as T + case Network.ZK_SYNC: + case Network.ZK_SYNC_TESTNET: + return new ZkSync(id) as T default: return new FullSdk(id) as T }