From cbf387556d4f9a913656e95829c4b6e84815de06 Mon Sep 17 00:00:00 2001 From: Danijel Radakovic <129277218+danijelTxFusion@users.noreply.github.com> Date: Sun, 29 Dec 2024 21:17:32 +0100 Subject: [PATCH] feat: provide public actions for fetching token addresses on L1 and L2 chains (#3133) 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 | 29 ++ src/zksync/actions/getL1TokenAddress.ts | 59 +++ src/zksync/actions/getL2TokenAddress.test.ts | 30 ++ src/zksync/actions/getL2TokenAddress.ts | 70 +++ src/zksync/constants/abis.ts | 431 ++++++++++++++++++ src/zksync/decorators/publicL2.test.ts | 53 ++- src/zksync/decorators/publicL2.ts | 62 +++ src/zksync/index.ts | 10 + 12 files changed, 882 insertions(+), 3 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..6da7b2cd8f --- /dev/null +++ b/src/zksync/actions/getL1TokenAddress.test.ts @@ -0,0 +1,29 @@ +import { expect, test } from 'vitest' +import { anvilZksync } from '~test/src/anvil.js' +import { mockAddresses } from '~test/src/zksync.js' +import { publicActionsL2 } from '~viem/zksync/decorators/publicL2.js' +import { type EIP1193RequestFn, padHex } from '../../index.js' +import { legacyEthAddress } from '../constants/address.js' +import { getL1TokenAddress } from './getL1TokenAddress.js' + +const daiL1 = '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55' +const daiL2 = '0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF' + +const client = anvilZksync.getClient().extend(publicActionsL2()) + +client.request = (async ({ method, params }) => { + if (method === 'eth_call') return padHex(daiL1) + if (method === 'eth_estimateGas') return 158774n + if (method === 'zks_getBridgeContracts') return mockAddresses + return anvilZksync.getClient().request({ method, params } as any) +}) as EIP1193RequestFn + +test('default', async () => { + expect(await getL1TokenAddress(client, { token: daiL2 })).toBe(daiL1) +}) + +test('args: legacyEthAddress', async () => { + expect( + await getL1TokenAddress(client, { token: legacyEthAddress }), + ).toBeDefined() +}) 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..efff398690 --- /dev/null +++ b/src/zksync/actions/getL2TokenAddress.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from 'vitest' +import { anvilZksync } from '~test/src/anvil.js' +import { mockAddresses, mockBaseTokenL1Address } from '~test/src/zksync.js' +import { publicActionsL2 } from '~viem/zksync/decorators/publicL2.js' +import { type EIP1193RequestFn, padHex } from '../../index.js' +import { legacyEthAddress } from '../constants/address.js' +import { getL2TokenAddress } from './getL2TokenAddress.js' + +const daiL1 = '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55' +const daiL2 = '0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF' + +const client = anvilZksync.getClient().extend(publicActionsL2()) + +client.request = (async ({ method, params }) => { + if (method === 'eth_call') return padHex(daiL2) + if (method === 'eth_estimateGas') return 158774n + if (method === 'zks_getBridgeContracts') return mockAddresses + if (method === 'zks_getBaseTokenL1Address') return mockBaseTokenL1Address + return anvilZksync.getClient().request({ method, params } as any) +}) as EIP1193RequestFn + +test('default', async () => { + expect(await getL2TokenAddress(client, { token: daiL1 })).toBe(daiL2) +}) + +test('args: legacyEthAddress', async () => { + expect( + await getL2TokenAddress(client, { token: legacyEthAddress }), + ).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..09d1e9f2c9 100644 --- a/src/zksync/decorators/publicL2.test.ts +++ b/src/zksync/decorators/publicL2.test.ts @@ -1,11 +1,12 @@ import { expect, test } from 'vitest' import type { Address } from 'abitype' -import { zksyncLocalNode } from '../../../src/chains/index.js' -import { getZksyncMockProvider } from '../../../test/src/zksync.js' +import { anvilZksync } from '~test/src/anvil.js' import { + getZksyncMockProvider, mockAccountBalances, mockAddress, + mockAddresses, mockBaseTokenL1Address, mockDetails, mockMainContractAddress, @@ -15,7 +16,9 @@ import { mockTransactionDetails, mockedGasEstimation, mockedL1BatchNumber, -} from '../../../test/src/zksync.js' +} from '~test/src/zksync.js' +import type { EIP1193RequestFn } from '~viem/types/eip1193.js' +import { padHex } from '~viem/utils/data/pad.js' import { createPublicClient } from '../../clients/createPublicClient.js' import { custom } from '../../clients/transports/custom.js' import { estimateFee } from '../actions/estimateFee.js' @@ -23,6 +26,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 { zksyncLocalNode } from '../chains.js' import { publicActionsL2 } from './publicL2.js' const mockedZksyncClient = createPublicClient({ @@ -208,3 +212,46 @@ test('getLogProof', async () => { expect(fee).to.deep.equal(mockProofValues) }) + +test('getL2TokenAddress', async () => { + const daiL1 = '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55' + const daiL2 = '0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF' + const client = anvilZksync.getClient() + + client.request = (async ({ method, params }) => { + if (method === 'eth_call') return padHex(daiL2) + if (method === 'eth_estimateGas') return 158774n + if (method === 'zks_getBridgeContracts') return mockAddresses + if (method === 'zks_getBaseTokenL1Address') return mockBaseTokenL1Address + return anvilZksync.getClient().request({ method, params } as any) + }) as EIP1193RequestFn + + const zksyncClient = client.extend(publicActionsL2()) + + expect( + await zksyncClient.getL2TokenAddress({ + token: daiL1, + }), + ).toBeDefined() +}) + +test('getL1TokenAddress', async () => { + const daiL1 = '0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55' + const daiL2 = '0xFC073319977e314F251EAE6ae6bE76B0B3BAeeCF' + const client = anvilZksync.getClient() + + client.request = (async ({ method, params }) => { + if (method === 'eth_call') return padHex(daiL1) + if (method === 'eth_estimateGas') return 158774n + if (method === 'zks_getBridgeContracts') return mockAddresses + return anvilZksync.getClient().request({ method, params } as any) + }) as EIP1193RequestFn + + const zksyncClient = client.extend(publicActionsL2()) + + expect( + await zksyncClient.getL1TokenAddress({ + token: daiL2, + }), + ).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 */