At a glance
- Factory:
createEthersSdk(client) → EthersSdk
- Composed resources:
sdk.deposits, sdk.withdrawals, sdk.helpers
- Client vs SDK: the client wires RPC/signing; the sdk adds high-level flows (quote → prepare → create → wait) and convenience helpers.
Import
import { createEthersClient, createEthersSdk } from '@dutterbutter/zksync-sdk/ethers';
Quick start
import { JsonRpcProvider, Wallet, parseEther } from 'ethers';
import { createEthersClient, createEthersSdk } from '@dutterbutter/zksync-sdk/ethers';
const l1 = new JsonRpcProvider(process.env.ETH_RPC!);
const l2 = new JsonRpcProvider(process.env.ZKSYNC_RPC!);
const signer = new Wallet(process.env.PRIVATE_KEY!, l1);
const client = createEthersClient({ l1, l2, signer });
const sdk = createEthersSdk(client);
// Example: deposit 0.05 ETH L1 → L2, wait for L2 execution
const handle = await sdk.deposits.create({
token: ETH_ADDRESS, // 0x…00 sentinel for ETH supported
amount: parseEther('0.05'),
to: await signer.getAddress(),
});
await sdk.deposits.wait(handle, { for: 'l2' });
// Example: resolve core contracts
const { l1NativeTokenVault } = await sdk.helpers.contracts();
Returns: EthersSdk
The SDK composes the client with resources: deposits, withdrawals, and
convenience helpers.
deposits: DepositsResource
L1 → L2 flows. See Deposits.
withdrawals: WithdrawalsResource
L2 → L1 flows. See Withdrawals.
helpers
Utilities for chain addresses, connected contracts, and L1↔L2 token mapping.
addresses() → Promise<ResolvedAddresses>
Resolve core addresses (Bridgehub, routers, vaults, base-token system).
const a = await sdk.helpers.addresses();
contracts() → Promise<{ ...contracts }>
Connected ethers.Contract instances for all core contracts.
const c = await sdk.helpers.contracts();
One-off contract getters
l1AssetRouter() → Promise<Contract>
l1NativeTokenVault() → Promise<Contract>
l1Nullifier() → Promise<Contract>
const nullifier = await sdk.helpers.l1Nullifier();
baseToken(chainId?: bigint) → Promise<Address>
L1 address of the base token for the current (or supplied) L2 chain.
const base = await sdk.helpers.baseToken(); // infers from client.l2
l2TokenAddress(l1Token: Address) → Promise<Address>
L2 token address for an L1 token.
- Handles ETH special case (L2 ETH placeholder).
- If token is the chain’s base token, returns the L2 base-token system address.
- Otherwise queries
IL2NativeTokenVault.l2TokenAddress.
const l2Crown = await sdk.helpers.l2TokenAddress(CROWN_ERC20_ADDRESS);
l1TokenAddress(l2Token: Address) → Promise<Address>
L1 token for an L2 token via IL2AssetRouter.l1TokenAddress. ETH placeholder resolves to canonical ETH.
const l1Crown = await sdk.helpers.l1TokenAddress(L2_CROWN_ADDRESS);
assetId(l1Token: Address) → Promise<Hex>
bytes32 asset ID via L1NativeTokenVault.assetId (ETH handled canonically).
const id = await sdk.helpers.assetId(CROWN_ERC20_ADDRESS);
Notes & pitfalls
- Client first: You must construct the client with
{ l1, l2, signer } before creating the SDK.
- Chain-derived behavior: helpers pull from on-chain sources; results depend on the connected networks.
- Error model: resource methods throw typed errors; prefer
try* variants on resources for result objects.