Skip to content
Prividium

Enabling Bridging

Bridging moves assets between Ethereum L1 and a Prividium™ network. Enabling it requires configuration across three independent layers.

  1. Permissions-API rules: function permissions on the L2 system contracts (and per-token L2 ERC-20 contracts) that the User Panel calls during bridging.
  2. L1 Transaction Filterer: an optional L1 contract that controls which L1→L2 deposits are accepted.
  3. Frontend settings: user-panel environment variables that show or hide the bridge UI.

How bridging uses permissions

Prividium™ applies its deny-by-default permission model to direct calls on system contracts. Functions without permission rules default to Forbidden. System-contract sync registers contracts without creating permissions. Create the rules below to enable bridging for a new environment.

Deposits and withdrawals use permissions differently:

  • Deposits move funds in through an L1 transaction. The L2 crediting runs as an automatic priority-queue transaction that bypasses Prividium and needs no L2 write permission. Before it can start a deposit, though, the User Panel reads token metadata from L2NativeTokenVault on L2. If those reads are Forbidden, the User Panel cannot start the deposit. Deposits therefore require L2 read permissions.
  • Withdrawals are user-signed L2 transactions. The User Panel needs both the read permissions (same token metadata) and write permissions on the withdrawal functions.

The four bridge entry-point and token-resolution contracts are registered automatically:

ContractAddress
L2BaseToken0x000000000000000000000000000000000000800A
L2AssetRouter0x0000000000000000000000000000000000010003
L2NativeTokenVault0x0000000000000000000000000000000000010004
InteropCenter0x000000000000000000000000000000000001000d

Create the permission rules below from each contract's Function Permissions tab in the Admin Dashboard, or through the API.

Token-resolution read permissions

The User Panel reads token metadata from L2 before every deposit and withdrawal, for both ETH and ERC-20. Grant these reads as All Users (public) because they are read-only view calls. You can also gate them by role if your policy requires it.

L2NativeTokenVault at 0x0000000000000000000000000000000000010004 (read access):

FunctionSelectorWhy needed
L1_CHAIN_ID()0x2f90b184Resolve the chain's asset identifiers
BASE_TOKEN_ASSET_ID()0xcb944decDetect whether the asset is the base token
WETH_TOKEN()0x37d277d4Returns the configured wrapped base token address
originChainId(bytes32)0x5f3455b5Determine where the asset originates
l2TokenAddress(address)0xf5f15168Map the L1 token to its L2 address
assetId(address)0xfd3f60dfResolve the L2 token's asset id (ERC-20 withdrawal on v33)

Withdrawals on protocol v31 also read one function on L2AssetRouter at 0x0000000000000000000000000000000000010003 (read access). v33 removed it; SDK releases for v33 use assetId(address) on the vault instead:

FunctionSelectorWhy needed
l1TokenAddress(address)0xf54266a2Map the L2 token back to its L1 address (withdrawal)

The User Panel reads the first four L2NativeTokenVault functions on every deposit and withdrawal, including plain ETH. Any user who lacks full sequencer access needs all four permitted to bridge in either direction.

Enabling deposits

A deposit requires:

  1. The token-resolution read permissions above (the four common functions for ETH; add l2TokenAddress for ERC-20).
  2. The L1 Transaction Filterer to allow the deposit (see Transaction Filterer).

The L2 execution of a deposit is a priority-queue transaction and needs no L2 write permission. The reads above are the only L2-side configuration a deposit needs.

Enabling withdrawals

A withdrawal requires the read permissions above plus the following write permissions. Grant as All Users (public) or Check Role.

The withdrawal entry point depends on the chain's protocol version. zksync-js reads the version from L1 and picks the route, so grant the row that matches your chain. Grant both sets on a chain that is about to upgrade from v31 to v33, so withdrawals keep working through the upgrade.

