Contracts
A secure, multisig-based cross-chain bridge enabling token transfers between Ethereum and Tezos blockchains. The bridge implements a wrap/unwrap mechanism with robust multisig governance for secure token transfers.
Table of Contents
Ethereum
The TezosBridge.sol contract is designed to facilitate secure and efficient token transfers between Ethereum and Tezos. It allows users to wrap ERC20 tokens on Ethereum, which can then be minted on the Tezos blockchain, and vice versa for unwrapping. The contract employs a multisig governance model to ensure that token unwrapping operations are secure and require multiple approvals from designated owners.
The TezosBridge.sol contract manages cross-chain transfers with the following components:
- Token Wrapping — Locks ERC20 tokens on Ethereum and emits metadata-enriched wrap events.
- Token Unwrapping — Releases locked tokens to users after multisig-approved Tezos burn proofs.
- Multisig Verification — Requires a threshold of owner signatures to authorize unwraps.
- Administrative Controls — Administrator can manage owners, fees, and thresholds.
- Fee System — A configurable percentage fee (in basis points) is applied to bridge transfers.
Key Features
-
Multisig Verification:
Unwrap operations require multiple valid owner signatures. -
Configurable Fees:
Supports a percentage-based fee (fee / 10000), automatically transferred to a fee receiver. -
Administrator Controls:
Administrator can add/remove owners, set fee/threshold, and update configuration addresses. -
Metadata Enriched Events:
Wrapevents include token name, symbol, and decimals for seamless Tezos-side minting. -
Nonce & Tx Tracking:
Each wrap generates a unique ID (keccak256(user, nonce)) timestamped for verification. -
Comprehensive Testing:
Unit, integration, and gas analysis test suites.
Getting Started
This section provides a quick guide to set up, test, and deploy the TezosBridge contract.
1. Environment Setup
First, copy the environment template and configure your parameters:
cp .env.example .envEdit .env with your specific values:
# Example environment variables for TezosBridge deployment
# Private key of the deployer (with 0x prefix)
PRIVATE_KEY=""
# Administrator address for the bridge
BRIDGE_ADMIN=""
# Individual multisig owner addresses (required by script)
MULTISIG_OWNER_1=""
MULTISIG_OWNER_2=""
MULTISIG_OWNER_3=""
# Multisig threshold (minimum number of signatures required for unwrap operations)
MULTISIG_THRESHOLD=2
# Comma-separated list of multisig owner addresses
MULTISIG_OWNERS=""
# RPC URL for the network you're deploying to
RPC_URL=http://localhost:8545
# Optional: Etherscan API key for contract verification
ETHERSCAN_API_KEY=your_etherscan_api_key_here2. Install Dependencies
# Install Foundry if not already installed
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Install project dependencies
forge install3. Test Run
# Run all tests
forge test
# Run specific test suites
forge test --match-contract TestUnitTezosBridge # Unit tests
forge test --match-contract TestIntegratedBridge # Integration tests
forge test --match-contract TestGasAnalysis # Gas analysis4. Deploy
# Local testing
anvil
forge script script/TezosBridge.s.sol:TezosBridgeScript --rpc-url http://localhost:8545 --private-key $PRIVATE_KEY --broadcast
# Testnet deployment
forge script script/TezosBridge.s.sol:TezosBridgeScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --verifyProduction Deployment
For production deployment, ensure you follow these steps:
1. Security Checklist
- Private keys stored securely (hardware wallet/secure key management)
- Multisig owners verified and trusted
- Threshold set appropriately (recommend 2/3 or 3/5)
- Admin address is a multisig wallet
2. Configure Production Environment
# Production configuration
PRIVATE_KEY="0x..." # Deployer key (preferably from hardware wallet)
BRIDGE_ADMIN="0x..." # Production admin multisig address
MULTISIG_OWNER_1="0x..." # Production owner #1
MULTISIG_OWNER_2="0x..." # Production owner #2
MULTISIG_OWNER_3="0x..." # Production owner #3
MULTISIG_THRESHOLD=2 # Or higher for production
RPC_URL="https://${NETWORK}.infura.io/v3/YOUR_INFURA_KEY"
ETHERSCAN_API_KEY="your_etherscan_api_key"3. Deploy to Mainnet
# Deploy with verification
forge script script/TezosBridge.s.sol:TezosBridgeScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --verify
# Alternative: Use production deployment function
forge script script/TezosBridge.s.sol:TezosBridgeScript --sig "runProduction()" --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --verifyBridge Operations
The TezosBridge contract supports two main operations: wrapping tokens from Ethereum to Tezos and unwrapping tokens from Tezos back to Ethereum. The process involves user interactions, multisig approvals, and event emissions for cross-chain communication.
Wrapping Tokens (Ethereum → Tezos)
- Approve the bridge to spend tokens:
token.approve(bridgeAddress, amount);- Wrap the tokens for Tezos:
bridge.wrapToken(tokenAddress, amount, tezosAddress);- Event emitted:
Event is monitored by off-chain signers, tokens are minted on Tezos side.
Wrap(
id, user, token, amountAfterFee,
tezosAddress, name, symbol, decimals
)Unwrapping Tokens (Tezos → Ethereum)
-
User burns tokens on Tezos side, generating a unique burn tx ID.
- Bridge owners sign the unwrap payload off-chain:
Encoded data format:
(string id, address tokenOut, address to, uint256 amount)-
User calls
unwrapToken:
bridge.unwrapToken(params, signatures);-
Bridge verification flow:
- Validates that
idwasn't processed before. - Checks multisig signatures using
ecrecover. - Deducts the bridge fee and transfers tokens to the user.
- Validates that
- Event emitted:
Unwrap(user, token, amountAfterFee)Administration
The following functions are available exclusively to the administrator for managing the bridge's configuration and multisig governance.
| Function | Description |
|---|---|
addOwner(address owner) | Adds a new multisig owner |
removeOwner(address prevOwner, address owner) | Removes an existing owner |
changeThreshold(uint256 threshold) | Updates required signature count |
changeFee(uint256 fee) | Sets the bridge fee (in basis points) |
changeFeeReceiver(address newReceiver) | Updates the fee receiver address |
changeAdmin(address newAdmin) | Transfers administrative rights |
- All administrative functions are protected by the
authorizedmodifier. _administratoris the only address allowed to perform these actions.
Views
The following view (read-only) functions allow users, relayers, or monitoring services to query the current state of the TezosBridge contract without sending transactions.
| Function | Description |
|---|---|
getFee() → uint256 | Returns the current bridge fee, expressed in basis points (1/100 of a percent). |
getThreshold() → uint256 | Returns the number of required signatures for multisig operations. |
getAdministrator() → address | Returns the current administrator address. |
getOwners() → address[] | Returns the list of all registered multisig owners. |
isOwner(address owner) → bool | Checks whether a given address is one of the registered multisig owners. |
getNonce(address user) → uint256 | Returns the current nonce associated with a user (used to compute wrap IDs). |
getIdsDesc(address user, uint256 fromNonce, uint256 count) → (bytes32[], uint256[]) | Retrieves a descending list of wrap transaction IDs and their timestamps. |
getId(bytes32 id) → uint256 | Returns the timestamp of a specific wrap transaction by ID. |
getProcessedId(string id) → (bool processed, uint256 timestamp) | Checks whether a given unwrap ID from Tezos has already been processed. |
- All these functions are read-only and do not require gas when called off-chain (via
eth_call). - They can be used by indexers, off-chain signers, or front-end applications to track bridge activity and status.
- Nonces and IDs are essential for cross-chain proof validation and preventing double-processing of unwrap operations.
Events
The following events are emitted by the bridge contract to record key operations and configuration changes on-chain.
| Event | Description |
|---|---|
Wrap(bytes32 id, address user, address token, uint256 amount, string tezosAddress, string name, string symbol, uint8 decimals) | Emitted when tokens are wrapped for Tezos |
Unwrap(address user, address token, uint256 amount) | Emitted after successful unwrapping |
AddedOwner(address owner) | New multisig owner added |
RemovedOwner(address owner) | Owner removed |
ChangedThreshold(uint256 threshold) | Updated multisig threshold |
ChangedFee(uint256 fee) | Updated fee rate |
ChangedFeeReceiver(address feeReceiver) | Updated fee receiver address |
Troubleshooting
If you encounter issues with the TezosBridge contract, here are some common troubleshooting lists and solutions.
| Error | Cause | Solution |
|---|---|---|
METHOD_CAN_ONLY_BE_CALLED_BY_ADMINISTRATOR | Unauthorized caller | Ensure msg.sender is the admin address |
INVALID_SIGNATURES_LENGTH | Incorrect signature count | Check that signatures.length == threshold * 65 |
INVALID_OWNER_PROVIDED | One or more invalid signer addresses | Verify that all signers are registered owners |
TRANSACTION_ALREADY_PROCESSED | Duplicate unwrap ID | Ensure each unwrap id is unique |
TOKEN_TRANSFER_FAILED | ERC20 transfer error | Ensure token complies with ERC20 and has sufficient allowance |
Tezos
The tezos_bridge contract is the Tezos-side implementation of the cross-chain bridge.
It manages the creation, minting, and burning of wrapped tokens corresponding to ERC20 assets locked on Ethereum.
This contract ensures that the state of wrapped tokens on Tezos always matches the locked balances on Ethereum.
The tezos_bridge contract works in conjunction with the Ethereum TezosBridge.sol contract to maintain cross-chain synchronization through event-driven communication and signature verification.
The bridge manages the following components:
- Token Creation — Deploys new FA2-compatible token contracts for each supported Ethereum token.
- Token Wrapping — Mints wrapped tokens on Tezos after validated wrap events on Ethereum.
- Token Unwrapping — Burns Tezos tokens and emits events for Ethereum-side unwrapping.
- Multisig Verification — Validates actions using signatures from multiple authorized owners.
- Administrative Controls — Administrator can manage owners and update the required signature threshold.
Key Features
-
Cross-Chain Consistency:
Maintains a one-to-one mapping between Ethereum and Tezos tokens through event-driven mint/burn mechanisms. -
Multisig Validation:
Ensures secure minting and token creation by requiring multiple valid signatures. -
Dynamic Ownership:
Administrator can add or remove owner keys and adjust the multisig threshold. -
Event Transparency:
EmitsWrap,Unwrap, andCreateevents for cross-chain coordination and auditing. -
ID and Nonce Tracking:
Maintains unique transaction identifiers (keccak(user, nonce)) and timestamps for traceability.
Getting Started
This section provides a quick overview for setting up, testing, and deploying the tezos_bridge contract.
1. Environment Setup
The project uses Completium CLI for compilation and deployment, and ts-mocha for testing.
Make sure you have both installed globally:
npm install -g completium-cli
npm install -g ts-mochaYou can modify the deployment parameters directly in this line of the package.json file to fit your setup for example, changing the admin address or the multisig threshold:
"deploy-bridge": "completium-cli deploy src/bridge.arl --parameters '{\"admin\" : \"tz1YourAdminAddressHere\", \"threshold\" : \"3\"}'"2. Install Dependencies
# Install project dependencies
npm install3. Test Run
All tests are written in TypeScript and executed using ts-mocha.
To run the full test suite:
npm run test-bridge4. Deploy
You can deploy the Tezos bridge directly using the provided script:
npm run deploy-bridgeProduction Deployment
For production deployment, ensure you follow these steps:
1. Security Checklist
- Private keys stored securely (hardware wallet/secure key management)
- Multisig owners verified and trusted
- Threshold set appropriately (recommend 2/3 or 3/5)
- Admin address is a multisig wallet
2. Configure Production Environment
Modify the deployment parameters directly in this line of the package.json file to fit your setup for example, changing the admin address or the multisig threshold:
"deploy-bridge": "completium-cli deploy src/bridge.arl --parameters '{\"admin\" : \"tz1YourAdminAddressHere\", \"threshold\" : \"3\"}'"3. Deploy to Mainnet
npm run deploy-bridgeBridge Operations
The Tezos bridge supports three core operations: create, wrap, and unwrap. These correspond to different cross-chain events between Ethereum and Tezos.
Creating a Wrapped Token (Ethereum → Tezos Initialization)
When a new ERC20 token is locked on Ethereum, the Tezos bridge must deploy a wrapped FA2 token contract.
entry create(params_c: params_create, signatures: bytes)
- Off-chain signers approve the creation by signing the payload.
- The bridge verifies signatures using
check_signatures. - A new
permitscontract and FA2 token contract are deployed. - A
Createevent is emitted to confirm token creation.
Create(
eth_token_address,
tez_token_address,
contract_metadata,
token_name,
token_symbol,
token_decimals
)Wrapping Tokens (Ethereum → Tezos)
Triggered when the Ethereum bridge emits a Wrap event.
entry wrap(params_w: params_wrap, signatures: bytes)
- Verifies that the Ethereum transaction ID (
eth_id) has not been processed. - Checks the provided multisig signatures.
- Mints the wrapped token on Tezos to the user's address.
- Records the processed ID to prevent duplicates.
- Emits a
Wrapevent.
Wrap(
eth_id,
user_address,
ethereum_token,
tez_token,
token_amount
)Unwrapping Tokens (Tezos → Ethereum)
When a user wants to send tokens back to Ethereum, they call:
entry unwrap(tz_addr: address, tz_at: nat, eth_addr: string)
- Confirms that the Tezos token exists in the mapping.
- Increments the caller's nonce and generates a unique transaction ID.
- Burns the tokens from the user's balance.
- Emits an
Unwrapevent containing the data needed by Ethereum signers.
Unwrap(
id,
user,
tez_token_address,
token_amount,
eth_token_address,
eth_address
)Administration
The Tezos bridge provides strong administrative control for managing ownership, thresholds, and governance configuration. Only the contract's administrator may execute these operations, ensuring security and consistency across the bridge.
| Function | Description |
|---|---|
change_admin(new_admin: address) | Transfers administrator privileges to another address |
change_threshold(new_threshold: nat) | Updates the multisig signature threshold |
add_owner(new_owner: key) | Adds a new authorized signer key |
remove_owner(owner_to_remove: key) | Removes an existing authorized signer |
- All administrative entries can only be called by the
adminaddress. - Threshold must always be greater than zero.
- Owner public keys are stored in the
ownersbig_map.
Views
The contract provides several read-only views for querying state information.
| View | Description |
|---|---|
created(eth_tn_addr: string) | Checks if an Ethereum token is already mapped |
get_processed_id(eth_pr_id: string) | Returns whether an Ethereum transaction ID was processed |
get_ids_desc(address, from_nonce, count) | Retrieves recent unwrap IDs with timestamps |
get_id(bytes specific_id) | Returns the timestamp of a specific unwrap ID |
get_nonce(address user) | Returns the current nonce for a user |
get_owners() | Returns the list of registered owner keys |
Events
The following events are emitted by the Tezos bridge to synchronize operations with Ethereum and ensure transparency of cross-chain activity.
| Event | Description |
|---|---|
Create(string eth_token_address, address tez_token_address, bytes contract_metadata, bytes token_na, bytes token_sy, bytes token_de) | Emitted when a new wrapped token contract is created |
Wrap(string eth_id, address user, string ethereum_token, address token_address, nat token_amount) | Emitted when tokens are minted (wrap) |
Unwrap(bytes id, address user, address token_address, nat token_amount, string eth_token_address, string eth_address) | Emitted when tokens are burned (unwrap) |
Troubleshooting
If you encounter issues with the tezos_bridge contract, refer to the following common errors and their potential causes.
| Error | Cause | Solution |
|---|---|---|
TOKEN_NOT_SUPPORTED | The given Tezos token is not registered | Ensure the token mapping exists before unwrapping |
ALREADY_PROCESSED_ID | The Ethereum transaction ID was reused | Use a unique eth_id per wrap |
NOT_ENOUGH_SIGNATURES | Insufficient valid signatures provided | Ensure signatures match registered owner keys |
THRESHOLD_MUST_BE_GREATER_THAN_ZERO | Threshold set to 0 | Use a threshold ≥ 1 |
OWNER_ALREADY_EXISTS | Attempted to re-add an existing owner | Verify key list before adding |
OWNER_NOT_FOUND | Tried to remove an unregistered owner | Ensure the key exists before removal |
SIGNATURE_SLICING_FAILED | Signature length or slicing incorrect | Check off-chain signature formatting |