Skip to content

feat(automation): generic Uniswap v4 liquidity module + OUSD/USDe one-tick Kyber pricing - #3036

Merged
clement-ux merged 5 commits into
masterfrom
clement/ousd-usde-v4-liquidity-module
Oct 19, 2026
Merged

clement-ux merged 5 commits into
masterfrom
clement/ousd-usde-v4-liquidity-module

Conversation

@clement-ux

@clement-ux clement-ux commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Why

The OUSD/USDe v4 position ([0, 5], module 008, keeper every 30 min) has made about $0 since launch. The pool is fee 0 and the position stays two-sided between pulls, so bots buy back almost everything it buys at the same price (Oct 13–18: 125k USDe bought, 114k bought back). Its first tick also sits at par, so arbitrageurs fill it with no margin.

We are changing the pricing to one tick, re-ranged every 2 minutes on the KyberSwap bid (+1 bp), capped 3 bp below what Ethena pays to redeem USDe, and following the market down during a depeg. Module 008 cannot move its range, so a new module is needed. Design decision: the module holds no pricing logic (no floor, NAV or tick bounds); Talos owns pricing, so future pricing changes (or new tokens) never need a redeploy.

Backtest at the Safe's current capital (~$50k), Safe-only P&L: ≈ $2.1k/yr over 13.5 months (vs $1.0k for a static [4, 5] and −$1.3k for the current [0, 5]). No difference over the last 60 days (no depeg). Analysis: private page shared separately.

What

The pricing model in one picture

We want to buy USDe with OUSD as cheaply as possible, while still being the best bid on the market so sellers come to us.

Every 2 minutes, Talos:

  1. Asks the market what a seller gets for 10k USDe today (KyberSwap USDe → USDC, our own v4 pools excluded).
  2. Bids 1 bp better, converted into OUSD at what OUSD sells for (OUSD → USDC exit rate).
  3. Never pays more than what Ethena would give us for that USDe (par at best) minus 3 bp.
  4. Puts all its OUSD on a single tick at that price.
  5. As soon as a seller fills it, pulls the position and re-posts it. The USDe we bought goes to the Safe, so nobody can buy it back from the pool.
price = min( Kyber bid × 1.0001 / OUSD exit rate ,  min(1, Ethena redeem rate) × 1.0001^-3 )
tick  = first tick at or below that price (we never pay more than the price)
Market Kyber bid Ethena redeem Result We pay (OUSD per USDe)
Calm (most of the time) 0.99978 0.99998 (≈1 USDC flat fee) cap binds → tick 4 0.9996
USDe depeg 0.997 0.99998 follow the market → tick 28 0.9972
Ethena charges a redeem fee (e.g. −10 bp, Oct 1) 0.99978 0.999 cap follows Ethena → tick 14 0.9986

In short, we are the best bid by 1 bp, but never above 3 bp under what we can redeem the USDe for. Two rules make it profitable:

  • The cap keeps a margin. Every USDe bought is worth at least 3 bp more than it cost.
  • The fast pull makes buying one-way. Today's [0, 5] position stays two-sided for 30 minutes, so bots sell us USDe and buy it straight back at the same price.

Without a market quote, Talos keeps the current tick and only pulls fills. Without an Ethena quote, the cap is taken from par. When a redemption costs more than 2 bp, the run warns to hold the USDe until the quote is back to normal. Redemption itself is still manual.

UniswapV4LiquidityModule (deploy 010): the executor

The module does not price anything. It does what Talos asks in one call, rebalance(params): burn the Safe's position, optionally swap the pool price back down to the new range, then mint the new Safe position. Its guardrails only protect funds:

  • it acts on hookless pools only;
  • it burns only Safe-owned positions in that pool;
  • every token ends up in the Safe;
  • maxPerDay[token], set by the Safe, bounds the Safe's net outflow of each token per UTC day (200k OUSD initially).

Since all pricing lives in Talos, a pricing change never needs a redeploy. The module has no pool allowlist, per-call cap or pause: the daily cap and disableModule already cover those. Accepted residual risk: a compromised operator can buy at a bad price, bounded by maxPerDay.

ousdUsdeV4Rebalance Talos action (cron */2, seeded disabled)

Implements the model above. Every number is a flag that can be changed from the Talos UI:

Flag Default Role
--offset-bps 1 how much better than Kyber we bid
--cap-bps 3 margin below the Ethena redemption value
--quote-size / --exit-size 10000 sizes of the Kyber reference quotes
--redeem-size 50000 size of the Ethena redemption quote
--max-redeem-cost-bps 2 above this, warn to hold the USDe
--min-token1 500 USDe filled before we pull and re-post
--max-drift 1 ticks the target must move before we follow it
--target-ousd 50000 OUSD put on the tick (capped by the Safe balance and the daily room)

