Enabling Bridging
Bridging moves assets between Ethereum L1 and a Prividium™ network. Enabling it requires configuration across three independent layers.
- Permissions-API rules: function permissions on the L2 system contracts (and per-token L2 ERC-20 contracts) that the User Panel calls during bridging.
- L1 Transaction Filterer: an optional L1 contract that controls which L1→L2 deposits are accepted.
- 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
L2NativeTokenVaulton 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:
| Contract | Address |
|---|---|
L2BaseToken | 0x000000000000000000000000000000000000800A |
L2AssetRouter | 0x0000000000000000000000000000000000010003 |
L2NativeTokenVault | 0x0000000000000000000000000000000000010004 |
InteropCenter | 0x000000000000000000000000000000000001000d |
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):
| Function | Selector | Why needed |
|---|---|---|
L1_CHAIN_ID() | 0x2f90b184 | Resolve the chain's asset identifiers |
BASE_TOKEN_ASSET_ID() | 0xcb944dec | Detect whether the asset is the base token |
WETH_TOKEN() | 0x37d277d4 | Returns the configured wrapped base token address |
originChainId(bytes32) | 0x5f3455b5 | Determine where the asset originates |
l2TokenAddress(address) | 0xf5f15168 | Map the L1 token to its L2 address |
assetId(address) | 0xfd3f60df | Resolve 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:
| Function | Selector | Why needed |
|---|---|---|
l1TokenAddress(address) | 0xf54266a2 | Map 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:
- The token-resolution read permissions above (the four common functions for ETH; add
l2TokenAddressfor ERC-20). - 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.
| Contract | Function | Selector | Asset | Protocol |
|---|---|---|---|---|
InteropCenter | sendBundle(bytes,(bytes,bytes,bytes[])[],bytes[]) | 0x5ef7e104 | ETH / base and ERC-20 | v33+ |
L2BaseToken | withdraw(address) | 0x51cff8d9 | ETH / base | v31 |
L2AssetRouter | withdraw(bytes32,bytes) | 0x4a2e35ba | ERC-20 | v31 |
L2NativeTokenVault | ensureTokenIsRegistered(address) | 0x19a2a285 | ERC-20 | all |
| L2 ERC-20 token | approve(address,uint256) | 0x095ea7b3 | ERC-20 | all |
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, andallowance(address,address)andbalanceOf(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 inL2NativeTokenVaultand 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.
| Contract | Address | Methods Allowed Under an Umbrella |
|---|---|---|
| L2Bridgehub | 0x0000000000000000000000000000000000010002 | baseTokenAssetId(uint256) |
| L2InteropAttributeParser | 0x0000000000000000000000000000000000010015 | parseAttributes(bytes[],uint8), parseAtomicSend(bytes[]) |
| L2BaseTokenHolder | 0x0000000000000000000000000000000000010011 | burnAndStartBridging(uint256) |
| L2AssetTracker | 0x000000000000000000000000000000000001000f | handleInitiateBaseTokenBridgingOnL2(uint256,uint256), handleInitiateBridgingOnL2(uint256,bytes32,uint256,uint256) |
| SystemContext | 0x000000000000000000000000000000000000800b | currentSettlementLayerChainId() |
| L1Messenger | 0x0000000000000000000000000000000000008008 | sendToL1(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.
| Variable | Purpose | Default |
|---|---|---|
VITE_BRIDGING_ENABLED | Show the bridge UI (deposits) | false |
VITE_WITHDRAWAL_ENABLED | Show withdrawal features in the UI | false |
VITE_BRIDGE_TOKENS | Additional ERC-20 tokens for bridging | [] |
VITE_L1_RPC_URL | L1 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
| Symptom | Likely cause |
|---|---|
| Deposit fails immediately, no L1 transaction is sent | A 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 appears | Transaction 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). |