Native and ERC-20 deposits
The PoC bridge accepts native ETH, converts finalized deposit logs into exact Zeko outer Witness actions, and waits for a later real Zeko commit to synchronize those actions. A bridge proof by itself does not finalize an L2 deposit.
Fixed bridge identity
The Ethereum bridge proxy is represented inside the OCaml circuit as the synthetic compressed key:
x = uint160(EthereumZekoBridge proxy)
is_odd = falseThat address is proof identity. Reserve the final CREATE2 proxy address before building the OCaml bridge verification key and settlement guest. Changing the proxy later requires rebuilding the OCaml circuit artifacts and SP1 programs.
User deposit
The user calls the canonical depositETH(zekoRecipient) overload. The bridge:
- requires a nonzero value with 1 gwei granularity
- normalizes 18-decimal wei to Zeko's 9-decimal native unit
- fixes timeout and upper slot to
UInt32.max - appends a chain- and bridge-bound Keccak leaf to the deposit accumulator
- increments
depositNonceand native escrow liability - emits
BridgeDeposit
The overload with a caller-selected timeout and the old arbitrary-token path is disabled unless an administrator explicitly enables the legacy compatibility switch. Canonically registered ERC-20 assets instead use submitDeposit and do not depend on that switch.
Canonical ERC-20 deposit
Each ERC-20 registry record immutably binds its stable index, Ethereum token, standard Mina FungibleToken owner, derived L2 token ID, decimals, capacity, and approved VK identities. The asset ID separately binds the chain, bridge proxy, token, owner, token ID, and decimals. submitDeposit(token, amount, zekoRecipient) requires matching nine-or-fewer decimals on both chains, exact transfer custody, a positive UInt64 amount, and remaining registered capacity. Its timeout is fixed to UInt32.max.
Registration is a three-stage flow. An administrator first calls proposeAsset, which stores a dense append as Pending but cannot enable custody. The OCaml registry transition must then be verified in a V3 settlement for one record or a V4 settlement for an ordered batch. Finally, activateAsset or activateAssetFromBatch checks the settlement-bound record hash and canonical Mina Poseidon record commitment before making the asset active. registerToken cannot create a registry-backed asset: it is retained only for the explicit one-token V1 fixture path and reverts unless the legacy deposit switch is enabled.
The one-token compatibility path is action encoding V1. It keeps the ZEKO_ERC20_DEPOSIT_LEAF_V2 Keccak leaf and this auxiliary value:
Poseidon("Ethereum ERC20 deposit V1", [
asset_id_high,
asset_id_low,
empty_call_forest,
bridge_address_as_field,
false,
amount,
recipient_x,
recipient_is_odd,
UInt32.max
])The universal registry path is action encoding V2. Its ZEKO_ERC20_DEPOSIT_LEAF_V3 Keccak preimage is:
[
chain_id,
bridge_address,
token,
encoding_version = 2,
registry_index,
record_commitment,
asset_id,
zeko_recipient,
amount,
UInt32.max,
nonce
]The guest uses the same immutable identity in the Mina action:
Poseidon("Ethereum ERC20 deposit V2", [
encoding_version = 2,
registry_index,
record_commitment,
asset_id_high,
asset_id_low,
empty_call_forest,
bridge_address_as_field,
false,
amount,
recipient_x,
recipient_is_odd,
UInt32.max
])The universal OCaml circuit authenticates the complete record against the registry zkApp root/count, derives the token ID from the registered MFT owner, and checks both asset-ID limbs before allowing the token-specific shared-vault account to debit its pre-minted inventory. The browser SDK then places that proved forest beneath the unmodified Mina Foundation FungibleToken owner's approveBase proof. Different assets share the vault public key and bridge VK but use distinct derived token IDs, balances, and replay-helper domains.
Canonical proof input
After the deposit block is at or below Ethereum's consensus-finalized JSON-RPC head, an operator calls POST /v1/bridge/deposits/prove. The gateway constructs the batch itself from the next contiguous canonical finalized BridgeDeposit rows. A caller cannot substitute deposit contents.
For native deposits the guest recomputes:
deposit_leaf = keccak256(
domain, chain_id, bridge_address, native_token,
zeko_recipient, zeko_amount, UInt32.max, nonce
)
deposit_state_after = keccak256(
state_domain, deposit_state_before, deposit_leaf
)It also computes the OCaml-compatible auxiliary value:
Poseidon("Ethereum deposit V1", [
empty_call_forest,
bridge_address_as_field,
false,
zeko_amount,
recipient_x,
recipient_is_odd,
UInt32.max
])and emits the exact five-field action:
[Witness = 1, aux, children_digest = 0, slot_lower = 0, slot_upper = UInt32.max]Each action includes its resulting Poseidon action-state checkpoint in the V2 bridge receipt.
Ethereum acceptance
EthereumZekoBridge.submitBridgeTransition verifies the SP1 proof and binds the receipt to:
- the bridge's proven deposit nonce and historical accumulator checkpoint
- the current on-chain deposit nonce and accumulator
- the settlement contract's current outer action state and length
- a nonempty, contiguous action range whose length equals the deposit count
The bridge then calls appendOuterWitnessBatch for every proof-emitted action. This advances the settlement contract's outer action state and makes the exact action bytes visible through the gateway's Mina actions query.
Deposit synchronization
The sequencer reads those outer actions from the gateway. The next appropriate OCaml commit must bind the final deposit action checkpoint as its synchronized outer action state and length. Only after that settlement is confirmed does the gateway mark the deposit synchronized.
The Ethereum gateway's Mina-compatible actions query returns finalized rows only. The Ethereum sequencer profile therefore uses --deposit-delay-blocks 0. Mina still uses the unchanged OCaml block-delay filter in update_inner_account_unlocked; the adapter does not alter Mina's behavior.
The user then obtains and signs the normal Zeko helper-account finalizeDeposit update and submits it to the sequencer. The gateway never forges that signature and the helper account remains responsible for its processed-deposit cursor.
User-facing status
GET /v1/bridge/deposits/:nonce reports Ethereum finality, bridge proof job, exact outer action, synchronized settlement, and the next action. Every response also carries the immutable action identity:
{
"assetId": "0x...",
"encodingVersion": 2,
"registryIndex": 7,
"recordCommitment": "0x..."
}Native deposits use encoding version 0; legacy ERC-20 V1 deposits use encoding version 1. Both return null for registryIndex and recordCommitment. Registry V2 deposits return the values emitted with that specific deposit, rather than values inferred from the registry's current state.
waitForEthereumFinality
requestBridgeProof
waitForSettlementSynchronization
finalizeDepositOnZekoCancellation is intentionally absent. Do not deposit funds into a PoC deployment unless the operator is online and the lack of a refund path is acceptable.