ETHGlobal Unite DeFi — How We Won with ICP

ETHGlobal Unite DeFi — How We Won with ICP

8/8/202510 min • hackathons
ICPETHGlobal1inchDeFiCross-chainFusion+

TL;DR

  • I rewrote the entire Fusion+ relayer/auction/escrow flow as Internet Computer canisters: Orderbook, Relayer, Resolver, Factory, and a universal Escrow.
  • Everything runs on‑chain on ICP; EVM/BTC legs are reached via Chain‑Fusion HTTPS outcalls (no off‑chain servers).
  • We won 🥈 2nd prize extending 1inch Fusion+ to ICP. This post documents the architecture, why ICP was the right choice, and the roadmap to production.

Recently I participated in an online ETH Global hackathon mainly featured by 1inch and I focused on something I was thinking about for a while since I first got exposed to 1inch in ETHGlobal Prage some months prior to that. Although I didn't attack that bounty back then, I started wondering how to extend 1inch fusion+'s protocol to ICP. I have already been working in a project for almost a year on the ICP blockchain where I am building a multichain p2p onramping platform so I have gained some experience and gathered some useful insights and knowledge on ICP's setup and framework.

The Unite Defi hackathon featured bounties to extend fusion+ swaps to other chains, so I picked the task of implementing the extension of 1inch swap to ICP. Although I did not focus specifically on the end-to-end swap, I implemented many canisters on ICP to bring many offchain infra needed to secure the swaps in 1inch network onchain using ICP's capabilities. In this writeup I want to summarize my endgoals surrounding all those canisters and explain and showcase each one of them, showcasing ICP's used features and delving into 1inch fusion+ whitepaper along the way of implementing it.

If you follow along you will get some insights into:

  • 1inch fusion+ whitepaper theory and implementation details.
  • ICP chain fusion and fully-onchain capabilities.
  • A modular and scalable canister development framework on ICP.

Feel free to add suggestions and improvements! You can add a PR directly into my repository. And also, don't doubt to contact me if you want to collaborate and bring this project further away.

Why ICP for Fusion+

Fusion+ shines when intents are matched quickly, safely, and credibly. ICP contributes:

  • On‑chain automation: deterministic timers (5s ticks), background jobs in canisters, and stable memory for registries.
  • Chain‑Fusion HTTPS outcalls: JSON‑RPC to EVM and Bitcoin endpoints directly from canisters—no off‑chain relayer boxes.
  • Native ECDSA: canisters sign secp256k1 for EVM flows; clean CREATE2 verification from Rust.
  • ICRC‑1/2 ledgers: standard token flows; ICRC‑2 enables auto‑pull escrows (no manual transfer UX).
  • Upgrade‑safe state: StableBTreeMap across canisters: Orderbook, Factory registry, Escrow state.

This lets us move 1inch’s off‑chain relayer/resolver logic on‑chain without sacrificing composability or transparency.

Architecture Overview

Unite Escrow architecture

Factory → Escrow → 1inch/EVM/Bitcoin

Roles & Canisters

  • Orderbook — Dutch‑auction engine; stores intents and exposes a price curve (piecewise linear multipliers over time).
  • Relayer — pulls intents, ticks auctions, elects provisional winners, guards the secret, verifies escrows on both legs, and finally reveals the secret.
  • Factory — deploys universal Escrow canisters (ICP/ICRC) and keeps a registry. Funds can be pulled via ICRC‑2 or locked manually (ICRC‑1).
  • Escrow — hash‑time‑lock contract with safety deposit: withdraw(secret) and symmetric cancel windows to match 1inch Solidity.
  • Resolver(s) — permissionless on‑chain bots that accept prices, deploy escrows on EVM and ICP, and request the secret once both legs verify.

Cross‑chain

  • EVM: Relayer/Resolver use HTTPS outcalls to RPC (e.g., Arbitrum Sepolia) to verify constructor immutables and event logs.
  • Bitcoin: HTLC P2WSH scripts are generated/checked from Rust (scaffolded during the hackathon). Broadcasting and monitoring also via HTTPS.

