Your ENS name becomes your DeFi autopilot. Anyone pays you.eth → USDC lands on Arc → bridges to your preferred chain via Circle Gateway → Across Protocol fills your token split via intents — all configured via ENS text records you control.
Today, receiving crypto payments is broken in three ways:
- Chain fragmentation — "Send me USDC" immediately raises the question: on which chain? Senders have to ask, recipients have to check balances, and mistakes are costly.
- Post-receipt manual labor — After receiving USDC, users manually bridge to their preferred chain and manually swap into their desired token mix. Every time.
- No portable preferences — Your DeFi preferences (chain, slippage, token allocations) live nowhere. They're in your head. Every app makes you reconfigure from scratch.
- Gas on destination chains — Even if bridging is solved, swapping tokens on the destination chain requires gas tokens the sender doesn't have.
ENSRouter turns your ENS name into a self-describing payment endpoint. You set your liquidity preferences once in ENS text records, and any payment to your ENS name automatically:
- Lands as USDC on Arc (the liquidity hub)
- Bridges via Circle Gateway/CCTP to your preferred destination chain (instant, <500ms)
- Swaps into your preferred token allocation via Across Protocol intents on the destination chain — solvers bear the gas cost, not the sender or recipient
The sender just needs your ENS name. Everything else is handled.
1. Connect wallet to ENSRouter dashboard
2. Link your ENS name (e.g., alice.eth)
3. Configure preferences via a clean UI:
- Destination chain: Base
- Token allocation: 50% USDC, 30% ETH, 20% USDT
- Slippage tolerance: 0.5%
- Auto-swap: enabled
4. Dashboard writes these as ENS text records:
- ENSRouter.chain → "base"
- ENSRouter.alloc → "USDC:50,ETH:30,USDT:20"
- ENSRouter.slippage → "0.5"
- ENSRouter.autoswap → "true"
5. Done. Your ENS name is now a smart payment endpoint.
1. Go to ENSRouter "Send" page
2. Type recipient ENS name: alice.eth
3. ENSRouter resolves:
- alice.eth address (from ENS)
- alice's preferences (from ENS text records)
4. UI shows: "Alice prefers to receive on Base as 50% USDC / 30% ETH / 20% USDT"
5. Sender approves USDC transfer on Arc
6. Backend orchestrates:
a. USDC deposited into Circle Gateway on Arc
b. Gateway bridges USDC to Base via CCTP (instant, <500ms)
c. USDC arrives on Base
d. For non-USDC portions, Across Swap API creates intents on Base:
- Intent 1: "Swap 150 USDC → ETH for alice.eth"
- Intent 2: "Swap 100 USDC → USDT for alice.eth"
e. Across solvers/relayers fill the intents (they bear the gas cost)
f. Remaining 250 USDC transferred directly to alice.eth on Base
7. Sender sees: "✅ Delivered to alice.eth on Base"
1. Upload CSV: [alice.eth, 5000], [bob.eth, 3000], [carol.eth, 7000]
2. ENSRouter reads each recipient's ENS preferences
3. Shows preview:
- alice.eth → Base (50% USDC, 30% ETH, 20% USDT)
- bob.eth → Arbitrum (100% USDC)
- carol.eth → Ethereum (70% ETH, 30% USDC)
4. Single approval on Arc → Gateway fans out to all chains
5. Across intents handle token swaps on each destination chain
6. Each person receives funds exactly how they want them
Traditional swaps require someone to pay gas on the destination chain. With intent-based execution via Across:
- The sender only pays gas on Arc (USDC-denominated, predictable)
- The Across solver/relayer bears destination chain gas costs
- The solver recoups gas + earns a spread from the swap execution
- The recipient receives slightly less due to solver spread (transparent, shown upfront)
- Nobody needs native gas tokens on the destination chain
The sender sees the exact breakdown before confirming:
Sending 500 USDC to alice.eth
alice.eth preferences:
Chain: Base | Allocation: 50% USDC / 30% ETH / 20% USDT
Breakdown:
250.00 USDC → delivered as USDC (no swap needed)
148.80 USDC → swapped to ~0.06 ETH via Across
99.10 USDC → swapped to ~99.05 USDT via Across
2.10 USDC → fees (Circle bridge + Across solver spread)
Using custom-prefixed keys per ENS best practices (custom records with app prefix):
| Key | Example Value | Purpose |
|---|---|---|
ENSRouter.chain |
"base" |
Preferred destination chain |
ENSRouter.alloc |
"USDC:50,ETH:30,USDT:20" |
Token allocation percentages (must sum to 100) |
ENSRouter.slippage |
"0.5" |
Max slippage tolerance (%) for Across swaps |
ENSRouter.autoswap |
"true" |
Enable auto-swap on arrival |
ENSRouter.fallback |
"arc" |
Fallback chain if preferred unavailable |
Why this schema works:
- Human-readable when inspecting ENS records directly
- Compact enough to fit ENS text record limits
- Parseable with simple string splitting (no JSON overhead)
- Prefixed with
ENSRouter.to avoid collisions per ENS convention - When
autoswapis"false", only bridging happens (USDC delivered as-is on preferred chain)
┌──────────────────────────────────────────────────────────────────┐
│ FRONTEND (Next.js) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ Profile │ │ Send USDC │ │ Batch Payroll │ │
│ │ Setup Page │ │ Page │ │ Dashboard │ │
│ └──────┬──────┘ └──────┬───────┘ └────────────┬────────────┘ │
└─────────┼────────────────┼───────────────────────┼──────────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────────┐
│ CORE ENGINE (API Routes) │
│ │
│ ┌──────────────┐ ┌───────────────┐ ┌───────────────────────┐ │
│ │ ENS Module │ │ Bridge Module │ │ Swap Module │ │
│ │ │ │ │ │ │ │
│ │ Read/write │ │ Circle │ │ Across Swap API │ │
│ │ text records │ │ Gateway/CCTP │ │ /swap/approval │ │
│ │ via wagmi │ │ + Bridge Kit │ │ │ │
│ │ │ │ │ │ Creates intents on │ │
│ │ Resolve ENS │ │ Arc → dest │ │ destination chain │ │
│ │ name + prefs │ │ chain bridge │ │ Solvers fill + pay gas│ │
│ └──────────────┘ └───────────────┘ └───────────────────────┘ │
└──────────┬───────────────┬───────────────────────┬──────────────┘
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌─────────────────────────┐
│ ENS │ │ Circle Arc │ │ Destination Chain │
│ (Ethereum) │ │ │ │ (Base / Arb / Eth) │
│ │ │ USDC hub │ │ │
│ Text records │ │ Sender pays │ │ USDC arrives via CCTP │
│ storage & │ │ here in USDC │ │ │ │
│ resolution │ │ │ │ │ ▼ │
│ │ │ ▼ │ │ Across solver fills │
│ │ │ Gateway/CCTP │ │ intents, swaps USDC │
│ │ │ bridges to │──│→ into preferred tokens │
│ │ │ dest chain │ │ │ │
│ │ │ │ │ ▼ │
│ │ │ │ │ Tokens delivered to │
│ │ │ │ │ recipient wallet │
└────────────────┘ └────────────────┘ └─────────────────────────┘
| Layer | Tool | Responsibility |
|---|---|---|
| Preferences | ENS | Store & resolve user's chain + token preferences |
| Bridging | Circle Gateway / CCTP / Bridge Kit | Move USDC from Arc to destination chain. Arc is the liquidity hub. |
| Swapping | Across Swap API (intents) | Convert USDC to preferred tokens on destination chain. Solvers bear gas. |
Arc has no DEX liquidity yet — Uniswap, Curve, etc. are testnet participants but pools aren't live. So swaps MUST happen on the destination chain where liquidity exists. Across Protocol is an official Arc crosschain partner (alongside Stargate and Wormhole), making this integration story very clean for judges.
| Layer | Technology | Why |
|---|---|---|
| Frontend | Next.js 14 + App Router | Fast, SSR for ENS resolution |
| Wallet | wagmi v2 + viem + RainbowKit | ENS hooks built-in (but custom ENS code added to qualify) |
| ENS Read | useEnsText from wagmi |
Read text records for any .eth name |
| ENS Write | Resolver contract interaction via viem | Set text records from profile UI |
| Bridge | Circle Gateway / CCTP + Bridge Kit SDK | Arc → destination chain USDC bridge |
| Liquidity Hub | Arc (Circle L1) | USDC entry point, stablecoin gas, sub-second finality |
| Swaps | Across Swap API (/swap/approval) |
Intent-based token swaps on destination chain |
| Swap SDK | @across-protocol/app-sdk |
TypeScript client for quotes, execution, tracking |
| Styling | Tailwind CSS + shadcn/ui | Clean, fast to build |
1. Reading ENS preferences (wagmi):
import { useEnsText } from 'wagmi'
import { normalize } from 'viem/ens'
function useENSRouterProfile(ensName: string) {
const chain = useEnsText({ name: normalize(ensName), key: 'ENSRouter.chain' })
const alloc = useEnsText({ name: normalize(ensName), key: 'ENSRouter.alloc' })
const slippage = useEnsText({ name: normalize(ensName), key: 'ENSRouter.slippage' })
const autoswap = useEnsText({ name: normalize(ensName), key: 'ENSRouter.autoswap' })
return {
chain: chain.data, // "base"
alloc: parseAlloc(alloc.data), // [{token:"USDC",pct:50}, ...]
slippage: parseFloat(slippage.data || "0.5"),
autoswap: autoswap.data === "true",
}
}
function parseAlloc(raw: string | undefined) {
if (!raw) return [{ token: "USDC", pct: 100 }]
return raw.split(",").map(part => {
const [token, pct] = part.split(":")
return { token, pct: parseInt(pct) }
})
}2. Writing ENS preferences (viem — profile setup):
import { namehash } from 'viem/ens'
async function setENSRouterProfile(
walletClient: WalletClient,
ensName: string,
preferences: { chain: string; alloc: string; slippage: string; autoswap: string }
) {
const node = namehash(ensName)
const resolverAddress = '0x...' // ENS Public Resolver
for (const [key, value] of Object.entries(preferences)) {
await walletClient.writeContract({
address: resolverAddress,
abi: resolverAbi,
functionName: 'setText',
args: [node, `ENSRouter.${key}`, value],
})
}
}3. Bridge USDC from Arc → destination chain (Circle Bridge Kit):
import { BridgeKit } from '@circle-fin/bridge-kit'
async function bridgeToDestination(
amount: string,
destinationChain: string,
recipientAddress: string
) {
const kit = new BridgeKit({ /* config */ })
const result = await kit.bridge({
from: { adapter: viemAdapter, chain: "Arc" },
to: { adapter: viemAdapter, chain: destinationChain },
amount: amount,
recipient: recipientAddress,
})
return result // USDC now on destination chain
}4. Swap via Across intents on destination chain:
import axios from 'axios'
import { parseUnits } from 'viem'
// Token addresses on Base (example)
const TOKENS_BASE: Record<string, string> = {
USDC: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
ETH: '0x0000000000000000000000000000000000000000', // native
USDT: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2',
}
async function getAcrossSwapQuote(
inputAmount: string, // in USDC base units
outputToken: string, // token address on destination
destinationChainId: number,
depositorAddress: string
) {
const { data } = await axios.get('https://app.across.to/api/swap/approval', {
params: {
tradeType: 'minOutput',
amount: inputAmount,
inputToken: TOKENS_BASE.USDC, // USDC on destination chain
originChainId: destinationChainId, // same chain — swap only
outputToken: outputToken,
destinationChainId: destinationChainId,
depositor: depositorAddress,
}
})
return {
expectedOutput: data.expectedOutput,
totalFee: data.fees?.totalRelayFee,
approvalTx: data.approvalTx, // ready-to-submit approval tx
swapTx: data.swapTx, // ready-to-submit swap tx
}
}5. Full orchestration — the core engine:
async function executeENSRouterPayment(
senderAddress: string,
recipientEns: string,
totalUsdc: number
) {
// Step 1: Resolve ENS preferences
const profile = await resolveENSRouterProfile(recipientEns)
const recipientAddress = await resolveEnsAddress(recipientEns)
// Step 2: Bridge USDC from Arc → destination chain via Circle Gateway
const bridgeResult = await bridgeToDestination(
totalUsdc.toString(),
profile.chain, // e.g., "base"
recipientAddress
)
// Step 3: Wait for USDC to arrive on destination chain
await waitForBridgeCompletion(bridgeResult)
// Step 4: For each allocation, execute Across swap or direct transfer
for (const { token, pct } of profile.alloc) {
const amount = (totalUsdc * pct) / 100
if (token === "USDC") {
// No swap needed — USDC already on destination chain
await transferUSDC(recipientAddress, amount, profile.chain)
} else if (profile.autoswap) {
// Create Across intent for this token portion
const quote = await getAcrossSwapQuote(
parseUnits(amount.toString(), 6).toString(),
TOKENS[profile.chain][token],
CHAIN_IDS[profile.chain],
recipientAddress
)
// Execute the swap via Across (solver fills it, pays gas)
await executeAcrossSwap(quote)
}
}
}6. Across App SDK setup (alternative to raw API):
import { createAcrossClient } from "@across-protocol/app-sdk"
import { base, arbitrum, mainnet } from "viem/chains"
const acrossClient = createAcrossClient({
integratorId: "ENSRouter", // request from Across team
chains: [base, arbitrum, mainnet],
})
// Get quote
const quote = await acrossClient.getQuote({
inputToken: USDC_ADDRESS,
outputToken: ETH_ADDRESS,
originChainId: base.id,
destinationChainId: base.id, // same-chain swap
inputAmount: parseUnits("150", 6),
})
// Execute deposit
await acrossClient.executeQuote({
walletClient,
deposit: quote.deposit,
})
// Track status
const status = await fetch(
`https://app.across.to/api/deposit/status?depositTxHash=${txHash}`
)- Hero: "Your ENS name. Your rules. Pay anyone, everywhere."
- How it works in 3 steps with animation
- CTA: "Set Up Your Profile" / "Send USDC"
- Connect wallet → detect ENS name
- Form: select destination chain (dropdown), drag sliders for token allocation (pie chart updates live)
- Set slippage, toggle auto-swap
- "Save to ENS" button → writes text records (multiple txns)
- Show live preview of what senders will see
- Input: recipient ENS name
- Auto-resolve: shows recipient's avatar + preferences
- Input: USDC amount
- Preview breakdown:
- USDC portion → direct transfer (no fee)
- Non-USDC portions → Across quote with estimated output + solver spread
- Total fees (bridge + solver)
- Approve + Send button
- Real-time status tracker:
Depositing on Arc → Bridging via Gateway → Swapping via Across → Delivered ✅
- Upload CSV or manually add rows: [ENS name, amount]
- Table shows resolved preferences for each recipient
- Total cost summary with aggregated fees
- Single-click execute all
- Your current ENS preferences (live from chain)
- Transaction history: who paid you, when, what you received
- Edit preferences shortcut
-
npx create-next-app@latest ENSRouter --typescript --tailwind - Install dependencies:
wagmi,viem,@rainbow-me/rainbowkit(wallet + ENS)@circle-fin/bridge-kit(Arc bridging)@across-protocol/app-sdk(intent-based swaps)axios(Across API fallback)
- Set up wallet connection (RainbowKit) with Ethereum + Arc + Base chains
- Create Circle Developer Account at console.circle.com
- Get Arc testnet funds from faucet.circle.com
- Request Across integrator ID (proceed without it — add later)
- Set up project structure:
/app/profile,/app/send,/app/batch,/app/dashboard
- Build
useENSRouterProfilehook (read text records viauseEnsText) - Build profile setup form with live pie chart for allocation
- Implement
setTextcalls to write preferences to ENS resolver - Test with a real .eth name on Sepolia/mainnet
- Build ENS preview card component (shows name + avatar + preferences)
- IMPORTANT: Use wagmi hooks directly (not just RainbowKit) — this is required for ENS bounty qualification
- Implement USDC deposit flow on Arc
- Integrate Circle Bridge Kit for Arc → destination chain bridging
- OR integrate Circle Gateway API directly for instant USDC bridging
- Build the route planner: read ENS prefs → determine bridge path → estimate fees
- Build real-time bridge status tracker
- Set up Across App SDK client with supported chains
- Implement
getAcrossSwapQuote()for each non-USDC token in allocation - Build the swap executor that creates intents per allocation split
- Wire up the complete Send page: ENS lookup → preview with Across quotes → approve → bridge → swap → status
- Handle edge cases:
- 100% USDC allocation (no swaps, bridge only)
autoswap = false(bridge USDC only, skip Across)isAmountTooLowfrom Across API (show error, suggest higher amount)- Unsupported tokens on destination chain
- Build CSV upload + parsing
- Multi-recipient ENS resolution (parallel reads)
- Batch execution with progress tracking per recipient
- Error handling and retry logic
- Transaction history view
- Responsive design pass
- Loading states, error toasts, success animations
- "Edit Profile" flow from dashboard
- End-to-end test: set profile → send → bridge → swap → receive on correct chain
- Record demo video (screen recording + narration)
- Test on Arc testnet with real Circle Gateway
- Test Across swaps on Base/Arbitrum testnet
- Fix any broken flows
- Clean architecture diagram (for submission)
- Write product feedback for Circle (required by bounty!)
- Write README with setup instructions
- Push to GitHub (open source, required by both bounties)
- Record final demo video (2-3 minutes)
- Write project description for ETHGlobal showcase
- Submit to bounties:
- ENS: "Integrate ENS" ($3,500 pool) + "Most Creative ENS for DeFi" ($1,500)
- Arc: "Best Crosschain Financial Apps Using Arc as Liquidity Hub" ($5,000)
| Priority | Feature | Keep/Cut | Notes |
|---|---|---|---|
| P0 | ENS text record read/write | MUST KEEP | Required for ENS bounty |
| P0 | Single send flow with ENS resolution | MUST KEEP | Core product |
| P0 | Arc USDC deposit + Circle Gateway bridge | MUST KEEP | Required for Arc bounty |
| P0 | Circle product feedback | MUST KEEP | Required for Arc bounty qualification |
| P1 | Across intent-based swaps | KEEP | Core differentiator, solves gas problem |
| P1 | Real-time status tracker | KEEP | Great for demo |
| P1 | Send preview with fee breakdown | KEEP | Shows product polish |
| P2 | Batch payroll | CUT IF NEEDED | Nice to have |
| P2 | Dashboard/history | CUT IF NEEDED | Nice to have |
| P3 | Multiple destination chains | Can hardcode to Base only | Simplify |
| P3 | Fancy landing page | CUT | Go straight to app |
Minimum viable demo (~10 hours): Profile setup (ENS write) + Single send with ENS resolution + Arc → Base bridge via Circle Gateway + Across swap on Base + Delivery. No batch, no dashboard. Still qualifies for all bounties.
Even leaner MVP (~7 hours): Same as above but with autoswap = false only (bridge USDC to preferred chain, no token swaps). Add Across swaps as "V2 feature" in demo. Still qualifies for both bounties but weaker on the "creative ENS" prize.
- Custom ENS code beyond RainbowKit (wagmi
useEnsTexthooks) - Functional demo, no hard-coded values
- Video recording or live demo link
- Open source on GitHub
- ENS used beyond simple name → address mapping
- Text records store DeFi preferences (chain, allocation, slippage)
- ENS is central to the product, not an afterthought
- Novel application: ENS names as self-describing payment endpoints
- Required tools: Arc ✅, Circle Gateway ✅, USDC ✅, Circle Wallets ✅
- Functional MVP with frontend + backend
- Architecture diagram
- Product feedback for Circle (clear and actionable)
- Video demonstration + presentation
- GitHub repo link
- App treats multiple chains as one liquidity surface
- Arc used as hub to move USDC where needed
- Seamless UX despite crosschain complexity
For ENS judges:
- Goes way beyond name → address resolution
- Uses ENS text records as a portable DeFi preference layer (novel!)
- Custom text record schema shows deep understanding of ENS capabilities
- Not an afterthought — ENS IS the product
- Users' wallets become their DeFi identity
For Arc/Circle judges:
- Arc is the central USDC liquidity hub (exactly what they asked for)
- Uses Circle Gateway for instant crosschain bridging (their flagship product)
- Uses Circle Bridge Kit SDK (shows familiarity with their developer tools)
- Real use case: crosschain payments with preference-aware routing
- Demonstrates chain abstraction: sender on Arc, recipient on any chain
- Across is an official Arc crosschain partner — this integration is blessed
For the "creative" factor:
- Nobody has turned ENS names into self-describing payment endpoints before
- The concept is simple to explain in a 2-min demo
- Visual demo is compelling: type a name → see preferences → watch money flow across chains
- Intent-based architecture means zero gas friction for users
| Decision | Rationale |
|---|---|
| No swaps on Arc | No DEX liquidity on Arc yet (Uniswap, Curve are testnet-only participants). Swaps must happen on destination chains where pools exist. |
| Across for swaps, not Uniswap SDK | Across uses intent-based execution: solvers bear gas costs, users never need destination chain gas tokens. Also, Across has a simple REST API (/swap/approval) vs. complex Uniswap contract interactions. |
| Across over UniswapX | Across is an official Arc partner, has better docs for third-party integrators, provides ready-to-submit transactions via API, and has an App SDK. |
| Circle Gateway for bridging (not Across bridge) | Arc bounty requires Circle tools. Using Gateway ensures qualification. Across handles only the swap layer on the destination chain. |
| ENS text records (not a database) | Fully decentralized, portable across any app. User owns their preferences. No backend database needed. Perfect for hackathon scope. |
Custom ENSRouter.* prefix |
Per ENS convention for custom records. Avoids collisions with other apps. |
- ENS Subnames for teams:
alice.company.ethwith inherited default preferences - Streaming payments: Combine with Superfluid for ENS-configured salary streams
- Privacy mode: Use Arc's opt-in privacy features for confidential payroll
- Agent integration: AI agents that can pay any ENS name without knowing chain details
- Widget/SDK: Embed a "Pay me" button on any website that reads ENS preferences
- Multi-stablecoin support: EURC, USDT as input currencies (Arc supports EURC gas)
- Conditional preferences: "If amount > 10k, route to Ethereum. Otherwise, Base."