New shared helper: tasks/lib/v4Liquidity.ts (position math).

Core contracts

No change to the Vault, OTokens, Morpho or the Curve AMO. The module sits on the OUSD v4 liquidity Safe (0x13eb…eB26). No governance proposal.

Deploy and Safe steps (simulated in 010._fork())

  1. make deploy-mainnet (010).
  2. Safe: enableModule, setMaxPerDay(OUSD, 200k). The OUSD → Permit2 → PositionManager approvals already exist from 008/009.
  3. Talos: run ousdUsdeV4Rebalance --dryrun, then disable ousd_v4_rebalance and enable ousd_usde_v4_rebalance; then the Safe runs disableModule(008).
  4. The first rebalance takes position 457273 over (burn + mint at tick 4).

Talos impact (CLAUDE.md checklist)

  • Deployed: UniswapV4LiquidityModule only. The descriptor deployments/mainnet/UniswapV4LiquidityModule.json is written by make deploy-mainnet and read by the new action.
  • ousdV4Rebalance (module 008 descriptor) is to be disabled, not changed.
  • ABIs stay inline in the action; no abi/*.json change; no pinned address added.
  • Talos UI: the new flags (--cap-bps, --offset-bps, --exit-size, --redeem-size, --max-redeem-cost-bps) are whitelisted in oplabs/talos#56.
  • Not covered here: redeeming bought USDe at Ethena and minting OUSD back (still manual). The action reads Ethena's RFQ quote (the proxy behind Grafana ethena_rfq_price) for the cap only.

Tests

  • Unit: 24 (constructor, Safe setting, rebalance paths and reverts, net-outflow fuzz).
  • Fork (live Safe + live pool, FORK_BLOCK_NUMBER_MAINNET=26220000): takeover to [4, 5], fill then pull, follow the market down to [30, 31], daily cap revert. Note: a tiny third-party LP sits in the pool between ticks 0 and 4.
  • Smoke (DeployManager runs 010): wiring, caps, live takeover.
  • Task tests: 25 (tasks/test/ousd-usde-v4-rebalance.js); full pnpm test:tasks 150 passing; full unit suite 2322 passing.
  • Module 009: its fork and smoke tests on position 457274 now vm.skip once that position is burned (it was on Oct 15; module 009 is due for a revamp).

🤖 Generated with Claude Code

…ne-tick Kyber pricing

Add UniswapV4LiquidityModule (deploy 010), a Safe module that burns a Safe
position, optionally resets the pool price and mints a new Safe position on
any hookless pool the Safe allowed. It holds no pricing logic: per-call and
per-day caps (net outflow of each token from the Safe), the pool allowlist
and a pause are all set by the Safe, so a pricing change never needs a
redeploy.

Add the ousdUsdeV4Rebalance Talos action: every 2 minutes it keeps the
OUSD/USDe position on one tick just above the KyberSwap bid (+1 bp, divided
by the OUSD exit rate), capped at par - 4 bp in ticks, pulls fills before
bots can buy them back and follows the market down during a depeg. It
replaces ousdV4Rebalance on that pool; the schedule is seeded disabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clement-ux and others added 4 commits October 19, 2026 11:45
…y cap

Remove what the per-token daily cap already covers: the pool allowlist (a
hookless check stays in rebalance), maxPerCall, the pause (the Safe disables
the module instead), the inflow credit on the daily counter, the convenience
views and the redundant constructor checks. Talos now reads spentOn/maxPerDay
and the pool price from the PoolManager itself. The reset in both directions
and amount1Max stay, so pricing changes still need no redeploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… is burned

The module burned the OUSD/sUSDe position 457274 on Oct 15 2026, so these
fork and smoke tests fail with NOT_MINTED on any recent block. Module 009 is
due for a revamp, so they skip instead of tracking the current token id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uote

ousdUsdeV4Rebalance now caps the bid at min(par, Ethena RFQ redeem rate)
less --cap-bps instead of par less --cap-bps, so an Ethena fee regime moves
the bid down with it. Without a quote the cap falls back to par. The run
warns to hold the bought USDe while a redemption costs more than
--max-redeem-cost-bps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the cap now below the Ethena redemption quote, Ethena's ~1 USDC flat
fee (0.2 bp on 50k) pushed the calm-market range from tick 4 to tick 5 at
a 4 bp margin. 3 bp keeps it on tick 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@shahthepro shahthepro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@clement-ux
clement-ux merged commit 4120cba into master Oct 19, 2026
10 checks passed
@clement-ux
clement-ux deleted the clement/ousd-usde-v4-liquidity-module branch October 19, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants