
ETHGlobal Unite DeFi — How We Won with ICP
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
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
- Maker crafts an order and a 32‑byte secret; the orderbook stores only the hashlock = keccak256(secret).
- Relayer runs Dutch ticks, surfaces best price; Resolver accepts and deploys escrows on both legs (Source/Destination).
- Relayer verifies both escrows (immutables, roles, amounts, timelocks).
- If correct, Relayer reveals the secret to the Resolver.
- 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 forAuctionDetail
,PriceCurve
(points withtime_offset_secs
andprice_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
andDestination
roles mirror 1inch Solidity (private/public withdraw + cancellation windows) and pay asafety_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.
- Spin up
dfx start --clean
- 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
- 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
- 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 })'
- Submit order intent (with secret); Relayer starts Dutch auction immediately
dfx canister call relayer submit_order --argument-file tmp/submit_order_icp.did
- 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
- 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…"
)'
- 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.
Links & Demo
- Repo: https://github.com/reymom/Unite-DeFi-ICP
- Demo video: https://youtu.be/oiNU9ANHoNw
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 withautomatic_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.