Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
153400e
feat(contracts): scaffold foundry project with v4 deps
distroinfinity Sep 19, 2026
b9d3cc7
feat(contracts): add distroguard v4 hook with vol/size-aware dynamic fee
distroinfinity Sep 19, 2026
e7ca3d9
test(contracts): hook init seeding + volatility fee response
distroinfinity Sep 19, 2026
7b8345a
feat(shared): onchain payload, onchain_only mode, xlayer constants
distroinfinity Sep 19, 2026
f908e19
feat(api): onchain positions/events/pools schema + additive migration
distroinfinity Sep 19, 2026
ea9e89c
feat(api): register/list/delete onchain positions
distroinfinity Sep 19, 2026
5c3d524
feat(cli): onchain local keystore + onchain init/status commands
distroinfinity Sep 19, 2026
6c0cc68
feat(cli): lp guard slot renderer
distroinfinity Sep 19, 2026
de58321
feat(contracts): deploy script + hookminer, self-deploys pool/routers…
distroinfinity Sep 19, 2026
a5e7d2c
feat(api): viem xlayer read client for hook vol + tick
distroinfinity Sep 19, 2026
fe9adb2
feat(api): public pool snapshot + hedge action calldata
distroinfinity Sep 19, 2026
c2fa93c
feat(api): onchain slot selection + content dispatch
distroinfinity Sep 19, 2026
9d77732
feat(api): onchain range-breach evaluator + demo fire endpoint
distroinfinity Sep 19, 2026
01e6545
feat(cli): onchain wallet client + runAction one-click swap
distroinfinity Sep 19, 2026
77dc1a2
feat(cli): wire h/e/r keys to one-click onchain actions
distroinfinity Sep 19, 2026
762ecab
feat(cli): onchain hedge/exit/rebalance subcommands (one-click action)
distroinfinity Sep 19, 2026
187d573
docs: onchain lp guard engineering + product docs
distroinfinity Sep 19, 2026
8bb800c
feat(web): onchain lp guard position dashboard
distroinfinity Sep 19, 2026
f023adb
fix(web): onchain dashboard registers on x layer (1952), not base sep…
distroinfinity Sep 19, 2026
a16c2c4
fix(contracts): guard hook with onlyPoolManager + live currentTick vi…
distroinfinity Sep 19, 2026
85e332b
docs: update onchain addresses to redeployed pool + note hook access …
distroinfinity Sep 19, 2026
7376a9e
chore(contracts): untrack vendored lib, install via forge
distroinfinity Sep 19, 2026
12737e8
Merge remote-tracking branch 'origin/main' into feature-onchain
distroinfinity Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist
.next
.turbo
pnpm-lock.yaml
packages/contracts/lib/
2 changes: 2 additions & 0 deletions docs/Dev_Drip_PRD.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Ad-Subsidized AI Coding Tools via Opt-In Idle-Time Monetization

> **Note (post-pivot).** Dev Drip became Distro TV, a channel surface. Channels are the verticals: CH 01 NEWS, CH 02 MARKETS, and now **CH 03 ONCHAIN — LP GUARD** (a Uniswap v4 hook piloted from the terminal). See [product/onchain-lp-guard-prd.md](./product/onchain-lp-guard-prd.md) and [product/onchain-lp-guard-feature-spec.md](./product/onchain-lp-guard-feature-spec.md).

---

## 1. Problem Statement
Expand Down
50 changes: 50 additions & 0 deletions docs/product/onchain-lp-guard-feature-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# CH 03 ONCHAIN — LP GUARD: Feature Spec

Companion to the [PRD](./onchain-lp-guard-prd.md). Engineering detail in [Onchain LP Guard](../../gitbook-docs/architecture/onchain-lp-guard.md) and [CLI: onchain](../../gitbook-docs/cli/onchain.md). All behavior here is **as-built and verified live** on X Layer testnet (chainId 1952).

## User stories

