
Smart1inch
An extension of 1inch Fusion+ to facilitate Tezos-EVM bidirectional cross-chain swaps, using SmartPy
Demo
About
# Cross-Chain Bridge Documentation ## 1. What We're Building This project is a **demonstration-grade bridge** that lets two parties atomically swap assets between Sepolia (Ethereum) and Ghostnet (Tezos). - In the demo the maker sells **Sepolia USDC** and receives **XTZ**. - The opposite direction (XTZ → USDC) is fully wired but not shown live. We kept every moving part identical to 1inch Fusion + wherever possible, then replaced anything heavy (Dutch auctions, Merkle leaves, WebSockets) with the lightest alternative that still shows the cross-chain logic. ## 2. Why the Architecture Looks Like This | Need | Design choice | Rationale | |------|---------------|-----------| | **Hard atomicity across L1 chains** | *Hash-timelocked escrows* on both chains | Simple, formally-understood security; no reliance on optimistic messages or light clients. | | **Low gas on Tezos** | **Hub pattern** – single `EscrowHub` holds every deal | One contract deployment, per-escrow big-map rows. | | **No contract-wide DoS** | *Per-escrow locks* instead of a global mutex | If a buggy FA2 token hangs, every other escrow still works. | | **Cross-chain identity** | **32-byte hash** `sha256(pack(tz) ‖ SALT)` | - Same fixed constant on both chains.<br>- `bytes32` fits Solidity slots; Tezos re-derives to stop spoofing. | | **Off-chain coordination** | **Relayer + Resolver pattern** | 1inch's model: relayer matches orders, resolver pays gas and earns spread. | | **No back-end servers for demo** | **Observer polling** (cron) instead of WebSockets | Easiest to host: a CLI watcher checks `EscrowHub` / `EscrowSrc` events every N seconds and triggers the next call. | | **Maker convenience** | One **ERC-20 approve** only, no signature | We store the resolver address in `allowedSender`, so LOP skips maker sig verification. | | **Judge-friendly codebase** | Keep Solidity and SmartPy separate; no diagram assets | Reviewers can open each contract in a block-explorer and follow the README steps verbatim. | ## 3. Main Components | Chain | Contract | Short role | |-------|----------|------------| | **Sepolia** | `EscrowFactory.sol` | Deploys minimal-proxy `EscrowSrc` with `CREATE2`; holds maker tokens + ETH deposit. | | | `EscrowSrc.sol` | HTLC escrow (source leg). | | | `Resolver.sol` | Helper that *atomically* sends ETH deposit, deploys source escrow and calls Limit-Order-Protocol (`fillOrderArgs`). | | | Limit-Order-Protocol v4 | Used only as a trampoline; we bypass signature with `allowedSender`. | | **Ghostnet** | `EscrowHub.py` | Single Tezos contract for every destination escrow; stores maker/taker Tezos addresses, hashlocks, timelocks, deposits. | | | `TimelockLib.py` | Calculates matching timestamp windows so `dst_withdrawal` < `src_cancellation`. | | | `AddressConverter.py` | View that converts `tz` → `bytes32` and back. | | | (Optional) `DutchAuction.py`, `MerkleValidator.py` | Present but **disabled** in MVP; ready for future public auction upgrade. | ## 4. End-to-End Flow (Sepolia USDC → Ghostnet XTZ) ### 1. Maker - Wraps nothing (USDC already ERC-20). - Calls `USDC.approve(FACTORY, 5 USDC)`. - Shares `secret0` with the relayer. ### 2. Resolver (single transaction) - Calls `Resolver.deploySrc` with - `immutables` (hashes, USDC address, 5 USDC, timelocks, deposits) - and *dummy* LOP order where `allowedSender = Resolver`. - Eth value = 0.1 ETH (safety deposit). - Contract sequence inside the tx: 1. `FACTORY.addressOfEscrowSrc` predicts address. 2. ETH deposit sent to that address. 3. `EscrowSrc` deployed → pulls maker's 5 USDC using the approve. ### 3. Relayer (Ghostnet) - Calls `EscrowHub.create_escrow` (value = 0.1 ꜩ deposit). - Timelocks mirror Sepolia's but shifted by coordination buffer. ### 4. Reveal - Relayer posts `secret0` to IPFS / Slack channel. - Resolver withdraws on Ghostnet (gets XTZ + ꜩ deposit), then on Sepolia (gets USDC + ETH deposit). Fallbacks (`publicWithdraw`, `cancel`) work automatically after their timestamps; deposits reward the caller who finishes the swap.
How it's made
For the MVP we replaced live push events with pollers: - **UI (React hook)** – every 15 s: - Reads `EscrowHub.is_timelock_expired` for each stage. - Reads Sepolia event logs for `EscrowSrc.withdrawn`. - Updates status badges in the browser. - **Relayer cron** – every 10 s: - Checks whether both escrows are confirmed (using block numbers). - When ready, writes `secret0` to a pinned gist + calls Ghostnet withdraw if resolver stalled. - **Resolver bot** – every 10 s: - Watches IPFS CID list; when a new secret appears, triggers both withdrawals. Polling is stateless, HTTP-only, so the demo runs fine on GitHub Pages + a free cron job without needing WebSocket infra.