← Back to home

Sway

Bidirectional cross-chain swaps between Ethereum and Sui using 1inch Fusion+ extended protocol

Problem Statement

Sway is a production-ready implementation that extends 1inch Fusion+ to enable secure, trustless cross-chain atomic swaps between Ethereum and Sui blockchain. The project bridges the gap between EVM and Move ecosystems by implementing hash-timelock contracts (HTLC) that work seamlessly across both networks.Key features include bidirectional swap capabilities (Ethereum↔Sui), automated event-driven relayer system, and preservation of atomic guarantees through cryptographic hashlocks. The implementation leverages existing battle-tested 1inch contracts on Ethereum while introducing custom Move smart contracts on Sui, ensuring compatibility with both blockchain architectures.Users can swap tokens between chains with complete security - either both transactions succeed or both revert, eliminating the risk of partial execution. The system supports any ERC20 tokens on Ethereum and Sui coin types, with gas-optimized execution and real-time cross-chain event propagation.Built for developers and DeFi protocols seeking reliable cross-chain infrastructure, Sway demonstrates how traditional AMM protocols can be extended to support next-generation blockchains while maintaining security and decentralization principles.

Solution

🏗️ How This Was BuiltThis section explains the technical implementation of extending 1inch Fusion+ to support Sui blockchain.🧠Core Architecture DesignThe implementation bridges two fundamentally different blockchain architectures:Ethereum (EVM): Account-based model with Keccak256 hashingSui (Move): Object-centric model with BLAKE2b hashingKey Challenge: Maintaining atomic swap guarantees across incompatible hash functions and execution models.🔧Technical Components1. Sui Move Smart Contractsshared_locker.move- Maker-initiated swaps:public entry fun maker_lock<T>( coin: coin::Coin<T>, hashlock: vector<u8>, // BLAKE2b hash of secret unlock_date: u64, // Timestamp for refund resolver: address, // Who can claim with secret ctx: &mut TxContext, ) { let locker = SharedLocker { id: object::new(ctx), coin, hashlock, unlock_date, maker: ctx.sender(), resolver, }; transfer::share_object(locker); // Make accessible to resolver } public entry fun claim_shared<T>( locker: SharedLocker<T>, secret: vector<u8>, // Reveals the secret receiver: address, clock_obj: &clock::Clock, ctx: &mut TxContext, ) { assert!(hash::blake2b256(&secret) == locker.hashlock, E_HASH_MISMATCH); let ev = SrcSecretRevealed { secret }; event::emit<SrcSecretRevealed>(ev); // Triggers cross-chain relay }locker.move- Resolver-initiated swaps:public entry fun lock<T>( coin: coin::Coin<T>, hashlock: vector<u8>, unlock_date: u64, receiver: address, ctx: &mut TxContext, ): Locker<T> { Locker { id: object::new(ctx), coin, hashlock, unlock_date, locker: ctx.sender(), receiver, } }2. Hash Compatibility LayerProblem: Sui uses BLAKE2b, Ethereum uses Keccak256 - same secret produces different hashes.Solution: Dual hash generation in TypeScript:// Generate single secret, create both hash types const secret = crypto.randomBytes(32); const hashBlake = blake2b(secret, { dkLen: 32 }); // For Sui const hashKeccak = keccak256(secret); // For Ethereum // Store mapping for cross-chain verification const mapping = { [hashKeccak]: { lockerId: suiLockerId, escrowAddress: ethEscrowAddress, }, };3. Bidirectional Event RelayerCore Logic: Listen for secret reveals on both chains and propagate cross-chain.// Sui Event Listener await suiProvider.subscribeEvent({ filter: { MoveEventType: `${PACKAGE}::shared_locker::SrcSecretRevealed` }, onMessage: async (event) => { const secret = new Uint8Array(event.parsedJson.secret); await submitSecretToEthereum(secret); // Cross-chain relay }, }); // Ethereum Event Listener const filter = escrowContract.filters.SecretRevealed(); escrowContract.on(filter, async (secret, escrowAddress) => { await submitSecretToSui(secret, lockerId); // Cross-chain relay });4. Ethereum IntegrationReused 1inch Contracts: No modifications needed to existing Fusion+ contracts.// Existing 1inch EscrowFactory creates escrows with Keccak256 hashlocks contract EscrowSrc { function withdraw(bytes32 secret) external { require(keccak256(abi.encodePacked(secret)) == hashlock); // Transfer tokens and emit SecretRevealed event } }🔄Cross-Chain Flow ImplementationSui → Ethereum SwapMakercallsmaker_lock()on Sui with BLAKE2b hashlockSystemdeploys Ethereum escrow with corresponding Keccak256 hashlockResolvercallsclaim_shared()on Sui, revealing secretRelayerdetectsSrcSecretRevealedevent, extracts secretRelayercallswithdraw()on Ethereum escrow with same secretAtomic completion: Both chains settle simultaneouslyEthereum → Sui SwapMakerdeploys Ethereum escrow with Keccak256 hashlockResolvercallslock()on Sui with corresponding BLAKE2b hashlockResolvercallsclaim()on Sui, revealing secretRelayerdetects Sui event, submits secret to EthereumAtomic completion: Cross-chain settlement🛡️Security GuaranteesHash SecurityBLAKE2b(Sui): Cryptographically secure, 256-bit outputKeccak256(Ethereum): Battle-tested, same security levelSame Secret: Both hashes derived from identical preimageTimelock Protection// Sui: Timestamp-based expiry assert!(clock::timestamp_ms(clock_obj) < locker.unlock_date, E_CLAIM_TOO_EARLY); // Ethereum: Block-based expiry require(block.timestamp < timelock, "Timelock expired");Atomic GuaranteesEither both succeed: Secret revealed → both chains settleOr both revert: Timelock expires → both chains refundNo partial states: Cryptographic proof prevents selective execution🚀Performance OptimizationsGas EfficiencySui: Object-centric design minimizes state changesEthereum: Reuse battle-tested 1inch contractsRelayer: Batched event processingScalabilityEvent-driven: No polling, pure push-based architectureStateless relayer: Can restart without losing stateParallel processing: Multiple swaps simultaneously

Hackathon

ETHGlobal Unite

2025

Contributors