- As a terminal-native LP, I want my Uniswap v4 position **defended on-chain by a fee that reacts to volatility**, so I'm compensated for toxic flow without managing fee tiers myself.
- As a developer with an agent running, I want my **LP position rendered as an ambient channel** while I code, so I keep an eye on it without leaving the terminal.
- As an LP, I want a **terminal alert the instant price exits my range**, so I don't discover a dead position hours later.
- As an LP, I want to **act in one command** (`distro onchain hedge`) that signs and broadcasts, so reacting to a breach is friction-free.
- As a cautious user, I want the hook to be an **advisor, not a custodian** — it never holds or moves my funds.

## Acceptance criteria

- [x] **Hook deployed + verifiable on X Layer.** `DistroGuardHook` live at `0xD8eB6573A3192387dd71c2e646ad23E8DB36d0c0` on chainId 1952; pool `0xe6d570f5…` initialized as a dynamic-fee pool.
- [x] **Fee rises with volatility on-chain.** Demo swaps emitted `FeeApplied` rising `3000 → 3250` as `VolUpdated` `ewmaVolBps` climbed to 53; live snapshot reported `feeBps` 57. Fee = `clamp(3000 + 50×ewmaVolBps + 20×sizeUnit, 3000, 10000)` pips.
- [x] **Range breach → terminal alert.** The 1-min read-only evaluator detects tick outside `[lower, upper]` and pushes a `range_breach` alert through the real ticker alert pipeline; the daemon renders the alert (red) slot variant.
- [x] **One-click hedge broadcasts.** `distro onchain hedge` resolves the active position, fetches unsigned calldata, signs with the local key, and broadcast real swaps (e.g. tx `0xf14c0c9d…`) that moved the hook's on-chain volatility.

## Slot / alert UX

- **Normal slot** (`OnchainPayload`, `kind: "onchain"`) renders pool label, price, tick, range with in/out indicator, live `feeBps`, `volBps`, `feesEarnedUsd`, `ilPct`, `asOf`.
- **Alert slot** carries an `OnchainAlert` (`range_breach` | `near_breach` | `vol_spike`); the renderer wraps the box in red, mirroring the ticker alert path.
- Alerts are **debounced 60 min** per `(device, type)` so a flapping price doesn't spam slots.
- Alerts ride the **existing ticker alert pipeline** (LPUSH-to-device-queue, consume-once LPOP on selection, LPUSH-before-INSERT ordering).

## Action semantics

- **`hedge`** — implemented end-to-end. Server returns unsigned swap calldata targeting the swapRouter; CLI signs + broadcasts.
- **`exit`, `rebalance`** — CLI subcommands exist and are wired, but the server `actions/prepare` returns an error for them today (pending server impl).
- Actions are **client-signed**: the server never holds a key. The CLI uses a local testnet key at `~/.distro/onchain-key.json` (mode 600), created/imported via `distro onchain init`.
- The position is resolved from `--position <id>` or the first `active` position via `GET /me/onchain/positions`.

## Edge cases

- **No key** → CLI prints `run: distro onchain init`, no broadcast.
- **No active position** → CLI prints a hint to register one in the dashboard.
- **Unfunded key** → broadcast reverts on gas/balance; faucet the address (advisor model — no server fallback).
- **`XLAYER_RPC_URL` unset** → backend `ONCHAIN_ENABLED` is false (worker no-op, actions reject); CLI broadcast can't connect.
- **Device offline at breach** → alert sits on the device queue (60-min TTL), delivered on next sync.
- **Redis lpush fails in evaluator** → no `onchain_events` row → next tick re-evaluates without a debounce penalty.
- **Demo** → `POST /__test/fire-onchain` (404 in production) forces a `range_breach` deterministically.

## Out of scope (pilot)

