Alps

Agent-managed Uniswap vault turning onchain volume into yield

Alps

Created At

Open Agents

Winner of

Uniswap

Uniswap Foundation - Best Uniswap API integration 3rd place

Project Description

ALPS (Automated Liquidity Positions) is an autonomous concentrated-liquidity vault on Base. Deposit USDC into a single ERC4626 vault and an off-chain agent runs a diversified basket of CL positions for you across Uniswap V3 and V4, monitoring them every five minutes and rebalancing when the context says it's worth it. The vault is live on Base mainnet with three real rebalance bundles already on-chain across all three production pools, including a V4 ETH/USDC pool that uses a dynamic fee hook.

The problem: roughly half of all concentrated-liquidity positions on Uniswap sit out of range at any given time, earning zero fees. CL is by far more capital-efficient than full-range LP, but only while the position is in range. Manually managing a basket of positions across multiple pools is a 24/7 job that very few LPs do well, leaving hundreds of millions in idle liquidity earning nothing. In fact, over 50% of LPs would be better off just holding the underlying tokens.

ALPS removes that operational overhead end-to-end. Three KeeperHub workflows drive the loop: a polling workflow that generates fresh chain context every five minutes and gates the rebalance call on a dynamic gas floor; a reactive workflow that audits every on-chain rebalance with a basket-wide health snapshot the moment LiquidityAdded fires; and a manual-trigger workflow that gives operators a "Run now" override for demos and incidents. Inside the keeper, a five-policy decision engine (range drift, anti-whipsaw cooldowns, realized volatility, idle reserve, cap pressure) decides whether to rebalance, and four Claude-driven narrators (action / rollup / react / signal) curate the engine's reasoning into a high-signal agent feed users can read in real time.

Finally, even a fully compromised agent key cannot withdraw the vault or route liquidity to a non-whitelisted pool: the safety bounds that matter are enforced on-chain.

How it's Made

The codebase is split into five units:

  1. a Foundry contracts package (ALPVault ERC4626, PoolRegistry, and three adapter contracts wrapping Uniswap's V3 NonfungiblePositionManager, V4 PositionManager, and UniversalRouter).
  2. a Bun + Hono keeper that runs the five-policy decision engine and signs transactions as the vault's agent role with viem.
  3. a Bun + Hono backend with sqlite that multiplexes WSS topics and folds chain events into the agent feed.
  4. a Next.js 15 frontend with Reown AppKit + wagmi + viem.
  5. three KeeperHub workflows checked in as JSON snapshots.

Uniswap stack: The keeper hits the Trading API at POST /v1/quote for the optional middle-leg swap of every rebalance, riding the legacy auto-router behavior where the response carries methodParameters.calldata ready to push at UniversalRouter. We strip the four-byte execute(...) selector and feed the inner (commands, inputs, deadline) tuple into our URAdapter contract, which handles Permit2 entirely on-chain (permit2.approve(token, router, amount, deadline) before each swap, max-approve to Permit2 on first use), no off-chain signature, no /v1/swap follow-up. All LP math runs through @uniswap/v3-sdk and @uniswap/v4-sdk: Position.fromAmounts for mint sizing, mintAmountsWithSlippage / burnAmountsWithSlippage for slippage floors, and the V4 SDK's V4Pool(c0, c1, fee, tickSpacing, hooksAddress, …) constructor lets us reason about the ETH/USDC position under the Alphix dynamic-fee hook directly without the SDK treating the hook as opaque.

KeeperHub: The polling workflow chains web3/read-contract (TVL + active pool roster + L2 gas price) → web3/check-balance (agent ETH) → a Condition node with a single inline expression (agentEth > gasPrice * 14_000_000) that short-circuits the keeper call when there isn't enough gas budget for two rebalance bundles. The reactive workflow uses web3/batch-read-contract as a Multicall3 over all three pool keys, then math/aggregate (sum) folds the result array into a deployed-capital total without dropping to a custom code node. Both workflows POST results back to the keeper over a Cloudflare Tunnel.

Agent feed narration: A five-policy engine across three pools produces a lot of raw reasoning per tick; surfacing it unfiltered would bury the user under spam. We curate everything into a high-signal feed using four Claude-driven narrators that fire under different conditions (action when a tx lands, rollup-or-SILENCE on every held tick, react on user deposit/withdraw, signal on external events like the KeeperHub post-rebalance audit). The Sherpa chat surface — where users can ask the agent questions in natural language — runs claude -p as a subprocess and answers from the live agent ring, no streaming-LLM-API plumbing required.

Particularly hacky. The methodParameters.calldata field on /v1/quote is undocumented in Uniswap's new docs (the recommended flow is /quote → /swap, with /swap returning the executable calldata) but the legacy gateway endpoint still honors it — for a contract integration where Permit2 lives on the adapter, the one-shot flow is genuinely the right shape, just relying on undocumented surface. On the KH side, batching three pool-value reads via web3/batch-read-contract + math/aggregate collapses what would otherwise have been three serial reads or a custom Multicall outside KeeperHub into a single in-workflow round-trip. That composition is the integration's defining feature for our use case.

background image mobile

Join the mailing list

Get the latest news and updates