ContractFunctionSelectorAssetProtocol
InteropCentersendBundle(bytes,(bytes,bytes,bytes[])[],bytes[])0x5ef7e104ETH / base and ERC-20v33+
L2BaseTokenwithdraw(address)0x51cff8d9ETH / basev31
L2AssetRouterwithdraw(bytes32,bytes)0x4a2e35baERC-20v31
L2NativeTokenVaultensureTokenIsRegistered(address)0x19a2a285ERC-20all
L2 ERC-20 tokenapprove(address,uint256)0x095ea7b3ERC-20all

Withdrawals must also be enabled in the frontend with VITE_WITHDRAWAL_ENABLED=true (see Frontend configuration).

After a chain ID change

Changing a chain's ID invalidates every wallet that already has the network saved. Wallets ignore wallet_addEthereumChain for a chain ID they already know, so an entry pointing at the raw /rpc URL survives the change and every send fails with eth_sendRawTransaction: Unauthorized even though reads, gas estimation and transaction authorization all succeed. Users must remove the network from their wallet and add it again from the User Panel, which registers their personal /rpc/wallet/{token} URL.

ERC-20 considerations

  • Per-token permissions are required. Each bridgeable ERC-20 has its own L2 address and needs its own rules: approve(address,uint256) (write) for withdrawals, and allowance(address,address) and balanceOf(address) as reads. Add a rule for every token you intend to bridge, because no wildcard covers them.
  • Beacon registration. Register the token's deployment-specific beacon and grant read access to implementation() for token approvals. Startup sync does not register deployment-specific beacons.
  • Token registration. ensureTokenIsRegistered(address) registers the token in L2NativeTokenVault and returns its asset id; it must be permitted (write) before an ERC-20 withdrawal can be routed.
  • Frontend token list. Additional ERC-20s shown in the user panel are configured with VITE_BRIDGE_TOKENS (JSON). The L2 token address for each entry must be resolved correctly; see the private user-panel environment variable docs for the schema.

Umbrella Permissions in Multi-Org Mode

An umbrella permission lets a permitted entry point authorize internal contract calls. Mark the withdrawal entry-point permission as an umbrella to use this inheritance. With MULTI_ORG_ENABLED=true, the policy listener's /judge endpoint still checks those internal calls.

Startup sync registers these six shared dependencies zone-wide. Only the listed methods can inherit umbrella authorization by default; other methods require their own permissions. Registration and organization ownership checks still apply. Argument restrictions apply where an administrator has configured a rule; startup sync creates no permission rules.

ContractAddressMethods Allowed Under an Umbrella
L2Bridgehub0x0000000000000000000000000000000000010002baseTokenAssetId(uint256)
L2InteropAttributeParser0x0000000000000000000000000000000000010015parseAttributes(bytes[],uint8), parseAtomicSend(bytes[])
L2BaseTokenHolder0x0000000000000000000000000000000000010011burnAndStartBridging(uint256)
L2AssetTracker0x000000000000000000000000000000000001000fhandleInitiateBaseTokenBridgingOnL2(uint256,uint256), handleInitiateBridgingOnL2(uint256,bytes32,uint256,uint256)
SystemContext0x000000000000000000000000000000000000800bcurrentSettlementLayerChainId()
L1Messenger0x0000000000000000000000000000000000008008sendToL1(bytes) and the empty-calldata gas-burning fallback

The messenger uses its fallback internally to burn message gas. Its on-chain implementation rejects other callers and always reverts that subcall. Direct empty-calldata calls receive no new permission.

Startup sync also registers L1MessengerHook at 0x0000000000000000000000000000000000007001. The hook, the four entry-point and token-resolution contracts, and application contracts retain their existing umbrella behavior. This method policy covers only the six dependencies in the table.

Optional Shared Accounting Reads

ALLOW_UMBRELLA_SYSTEM_READS enables umbrella inheritance for a code-maintained allowlist of optional system-contract reads. It defaults to false. The current allowlist contains these four L2AssetTracker getters:

  • interopInfo(bytes32)
  • chainBalance(uint256,bytes32)
  • totalPreV31TotalSupply(bytes32)
  • isAssetRegistered(bytes32)

Setting the flag to true lets these getters inherit umbrella authorization without their own method permissions. They expose shared accounting or registration state, which can reveal information about other organizations through an authorized umbrella call. Enable the flag only when this exposure matches your access policy.

