KITE

One-click cross-chain vault deposits using ENS — swap, bridge, deposit in one transaction.

KITE

Created At

HackMoney 2026

Winner of

ENS

ENS - Integrate ENS

Prize Pool

ENS

ENS - Most creative use of ENS for DeFi

Project Description

Kite: Cross-Chain DeFi Made Human Kite is a unified DeFi interface that lets you send tokens to anyone across any EVM chain by just typing their ENS name—no addresses, no manual bridging, no multi-step workflows. It combines LI.FI's cross-chain execution engine with ENS's identity layer to create a payment experience that feels as simple as Venmo, but orchestrates complex multi-chain DeFi operations under the hood. The Core Problem: Today, if you want to send someone tokens on a different chain or deposit into their DeFi vault, you need to:

Ask them for their wallet address Figure out which chain they're on Figure out which token they want Manually swap your tokens to match theirs Manually bridge to their chain Manually deposit into their vault contract (if applicable) Hope you got all the addresses and parameters right

This process takes 15+ minutes, requires deep DeFi knowledge, and is error-prone. Most people give up. Kite collapses this entire workflow into three inputs: a name, a token, an amount. How Kite Works: Kite introduces a two-layer architecture where ENS stores intent and LI.FI executes it. Layer 1: ENS as the Routing Engine Every Kite user sets up their receiving preferences once by writing three custom ENS text records to their name:

kite.preferred_chain → Example: arbitrum (where they want to receive funds) kite.preferred_token → Example: USDC (which token they want) kite.deposit_target → Example: 0x7BfA... (optional vault/LP pool contract address)

These aren't just labels—they're machine-readable routing instructions. When someone searches for alex.eth in Kite, we read these text records from Ethereum mainnet and use them to construct the entire transaction pipeline. This makes ENS the "configuration layer" of DeFi. Your ENS name becomes a programmable payment endpoint. Want to change where you receive funds? Update your ENS text records. Everyone who sends to you automatically adapts. Layer 2: LI.FI as the Execution Engine Once Kite knows where the funds should go (from ENS), it asks LI.FI how to get them there. Here's the flow when Alice (holding ETH on Ethereum) sends to alex.eth (who wants USDC on Arbitrum deposited into an Aave vault):

Route Discovery: Kite calls the LI.FI API with:

