An extension of 1inch Fusion+ to facilitate Tezos-EVM bidirectional cross-chain swaps, using SmartPy
This project is a demonstration-grade bridge that lets two parties atomically swap assets between Sepolia (Ethereum) and Ghostnet (Tezos).
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.
| 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. |
| 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. |
USDC.approve(FACTORY, 5 USDC).secret0 with the relayer.Resolver.deploySrc with
immutables (hashes, USDC address, 5 USDC, timelocks, deposits)allowedSender = Resolver.FACTORY.addressOfEscrowSrc predicts address.EscrowSrc deployed → pulls maker's 5 USDC using the approve.EscrowHub.create_escrow (value = 0.1 ꜩ deposit).secret0 to IPFS / Slack channel.Fallbacks (publicWithdraw, cancel) work automatically after their timestamps; deposits reward the caller who finishes the swap.
For the MVP we replaced live push events with pollers:
UI (React hook) – every 15 s:
EscrowHub.is_timelock_expired for each stage.EscrowSrc.withdrawn.Relayer cron – every 10 s:
secret0 to a pinned gist + calls Ghostnet withdraw if resolver stalled.Resolver bot – every 10 s:
Polling is stateless, HTTP-only, so the demo runs fine on GitHub Pages + a free cron job without needing WebSocket infra.

