From 2873078ee8b1c064a47265c797cbcc46cd0de104 Mon Sep 17 00:00:00 2001 From: danijelTxFusion Date: Mon, 16 Dec 2024 22:59:55 +0100 Subject: [PATCH] feat(zksync): add `getL1TokenAddress` and `getL2TokenAddress` public actions --- .changeset/honest-apricots-jump.md | 5 + .../pages/zksync/actions/getL1TokenAddress.md | 58 +++ .../pages/zksync/actions/getL2TokenAddress.md | 70 +++ site/sidebar.ts | 8 + src/zksync/actions/getL1TokenAddress.test.ts | 53 +++ src/zksync/actions/getL1TokenAddress.ts | 59 +++ src/zksync/actions/getL2TokenAddress.test.ts | 61 +++ src/zksync/actions/getL2TokenAddress.ts | 70 +++ src/zksync/constants/abis.ts | 431 ++++++++++++++++++ src/zksync/decorators/publicL2.test.ts | 39 +- src/zksync/decorators/publicL2.ts | 62 +++ src/zksync/index.ts | 10 + test/src/zksync.ts | 4 +- 13 files changed, 924 insertions(+), 6 deletions(-) create mode 100644 .changeset/honest-apricots-jump.md create mode 100644 site/pages/zksync/actions/getL1TokenAddress.md create mode 100644 site/pages/zksync/actions/getL2TokenAddress.md create mode 100644 src/zksync/actions/getL1TokenAddress.test.ts create mode 100644 src/zksync/actions/getL1TokenAddress.ts create mode 100644 src/zksync/actions/getL2TokenAddress.test.ts create mode 100644 src/zksync/actions/getL2TokenAddress.ts diff --git a/.changeset/honest-apricots-jump.md b/.changeset/honest-apricots-jump.md new file mode 100644 index 0000000000..5de26fa215 --- /dev/null +++ b/.changeset/honest-apricots-jump.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Added `getL1TokenAddress` and `getL2TokenAddress` public actions in ZKsync extension diff --git a/site/pages/zksync/actions/getL1TokenAddress.md b/site/pages/zksync/actions/getL1TokenAddress.md new file mode 100644 index 0000000000..d27b3c06c6 --- /dev/null +++ b/site/pages/zksync/actions/getL1TokenAddress.md @@ -0,0 +1,58 @@ +--- +description: Returns the L1 token address equivalent for a L2 token address as they are not equal. +--- + +# getL1TokenAddress + +Returns the L1 token address equivalent for a L2 token address as they are not equal. + +:::info + +Only works for tokens bridged on default ZKsync Era bridges. + +::: + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const address = await client.getL1TokenAddress({ + token: '0x3e7676937A7E96CFB7616f255b9AD9FF47363D4b' +}) +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zksync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zksync, + transport: http(), +}).extend(publicActionsL2()) +``` + +::: + +## Returns + +`Address` + +Returns the L1 token address equivalent for a L2 token address. + +## Parameters + +### token + +- **Type:** `Address` + +The address of the token on L2. + +```ts +const address = await client.getL1TokenAddress({ + token: '0x3e7676937A7E96CFB7616f255b9AD9FF47363D4b' +}) +``` diff --git a/site/pages/zksync/actions/getL2TokenAddress.md b/site/pages/zksync/actions/getL2TokenAddress.md new file mode 100644 index 0000000000..90cccd3b41 --- /dev/null +++ b/site/pages/zksync/actions/getL2TokenAddress.md @@ -0,0 +1,70 @@ +--- +description: Returns the L2 token address equivalent for a L1 token address as they are not equal. +--- + +# getL2TokenAddress + +Returns the L2 token address equivalent for a L1 token address as they are not equal. + +:::info +Only works for tokens bridged on default ZKsync Era bridges. +::: + +## Usage + +:::code-group + +```ts [example.ts] +import { client } from './config' + +const address = await client.getL2TokenAddress({ + token: '0x5C221E77624690fff6dd741493D735a17716c26B' +}) +``` + +```ts [config.ts] +import { createPublicClient, http } from 'viem' +import { zksync } from 'viem/chains' +import { publicActionsL2 } from 'viem/zksync' + +export const client = createPublicClient({ + chain: zksync, + transport: http(), +}).extend(publicActionsL2()) +``` + +::: + +## Returns + +`Address` + +Returns the L2 token address equivalent for a L1 token address. + +## Parameters + +### token + +- **Type:** `Address` + +The address of the token on L1. + +```ts +const address = await client.getL2TokenAddress({ + token: '0x5C221E77624690fff6dd741493D735a17716c26B' +}) +``` + +### bridgeAddress (optional) + +- **Type:** `Address` + +The address of custom bridge, which will be used to get l2 token address. + +```ts +const address = await client.getL2TokenAddress({ + token: '0x5C221E77624690fff6dd741493D735a17716c26B', + bridgeAddress: '0xf8c919286126ccf2e8abc362a15158a461429c82' // [!code focus] +}) +``` + diff --git a/site/sidebar.ts b/site/sidebar.ts index 2c1e39f28c..a63a17abcc 100644 --- a/site/sidebar.ts +++ b/site/sidebar.ts @@ -1706,6 +1706,14 @@ export const sidebar = { text: 'getL1ChainId', link: '/zksync/actions/getL1ChainId', }, + { + text: 'getL1TokenAddress', + link: '/zksync/actions/getL1TokenAddress', + }, + { + text: 'getL2TokenAddress', + link: '/zksync/actions/getL2TokenAddress', + }, { text: 'getLogProof', link: '/zksync/actions/getLogProof', diff --git a/src/zksync/actions/getL1TokenAddress.test.ts b/src/zksync/actions/getL1TokenAddress.test.ts new file mode 100644 index 0000000000..c2f3ed5b2e --- /dev/null +++ b/src/zksync/actions/getL1TokenAddress.test.ts @@ -0,0 +1,53 @@ +import { beforeAll, expect, test } from 'vitest' +import { + daiL1, + setupCustomHyperchain, + setupHyperchain, +} from '~test/src/zksync.js' +import { http, createClient } from '../../index.js' +import { + zksyncLocalCustomHyperchain, + zksyncLocalHyperchain, +} from '../chains.js' +import { legacyEthAddress } from '../constants/address.js' +import { getL1TokenAddress } from './getL1TokenAddress.js' +import { getL2TokenAddress } from './getL2TokenAddress.js' + +const client = createClient({ + chain: zksyncLocalHyperchain, + transport: http(), +}) + +const customChainClient = createClient({ + chain: zksyncLocalCustomHyperchain, + transport: http(), +}) + +beforeAll(async () => { + await setupHyperchain() + await setupCustomHyperchain() +}) + +test('ETH: provided token address is L2 ETH address', async () => { + expect(await getL1TokenAddress(client, { token: legacyEthAddress })).toBe( + legacyEthAddress, + ) +}) + +test('ETH: provided token address is L1 DAI address', async () => { + const daiL2 = await getL2TokenAddress(client, { token: daiL1 }) + expect(await getL1TokenAddress(client, { token: daiL2 })).toBe(daiL1) +}) + +test('Custom: provided token address is L2 ETH address', async () => { + expect(await getL1TokenAddress(client, { token: legacyEthAddress })).toBe( + legacyEthAddress, + ) +}) + +test('Custom: provided token address is L1 DAI address', async () => { + const daiL2 = await getL2TokenAddress(customChainClient, { token: daiL1 }) + expect(await getL1TokenAddress(customChainClient, { token: daiL2 })).toBe( + daiL1, + ) +}) diff --git a/src/zksync/actions/getL1TokenAddress.ts b/src/zksync/actions/getL1TokenAddress.ts new file mode 100644 index 0000000000..57d17f6078 --- /dev/null +++ b/src/zksync/actions/getL1TokenAddress.ts @@ -0,0 +1,59 @@ +import type { Address } from '../../accounts/index.js' +import { readContract } from '../../actions/public/readContract.js' +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import { isAddressEqual } from '../../utils/index.js' +import { l2SharedBridgeAbi } from '../constants/abis.js' +import { legacyEthAddress } from '../constants/address.js' +import { getDefaultBridgeAddresses } from './getDefaultBridgeAddresses.js' + +export type GetL1TokenAddressParameters = { + /** The address of the token on L2. */ + token: Address +} + +export type GetL1TokenAddressReturnType = Address + +/** + * Returns the L1 token address equivalent for a L2 token address as they are not equal. + * ETH address is set to zero address. + * + * @remarks Only works for tokens bridged on default ZKsync Era bridges. + * + * @param client - Client to use + * @param parameters - {@link GetL1TokenAddressParameters} + * @returns The L1 token address equivalent for a L2 token address. + * + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zksync } from 'viem/chains' + * + * const client = createPublicClient({ + * chain: zksync, + * transport: http(), + * }) + * + * const address = await getL1TokenAddress(client, {token: '0x...'}); + */ +export async function getL1TokenAddress< + chain extends Chain | undefined, + account extends Account | undefined, +>( + client: Client, + parameters: GetL1TokenAddressParameters, +): Promise
{ + const { token } = parameters + if (isAddressEqual(token, legacyEthAddress)) return legacyEthAddress + + const bridgeAddress = (await getDefaultBridgeAddresses(client)).sharedL2 + + return await readContract(client, { + address: bridgeAddress, + abi: l2SharedBridgeAbi, + functionName: 'l1TokenAddress', + args: [token], + }) +} diff --git a/src/zksync/actions/getL2TokenAddress.test.ts b/src/zksync/actions/getL2TokenAddress.test.ts new file mode 100644 index 0000000000..2ea4064677 --- /dev/null +++ b/src/zksync/actions/getL2TokenAddress.test.ts @@ -0,0 +1,61 @@ +import { beforeAll, expect, test } from 'vitest' +import { + daiL1, + setupCustomHyperchain, + setupHyperchain, +} from '~test/src/zksync.js' +import { http, createClient } from '../../index.js' +import { + zksyncLocalCustomHyperchain, + zksyncLocalHyperchain, +} from '../chains.js' +import { l2BaseTokenAddress, legacyEthAddress } from '../constants/address.js' +import { getBaseTokenL1Address } from './getBaseTokenL1Address.js' +import { getL2TokenAddress } from './getL2TokenAddress.js' + +const client = createClient({ + chain: zksyncLocalHyperchain, + transport: http(), +}) + +const customChainClient = createClient({ + chain: zksyncLocalCustomHyperchain, + transport: http(), +}) + +beforeAll(async () => { + await setupHyperchain() + await setupCustomHyperchain() +}) + +test('ETH: provided token address is L1 base token address', async () => { + const l1BaseToken = await getBaseTokenL1Address(client) + + expect(await getL2TokenAddress(client, { token: l1BaseToken })).toBe( + l2BaseTokenAddress, + ) +}) + +test('ETH: provided token address is L1 ETH address', async () => { + expect( + await getL2TokenAddress(client, { token: legacyEthAddress }), + ).toBeDefined() +}) + +test('ETH: provided token address is L1 DAI address', async () => { + expect(await getL2TokenAddress(client, { token: daiL1 })).toBeDefined() +}) + +test('Custom: provided token address is L1 base token address', async () => { + const l1BaseToken = await getBaseTokenL1Address(customChainClient) + + expect( + await getL2TokenAddress(customChainClient, { token: l1BaseToken }), + ).toBe(l2BaseTokenAddress) +}) + +test('Custom: provided token address is L1 DAI address', async () => { + expect( + await getL2TokenAddress(customChainClient, { token: daiL1 }), + ).toBeDefined() +}) diff --git a/src/zksync/actions/getL2TokenAddress.ts b/src/zksync/actions/getL2TokenAddress.ts new file mode 100644 index 0000000000..c9245edf44 --- /dev/null +++ b/src/zksync/actions/getL2TokenAddress.ts @@ -0,0 +1,70 @@ +import type { Address } from '../../accounts/index.js' +import { readContract } from '../../actions/public/readContract.js' +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Account } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import { isAddressEqual } from '../../utils/index.js' +import { l2SharedBridgeAbi } from '../constants/abis.js' +import { + ethAddressInContracts, + l2BaseTokenAddress, + legacyEthAddress, +} from '../constants/address.js' +import { getBaseTokenL1Address } from './getBaseTokenL1Address.js' +import { getDefaultBridgeAddresses } from './getDefaultBridgeAddresses.js' + +export type GetL2TokenAddressParameters = { + /** The address of the token on L1. */ + token: Address + /** The address of custom bridge, which will be used to get l2 token address. */ + bridgeAddress?: Address | undefined +} + +export type GetL2TokenAddressReturnType = Address + +/** + * 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 client - Client to use + * @param parameters - {@link GetL2TokenAddressParameters} + * @returns The L2 token address equivalent for a L1 token address. + * + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zksync } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zksync, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const address = await getL2TokenAddress(client, {token: '0x...'}); + */ +export async function getL2TokenAddress< + chain extends Chain | undefined, + account extends Account | undefined, +>( + client: Client, + parameters: GetL2TokenAddressParameters, +): Promise
{ + let { token, bridgeAddress } = parameters + if (isAddressEqual(token, legacyEthAddress)) token = ethAddressInContracts + + const baseToken = await getBaseTokenL1Address(client) + if (isAddressEqual(token, baseToken)) return l2BaseTokenAddress + + bridgeAddress ??= (await getDefaultBridgeAddresses(client)).sharedL2 + + return await readContract(client, { + address: bridgeAddress, + abi: l2SharedBridgeAbi, + functionName: 'l2TokenAddress', + args: [token], + }) +} diff --git a/src/zksync/constants/abis.ts b/src/zksync/constants/abis.ts index 18e7ccab73..d11c2e6fc3 100644 --- a/src/zksync/constants/abis.ts +++ b/src/zksync/constants/abis.ts @@ -461,3 +461,434 @@ export const paymasterAbi = [ type: 'function', }, ] + +export const l2SharedBridgeAbi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'l1Sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l2Receiver', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l2Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'FinalizeDeposit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'l2Sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l1Receiver', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'l2Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'WithdrawalInitiated', + type: 'event', + }, + { + inputs: [ + { + internalType: 'address', + name: '_l1Sender', + type: 'address', + }, + { + internalType: 'address', + name: '_l2Receiver', + type: 'address', + }, + { + internalType: 'address', + name: '_l1Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, + ], + name: 'finalizeDeposit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'l1Bridge', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'l1SharedBridge', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_l2Token', + type: 'address', + }, + ], + name: 'l1TokenAddress', + 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: [ + { + internalType: 'address', + name: '_l1Receiver', + type: 'address', + }, + { + internalType: 'address', + name: '_l2Token', + type: 'address', + }, + { + internalType: 'uint256', + name: '_amount', + type: 'uint256', + }, + ], + name: 'withdraw', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const + +export const ethTokenAbi = [ + { + 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/zksync/decorators/publicL2.test.ts b/src/zksync/decorators/publicL2.test.ts index 6469ab92de..73ad6e154b 100644 --- a/src/zksync/decorators/publicL2.test.ts +++ b/src/zksync/decorators/publicL2.test.ts @@ -1,9 +1,9 @@ -import { expect, test } from 'vitest' +import { beforeAll, expect, test } from 'vitest' import type { Address } from 'abitype' -import { zksyncLocalNode } from '../../../src/chains/index.js' -import { getZksyncMockProvider } from '../../../test/src/zksync.js' import { + daiL1, + getZksyncMockProvider, mockAccountBalances, mockAddress, mockBaseTokenL1Address, @@ -15,7 +15,11 @@ import { mockTransactionDetails, mockedGasEstimation, mockedL1BatchNumber, -} from '../../../test/src/zksync.js' + setupCustomHyperchain, + setupHyperchain, +} from '~test/src/zksync.js' +import { http } from '~viem/clients/transports/http.js' +import { legacyEthAddress } from '~viem/zksync/constants/address.js' import { createPublicClient } from '../../clients/createPublicClient.js' import { custom } from '../../clients/transports/custom.js' import { estimateFee } from '../actions/estimateFee.js' @@ -23,6 +27,7 @@ import { estimateGasL1ToL2 } from '../actions/estimateGasL1ToL2.js' import type { GetAllBalancesReturnType } from '../actions/getAllBalances.js' import { getLogProof } from '../actions/getLogProof.js' import { getTransactionDetails } from '../actions/getTransactionDetails.js' +import { zksyncLocalHyperchain, zksyncLocalNode } from '../chains.js' import { publicActionsL2 } from './publicL2.js' const mockedZksyncClient = createPublicClient({ @@ -32,6 +37,16 @@ const mockedZksyncClient = createPublicClient({ chain: zksyncLocalNode, }).extend(publicActionsL2()) +const zksyncClient = createPublicClient({ + chain: zksyncLocalHyperchain, + transport: http(), +}).extend(publicActionsL2()) + +beforeAll(async () => { + await setupHyperchain() + await setupCustomHyperchain() +}) + test('getL1ChainId', async () => { const chainId = await mockedZksyncClient.getL1ChainId() expect(chainId).to.be.equal('0x9') @@ -208,3 +223,19 @@ test('getLogProof', async () => { expect(fee).to.deep.equal(mockProofValues) }) + +test('getL2TokenAddress', async () => { + expect( + await zksyncClient.getL2TokenAddress({ + token: daiL1, + }), + ).toBeDefined() +}) + +test('getL1TokenAddress', async () => { + expect( + await zksyncClient.getL1TokenAddress({ + token: legacyEthAddress, + }), + ).toBeDefined() +}) diff --git a/src/zksync/decorators/publicL2.ts b/src/zksync/decorators/publicL2.ts index a144b61970..65807bd0c2 100644 --- a/src/zksync/decorators/publicL2.ts +++ b/src/zksync/decorators/publicL2.ts @@ -52,6 +52,16 @@ import { type GetL1ChainIdReturnType, getL1ChainId, } from '../actions/getL1ChainId.js' +import { + type GetL1TokenAddressParameters, + type GetL1TokenAddressReturnType, + getL1TokenAddress, +} from '../actions/getL1TokenAddress.js' +import { + type GetL2TokenAddressParameters, + type GetL2TokenAddressReturnType, + getL2TokenAddress, +} from '../actions/getL2TokenAddress.js' import { type GetLogProofParameters, type GetLogProofReturnType, @@ -411,6 +421,56 @@ export type PublicActionsL2< * const address = await client.getBaseTokenL1Address(); */ getBaseTokenL1Address: () => Promise + + /** + * 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 args - {@link GetL2TokenAddressParameters} + * @returns The L2 token address equivalent for a L1 token address. + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zksync } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zksync, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const address = await client.getL2TokenAddress({token: '0x...'}); + */ + getL2TokenAddress: ( + args: GetL2TokenAddressParameters, + ) => Promise + + /** + * Returns the L1 token address equivalent for a L2 token address as they are not equal. + * ETH address is set to zero address. + * + * @remarks Only works for tokens bridged on default ZKsync Era bridges. + * + * @param args - {@link GetL1TokenAddressParameters} + * @returns The L1 token address equivalent for a L2 token address. + * + * @example + * import { createPublicClient, http } from 'viem' + * import { zksync } from 'viem/chains' + * import { publicActionsL2 } from 'viem/zksync' + * + * const client = createPublicClient({ + * chain: zksync, + * transport: http(), + * }).extend(publicActionsL2()) + * + * const address = await client.getL1TokenAddress({token: '0x...'}); + */ + getL1TokenAddress: ( + args: GetL1TokenAddressParameters, + ) => Promise } export function publicActionsL2() { @@ -438,6 +498,8 @@ export function publicActionsL2() { estimateFee: (args) => estimateFee(client, args), getBridgehubContractAddress: () => getBridgehubContractAddress(client), getBaseTokenL1Address: () => getBaseTokenL1Address(client), + getL2TokenAddress: (args) => getL2TokenAddress(client, args), + getL1TokenAddress: (args) => getL1TokenAddress(client, args), } } } diff --git a/src/zksync/index.ts b/src/zksync/index.ts index dfec3a3451..ba86101c47 100644 --- a/src/zksync/index.ts +++ b/src/zksync/index.ts @@ -119,6 +119,16 @@ export { type SignTransactionReturnType, signTransaction, } from './actions/signTransaction.js' +export { + type GetL2TokenAddressReturnType, + type GetL2TokenAddressParameters, + getL2TokenAddress, +} from './actions/getL2TokenAddress.js' +export { + type GetL1TokenAddressReturnType, + type GetL1TokenAddressParameters, + getL1TokenAddress, +} from './actions/getL1TokenAddress.js' export { /** @deprecated Use `zksync` instead */ diff --git a/test/src/zksync.ts b/test/src/zksync.ts index 9d0daec01d..1f929d7af8 100644 --- a/test/src/zksync.ts +++ b/test/src/zksync.ts @@ -270,7 +270,7 @@ const walletClientL1 = createWalletClient({ /* This function prepares the Hyperchain for testing and is intended to be used only once. Subsequent executions do not modify the state of the chain. It is primarily designed to - be run in the beforeAll() function provided by the Vitest testing tool. +be run in the beforeAll() function provided by the Vitest testing tool. Since Vitest can run tests in parallel, this function is designed to handle parallel execution. The first invocation that successfully sends a transaction to the chain @@ -321,7 +321,7 @@ export async function setupHyperchain() { /* This function prepares the Hyperchain for testing and is intended to be used only once. Subsequent executions do not modify the state of the chain. It is primarily designed to - be run in the beforeAll() function provided by the Vitest testing tool. +be run in the beforeAll() function provided by the Vitest testing tool. Since Vitest can run tests in parallel, this function is designed to handle parallel execution. The first invocation that successfully sends a transaction to the chain