Skip to content

feat(uniswap-v4/gluehook): GlueHook V3 — replace the V2 address registered in #1599 with 0x03D4…A040 - #1672

Merged
NgoKimPhu merged 3 commits into
KyberNetwork:mainfrom
lalilulel0x:gluehook-v3
Sep 16, 2026
Merged

NgoKimPhu merged 3 commits into
KyberNetwork:mainfrom
lalilulel0x:gluehook-v3

Conversation

@lalilulel0x

@lalilulel0x lalilulel0x commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Why did we need it?

Address change. #1599 (merged) registered GlueHook at the V2 address 0x0F41715dc432692b66A5aDF8dCfef6Ac407b20c8. GlueHook has since shipped V3, and this PR replaces that address with the V3 deployment:

Address Bits
V2 (registered in #1599) 0x0F41715dc432692b66A5aDF8dCfef6Ac407b20c8 0x20C8
V3 (this PR) 0x03D482cB3Ff339C2d29736818D0F72c66dD6A040 0x2040

Same address on every chain (plain CREATE from a nonce-0 deployer), source-verified on each explorer: Ethereum, Base, Unichain, Arbitrum, Optimism, BNB, Polygon, Avalanche, X Layer, World Chain, Soneium, MegaETH, Robinhood, and now Arc (5042). V2 stays deployed and functional for its existing pools, but it is superseded — new pools land on V3 only, so it is no longer registered here.

Why V3 (what changed vs V1/V2)

Launch write-up with the full numbers: https://x.com/glue_fi/status/2100237021264450020

  • The pot can no longer empty itself in one transaction. V1/V2 ran two modes — buying on buys and defending on sells — and the defence could spend 100% of the pot at whatever price was on screen. V3 turns the defence into a buy and puts a TWAP underneath it: the most it spends behind any one swap is 0.8·f·R (0.8% of pool depth at a 1% pool), measured against its own ten-minute time-weighted reference. It buys in slices — hard into dips, pulling back as a rally runs ahead of the reference. Same money, more supply bought.
  • One mechanism, behind every swap, in both directions. Behind a buyer it adds to the buy; behind a seller it buys the dip that seller just made. Traffic is the trigger and the pump runs inside the swapper's own transaction — nothing in the mempool to front-run.
  • 20× to 67× harder to play. The sandwich was already closed in V2 (a −40% loss on fees). V3 closes the other two doors: unlocking the pot costs 50% of it in fees instead of 2.5%, round-trip farming needs a bag of ~16% of pool depth instead of ~1.25%, a pushed premium d shrinks the pump to f/d, and the reference rises at most 3% a minute. Spend = 0.8 · min(f·R, f·R·bucket, s(d)·B, pot) — derivations in the repo README ("The pump math").
  • The before-swap path is gone. V3 runs on permission bits 0x2040 — beforeInitialize + afterSwap only. No beforeSwap, no return delta: the swapper's trade is the pool's plain execution and the buyback happens after it. Quotes are exact (vanilla V4 math, nothing to model), swaps cannot be blocked (every hook action is a try/catch self-call — a failing buyback is skipped, never the swap), the surface shrank.
  • Cheaper Glue integration. Burns go through the Glue Protocol directly (wrapper-aware), and native programs stream live harvest data to Glue's staking/locking engines.

Nothing changes in the integration model. V3 is still a pure passthrough (BaseHook semantics, zero before/after-swap deltas) with the same measured gas ceiling (maxHookGas = 120_000; V3 idle ~10k, pump ~+88k, in-swap harvest+compound ~+111k). If anything it is simpler: with no beforeSwap on-chain there is nothing left that could ever alter a quote.

Changes:

  • hooks/gluehook/constant.go — HookAddresses = V3 (replaces V2); comments
  • hooks/gluehook/hook.go — doc comment for V3 semantics
  • hooks/gluehook/hook_test.go — registration test over the registered address

Live V3 pools (one per chain, swapped both ways) and on-chain V4Quoter results: Uniswap/routing-api#1414. Source: https://github.com/glue-finance/glueHook (V1/V2 in legacy/) · Audit: https://github.com/glue-finance/glueHook/tree/main/audit

Related Issue

#1598 (original proposal) · supersedes the V2 registration in #1599

Release Note

uniswap-v4-gluehook now points at the GlueHook V3 address 0x03D482cB3Ff339C2d29736818D0F72c66dD6A040 (was V2 0x0F41…20c8). Pools on the V2 hook are no longer routed; new GlueHook pools are V3. Arc (5042) is a new chain for this exchange.

How Has This Been Tested?

  • go test ./pkg/liquidity-source/uniswap/v4/hooks/gluehook/ — ok (registration, exchange id, passthrough deltas in all four CalcOut/ZeroForOne combinations, zero hook fee, gas budget)
  • gofmt clean
  • On-chain: a V3 pool on each of the 14 mainnets, launched, swapped both ways through the Universal Router, LP added and 50% removed — PoolIds and quoter output in chore: allowlist GlueHook V3 hook (replaces #1408) Uniswap/routing-api#1414

Screenshots (if appropriate):

n/a

V3 (0xbB021554C5294328b04fa313669715bD201BA040, bits 0x2040) is the canonical
generation for new pools; V2 stays live for its own. Both are pure quoting
passthroughs, so the existing hook and gas budget cover both.
@lalilulel0x lalilulel0x reopened this Sep 16, 2026
@lalilulel0x

Copy link
Copy Markdown
Contributor Author

Reopened with the final V3 deployment address 0x03D482cB3Ff339C2d29736818D0F72c66dD6A040 in HookAddresses alongside V2. The previous 0xbB02…A040 build was retired before launch (Glue contract generation swap; identical hook source except one constant). go test green.

…tered in KyberNetwork#1599 with 0x03D4…A040

V3 (bits 0x2040: beforeInitialize | afterSwap, no beforeSwap, no return
delta) supersedes V2. Same CREATE-from-nonce-0 address on every chain,
now incl. Arc. Quoting stays a pure passthrough with the same gas budget.
@NgoKimPhu
NgoKimPhu merged commit 2236fcb into KyberNetwork:main Sep 16, 2026
4 of 5 checks passed
NgoKimPhu added a commit that referenced this pull request Oct 8, 2026
* feat(ilyris): add Robinhood Chain discrete-bin AMM

Land the pull-based Ilyris adapter and in-package bin kernel so kyberswap-dex-lib can quote the Robinhood Chain pool without depending on the ilyris repo.

* fix(ilyris): mutate bins, pin guard+timestamp, decode pool address

Split quotes were reusing the pre-swap book, freezeEnd looked eternal when BlockTimestamp stayed 0, guard could be a different block than bins, and PoolCreated keyed the factory.

* feat(lunarbase): support v0.4.0 punishment-model pools alongside concentration-curve model (#1646)

lunarbase-pmm-math v0.4.0 replaced the concentration-curve pricing model with a
linear-anchor, directional-punishment fee mechanism. Verified live that Base
and Monad pools, plus one BSC pool, remain on the old model, while a second
BSC pool has upgraded (concentrationK() reverts on-chain). Both models are
now supported concurrently:

- math.go: port the punishment/linear quote math (punishmentX24,
  applyPunishment, applyFee), verified bit-exact against on-chain quoteXToY.
- helper.go: fetchRPCState uses TryBlockAndAggregate, fetching both
  concentrationK and maxPunishmentX24; requires success on all other calls,
  errors only if neither model getter resolves.
- abi.go/constant.go: add maxPunishmentX24() and the PunishmentApplied event
  (field order confirmed from live logs, not just the topic hash).
- pool_tracker.go/flash_block_subscriber.go: handle PunishmentApplied in both
  the poll and websocket log paths.
- pool_simulator.go: thread MaxPunishmentX24 through CalcAmountOut and persist
  the post-swap ratcheted fee via UpdateBalance, so split/multi-hop routes
  through a punishment pool price correctly.


Claude-Session: https://claude.ai/code/session_01JAjpHhsvSetZ82QdWjB3DL

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat/manta prop (#1648)

* feat: add manta-prop exchange and PropAMM classification fixes

Also backfills PropAMMSourceSet with caliber-prop, capricorn-pamm, and
uniswap-v4-aegisprop, and titan-prop/fermi-prop which were missing despite
being the same PropAMM family as prism-prop (already in the set). Reordered
the set by integration time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHpcXTMbiNqDyMcvTC6Mq3

* refactor(machima): drop redundant Router field, reuse pool.MetaInfo

Router and ApprovalAddress were always the same value. Collapsing PoolMeta
to pool.MetaInfo lets aggregator-encoding use the generic
PackPoolAddressFromApprovalInfo instead of a dedicated PackMachima.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHpcXTMbiNqDyMcvTC6Mq3

* feat(pool): add RouterMetaInfo for push-payment executor calldata

Mirrors MetaInfo's shape for the opposite executor pattern: the executor
transfers tokenIn to Router directly instead of approving it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHpcXTMbiNqDyMcvTC6Mq3

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat: add tidefi-prop liquidity source (#1649)

Integrates TideFi (Ulmo Markets' PropAMM) on Base: an on-chain RFQ-style
PropAMM with a quote() view function, sampled via the shared ladder
package (same shape as 1010-prop/manta-prop/fermi-prop). TideFi has no
enumerable on-chain token registry (pricing config is pushed by a trusted
off-chain signer with no event log), so the tracked token set is
config-driven rather than discovered. Uses the naming/config convention
of prism-prop and manta-prop, and a 15s MaxAge staleness gate matching
TideFi's observed ~60s on-chain price-feed expiry.


Claude-Session: https://claude.ai/code/session_01VdFcaJgkqr8inKJUWMAAfY

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(uniswap-v4): add o1 launchpad hook, reusing b20's LaunchHook logic (#1650)

o1 (0x1f91c998e7c2f4b690d75bdbf6502bdcd6e02acc on Base, verified via Sourcify)
is the same LaunchHook.sol lineage as hooks/b20: identical getHookPermissions
bitmask, byte-for-byte identical anti-snipe decay formula, and the same
fee-timing split between beforeSwap/afterSwap. It differs only in the
poolConfig getter's field layout (fee distribution moved to a FeeComponent[]
array that doesn't affect the swap-visible fee amount) plus launch-buy adapter
support that doesn't change generic-swap pricing.

Rather than duplicate the shared logic, b20.Extra's decay/fee/BeforeSwap/
AfterSwap methods and its sentinel errors are now exported so o1 can embed
b20.Extra directly; only o1's Track (its own poolConfig ABI decode) is
package-specific. Confirmed against a live pool on Base via
`cast call poolConfig(bytes32)`.


Claude-Session: https://claude.ai/code/session_01APFnpr2rSKgyenzYqdC5oQ

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(uniswap-v4): add pons-v2 meme hook (Robinhood chain) (#1651)

Implements PonsV2MemeHook, the singleton V4 hook shared by every graduated
pons-v2 pool on Robinhood chain (0xE5e702641Ea86F4ae6cC3cDaeD2B886f976Be044,
verified via Sourcify). Only afterSwap is enabled: the hook takes
hookFeeBps + creatorTaxBps of the swap's unspecified currency, both frozen
per pool at registration and read via the launches() getter.


Claude-Session: https://claude.ai/code/session_01DhDdQNR2QpnSUjPFrxR3zE

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat(uniswap-v4): add CashCatHookV2 hook (Robinhood chain) (#1652)

Integrates CashCatHookV2, the fee engine for CashCat pools: one flat fee
on the quote side (currency0), fixed for the pool's life. BeforeSwap skims
the quote input on buys, AfterSwap takes the quote output on sells, with
exact-output gross-up mirroring _feeFor. The rate is read once via
currentFeeRate and persisted in HookExtra; foreign schemas (e.g. the
auto-detection model) trigger a refetch so existing pools migrate on the
first track after deploy. Explicit registration takes precedence over the
auto-detection fallback.

* fix(ladder): prevent index out of range panic in CollectLadder (#1653)

* fix(ladder): prevent index out of range panic in CollectLadder

CollectLadder assumed len(results) == len(points), but TryAggregate
tolerates individual call failures and leaves the corresponding output
variable at its zero value (nil/empty slice) when a per-token-direction
estimateSwapBatch call reverts on-chain. This caused a panic in
liquidcore's pool tracker (index out of range [0] with length 0) when
one direction's batch quote reverted while the other succeeded.

Treat any point past the end of results as reverted instead of
indexing out of bounds.

* fix(liquidcore): pin estimateSwapBatch probes to the same block as getReserves

probeQuotes issued its own TryAggregate() without a block number, so
estimateSwapBatch ran at the latest block at call time while
getReserves had already been pinned to resp.BlockNumber a moment
earlier. Any swap landing between the two calls would make the probed
ladder reflect reserves that differ from what's persisted in
p.Reserves / p.BlockNumber, breaking state consistency for stateful
refreshes.

Wire resp.BlockNumber into probeQuotes and pin its request to it, per
kyberswap-dex-lib tracker convention (pin all multicalls in a refresh
to the same block).

* test: drop unused binX helper

ci / lint failed with `func binX is unused (unused)` at pool_simulator_test.go:237.
It is a symmetric twin of binY written alongside it and never called; binY and its
three call sites are untouched. gofmt clean, brace balance unchanged, 14 funcs to 13,
and the deletion is the file's only difference.

* feat: add Fables dynamic-fee hook (#1622)

* feat: add Fables dynamic-fee hook

Fables pools on Robinhood Chain use the v4 dynamic-fee flag and resolve their
LP fee inside beforeSwap, so slot0.lpFee is not authoritative and the router
cannot quote them without a hook handler.

Track reads the resolved fee per direction via currentFee(poolId, zeroForOne);
BeforeSwap replays it. No swap deltas, no hook fee, no custom calldata.

* chore: regenerate msgpack pool type registrations

* fables: refresh hook list from the registry (13 hooks) and pin its length

Regenerates HookAddresses from FablesPoolRegistry.poolAt(0..12) in registration
order: adds the four hooks launched 2026-09-04 (AMC/USDG, CASHCAT/USDG, ETH/AMC,
ETH/PONS; tick spacing 60) and corrects two contract labels (SPY/NVDA and SPY/GLD
are FablesRWA, not FablesRamp). Labels now read currency0/currency1 in PoolKey
order.

Newer Fables hooks carry a per-pool directional premium, so the comment claiming
both directions always return the same fee is replaced; Track already reads each
side separately and BeforeSwap replays the matching one, so no logic changes.

Tests: TestHookAddresses_RegistrySnapshot pins the list to 13 distinct registered
addresses so a stale snapshot fails loudly, and a second live RPC test runs Track
against the ETH/PONS wave-4 pool. Keeps ExchangeUniswapV4Fables in alphabetical
position in exchange.go.

* feat(range-pool): add Range Pool DEX connector (#1612)

* feat(range-pool): add Range Pool DEX connector

Range Pools are a concentrated-liquidity, Balancer-V3-shaped weighted DEX on
Ethereum mainnet, running on a CUSTOM, non-canonical Balancer V3 Vault
(0x955244EDC797A1C1b04134b600f819aC23C76081). Because the Vault is not the
canonical Balancer deployment, the existing balancer-v3 connector (subgraph +
canonical Vault) does not index them, so Range needs its own connector.

- pools_list_updater: on-chain discovery via RangePoolFactory.getPools()
  (no subgraph); immutable data (tokens, weights, scaling) into StaticExtra.
- pool_tracker: re-reads getRangePoolDynamicData + Vault getPoolConfig each
  cycle (all 6 liveness flags + static & aggregate swap fees); an unsafe pool
  (pool/vault pause, hook stop, recovery, uninitialised) is disabled.
- pool_simulator: weighted-product curve on VIRTUAL balances with the output
  capped by the fact balance; reuses balancer/v3 math (FixedPoint/LogExpMath),
  adds the Range-specific cap, EXACT_OUT guards, and the Vault's 1e12 minimum-
  trade gate. EXACT_IN and EXACT_OUT.
- registered in pooltypes, valueobject and msgpack.

Quotes verified wei-exact against Router.querySwapSingleTokenExactIn/Out on a
mainnet fork for both live pools, both directions and both sides.

* fix(range-pool): address review — validate multicall reads, drop unused const

- fetchState: TryBlockAndAggregate tolerates per-call reverts, so validate resp.BlockNumber and every per-call success flag; return ErrIncompleteState on a partial read instead of decoding zero/nil into MustFromBig (panic) or quoting on garbage.
- remove the unused factoryMethodGetPoolCount constant.
Addresses Copilot review comments.

* refactor(range-pool): rename to range/weighted, reuse shared helpers, add lazy tracker

Rename range-pool -> range/weighted and DexType "range-pool" -> "range-v3-weighted"
to match the balancer/v3/<pool-type> layout and coinhane-style exchange naming.
Reuse shared.ErrInvalidToken instead of a local duplicate, and derive
VaultAddress/RouterAddress from shared.VaultMap/BatchRouterMap (like coinhane)
instead of a second hardcoded copy. Switch logging to zerolog to match recent
DEX integrations. Add range/weighted/lazy, a batchable primary tracker
mirroring balancer/v3/weighted/lazy, with range/weighted.PoolTracker demoted
to the backup (RegisterBackupFactoryCE).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FuPayBTb9iRxPweqfaLmJ

---------

Co-authored-by: Phu Ngo <12547020+NgoKimPhu@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* Feat/ring few token v4 hook (#1655)

* feat(uniswap-v4): add Ring FewToken wrapper pools for few hook

Ring Protocol's FewToken wrapper pools let a token wrap 1:1 into its
FewToken representation via a dedicated hook (FewTokenHook / FewUSDTHook /
FewETHHook), consumed by the existing generic wrap/unwrap logic already
wired into uniswap-v4's PoolSimulator (CalcAmountOut/CalcAmountIn/
CanSwapTo/CanSwapFrom/GetMetaInfo).

- Populated the full set of 9 wrapper pools from Ring's published manifest
  (docs.ring.exchange), cross-checked against on-chain bytecode and a
  recomputed poolId hash.
- Read FewTokenHook/FewUSDTHook/FewETHHook source: wrap/unwrap is exact
  1:1, zero fee, both exact-in/out (_getWrapInputRequired/
  _getUnwrapInputRequired are identity functions) - the three hooks only
  differ in approve-call plumbing (USDT) and ETH<->WETH conversion (ETH),
  not pricing. No hook simulation is needed beyond the existing shortcut.
- Split into few/v1 and few/v2 sub-packages since WBTC/fwWBTC has two
  pool generations (v1: tickSpacing=60 pool predating the current
  manifest; v2: the currently active tickSpacing=1 pool). Both are
  registered in pool_simulator.go's tokenWrappers, v2 first so the active
  pool wins when both could match.
- Factored the shared TokenInfo/TokenWrapper implementation into the
  parent few package so v1/v2 only declare their own token list.

Docs: https://docs.ring.exchange/contracts/v4/guides/fewtoken-liquidity-aggregation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(midas): reject redeem below minAmount to match onchain RV revert

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* fix(uniswap-v4): register Ring FewToken hooks to avoid unnecessary auto-calibration

* uniswap-v4: add DualPoolHook (Twofold, Robinhood Chain) (#1647)

* uniswap-v4: add DualPoolHook (Twofold, Robinhood Chain)

DualPoolHook is Uniswap Labs' just-in-time liquidity hook: the pool holds no
resident v4 liquidity, and on every swap the hook deploys its reserves as
concentrated positions across weighted tick buckets, swaps on the v4 curve
at the key's static fee, and removes them. StateView shows no ticks, so this
hook package prices the whole swap in BeforeSwap from three reads
(getEffectiveLiquidity, getDistribution, StateView.getSlot0): per-bucket
liquidity via LiquidityAmounts.getLiquidityForAmounts exactly as the hook's
computeAllocations, then a v4 swap across those positions with
SwapMath.computeSwapStep and ProtocolFeeLibrary compounding.

Tests reproduce the V4 Quoter to the wei on the live NVDA/USDG pool at block
55199380 and reject a swap that would outrun the deployed buckets, matching
the on-chain revert. A live tracker test (skipped under CI) exercises the
RPC decoding against Robinhood Chain.

* gsm-4626: stop parallel tests racing on the shared simulator's Rate

CloneState copies the struct but keeps the Rate pointer, so TestCalcAmountOut
writing poolSim.Rate while TestGhoUsedRoundTrip reads its clone's Rate is a
data race under -race (fails most runs). Each test now builds or assigns its
own Rate instead of mutating the package-level simulator.

---------

Co-authored-by: TwoFoldFI <Dev@twofold.fi>
Co-authored-by: SunSpirit <48086732+sunspirit99@users.noreply.github.com>

* chore(deps): bump github.com/pion/stun/v3 from 3.1.2 to 3.1.5 (#1581)

Bumps [github.com/pion/stun/v3](https://github.com/pion/stun) from 3.1.2 to 3.1.5.
- [Release notes](https://github.com/pion/stun/releases)
- [Commits](https://github.com/pion/stun/compare/v3.1.2...v3.1.5)

---
updated-dependencies:
- dependency-name: github.com/pion/stun/v3
  dependency-version: 3.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump github.com/pion/dtls/v3 from 3.1.2 to 3.1.4 (#1582)

Bumps [github.com/pion/dtls/v3](https://github.com/pion/dtls) from 3.1.2 to 3.1.4.
- [Release notes](https://github.com/pion/dtls/releases)
- [Commits](https://github.com/pion/dtls/compare/v3.1.2...v3.1.4)

---
updated-dependencies:
- dependency-name: github.com/pion/dtls/v3
  dependency-version: 3.1.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat: add Uniswap V4 GlueHook (passthrough hook, one address on all chains) (#1599)

* feat: add Uniswap V4 GlueHook (passthrough hook, one address on all chains)

* Point GlueHook at the V2 deployment address

The hook was redeployed at 0x0F41715dc432692b66A5aDF8dCfef6Ac407b20c8 on the current 13-network set.

* feat(limitorder): cache operator signatures and fetch only the misses (#1637)

GetOpSignatures hits the limit-order backend for every order on every
RFQ, and that endpoint also writes to the database
(UpdateOpSignatureExpTimeIfActive), so each redundant call costs a write
as well as a round trip.

The cache is exact, not an approximation. The operator signs EIP-712
over (orderHash, opExpireTime) only — never amount, taker or recipient —
and the backend mints a new expiry only once the current one has less
than OperatorSigMinimumValidTimeInSecs left of its
OperatorSigExpTimeInSecs window (90s / 60s on mainnet). Repeated calls
for the same order inside that ~30s gap therefore return a
byte-identical signature, so serving one from memory is equivalent to
calling live.

Caching sits inside GetOpSignatures rather than around it because the
win is splitting one request into hits and misses and fetching only the
misses; a wrapper cannot do that, since the function is always asked for
the whole id set.

A cached entry is reused only while it is within its TTL and still valid
at now + validityMargin. The semantically right bound is "valid when the
tx executes", but dex-lib cannot see the target block time, so the
margin defaults to 24s (~2 Ethereum blocks). Negative results are not
cached.

Both knobs are new Config fields and the TTL defaults to 0, which
disables the cache entirely — a version bump alone cannot change
router-service behaviour.


Claude-Session: https://claude.ai/code/session_01HpoYU9hULTGoHo2kuosoax

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(ilyris): move math and state to uint256.Int

Follows the convention in AGENTS.md and the review comment on #1635.

math.go and pool.go no longer reference big.Int in code at all. The only
remaining uses are Info.Reserves and the CalcAmountOut/UpdateBalance
signatures, which are dex-lib's own types, so the conversion happens at
that boundary and nowhere else.

Two places needed care rather than a mechanical swap.

Overflow. big256.MulDiv and big256.MulDivUp call the right primitives but
discard the overflow flag and the error respectively. Silent truncation
is not acceptable here: BinPool's mulDiv reverts when the quotient will
not fit, and a truncated quote is a route the pool would refuse. MulDiv
now reads the carry from MulDivOverflow directly, and MulDivUp checks
the error from MulDivRoundingUpV2. The running totals in QuoteExactIn
and QuoteExactOut use AddOverflow for the same reason, since unsigned
addition wraps where big.Int simply grew.

Fee arithmetic is deliberately not folded into a 512-bit mulDiv. The
contract multiplies and then divides in uint256, so an intermediate
product that does not fit is a revert on chain; a mulDiv would succeed
where the contract fails. netOfFee keeps the checked multiply.

Reserve updates. applyFills subtracted and then tested for a negative
result. Unsigned that would wrap to roughly 1.16e77 and every later
route would quote against liquidity that does not exist, so the clamp
now happens before the subtraction.

The conversion also removed code rather than adding it. requireUint256
guarded every operand and every result to re-impose a bound the EVM gets
for free; the type is that bound now. Bin reserves can no longer be
negative, so that validation is gone from newBinSimulator, and
BinJSON.reserves parses with uint256.FromDecimal, which rejects a
negative and an out-of-range value as part of the parse.

VariableFeeRate is left unchecked on purpose, and says why: on chain
variableFeeControl and volatilityAccumulator are both uint24 and binStep
is capped at 1000, so the widest numerator is under 2^93.

All 43 tests pass, including the three that compare this port against
the deployed contract on oracle vectors generated from an in-process
EVM. Those are wei-exact and unchanged, which is the evidence that the
port still mirrors BinPool.

* style(ilyris): goimports -local grouping in new_test.go

AGENTS.md asks for goimports -local on every changed file. new_test.go had
the module's own imports interleaved with go-ethereum's; they belong in
their own trailing group. Import ordering only, no code change.

* feat: Add Caliber BSC (#1660)

* chore(deps): bump github.com/pion/dtls/v3 from 3.1.2 to 3.1.4 (#1582)

Bumps [github.com/pion/dtls/v3](https://github.com/pion/dtls) from 3.1.2 to 3.1.4.
- [Release notes](https://github.com/pion/dtls/releases)
- [Commits](https://github.com/pion/dtls/compare/v3.1.2...v3.1.4)

---
updated-dependencies:
- dependency-name: github.com/pion/dtls/v3
  dependency-version: 3.1.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat: Add Caliber BSC

* fix: BSC address

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* feat/range v3 weighted robinhood (#1662)

* feat: expand range-v3-weighted to Robinhood chain

Range Pools deploy Factory/Hook/Vault/Router at different addresses on
Robinhood than Ethereum. Make Vault and Hook chain-aware (Config.ChainID +
per-chain maps, mirroring brownfi-v3's pattern) instead of the previous
Ethereum-only package vars. shared.VaultMap gains a VaultOverrideMap
sibling so per-chain Vault exceptions can live next to BatchRouterMap
without forcing every prefix into a chain-keyed map.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUupoqxusUvTT6PAR1j1fW

* refactor(range-v3-weighted): move test-only addresses into test files; use hexutil.Encode

RouterAddress/FactoryAddress were package vars only referenced by the
integration tests, so move them there as unexported test-local vars.
Also switch every common.Address -> string conversion in the package from
.Hex() to hexutil.Encode(addr[:]) per repo convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUupoqxusUvTT6PAR1j1fW

* chore(balancer-v3): drop dead range vars; hexutil.Encode over .Hex()

SingleTokenRouterAddress/Permit2Address/WETHAddress in range/weighted were
unreferenced anywhere. Also replace the two remaining common.Address.Hex()
calls in balancer/v3/base (GetMetaInfo's ApprovalAddress) with
hexutil.Encode(addr[:]) per repo convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUupoqxusUvTT6PAR1j1fW

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* fix(lo1inch): always parse MakerTraits and implement CloneState (#1658)

* fix(lo1inch): always parse MakerTraits and implement CloneState

Two defects in the lo1inch pool simulator, both on the hot path for the
live Ethereum order book.

1. MakerTraits were skipped for extension-less orders

NewPoolSimulator looped over the orders twice and `continue`d on an empty
(or "0x") extension BEFORE assigning `order.MakerTraitsInstance`, so every
order without an extension kept nil traits. Both fill-amount paths gate the
full-fill-only rule on the instance being non-nil (`Order.CalcTakingAmount`
and `Order.CalcMakingAmount`), so a NO_PARTIAL_FILLS order without an
extension was quoted as freely partially fillable. The encoder then builds
`fillOrderArgs` with `amount < order.TakingAmount` and AggregationRouterV6
(0x111111125421ca6dc452d289314280a0f8842a65) reverts PartialFillNotAllowed,
killing the whole bundle.

MakerTraits are now parsed for every order, independent of the extension;
only the extension-derived instances (ExtensionInstance, FeeTakerExtension)
stay conditional. The two identical loops are folded into decodeOrders,
which also removes the divergence where the token0 loop swallowed the
fee-taker decode error silently and the token1 loop logged it.

A census of 802 open, tradable orders on the vtv2 reserve universe shows
22.8% set NO_PARTIAL_FILLS and 77.5% use the bit invalidator, so the
full-fill-only rule is a first-class case for this book, not an edge case.

2. CloneState was not implemented

The simulator did not override CloneState, so it fell through to
pool.Pool.CloneState, which returns nil. Order is a pointer shared between
the order slices and the orderHash -> index maps, so any caller that clones
and then calls UpdateBalance mutated the shared simulator's orders instead
of a copy — silently corrupting normal-flow state for every consumer that
prices against a cloned (pending / overlay) simulator.

CloneState now deep-copies the order slices and the Order values behind
them, which is exactly what UpdateBalance writes. The hash indexes and
minBalanceAllowanceByMakerAndAsset are never written after construction and
stay shared, per the repo rule in AGENTS.md.

Tests

- TestPoolSimulator_CalcAmountOut_NoPartialFillsWithoutExtension: a
  NO_PARTIAL_FILLS order with an empty extension, built from the real
  mainnet MakerTraits of maker 0x9a0ef3c32785daf9161ed6e9701308e622d36e4d
  with the expiration cleared. Before the fix the partial take is quoted;
  after the fix it returns ErrCannotFulfillAmountIn while the exact full
  take is still quoted.
- TestPoolSimulator_NoPartialFillsSurvivesMsgpack: the same rule after an
  EncodePoolSimulatorsMap / DecodePoolSimulatorsMap round-trip, the
  transport router-service uses.
- TestPoolSimulator_CloneState: asserts the clone's Order pointer differs
  from the original's, then mutates the clone via UpdateBalance and checks
  the original's order and its re-quote are untouched. Before the fix
  CloneState returns nil.

Note for consumers: NO_PARTIAL_FILLS orders are now correctly refused for
anything but an exact full fill, so quoted liquidity on such orders drops.
That matches what the protocol will actually execute.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HpoYU9hULTGoHo2kuosoax

* fix(limitorder): rename the colliding opSigServer test helper

main does not build: pkg/source/limitorder declares opSigServer twice.
#1628 added it as a function in rfq_test.go, #1637 later added a struct of
the same name in http_client_test.go, and the collision only appears once
both are on the same branch — so every PR since inherits a red CI:

  rfq_test.go:19:6: opSigServer redeclared in this block
  http_client_test.go:144:6: other declaration of opSigServer
  rfq_test.go:56:24: too many arguments in conversion to opSigServer

The struct keeps the name: it is the richer helper, it carries methods, and
renaming it would touch four sites instead of two. The function becomes
signedOrderServer, which also says more about what it does — it serves
signatures only for the ids the caller marked as signed.

Test-only change, no production code touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HpoYU9hULTGoHo2kuosoax

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* ft: support brownfi-v3 using redstone oracle (#1663)

* fix: support upgraded uniswap-v4 stable-stable hook (dropped on-chain logK) (#1664)

The newer stable-stable hook deployment (0x0000113dcf4add69999fad8f20f2b63f979bfcc0)
changed feeConfig()'s ABI: it dropped the on-chain logK column and now derives it
from k on the fly (StableFeeCalculation.deriveLogK). This broke two things for
pools on the new hook:

- Track(): the stale 5-field ABI decode misaligned every field after k against
  the contract's actual 4-word return.
- Pool discovery: FeeConfigUpdated's tuple signature changed too, so its topic0
  hash no longer matched what pool_factory.go computed from the stale ABI,
  silently dropping new-hook pools from discovery entirely.

The legacy hook deployment is still live and still returns the original 5-field
shape with a genuinely different logK scale (confirmed on-chain: deriveLogK(k)
for the legacy hook's k does not reproduce its stored logK — the two hooks use
different <<40 vs <<24 exponent scales), so this adds a second ABI/decode path
selected by hook address rather than replacing the legacy one.

- abis/StableStableHookV2.json: new hook's feeConfig()/FeeConfigUpdated shape.
- hook.go: Hook.HookAddress set once at construction (GetHook reruns the
  factory on every pool simulator rebuild); isLegacy() derives which decode/
  decay path to use from it.
- math.go: CalculateDecayingFeeV2/decayFactorX24V2/deriveLogK port the new
  contract's on-chain formula; legacy path is unchanged. pool_factory.go now
  recognizes both FeeConfigUpdated topics.

Verified against live chain data (cast call, debug_traceCall, Sourcify-fetched
verified source for both the proxy and implementation) and against pre-release
pool-service data for pools on both hook versions.


Claude-Session: https://claude.ai/code/session_0171HX441mMoCLaztoSStJcz

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat/doppler rehype v2 and bsc initializer (#1665)

* feat: register new RehypeDopplerHook v2 addresses and bsc/arbitrum DopplerHookInitializer

Ethereum RehypeDopplerHook v2 deployments (0xbd54a9e1..., 0x5f9eb5f6...) verified
against existing v2 hook's bytecode (identical MAX_SWAP_FEE=1e6 fee-bounds check).
bsc/arbitrum DopplerHookInitializer (0xf0d631d5...) added disabled pending go-live.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0171HX441mMoCLaztoSStJcz

* feat: register bsc/monad/robinhood/arbitrum DopplerHookInitializers and bsc RehypeDopplerHookInitializer v2

Audited whetstoneresearch/doppler deployments/*.md (1, 143, 42161, 4663, 8453) against
the current registry and closed gaps:
- bsc/arbitrum DopplerHookInitializer (0xf0d631d5...) and bsc RehypeDopplerHookInitializer v2
  (0xa7465b9d...) traced from BSC deployer tx
  0xbd91642715b8be843b6ac2af8898df643c620e6e7ae091cabec9b11ee1806953 via the CreateX
  ContractCreated log; verified against the existing v2 hook's bytecode fingerprint
  (MAX_SWAP_FEE=1e6) and fee-schedule selectors.
- robinhood DopplerHookInitializer (0x4e346895...), monad DopplerHookInitializer
  (0x56ea13da...), and arbitrum DopplerHookInitializer (0xaa7f809b...) confirmed live
  via the official deployments/{4663,143,42161}.md logs and selector-matched against
  the existing DopplerHookInitializer ABI.

Not included: SwapRestrictorDopplerHook (arbitrum/robinhood 0xc16c826f..., also found
live on bsc at 0xc8b56e57...) is an unhandled hook family with no factory in this
package yet — needs its swap-restriction semantics investigated before wiring in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0171HX441mMoCLaztoSStJcz

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* fix: uniswap-v4 Track sees this refresh's pool state, not the previous one's (#1668)

hook.Track saw the previous refresh's pool state: fetchOnchainState passes it
the pre-refresh entity while its own eth_calls run at the newly fetched
block, and the callers only wrote this refresh's liquidity/slot0/ticks into
p.Extra after resolveHookState returned. #1540 fixed this ordering for
Reserves, but not for Extra. For auto-calibrated pools this puts the fitted
fee on the wrong swap side, which can quote a zero fee and overquote.

resolveHookState now writes this refresh's liquidity, slot0 and ticks into
Pool.Extra before hook.Track. hX is left out: hooks read their state from
HookParam.HookExtra, and the callers persist Track's output afterwards.
FetchRPCResult.ToExtra is now the single mapping from fetched state to
Extra, used by resolveHookState, BootstrapPoolState and updateState.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* ft: expand to arc (#1670)

Adds ChainIDArc (5042) and its wrapped-native entry (the ERC-20 USDC
interface at 0x3600...0000). Arc has no wrapped-native contract: native
(18 decimals) and its ERC-20 interface (6 decimals) share one balance,
unlike every other chain where wrapped-native matches native's decimals
1:1. WrapNativeAmount/UnwrapNativeAmount are the shared conversion point
for that gap, mirroring the SC's ARC_NATIVE_USDC_SWITCH edge
(ks-dex-aggregator-sc PR #982) - callers (aggregator-encoding,
router-service) delegate to these instead of duplicating the scaling logic.


Claude-Session: https://claude.ai/code/session_01XmnuB369R6tHtUTKEwpur7

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* fix: rescale native-currency legs and add Arc addresses for v4-style pools (#1671)

Uniswap V4 and Pancake Infinity (CL, Bin) pools trade a native-flagged
currency directly at its own decimals, not through the chain's
wrapped-native address used for internal token indexing. On every chain
these decimals match 1:1, but Arc's native (18) and its wrapped-native
entry (6) differ, so native-flagged swap amounts were off by 1e12.
Rescale around each simulator's core math instead.

Also add Arc's PoolManager/Quoter addresses to the v4 constants maps,
previously missing and causing Arc v4 swaps to revert calling unlock()
on the zero address.


Claude-Session: https://claude.ai/code/session_01XmnuB369R6tHtUTKEwpur7

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* fix: rescale native-flagged pool reserves to wrapped-native decimals (#1673)

Uniswap V4 and Pancake Infinity (CL, Bin) trackers store a native-
flagged reserve under the wrapped-native address (dex-lib convention),
but the reserve value itself is read/computed at native's real
decimals. On every chain these match 1:1, but Arc's native (18) and
its wrapped-native entry (6) differ, so a native-flagged pool's
reserve was off by 1e12 - corrupting TVL/liquidity-score ranking for
any such pool (actual swap math was unaffected; that path was already
fixed separately).


Claude-Session: https://claude.ai/code/session_01XmnuB369R6tHtUTKEwpur7

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* +lista-stake (#1677)

* integrate/net staking (#1678)

* ft: integrate net-staking (NET/sNET/wsNET) on robinhood

Adds a pool simulator for the NET<>sNET<>wsNET staking/wrapping trio on
robinhood (chainId 4663): Staking (NET<>sNET, 1:1) and WrappedStakedNET
(sNET<>wsNET, gOHM-shaped index ratio) are two separate contracts, unlike
gohm's single-contract design, so this is a new package rather than a gohm
extension.

wsNET->NET has no route: the deployed on-chain executor helper only
implements a forward composite (StakeThenWrap), and pathfinder-lib forbids
reusing the same pool twice in one path, so there's no 2-hop workaround
either. CanSwapTo/CanSwapFrom exclude that pair explicitly rather than
leaving it to fail at CalcAmountOut.

GetApprovalAddress returns the wrap contract only for the Wrap action; every
other action, including Unwrap, approves the staking contract - matching
ExecutorV3Helper9.executeNetStaking's spender logic exactly, even though
Unwrap itself needs no allowance at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHpcXTMbiNqDyMcvTC6Mq3

* ft: generalize net-staking for optional wrap and per-fork selectors

Supports a second staking-only fork (NUKE/sNUKE on robinhood) alongside the
original NET/sNET/wsNET pool, without a new package:

- BaseTokenMethod/StakedTokenMethod pick the token-discovery getter name on
  the staking contract - different forks name these differently
  (net()/sNet() vs nuke()/stakedNuke()). The 4-byte selector is derived the
  same way solc does (keccak256(name+"()")[:4]). Empty defaults to
  net()/sNet(), so the existing NET config is unchanged.
- WrapAddress is now optional. Empty disables Wrap/Unwrap/StakeAndWrap
  entirely, leaving only the base<->staked 1:1 leg - matches NUKE, which has
  no wrap contract and no rebase index() on its staked token.

CanSwapTo/CanSwapFrom branch explicitly on hasWrap rather than indexing a
wsNET slot that may not exist in a 2-token pool - the naive alternative
(passing an empty wsNET address through unchanged) would have leaked an
empty-string "token" into the routable pair list instead of just omitting
it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHpcXTMbiNqDyMcvTC6Mq3

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat: integrate Deepstate onchain order book (deepstate-ob) (#1679)

Adds tracker/simulator for DeepstateV1's radix-tree CLOB on Robinhood
Chain. Book reads go through a deployed DeepstateBookLens contract
(0x76f0257f524133cf41e8abcb694ac70ea3e8feca) for a single round trip
per poll instead of walking tree() node by node; falls back to the
naive per-node multicall when no lens is configured.


Claude-Session: https://claude.ai/code/session_01LHpcXTMbiNqDyMcvTC6Mq3

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* chore: expand doppler to arc (#1680)

* chore: flap referer (#1681)

* fix(lunarbase): preserve coherent snapshots and swap state (#1667)

* fix(lunarbase): preserve coherent snapshots and swap state

Read complete pool state at a pinned block, validate every RPC result,
and verify the canonical block hash before publishing the snapshot.
Replace partial Flash cache updates with verified RPC refreshes while
retaining deprecated public entry points for source compatibility.

Carry exact post-swap reserves in SwapInfo and consume them in
UpdateBalance. Remove both output and the full fee from the output
trading reserve, preserve independent cloned states, and enforce input,
reserve, model-selection, and snapshot-freshness checks.

Add an explicit simulator MessagePack codec that reads legacy records
and requires a refresh for ambiguous legacy state. Preserve the legacy
K=0 pricing model instead of selecting a model from its numeric value.

Add offline regression coverage using pinned BSC quote and execution
fixtures, including sequential swaps, malformed snapshots, reorgs,
freshness boundaries, and serialization compatibility.

* perf(lunarbase): reuse snapshots after a fresh canonical head check

Cache up to 64 complete pool snapshots per tracker, keyed by chain,
pool address, block number, and block hash. Copy cached state before
reading the latest header and reuse it only when that header identifies
the exact cached block. Repeated reads of an unchanged head then need
one RPC instead of three.

Keep the full pinned aggregate and post-read canonical check for new
blocks and cache misses. Preserve minimum block and token metadata
validation, bypass the cache for overrides, and isolate mutable state
between callers. Do not use supplied header payloads as freshness proof.

Add regression tests for reorgs, cache eviction, cancellation, concurrent
clients, out-of-order completion, and mutable state isolation. Remove
unused event helpers and resolve the staticcheck findings from CI.

* perf(lunarbase): add opt-in single-call snapshots

Add singleCallSnapshot to read all pool getters in one latest-block
aggregate while retaining strict response validation, model selection,
token identity checks, and the caller's minimum snapshot height.
Keep the verified cache path as the default and preserve the strict
discovery and override paths.

Distinguish complete no-hash snapshots from legacy missing metadata so
zero-valued freshness and pricing-model parameters retain their meaning.
Use MessagePack v3 only when this completeness marker is required;
existing snapshots continue to use v2.

Document the reader-first rollout, rollback constraints, and removal
of the post-call canonicality guarantee when single-call mode is enabled.
Add focused coverage for latest reads, malformed responses, known-zero
metadata, and serialization. Preserve the existing quote and reserve math.

Validation: adapter and MessagePack packages build; staged diff check
passes. Test execution is deferred until after publication.

* test(lunarbase): trim duplicated quote fixtures

Keep one record per observed direction/input pair and move identical
caller metadata out of the quote rows. Remove unused simulator reports
and store the parameter grid's common expected price once.

Preserve all 256 fresh quote cases, 58 earlier quote cases, 160 parameter
combinations, and all 12 sequential execution steps. Expected contract
values are retained from the original fixtures, not regenerated.

Reduce audit JSON from 251,730 to 69,484 bytes and 9,698 to 983 lines.

---------

Co-authored-by: NebulaNomadPixel <258810809+NebulaNomadPixel@users.noreply.github.com>

* feat: add Lunya liquidity sources for Arc (lunya, lunya-fun) (#1676)

* feat: add Lunya liquidity sources for Arc (lunya, lunya-fun)

lunya prices all three Lunya DEX pool types - CL and CP on Uniswap V3's curve,
STABLE on the protocol's own StableSwap - and lunya-fun prices the launchpad's
bonding curve. Quotes match the on-chain quoter, the Solidity library's own
vectors and each launch's quote views to the wei on Arc testnet.

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

* feat(lunya-fun): add buy/sell ABI and IsBuy direction to PoolMeta

Downstream encoding needs the launch's write ABI (buy/sell) to build real
calldata, and needs to know swap direction since token0/token1 order isn't
itself buy/sell direction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: anton-erwang <322438277+anton-erwang@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Phu Ngo <12547020+NgoKimPhu@users.noreply.github.com>

* chore: add stable-stable hook on robinhood (#1669)

Co-authored-by: TwoFoldFI <Dev@twofold.fi>

* feat(uniswap-v4): add Arcade launchpad hook (Arc) (#1674)

* feat(uniswap-v4/gluehook): GlueHook V3 — replace the V2 address registered in #1599 with 0x03D4…A040 (#1672)

* feat(uniswap-v4/gluehook): register the GlueHook V3 address alongside V2

V3 (0xbB021554C5294328b04fa313669715bD201BA040, bits 0x2040) is the canonical
generation for new pools; V2 stays live for its own. Both are pure quoting
passthroughs, so the existing hook and gas budget cover both.

* gluehook: V3 final deployment address 0x03D4…A040

* feat(uniswap-v4/gluehook): GlueHook V3 — replace the V2 address registered in #1599 with 0x03D4…A040

V3 (bits 0x2040: beforeInitialize | afterSwap, no beforeSwap, no return
delta) supersedes V2. Same CREATE-from-nonce-0 address on every chain,
now incl. Arc. Quoting stays a pure passthrough with the same gas budget.

* adding all caliberprop deployed contracts (#1683)

Co-authored-by: samb-kr <197735898+samb-kr@users.noreply.github.com>

* feat(uniswap-v4): add OneToken hook liquidity source (#1659)

* feat: add arcade-curve liquidity source (Arcade PUMP bonding curve, Arc) (#1675)

* feat: add arcade-curve liquidity source (ArcadeHook PUMP bonding curve on Arc)

PUMP launches on ArcadeHook trade on a constant-product bonding curve
(ArcadeHook.buy / sell) before they graduate to their Uniswap V4 pool,
which #1674 covers. This source ports the curve math, the 1% curve fee,
the decaying anti-sniper haircut and the graduation cap with its refund,
tracked per launch from LaunchCreated / CurveBuy / CurveSell / Graduated.

Parity tests are bit-exact against the real hook run in Foundry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NLjP6noF6zmsWFKoZXWeT3

* rename arcade-curve to arcade-fun, use big256 MulDiv helpers, export HookABI

Renamed to match the product name. Replaced hand-rolled Mul+Div pairs with
big256.MulDivUp/MulDivDown/DivUp to match house convention. Exported HookABI
and extended MetaInfo with ApprovalAddress/IsBuy so aggregator-encoding can
build buy/sell calldata directly against this package's ABI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Phu Ngo <12547020+NgoKimPhu@users.noreply.github.com>

* feat(eth): per-call state overrides in BatchEthCall (#1682)

* feat(eth): per-call state overrides in BatchEthCall

BatchCall gains Overrides, the eth_call state override set, using the same
map[common.Address]gethclient.OverrideAccount type the uniswap v4 tracker
already passes around. It is sent as eth_call's third parameter only when
non-empty, so calls without it stay plain two-parameter eth_calls for nodes
that don't accept overrides.

Needed by the uniswap-v4 auto hook calibrator, which must run its quoter's
code at the router's executor address: PoolManager hands a hook its
immediate caller, so a hook that prices by caller otherwise shows the quoter
a fee our routes never pay.

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

* test(uniswap-v4): build resolveHookState's HookParam with Cfg

TestResolveHookState_TrackSeesFreshPoolState (#1668) built HookParam
without Cfg, which production never does: fetchOnchainState always passes
t.config. #1673 then made resolveHookState read Cfg.ChainID for
rescaleNativeReserves. Each passed CI alone; together on main the test
panics with a nil dereference.

Set Cfg the way fetchOnchainState does. No production change: a nil Cfg
cannot reach resolveHookState, since fetchOnchainState dereferences
t.config first.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* fix(uniswap-v4): seed HookExtra from persisted hX in tracker refresh (#1694)

fetchOnchainState built HookParam without HookExtra, so GetHook always
constructed the hook cold (Model nil) on every tracker refresh, even
though Pool.Extra carried the previously-calibrated hX. For the
auto-detect hook, whose Factory reads its carried Model exclusively
from HookParam.HookExtra, this silently discarded sticky flags
(GasAwareCheater0/1), LowGasBand/Draws/Failures, and any persisted fix
on every single refresh, forcing a from-scratch recalibration each
cycle. pool_simulator.go's NewPoolSimulator already seeds HookExtra
this way for the quote-time path; the tracker path just never did the
equivalent extraction.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* chore: update brownfi-v3 rbh router (#1695)

* feat: add GeniusMemeHook for Genius.fun graduated pools on PancakeSwap Infinity (#1696)

Genius.fun (BSC bonding-curve launchpad) forks Pons v2 verbatim for its
bonding curve (docs: "Names are the Pons v2 names"), so that side needs no
new code -- only a pool-service/router-service config entry keyed on the
existing pons-v2 DexType.

Graduated tokens migrate liquidity into the real, already-integrated
PancakeSwap Infinity CL deployment on BSC (same PoolManager/Vault addresses
already configured for pancake-infinity-cl), gated by GeniusMemeHook
(0xFf17F41c5Efd6CCe944Af0912F300097D62df5c9), which only registers afterSwap
and takes a live-read hookFeeBps() fee on the unspecified leg of every swap.
This adds that hook so genius.fun's graduated pools quote correctly instead
of falling back to BaseHook's zero fee.

Verified hookFeeBps()==200 and curve reserves/fees against a live launch on
BSC mainnet.


Claude-Session: https://claude.ai/code/session_01X2MVzxFjfTtSG1dPh74s2V

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* fix(uniswap-v4,pancake-infinity-cl): fix Arc native-decimals mismatch in v3 simulator construction and UpdateBalance (#1702)

entity.Pool.Reserves stores a native-flagged token at the wrapped-native
address's decimals (6 on Arc vs native's real 18), but the embedded
uniswapv3.PoolSimulator's tick/liquidity math - and its own
insufficient-balance reserve check - always operates in real decimals.
Feeding it the wrapped-scale reserve directly made every quote landing on
Arc's native side fail with "insufficient balance". UpdateBalance had the
same scale mismatch in reverse, drifting the reserve by 1e12x too little
per swap once the simulator's internal reserve is corrected to real scale.

Convert the native-flagged reserve back to real decimals when building the
v3 simulator, and rescale swap amounts back to real decimals before
applying them in UpdateBalance, mirroring the existing Unwrap/WrapNativeAmount
pair already used around CalcAmountOut/CalcAmountIn.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* Revert "fix: erc4626 reserves" (#1705)

This reverts commit c29079c8c0e936cfd6baea23a8d5654882656879.

* chore: erc4626 omitempty (#1706)

* feat(uniswap-v4): export DefaultGas (#1704)

* feat(uniswap-v4): export DefaultGas

Lets callers outside the package read the gas the V4 simulator charges per
swap and per crossed tick instead of mirroring the numbers.

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

* chore: fix lint in lunya-fun and arcade

- lunya-fun: drop the unused tradeEvent and a dead snipeBps reassignment
  (only the capped feeBps is read afterwards; quoteBuy is unchanged)
- arcade/curve: pass context.Background() instead of a nil Context

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* integrate/thog prop (#1707)

* feat: integrate ThogAMM (thog-prop) on Monad

Single 8-token pool wrapping ThogAMM's makerQuoteExactInput RFQ interface.
Math ported verbatim from the vendor's exact-input pseudocode (bit-packed
risk/price state) and verified exact-to-the-wei against 4 live on-chain
quotes, including same-category, cross-category-with-penalty, and the
XAUt0/index-7 special-case branches. One pool instead of 28 pairwise pools
because the risk penalty reads shared cross-pair inventory state that would
desync if split.

Registers thog-prop in PropAMMSourceSet, and separately backfills
kaliber-prop/kaliber-rwa-prop, two caliber-prop-handler dexIds in
pool-service that were never added despite sharing the same risk profile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kj8B7Z5J4MwwD5BoLzCXr

* fix: port thog-prop math from math/big to uint256.Int

The original port used math/big throughout, violating this project's
uint256 preference and skipping the mandatory go-uint256/go-muldiv/
go-variable-reuse skills entirely -- caught in review, not before commit.

Rewrites math.go and the State/PoolSimulator plumbing to uint256.Int:
- bits/signedN via shift-based sign extension instead of big.Int branching
- maxFlooredInput's (maximum+1)*divisor term, which genuinely needs
  >256-bit precision when maximum saturates at maxU256, rewritten
  algebraically (ceilDiv(N,M)-1 == floor((N-1)/M)) to avoid ever forming
  maximum+1, using MulDivOverflow/MulMod for the wide product/remainder
  instead of math/big -- the one spot in this file where naive uint256
  arithmetic would silently wrap; verified against 12 cases including
  random fuzz inputs and both saturating/non-saturating boundaries
  (TestMaxFlooredInput)
- signed penalty/covariance/exposure terms via Mul+SDiv, with the one
  near-255-bit-magnitude product (penaltyUsdWad's variance term) bound
  documented and verified against the protocol's own risk-notional cap
- Reserves/TokenAmount stay *big.Int at the pool.Pool public boundary
  (framework convention, see capricorn-pamm), converted via
  uint256.FromBig/.ToBig at the edges

Also removes the duplicate bigFromString/parse helpers (same three lines
written twice in one file). All 4 golden fixtures still pass exact-to-
the-wei; golangci-lint clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kj8B7Z5J4MwwD5BoLzCXr

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* merge/kipseli pamm prop (#1708)

* feat: integrate ThogAMM (thog-prop) on Monad

Single 8-token pool wrapping ThogAMM's makerQuoteExactInput RFQ interface.
Math ported verbatim from the vendor's exact-input pseudocode (bit-packed
risk/price state) and verified exact-to-the-wei against 4 live on-chain
quotes, including same-category, cross-category-with-penalty, and the
XAUt0/index-7 special-case branches. One pool instead of 28 pairwise pools
because the risk penalty reads shared cross-pair inventory state that would
desync if split.

Registers thog-prop in PropAMMSourceSet, and separately backfills
kaliber-prop/kaliber-rwa-prop, two caliber-prop-handler dexIds in
pool-service that were never added despite sharing the same risk profile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kj8B7Z5J4MwwD5BoLzCXr

* fix: port thog-prop math from math/big to uint256.Int

The original port used math/big throughout, violating this project's
uint256 preference and skipping the mandatory go-uint256/go-muldiv/
go-variable-reuse skills entirely -- caught in review, not before commit.

Rewrites math.go and the State/PoolSimulator plumbing to uint256.Int:
- bits/signedN via shift-based sign extension instead of big.Int branching
- maxFlooredInput's (maximum+1)*divisor term, which genuinely needs
  >256-bit precision when maximum saturates at maxU256, rewritten
  algebraically (ceilDiv(N,M)-1 == floor((N-1)/M)) to avoid ever forming
  maximum+1, using MulDivOverflow/MulMod for the wide product/remainder
  instead of math/big -- the one spot in this file where naive uint256
  arithmetic would silently wrap; verified against 12 cases including
  random fuzz inputs and both saturating/non-saturating boundaries
  (TestMaxFlooredInput)
- signed penalty/covariance/exposure terms via Mul+SDiv, with the one
  near-255-bit-magnitude product (penaltyUsdWad's variance term) bound
  documented and verified against the protocol's own risk-notional cap
- Reserves/TokenAmount stay *big.Int at the pool.Pool public boundary
  (framework convention, see capricorn-pamm), converted via
  uint256.FromBig/.ToBig at the edges

Also removes the duplicate bigFromString/parse helpers (same three lines
written twice in one file). All 4 golden fixtures still pass exact-to-
the-wei; golangci-lint clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011kj8B7Z5J4MwwD5BoLzCXr

* feat: merge kipseli-pamm into kipseli-prop, extract shared titan package

Unifies the two kipseli venues into a single config-driven package and
extracts shared Titan state-override RPC logic into pkg/liquidity-source/titan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsckoLF2xdvCBysSFEMLMU

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* feat: add UnwrapAndUnstake action to net-staking (wsNET -> NET) (#1709)

Adds the reverse composite direction (unwrap wsNET -> sNET, then unstake
sNET -> NET) to the simulator, matching the corresponding on-chain executor
action so this route can be quoted and executed end-to-end.


Claude-Session: https://claude.ai/code/session_011kj8B7Z5J4MwwD5BoLzCXr

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* fix/ladder one sided first probe (#1710)

* fix: ladder package fails to probe desk-style pools with zero input-side reserve

SamplePoints' first-probe fallback sized/gated the probe grid purely on
currentInputReserve, which is a valid AMM-depth proxy but not for desk-style
pools (caliber-prop and friends) that can legitimately hold zero inventory of
the token being sold in while still quoting against the other side's
capacity. This left every direction with a zero input reserve permanently
stuck with an empty ladder, even though on-chain quoting worked fine.

Falls back to a decimals-anchored geometric sweep when the input reserve
gives no usable basis, and stops appendClamped from clamping canary/farthest
points down to zero in that same case. Verified against a live one-sided
robinhood pool (NVDA/USDG, reserveX=0): first probe now recovers the full
ladder and CalcAmountOut matches the on-chain quoter within ~30bps.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: make ladder's first-probe sweep unconditional, widen its span

Every ladder embedder quotes via an opaque on-chain quote/estimateSwap call,
not a reserve-ratio formula, so the input-side reserve was never a
principled basis for the first-probe grid even when non-zero -- it only
failed loudly (empty ladder) at exactly zero. Drop the reserve-based branch
entirely in favor of the decimals-anchored sweep for every first probe.

Widen the sweep from base-10 to base-32 steps (same 7 magnitude anchors,
center +/- 3 steps) so it still brackets pools whose real tradeable range
sits well away from 10^decimals.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* chore: add aegisprop hook address (#1711)

* feat(kipseli-prop): resolve venue topology live via a deployless lens (#1712)

kipseli migrated ethereum's pAMM to a new PrioUpdateRegistry behind a new
quoter; the configured lensAddress still pointed at the old one, whose
registry Titan no longer pushes, so every quote reverted and the venue got no
quotes. Hardcoded lens/positionCap/verifier addresses go stale on every such
migration.

KipseliPropLens (ks-helper-sc) runs as a to-less eth_call whose constructor
reverts with a snapshot: it walks router -> swapImpl() hops to the leaf
SwapImpl, then reads wallet, quote token, balance-slippage caps,
getQuoter(dest) (unwrapping realQuoter) and every probe via the leaf's
quote(..., dest) - the exact swap-path amount. Reading router.swapImpl() first
also warms the slot CheckIfHot-gated pricers (base) key on, so quotes match
the signed router.quote exactly (verified base/bsc/robinhood at pinned blocks).

- one RPC per tracker cycle (was 2-3); prop and pAMM share one path, no EIP-712
  signing needed for tracking; dest defaults to the KyberSwap executor
- pAMM Titan override also pins block.number to Titan's target block
- ladders trimmed at the wallet-capped plateau SwapImpl returns
- lister returns the full list each poll instead of an offset cursor that
  could strand lost pool records forever
- Config drops lensAddress/positionCapAddress/multicall3Address/verifier/quoter;
  add pkg/util/eth.DeploylessCall


Claude-Session: https://claude.ai/code/session_01GbcvYKLRHKZ3UkbVRjLRAX

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* feat: migrate tidefi-prop to feePpm swap interface + Taker API pool lister (#1713)

TideFi's new contract (0x74FFeDA2...) adds a feePpm parameter to
quote()/swap(), replacing the old 4-arg interface which no longer works.
Switches the tracker to the new quote() signature, and the pool lister
from a static config-provided token list to discovering pairs via TideFi's
Taker API (websocket, tidefi_markets push).

Also fixes reserves: the new contract is a stateless fee wrapper that
never holds funds itself (confirmed via decompilation -- the actual
liquidity engine is a hardcoded bytecode literal, not discoverable
on-chain), so balanceOf checks now target the configured Vault address
instead of the wrapper.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* fix: erc4626 reserves lazy missed by 34678146 (#1717)

* feat: add slyng-fun, Slyng's launchpad bonding curve on Robinhood Chain (#1685)

Slyng is a token launchpad on Robinhood Chain (4663). One Launchpad
contract holds every curve, keyed by the coin it sells, and a coin
trades only against it until its reserve reaches the graduation
target, when the launchpad moves the liquidity to a Uniswap v4 pool.

Discovery decodes TokenCreated on the launchpad; the coin's address is
the pool, an ETH-quoted curve is listed under WETH. Bought, Sold and
Graduated are emitted by the launchpad itself with the coin indexed,
so DecodePoolAddressesFromFactoryLog routes them back to their curve.
The tracker reads curves(token) and the launchpad's trade constants in
one block-pinned multicall. The simulator ports _buy and sell to the
wei: constant product over virtual reserves, 1% fee on the quote leg,
a flat opening surcharge for the first thirty seconds, a sell clamped
to the reserve, and the buy that fills the curve marked as graduating
so nothing routes through it afterwards.

Tests cover the maths against the launchpad's own quoteToTokens and
tokensToQuote at a pinned block, the surcharge window edges, the
graduating buy, clone and update behaviour, log decoding, and a live
discover-track-quote pass against mainnet.

* feat(uniswap-v4): register the Flaunch v1.3 / v1.4 / vested hooks, read swapFee and spend gates on-chain (#1690)

Flaunch has shipped new PositionManager generations since the original integration
(#1047): v1.3 on Base, Arbitrum and Robinhood Chain, v1.4.0 on Ethereum, and the
vested-launch AnyPositionManager (one CREATE3 address on Base, Arbitrum and Robinhood).
None were registered, so their pools fell through to the auto-detection fallback.

- constant.go: register the missing hooks (address-keyed, so the parity address is
  listed once).
- hook.go: Track reads FeeDistribution.swapFee from getPoolFeeDistribution instead of
  hardcoding 1% (the protocol default, but owner-overridable per pool), and follows
  feeCalculator -> FeeCalculatorDispatcher.poolCalculator -> spendGateSettings so a pool
  behind an enforcing spend gate (Game Mode launches revert without a signed
  authorization) refuses to quote until the gate expires. Older generations without a
  dispatcher take the tolerant path and report no gate.
- Pools tracked before this change keep pricing at the 1% default until their next Track.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* feat: add deployed INVERSE routed v4 hook on Robinhood (#1692)

* feat: add draft INVERSE v4 hook tracker and exact-input simulator

* test: verify inverse dispatch in both currency orders

* test: cover zero-liquidity inverse tracker snapshots

* feat: register verified Robinhood INVERSE deployment

* feat(uniswap-v4): support Arcade launchpad hook v2 (Arc) (#1697)

* feat(uniswap-v4): support Arcade launchpad hook v2 (Arc)

ArcadeHook v2 (0x7706d261f0C370e8f0E273164A603E885C89beC2 on Arc, permission
bitmap 0x3EC2) is v1 without the two RETURNS_DELTA bits. A graduated PUMP pool
now carries a static 1% LP fee in its PoolKey and the hook takes no swap
delta, so the V4 pool math is the whole quote. The feeObs oracle is gone.

uniswap-v4-arcade: the generation is decided from the hook address
(Generations). v2 pools pay no hook fee, Track skips the feeObs read that
would revert the batch, and exact-out is allowed. v1 pools are quoted exactly
as before (#1674).

arcade-fun: the config takes several hooks; a launch is stamped with the hook
that emitted it and trades there. Curve math is unchanged (#1675): every
parity vector was re-executed against the v2 bytecode, bit-exact.

Parity fixtures for v2 come from the real hook run against a v4-core
PoolManager in Foundry, both currency orderings, exact-in and exact-out.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NLjP6noF6zmsWFKoZXWeT3

* test(uniswap-v4): correct the measured word-step gap in the Arcade v2 fixture note

The three crossing buys sit at 0.55 to 1.23 input base units, not 0.6 to 1.3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NLjP6noF6zmsWFKoZXWeT3

* test(arcade-fun): pass a real context to the factory log decoder (staticcheck SA1012)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NLjP6noF6zmsWFKoZXWeT3

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* feat: register ProtocolFeeHook as uniswap-v4-stables-fast (Robinhood chain) (#1699)

* feat: register ProtocolFeeHook as uniswap-v4-stables-fast on Robinhood chain

The aggregator already routes these pools under the fitted uniswap-v4-fee
label. This replaces the inference with a read of the rate the contract
charges, and pins an exchange id for it.

- The whole fee is in afterSwap, on the unspecified leg: the OUTPUT of an
  exact-in swap, the realised INPUT of an exact-out one. beforeSwap returns
  a zero delta and only writes the oracle observation
- A partial fill is therefore charged on the fill, which is what a router
  passing its own sqrtPriceLimitX96 needs
- Exact-out is flat, not grossed up: net + floor(net*fee/1e6)
- Read feePipsFor every Track: the rate is owner-mutable and the guard
  zeroes it during a halt, neither of which emits an event on the pool
- Verified against the deployed hook by a mainnet-fork test in the
  protocol's own repo, not only by these unit tests

* test: cover Track's RPC path without a live node

The two live tests skip whenever CI is set, so the multicall that reads the
hook was only ever exercised on a developer's m…
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