Secret & Hashlock Lifecycle

  1. Maker crafts an order and a 32‑byte secret; the orderbook stores only the hashlock = keccak256(secret).
  2. Relayer runs Dutch ticks, surfaces best price; Resolver accepts and deploys escrows on both legs (Source/Destination).
  3. Relayer verifies both escrows (immutables, roles, amounts, timelocks).
  4. If correct, Relayer reveals the secret to the Resolver.
  5. Resolver withdraws from both escrows using withdraw(secret); safety deposit flows as per role.
+-------------+        list_active_auctions()        +--------------+
|  Resolver   | <----------------------------------- |    Relayer   |
+-------------+                                      +--------------+
       | accept_price(order_hash) if willing
       v
+-------------+       deploy escrows       +--------------+
|  Resolver   | -------------------------> |   ICP/EVM    |
+-------------+                            |   Escrows    |
       |                                    +--------------+
       | verify_and_reveal_secret()
       v
+-------------+ <------------------------- +--------------+
|  Resolver   |          secret            |    Relayer   |
+-------------+                            +--------------+
       | withdraw(secret) both chains

Per‑Canister Deep Dive

1) Orderbook (Dutch Auction)

  • State: StableBTreeMap<OrderId, Order> with compact structs for AuctionDetail, PriceCurve (points with time_offset_secs and price_multiplier).
  • Tick: piece‑wise linear interpolation to compute current multiplier. Configurable waiting_period, auction_start_rate, min_return_amount.
  • APIs: add_order, get_order, list_orders.

Example order args (abridged)

record {
  maker_asset = variant { ICP };
  taker_asset = variant { Erc20 = record { chain_id = 421614; address = opt "0x…" } };
  making_amount = 100_000_000_000 : nat; # e8s
  auction_start_at = 1725000000 : nat64;
  waiting_period = 300 : nat64;
  price_curve = record {
    points = vec {
      record { time_offset_secs = 0;   price_multiplier = 1.20 };
      record { time_offset_secs = 300; price_multiplier = 1.00 };
      record { time_offset_secs = 600; price_multiplier = 0.80 };
    }
  };
  timelocks = record { finality_lock = 60; withdrawal = 300;  };
  hashlock = vec { …32 bytes… };
}

2) Factory

  • Responsibilities: store escrow WASM; create new escrows with typed EscrowParams; keep a registry for lookups.
  • Cycle‑aware: top‑up friendly; deploy then install WASM with initialization args.
  • ICRC‑2 path: auto‑pull on init when allowance is set; ICRC‑1 path: manual lock() after funding.

3) Universal Escrow

  • Assets: ICP / any ICRC‑1/2 token.
  • Symmetric logic: Source and Destination roles mirror 1inch Solidity (private/public withdraw + cancellation windows) and pay a safety_deposit to the executor.
  • API: get_escrow, withdraw(secret), cancel(), rescue_after_public_cancel().

4) Relayer

  • Auction brain: imports intents from Orderbook; runs 5s tick() timer; computes current price and provisional winner.
  • Secret guardian: stores the maker secret; only reveals after both escrows verify.
  • Cross‑chain verification: encodes/validates EVM constructor immutables (encode_src|dst_immutables) and CREATE2 address math; checks ICP escrow params from Factory registry.
  • API: submit_order(order, secret), accept_price(order_hash, resolver_addr), verify_and_reveal_secret(evm_escrow, icp_escrow, order_hash, resolver_evm).

5) Resolver (Manual or Automatic)

  • Manual demo mode: deploy EVM escrow via HTTPS‑RPC; deploy ICP escrow via Factory; then call verify_and_reveal_secret().
  • Automatic mode: configurable policy:
automatic_tick = {
  max_slippage_bps = 100;       # 1%
  poll_interval_sec = 30;       # cadence
  min_profit_icp = 1_000_000;   # 0.01 ICP floor
  filters = { allow_assets = []; deny_assets = [] };
}

This enables a permissionless network of resolvers competing on auctions with risk‑controlled strategies.

End‑to‑End Demo (Local, Reproducible)

Full scripts live in the repo under docs/. Below is the condensed path judges saw live.

  1. Spin up
dfx start --clean
  1. Deploy Orderbook and Relayer
# orderbook
cargo build -p orderbook --target wasm32-unknown-unknown --release
candid-extractor target/wasm32-unknown-unknown/release/orderbook.wasm > backend/orderbook/orderbook.did
dfx deploy orderbook
 
# relayer (init with RPC + orderbook id)
dfx deploy relayer --argument-file tmp/relayer_init.did
  1. Factory + Escrow WASM + Local ICP ledger
cargo build -p factory -p escrow --target wasm32-unknown-unknown --release
candid-extractor target/…/factory.wasm > backend/factory/factory.did
candid-extractor target/…/escrow.wasm  > backend/escrow/escrow.did
 
dfx deploy factory --with-cycles 1_000_000_000_000
python3 scripts/gen_escrow_arg_did.py
 
dfx canister call factory set_escrow_wasm --argument-file tmp/escrow_arg_hex.did
  1. Mint test ICP; approve Factory (ICRC‑2)
dfx deploy icp_ledger_canister --argument "(variant { Init = record { … }})"
dfx canister call icp_ledger_canister icrc2_approve '(record { … amount = 100_000_000_000 })'
  1. Submit order intent (with secret); Relayer starts Dutch auction immediately
dfx canister call relayer submit_order --argument-file tmp/submit_order_icp.did
  1. Resolver accepts price and deploys escrows (EVM+ICP)
dfx canister call relayer accept_price '("0x…order_hash…", "0x…resolver_evm…")'
# ICP leg via Factory; EVM leg via Resolver RPC call
  1. Relayer verifies immutables → returns secret
dfx canister call relayer verify_and_reveal_secret '(
  "0x…evm_escrow…",
  "…icp_escrow_principal…",
  "0x…order_hash…",
  "0x…resolver_evm…"
)'
  1. Withdraw both legs with the secret
# ICP
dfx canister call <ESCROW_ID> withdraw '(blob "0x…32‑bytes…")'
# EVM (manual helper on Resolver for demo)
dfx canister call resolver withdraw_evm_manual '("0x…order_hash…", blob "0x…")'

The funds and safety deposits move exactly according to 1inch escrow semantics.

What Went Well vs. What I’d Change

Went well

  • Full ICP leg is 100% on‑chain and modular; per‑role canisters with minimal trust surfaces.
  • Secret flow is provably correct and symmetric; escrow registry makes audits straightforward.
  • Resolver policy knobs (min_profit_icp, max_slippage_bps, asset filters) make decentralization practical.

Change next

  • Ship a thin UI for resolvers/makers; today’s CLI is fine for power users only.
  • Roll EVM escrow on Arbitrum Sepolia + CREATE2 verifiers in relayer.
  • Add partial fills and multi‑winner splits in the Orderbook (stretch goal).

Why This Matters to 1inch (Grant Angle)

Today: Fusion+ relies on a curated, KYC’d relayer set.

Proposal: Keep Fusion+ semantics but offer an optional fully on‑chain resolver/relayer layer on ICP:

  • Credible neutrality — auctions, secrets, and reveals happen in publicly verifiable canisters.
  • Permissionless resolver market — anyone can run a resolver with risk controls; fees and safety deposits align incentives.
  • Operational simplicity — no off‑chain service to host; Chain‑Fusion brings RPC right into canisters.
  • Composable — other IC DeFi protocols can integrate intents natively (vaults, perps, treasuries).
  • Resilience — canisters upgrade safely; state is durable; timers are deterministic.

Net effect: broaden Fusion+’s reach without weakening guarantees, while opening new liquidity venues on ICP.