Direct getter calls still require explicit permissions. The flag grants no additional access to unknown selectors or other write methods. Organization ownership checks and configured argument restrictions remain enforced. Required bridge operations, including the required reads in the table, work with either setting.

Adding getters to the allowlist expands access in environments where the flag is already enabled. Review allowlist changes before upgrading those environments.

This protection requires multi-org judge enforcement. Environments without judge or with multi-org disabled retain their existing authorization behavior. Restart every permissions-api replica with the same flag value after changing it.

Transaction Filterer

The Transaction Filterer is an optional L1 contract that gates which L1→L2 transactions (including deposits) are queued. It is the deposit-side admission control and is independent of the L2 permission rules above.

Key behaviors:

  • setDepositsAllowed(bool) toggles whether non-whitelisted addresses may deposit.
  • When deposits are allowed, a non-whitelisted EOA may deposit only to its own address.
  • Whitelisted addresses (grantWhitelist / revokeWhitelist) may perform arbitrary L1→L2 transactions.
  • L1 contract addresses are aliased during deposit, so L1 contracts cannot deposit unless explicitly whitelisted.

If deposits are rejected at L1 (no L2 priority transaction appears) even though permissions are correct, check the filterer's depositsAllowed flag and the sender's whitelist status. For deployment and the full method reference, see Transaction Filterer.

Frontend configuration

These user-panel environment variables control the bridge UI only. They do not create permission rules and do not affect the Transaction Filterer.

VariablePurposeDefault
VITE_BRIDGING_ENABLEDShow the bridge UI (deposits)false
VITE_WITHDRAWAL_ENABLEDShow withdrawal features in the UIfalse
VITE_BRIDGE_TOKENSAdditional ERC-20 tokens for bridging[]
VITE_L1_RPC_URLL1 RPC endpoint (required when bridging)–

See the private user-panel environment variable docs for the full list.

Verification checklist

Confirm a permission rule exists for each (contract, function) below, that its access type matches, and that its rule is All Users (public) or a Check Role rule the user holds. None should remain Forbidden.

Deposit — ETH

  • L2NativeTokenVault.L1_CHAIN_ID() — read
  • L2NativeTokenVault.BASE_TOKEN_ASSET_ID() — read
  • L2NativeTokenVault.WETH_TOKEN() — read
  • L2NativeTokenVault.originChainId(bytes32) — read
  • Transaction Filterer allows the sender

Deposit — ERC-20 (all of the above, plus)

  • L2NativeTokenVault.l2TokenAddress(address) — read

Withdraw — ETH (the four common reads, plus)

  • InteropCenter.sendBundle(bytes,(bytes,bytes,bytes[])[],bytes[]) — write (protocol v33)
  • L2BaseToken.withdraw(address) — write (protocol v31)
  • VITE_WITHDRAWAL_ENABLED=true

Withdraw — ERC-20 (the four common reads, plus)

  • L2NativeTokenVault.assetId(address) — read (protocol v33)
  • L2AssetRouter.l1TokenAddress(address) — read (protocol v31)
  • L2NativeTokenVault.ensureTokenIsRegistered(address) — write
  • <token>.approve(address,uint256) — write
  • <token>.allowance(address,address) — read
  • InteropCenter.sendBundle(bytes,(bytes,bytes,bytes[])[],bytes[]) — write (protocol v33)
  • L2AssetRouter.withdraw(bytes32,bytes) — write (protocol v31)
  • VITE_WITHDRAWAL_ENABLED=true

Troubleshooting

SymptomLikely cause
Deposit fails immediately, no L1 transaction is sentA token-resolution read permission on 0x…010004 is Forbidden (e.g. L1_CHAIN_ID). Grant the four common reads.
Deposit rejected at L1 / no L2 priority transaction appearsTransaction Filterer depositsAllowed=false, or the sender is not whitelisted (or is an aliased L1 contract).
Withdrawal "not authorized"A write permission is missing (withdraw, ensureTokenIsRegistered, or the token's approve).