Source: ETH on Ethereum (what Alice has) Destination: USDC on Arbitrum (what alex.eth wants) Vault: 0x7BfA... (alex.eth's deposit target from ENS)

LI.FI returns the optimal path: Swap ETH→USDC on Ethereum via Uniswap V3 → Bridge USDC to Arbitrum via Stargate → Deposit USDC into Aave Vault Route Visualization: Kite displays this full pipeline to Alice before execution:

Visual pipeline: [Swap - Uniswap V3] → [Bridge - Stargate] → [Deposit - Aave Vault] Gas breakdown: Swap: $0.15 | Bridge: $0.22 | Deposit: $0.05 | Total: $0.42 Output estimate: 490.2 USDC in vault shares (after fees/slippage) Time estimate: ~30 seconds

Execution (LI.FI Composer): Alice clicks "Confirm" and signs once. The LI.FI SDK executes the entire pipeline:

Step 1: Approve ETH for Uniswap V3 router Step 2: Swap ETH → USDC on Ethereum (Uniswap V3 contract call) Step 3: Bridge USDC from Ethereum to Arbitrum (Stargate bridge initiation) Step 4: Poll bridge status until confirmed on destination Step 5: Deposit USDC into Aave Vault on Arbitrum (vault contract call)

Alice sees real-time progress as each step completes. The entire workflow happens automatically—no manual intervention after the signature. Receipt: Final confirmation shows:

Output: 490.2 USDC deposited into Aave Vault Vault shares received: 489.8 aArbUSDC Transaction hashes for every step (swap tx, bridge tx, deposit tx) alex.eth now holds interest-bearing vault shares on Arbitrum

Alice never needed to know what Arbitrum is, what Stargate is, or how to call a vault contract. She just typed a name and confirmed once. Why This Uses LI.FI Composer: LI.FI Composer is the specific feature that allows a cross-chain route to end with a contract call instead of just a token transfer. Without Composer, the route would end with USDC sitting in alex.eth's wallet on Arbitrum—they'd have to manually deposit into the vault themselves. Composer makes the vault deposit part of the automated pipeline. This is critical for Kite because the goal is never to just move tokens—the goal is to put them to work in DeFi. Every Kite route uses Composer because every recipient has a deposit_target configured via ENS. The technical implementation uses LI.FI's getQuote() API with the vault contract address passed as the toToken parameter. This tells LI.FI to construct a route that terminates in a contract call to that address. We then execute via the LI.FI SDK's executeRoute() function, which handles all the on-chain orchestration. Supported Workflows: Kite currently supports:

Simple transfers: No vault configured → tokens arrive directly in recipient's wallet Vault deposits: Aave, Yearn, or any ERC4626 vault → funds auto-deposit into yield strategies LP provision: Uniswap V2/V3 pools → tokens convert to LP shares Restaking: EigenLayer or similar → tokens stake automatically

All workflows support:

4 EVM chains: Ethereum, Arbitrum, Base, Polygon (expandable to any LI.FI-supported chain) 10+ major tokens per chain: ETH, USDC, USDT, DAI, WBTC, etc. Any source chain to any destination chain (full cross-chain matrix)

User Journeys: Journey 1: Recipient Setup (One-Time)

Open Kite, connect wallet with ENS name (e.g., alex.eth) Navigate to "My Profile" Select preferred chain: Arbitrum Select preferred token: USDC Paste vault address: 0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A (optional) Click "Save" → writes three ENS text records to Ethereum mainnet Done. Anyone can now deposit to alex.eth cross-chain.

Journey 2: Sending Funds

Open Kite, search for alex.eth Kite reads alex.eth's ENS preferences and displays their profile:

"Receives on: Arbitrum" "Prefers: USDC" "Auto-deposits to: Aave Vault ✓"

Click "Send" Select your token (e.g., "ETH on Ethereum, Balance: 2.5 ETH") Enter amount: 0.5 ETH Click "Get Route" → LI.FI finds the best path (1-2 second load) Review route details:

Pipeline: Swap → Bridge → Deposit DEX: Uniswap V3 Bridge: Stargate Gas: $0.42 Output: 490.2 USDC in vault shares

Click "Confirm" → Sign once in MetaMask Watch real-time progress as each step executes Receipt shows final vault shares, all tx hashes, timestamp

Total time: ~60 seconds from search to completion Technical Architecture: Frontend Stack:

React/Next.js: Component-based UI with server-side rendering Tailwind CSS: Utility-first styling with custom design tokens TypeScript: Full type safety across 40+ components

Blockchain Layer:

wagmi: React hooks for all blockchain interactions (accounts, chains, contracts) viem: Low-level Ethereum library (replaces ethers.js) RainbowKit: Wallet connection UI (MetaMask, Coinbase, WalletConnect, etc.)

ENS Integration:

Reading: useEnsText hook fetches text records from Ethereum mainnet Writing: Custom useENSWrite hook that:

Resolves ENS name to resolver contract Encodes three setText() calls for Kite preferences Batches them into one multicall() transaction Automatically switches to Ethereum mainnet if user is on wrong chain

Validation: Client-side checks for valid ENS name format, address validation

LI.FI Integration:

Custom Hooks:

useLifiRoute: Fetches optimal route from LI.FI API with debouncing, caching, deduplication useLifiExecute: Wraps LI.FI SDK execution with real-time progress tracking

Route Logic:

Regular path: getRoutes() for simple swaps/bridges Vault path: getQuote() with vault address as destination (enables Composer)

Execution:

One signature from user triggers full pipeline SDK handles: approvals, swaps, bridges, contract calls Progress events update UI in real-time (pending, executing, completed, failed) Automatic chain switching if user is on wrong source chain

Data Flow: User searches ENS → wagmi reads text records → Parse preferences (chain, token, vault) → Construct LI.FI route request → LI.FI returns optimal path → Display route to user → User confirms → LI.FI SDK executes → Real-time progress updates → Receipt Key Technical Innovations:

Dynamic vault deposits: We pass vault addresses as toToken in LI.FI's getQuote() API, which technically expects token addresses. LI.FI accepts any address, so we exploit this to enable vault deposits without LI.FI needing to explicitly support each vault type. ENS as data source: Most DeFi apps use ENS cosmetically (name display). We use it as the routing configuration layer that drives execution logic. Multi-step progress UI: LI.FI routes are multi-transaction workflows, but we present them as a single atomic action with real-time status per step. Chain switch orchestration: We detect chain mismatches before execution and force wallet chain switches automatically, then wait 1.5 seconds for wallet state to settle before proceeding.

Security & Safety:

All routes show gas estimates, slippage tolerance, and output amounts before execution Users can adjust slippage (default 0.5%) Invalid addresses rejected client-side before API calls Failed steps show detailed error messages with recovery options All transactions are non-custodial (user wallet executes everything)

Why This Matters: Kite proves that DeFi can have a UX as simple as Web2 payments without sacrificing decentralization or composability. By combining ENS (programmable identity) with LI.FI (programmable execution), we've built a system where:

Users configure their preferences once and never manage addresses again Senders don't need to know what chains, DEXs, bridges, or vaults are Complex multi-step DeFi operations happen with one click

This is the future of DeFi onboarding—hide the complexity, surface the value.

How it's Made

Kite is architected as a React/Next.js application that orchestrates two core partner integrations—LI.FI for cross-chain execution and ENS for intent-driven routing—into a unified user experience. Core Architecture & Data Flow: The application follows a hub-and-spoke pattern where ENS acts as the configuration layer and LI.FI serves as the execution engine. When a user searches for a recipient (e.g., alex.eth), we use wagmi's useEnsText hooks to read custom text records (kite.preferred_chain, kite.preferred_token, kite.deposit_target) from Ethereum mainnet. These records aren't cosmetic—they're the routing parameters that feed directly into LI.FI's route calculation. LI.FI Integration (Primary Execution Layer): We built custom React hooks (useLifiRoute, useLifiExecute) that wrap the LI.FI SDK and API. The useLifiRoute hook handles two distinct code paths:

Regular transfers: Calls getRoutes() with standard parameters when no vault is configured Vault deposits: Calls getQuote() with the vault address as toToken when a deposit target exists in ENS

This vault deposit path is where LI.FI Composer shines. By passing the vault contract address as the destination token, LI.FI automatically constructs a multi-step route: swap on source chain → bridge to destination chain → contract call to deposit. We don't manually chain these steps—LI.FI's routing engine handles the orchestration. The useLifiExecute hook wraps executeRoute() from the LI.FI SDK and implements real-time progress tracking via the updateRouteHook callback. We parse step execution status (PENDING, ACTION_REQUIRED, DONE, FAILED) and update our UI state machine accordingly. One particularly tricky aspect was handling chain switching—we added pre-flight validation that detects when the user's current chain doesn't match route.fromChainId and triggers wagmi's switchChainAsync before execution begins. ENS Read/Write Architecture: Reading ENS data is straightforward via wagmi hooks, but writing presented challenges. We built useENSWrite as a custom hook that:

Resolves the ENS name to its resolver contract address using publicClient.getEnsResolver() Encodes three setText() calls (one for each Kite preference) using viem's encodeFunctionData() Batches them into a single multicall() transaction to the resolver Ensures the wallet is on Ethereum mainnet before executing (switches chains if needed)

This multicall approach was critical—it saves users from signing three separate transactions and significantly improves UX. State Management & Route Caching: We implemented aggressive request deduplication in useLifiRoute using refs to track the previous request signature. Routes are only re-fetched when parameters actually change (token, amount, addresses). We also debounce route requests by 500ms to prevent API spam during amount input. A isFetchingRef flag prevents concurrent fetches. Hacky Bits Worth Mentioning:

Vault validation bypass: LI.FI's getQuote() technically expects a token address as toToken, but it accepts ANY address—including vault contracts. We exploited this to enable vault deposits without needing LI.FI to explicitly support each vault. We validate it's a real vault client-side using isValidVault(), but continue even if validation fails (with a warning). This "try anyway" approach maximizes compatibility. Chain switch timing: After forcing a chain switch via MetaMask, we discovered the wallet state doesn't update instantly. We added a hardcoded 1.5-second delay (setTimeout) after switchChainAsync() resolves to let the wallet settle before executing the route. Hacky, but necessary for reliability. Progress step synthesis: LI.FI's route steps don't always map cleanly to user-facing progress. We built a step type classifier (getStepType()) that inspects step.tool, step.type, and step.action to determine if a step is a swap, bridge, or deposit. This lets us show meaningful labels like "Depositing to Vault" instead of raw tool names. Amount parsing edge cases: Users input amounts as strings with varying formats (decimals, commas, etc.). We normalize these using parseUnits() but wrap it in a try-catch that defaults to BigInt(0) on failure. This prevents the route fetcher from crashing on invalid input.

Tech Stack Specifics:

wagmi + viem: All blockchain reads/writes (no ethers.js) RainbowKit: Wallet connection only—we don't use its ENS name display; we resolve everything manually LI.FI SDK v3.0: Latest version with Composer support Tailwind CSS: Component styling with custom design tokens (cyber-yellow, charcoal, etc.) TypeScript: Full type safety across the codebase with custom types for KiteRoute, ENSProfile, ExecutionProgress

Production Considerations: We built this with production viability in mind. Error states are comprehensive (route not found, slippage too high, vault deposit revert). We surface gas estimates per step. We validate addresses client-side before sending to LI.FI. The UI never blocks—route fetching and execution happen in the background with loading states. The hardest technical challenge was making LI.FI Composer routes feel like a single atomic action to the user when they're actually 3+ on-chain transactions. The real-time status updates and visual pipeline display were critical to building that mental model.

background image mobile

Join the mailing list

Get the latest news and updates