Skip to content

Commit

Permalink
feat(zksync): add getL1TokenAddress and getL2TokenAddress public …
Browse files Browse the repository at this point in the history
…actions
  • Loading branch information
danijelTxFusion committed Dec 22, 2024
1 parent 9e79e14 commit fe04f25
Show file tree
Hide file tree
Showing 13 changed files with 924 additions and 6 deletions.
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 { account, publicClient } 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 { account, publicClient } 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
53 changes: 53 additions & 0 deletions src/zksync/actions/getL1TokenAddress.test.ts
Original file line number Diff line number Diff line change
@@ -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,
)
})
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],
})
}
61 changes: 61 additions & 0 deletions src/zksync/actions/getL2TokenAddress.test.ts
Original file line number Diff line number Diff line change
@@ -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()
})
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

0 comments on commit fe04f25

Please sign in to comment.