Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZKsync: provide public actions for fetching token addresses on L1 and L2 chains #3133

Merged
merged 1 commit into from
Dec 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/honest-apricots-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `getL1TokenAddress` and `getL2TokenAddress` public actions in ZKsync extension
58 changes: 58 additions & 0 deletions site/pages/zksync/actions/getL1TokenAddress.md
Original file line number Diff line number Diff line change
@@ -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'
})
```
70 changes: 70 additions & 0 deletions site/pages/zksync/actions/getL2TokenAddress.md
Original file line number Diff line number Diff line change
@@ -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]
})
```

8 changes: 8 additions & 0 deletions site/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
29 changes: 29 additions & 0 deletions src/zksync/actions/getL1TokenAddress.test.ts
Original file line number Diff line number Diff line change
@@ -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()
})
59 changes: 59 additions & 0 deletions src/zksync/actions/getL1TokenAddress.ts
Original file line number Diff line number Diff line change
@@ -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<Transport, chain, account>,
parameters: GetL1TokenAddressParameters,
): Promise<Address> {
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],
})
}
30 changes: 30 additions & 0 deletions src/zksync/actions/getL2TokenAddress.test.ts
Original file line number Diff line number Diff line change
@@ -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()
})
70 changes: 70 additions & 0 deletions src/zksync/actions/getL2TokenAddress.ts
Original file line number Diff line number Diff line change
@@ -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<Transport, chain, account>,
parameters: GetL2TokenAddressParameters,
): Promise<Address> {
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],
})
}
Loading
Loading