- **Autonomy** — no keeper executing on the user's behalf; the human acts. (Advisor → autonomous is roadmap.)
- **Mainnet** — X Layer testnet only.
- **Multi-position** — actions resolve a single active position; portfolio-wide management is not modeled.
- **Live keypress capture** — the daemon does not capture `h`/`e`/`r`; those keys are wired but dormant. The one-click is the `distro onchain` subcommand.
- **`exit` / `rebalance` server impl** — only `hedge` is implemented server-side.
68 changes: 68 additions & 0 deletions docs/product/onchain-lp-guard-prd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# CH 03 ONCHAIN — LP GUARD: Product Requirements

## 1. Problem

Concentrated-liquidity LPs on Uniswap (v3/v4) face three structural costs that compound while they are away from the screen:

- **Toxic flow.** Informed swappers pick off LPs during volatility. A static fee tier can't widen when the pool turns adversarial, so the LP subsidizes arbitrageurs exactly when it hurts most.
- **Impermanent loss.** Price drift against the position erodes value relative to holding.
- **Range babysitting.** A concentrated position only earns fees while the price sits inside its tick range. The moment price exits, the position stops earning and starts behaving like a single-sided bag — and the LP has no idea until they next check a dashboard.

Terminal-native DeFi LPs and builders live in their editor and terminal, not in a DEX UI. They want their position defended automatically and to be told — where they already are — the instant something needs a decision.

## 2. Target user

Terminal-native DeFi LPs and builders: people running Uniswap v4 positions who spend their day in a coding terminal, often with an AI agent working in the background. They are comfortable with a CLI, a private key, and reading on-chain state — but they don't want to babysit a position or sit in a DEX dashboard.

## 3. Value proposition — LP GUARD

**A Uniswap v4 hook that defends LPs with a volatility-reactive fee, piloted from the terminal you code in.**

- The **hook** raises the swap fee on-chain as the pool gets volatile (and adds a premium for outsized swaps), so the LP is compensated for toxic flow at the moment it arrives — not on a delay, not via a static tier.
- The **terminal** renders the live position — price, range, in/out, current fee, volatility — as an ambient channel while the agent works.
- A **range breach fires a terminal alert**, surfaced where the user already is.
- The user **acts with one CLI command** (`distro onchain hedge`), signing and broadcasting from a local key.

**Advisor model.** The hook sets the fee and emits state; the human decides and acts. The hook never custodies or moves LP funds. This is deliberate — it keeps the trust surface minimal and the loop legible.

## 4. Positioning within Distro TV

Distro TV is a **channel surface** that runs in the developer's terminal while AI tools work. The product is the surface; channels are the verticals. CH 01 NEWS and CH 02 MARKETS were the launch channels. **CH 03 ONCHAIN — LP GUARD is the first channel that is also an on-chain product** — it doesn't just display a feed, it ships a Uniswap v4 hook and a control surface for acting on it.

It validates the channel-surface thesis: a new vertical slots into the same ambient terminal surface (same daemon, same selection path, same alert pipeline) without disturbing the others. ONCHAIN runs independently of the news/markets data pipeline — its own contracts, viem read client, and worker tick.

## 5. UX flows

### Monitor

User selects `onchain_only` (mode `onchain_only`) via `distro channel onchain` or the prefs picker. The terminal renders the LP GUARD slot: pool, price, tick, range with in/out indicator, live fee (bps), volatility, fees earned, IL.

### Alert

A background evaluator checks the live tick every minute. When price exits the position's range, a `range_breach` alert is pushed to the device and the next slot renders the alert variant (red box). Debounced 60 min per breach type so a flapping price doesn't spam.

### One-click act

The user runs `distro onchain hedge`. The CLI resolves the active position, fetches unsigned swap calldata from the API, signs with the local testnet key, and broadcasts to X Layer. The broadcast moves real flow through the pool, which the hook folds back into its volatility signal — the terminal then re-renders the updated state. (Keystroke capture is intentionally off; the one-click is the subcommand, run directly or inline as `!distro onchain hedge` inside Claude Code.)

## 6. Hackathon framing — Build X / Hook the Future

CH 03 was built for the **Build X / Hook the Future** hackathon:

- **X Layer** — deployed and verified live on X Layer testnet (chainId 1952).
- **Uniswap v4** — a real v4 hook (`DistroGuardHook`) with a state-driven dynamic fee, the v4-only capability that makes the product possible.
- **Flap** — part of the hackathon's sponsor/ecosystem framing.

Judged on **Innovation**, **Market**, and **Completion**. The pitch leans on all three: a novel LP-defense primitive (innovation), a real and underserved LP pain (market), and a fully working, on-chain-verified loop from hook to terminal to broadcast (completion).

## 7. Roadmap

- **Advisor → autonomous.** Today the human acts. A future mode could let a funded keeper execute the hedge on breach, with explicit user-set bounds. (Out of scope for the pilot — see the feature spec.)
- **`exit` and `rebalance` actions.** `hedge` is implemented end-to-end; `exit` and `rebalance` are wired in the CLI but await the server-side `actions/prepare` implementation.
- **Mainnet.** The pilot is X Layer testnet only. Mainnet requires a real fee/volatility calibration pass and a hardened key story (no hot key in a dotfile).

## 8. Related docs

- [Onchain LP Guard — engineering](../../gitbook-docs/architecture/onchain-lp-guard.md)
- [CLI: onchain](../../gitbook-docs/cli/onchain.md)
- [Feature spec](./onchain-lp-guard-feature-spec.md)
8 changes: 7 additions & 1 deletion eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ export default tseslint.config(
},
},
{
ignores: ["**/dist/**", "**/node_modules/**", "**/*.config.*", "packages/dashboard/**"],
ignores: [
"**/dist/**",
"**/node_modules/**",
"**/*.config.*",
"packages/dashboard/**",
"packages/contracts/lib/**",
],
}
)
75 changes: 75 additions & 0 deletions frontend/app/dashboard/onchain/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use server"

import { redirect } from "next/navigation"
import {
createOnchainPosition,
deleteOnchainPosition as apiDeleteOnchainPosition,
getPoolSnapshot,
type CreateOnchainPositionBody,
type OnchainPosition,
type PoolSnapshot,
} from "@/lib/dashboard-api"
import { ApiError, UnauthenticatedError } from "@/lib/api"

export interface CreatePositionResult {
ok: boolean
position?: OnchainPosition
error?: string
}

// register form submit calls this, then the client appends the returned row
export async function registerPosition(
body: CreateOnchainPositionBody
): Promise<CreatePositionResult> {
try {
const position = await createOnchainPosition(body)
return { ok: true, position }
} catch (err) {
if (err instanceof UnauthenticatedError) {
redirect("/auth/refresh?next=/dashboard/onchain")
}
if (err instanceof ApiError) {
const b = err.body as { error?: string } | null
return { ok: false, error: b?.error ?? `api_error_${err.status}` }
}
return { ok: false, error: "network_error" }
}
}

export interface DeletePositionResult {
ok: boolean
error?: string
}

// row stop-watching button calls this after optimistic remove
export async function stopWatchingPosition(id: string): Promise<DeletePositionResult> {
try {
await apiDeleteOnchainPosition(id)
return { ok: true }
} catch (err) {
if (err instanceof UnauthenticatedError) {
redirect("/auth/refresh?next=/dashboard/onchain")
}
if (err instanceof ApiError) {
const b = err.body as { error?: string } | null
return { ok: false, error: b?.error ?? `api_error_${err.status}` }
}
return { ok: false, error: "network_error" }
}
}

export interface RefreshSnapshotResult {
ok: boolean
snapshot?: PoolSnapshot
error?: string
}

// live pool panel re-fetch (public endpoint) for optional polling
export async function refreshPoolSnapshot(poolId: string): Promise<RefreshSnapshotResult> {
try {
const snapshot = await getPoolSnapshot(poolId)
return { ok: true, snapshot }
} catch {
return { ok: false, error: "snapshot_unavailable" }
}
}
Loading