Bridge Cross-chain
LayerZero V2 OFT bridge for Twin tokens across chains (Base, Polygon, etc.).
Overview
The bridge uses LayerZero V2 OFT (Omnichain Fungible Token) adapters to transfer Twin tokens between chains. The mechanism is burn-on-source, mint-on-destination. Each token has adapter contracts deployed per chain, grouped by a bridge_token_key.
Bridge Flow
Non-blocking Record
The POST /bridge/transaction call is non-blocking by design. If it fails (network error, server down), the bridge transaction is already on-chain and will complete via LayerZero regardless. The frontend logs a warning but does not block the user.
Code
Entity Relationship Diagram
bridge_token_key Concept
A bridge_token_key (e.g. "ARGT") groups routes for the same conceptual token across chains. This is necessary because ops_token has one row per (address, network) pair -- the bridge thinks in terms of "ARGT" not "ARGT on Base + ARGT on Polygon".
Code
Auto-uppercased in the backend. The frontend groups routes by this key to show available source/destination pairs.
Partial Unique Index
Code
Soft-deleted routes don't block re-creation. Same pattern as ops_token and circulation_wallet.
Frontend Hooks
All hooks are parameterized -- they receive token address, adapter address, and chain ID as arguments rather than using constants.
| Hook | Purpose | Key Params |
|---|---|---|
useBalance | Read user's token balance | tokenAddress, chainId |
useAllowance | Check ERC20 allowance for adapter | tokenAddress, adapterAddress, chainId |
useApprove | Approve adapter to spend tokens | tokenAddress, adapterAddress, amount |
useQuoteSend | Get LayerZero fee estimate | adapterAddress, dstEid, amount |
useSend | Execute bridge send TX | adapterAddress, dstEid, to, amount, nativeFee |
Hook Design Pattern
Code
Safe Wallet Limitations
| Constraint | Reason |
|---|---|
No useWaitForTransactionReceipt | Safe multisig returns safeTxHash (off-chain hash). The on-chain TX hash only exists after threshold signers approve in Safe UI. Polling for safeTxHash on-chain would never resolve. |
No useSwitchChain() | Safe wallets don't support wallet_switchEthereumChain RPC method. Display an Alert instructing the user to connect their wallet on the correct network. |
API Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/operations/bridge/tokens | List bridgeable tokens (grouped by bridge_token_key) |
GET | /api/operations/bridge/routes | List all bridge routes |
POST | /api/operations/bridge/routes | Create a bridge route |
PUT | /api/operations/bridge/routes/:id | Update a bridge route |
DELETE | /api/operations/bridge/routes/:id | Soft-delete a bridge route |
GET | /api/operations/bridge/transactions | List bridge transactions (paginated) |
POST | /api/operations/bridge/transaction | Record a bridge transaction |
Dashboard Pages
| Page | Path | Access | Purpose |
|---|---|---|---|
| Bridge (operational) | /operations/bridge | Admin, Ops | Execute bridge transfers via connected wallet |
| Bridge Routes (CRUD) | /wallets/bridge | Admin | Manage bridge routes (adapter addresses, LZ EIDs) |
- Sidebar: operational page under "Operaciones", CRUD under "Configuracion" (formerly "Wallets").
BridgeServiceinshared/src/service/: CRUD admin +getBridgeableTokens()(groupBy + join OpsToken) +recordTransaction()+getTransactions()paginated.- UniqueViolationError handler for duplicate
(bridge_token_key, network).
LayerZero Details
- Finality: 1-5 minutes depending on source/destination chain confirmation requirements.
- Fee: Paid in native token (ETH on Base) via
msg.valuein thesend()call. Quoted viaquoteSend()before execution. - Endpoint IDs: Base = 30184, Polygon = 30109. Stored per route in
lz_eidcolumn. - Message format: Standard OFT
send()withSendParamstruct containingdstEid,to(bytes32),amountLD,minAmountLD.