Roadmap (Grant Milestones)

M1 — EVM Escrows on Arbitrum Sepolia (4–6 weeks)

  • Deploy Source/Destination escrows; finalize CREATE2 + event verification in Relayer.
  • Happy‑path bidirectional swaps (ICP ↔ ETH/USDC). CI + reproducible local+testnet scripts.

M2 — Production Resolver Network (4–6 weeks)

  • Automatic Resolver with policy DSL, gossip for auction discovery, and slippage/profit guards.
  • Monitoring + metrics (success rate, reveal time, MEV windows). Partial fills prototype.

M3 — UI + Docs + Security Hardening (4–6 weeks)

  • Minimal Maker/Resolver web UI (Vite + React), wallet flows for ICRC‑2 approvals.
  • Formalized audits for Escrow/Factory; invariant tests; fuzzing secret/timeout edges.
  • Mainnet rollout plan with phased limits.

Dev Notes & Edge Cases

  • Timelocks: finality_lock → withdrawal → public_withdrawal → cancellation → public_cancellation mirrors 1inch; both legs must be configured consistently.
  • ICRC‑1 vs ICRC‑2: auto‑pull only when spender allowance is set; else fall back to explicit lock().
  • Verification: verify_and_reveal_secret rejects mismatched immutables, wrong roles/amounts, or out‑of‑window escrows.
  • Latency: 5s relayer ticks felt good locally; remote RPC variance suggests adaptive backoff in production.

Timestamps

  • 00:00 — Architecture (Orderbook ▸ Relayer ▸ Factory ▸ Escrow ▸ Resolver)
  • 04:24 — Local dfx swap test: deploy orderbook
  • 06:20 — Factory deployment and escrow WASM upload
  • 07:14 — Relayer config and deployment
  • 08:05 — Resolver (manual)
  • 08:45 — Submit order intent
  • 10:45 — Dutch auction: accept_price
  • 11:30 — Deploy escrows; relayer verification
  • 14:00 — Withdraw ICP + mocked EVM leg
  • 14:30 — Why this matters

Appendix — Local Repro Cheat‑Sheet

Full versions in docs/:

  • Orderbook: build, deploy, add_order, list_orders.
  • Relayer: init with RPC + orderbook id; submit_order; list_active_auctions; accept_price.
  • Factory/Escrow: set escrow WASM; create_escrow; get_escrow; withdraw(secret).
  • Resolver: manual deploy_evm_escrow; automatic mode with automatic_tick policy.
# Start network
$ dfx start --clean
 
# Quick calls you’ll actually use while testing
$ dfx canister call relayer submit_order --argument-file tmp/submit_order_icp.did
$ dfx canister call relayer accept_price '("0x…", "0x…")'
$ dfx canister call factory create_escrow --argument-file tmp/create_icp_source_escrow.did
$ dfx canister call relayer verify_and_reveal_secret '("0x…evm","…icp","0x…order","0x…resolver")'
$ dfx canister call <ESCROW_ID> withdraw '(blob "0x…32bytes…")'

If you’re from 1inch and want a live walkthrough or code deep‑dive, ping me—happy to demo resolvers racing on a local network and discuss a phased mainnet rollout.

Stay Updated

Get notified when I publish new articles about Web3 development, hackathon experiences, and cryptography insights.

You might also like

icRamp Devlog 12 — Milestone Submission: Solana P2P Onramping (SOL + BONK)

Final wrap-up for the Solana Integration milestone: 5-min demo, slides, deliverables checklist, tests, and canister URLs.

icRamp Devlog #6 — icRamp Orders with Solana

Everything is ready for us to create orders in the frontend containing solana and executing the full offramping flow.

icRamp Devlog #4 — icRamp frontend Deployment Setup with Solana

Third Chain Fusion grant log: wiring icRamp's core backend with the Solana canister, persisting canister IDs, and preparing escrow flows for SOL/SPL assets.