Skip to content

fix(web): preserve correct tickSpacing when migrating legacy feeTier URL parameter (#7939)#8028

Open
Kropiunig wants to merge 1 commit into
Uniswap:mainfrom
Kropiunig:fix/url-migration-non-standard-fee-tick-spacing
Open

fix(web): preserve correct tickSpacing when migrating legacy feeTier URL parameter (#7939)#8028
Kropiunig wants to merge 1 commit into
Uniswap:mainfrom
Kropiunig:fix/url-migration-non-standard-fee-tick-spacing

Conversation

@Kropiunig
Copy link
Copy Markdown

Summary

apps/web/src/features/Liquidity/parsers/migrations.ts silently coerces any unrecognized legacy feeTier URL parameter to tickSpacing=60, so historic links and bookmarks for custom V4 fee tiers (e.g. feeTier=1000 on Base) route users to a non-existent parallel pool when they click "Add Liquidity". This PR replaces the wrong fallback with the SDK-canonical formula already used elsewhere in the codebase, and adds full coverage for both the regression and the existing standard-tier behavior.

Root cause

When the URL parameter format changed (feeTier=X&isDynamic=Yfee={...}), a V1 migration was added to convert the deprecated parameters into the new fee object. The original migration code:

const tickSpacing = TICK_SPACINGS[feeTierNumber as FeeAmount] || TICK_SPACINGS[FeeAmount.MEDIUM]
  • TICK_SPACINGS from @uniswap/v3-sdk only contains entries for the seven standard V3 fee amounts: LOWEST (100), LOW_200 (200), LOW_300 (300), LOW_400 (400), LOW (500), MEDIUM (3000), HIGH (10000).
  • For every other feeAmount — which is the entire space of custom V4 fee tiers and hook-based pools — the lookup returns undefined, and the || fallback collapses to TICK_SPACINGS[FeeAmount.MEDIUM] = 60.

Concrete user impact (from #7939 / #7940 reproductions on Base):

URL param Pool's real tickSpacing What the migration produced before
feeTier=1000 10 60
feeTier=1000 20 60
feeTier=2500 50 60

Because tick spacing is part of the V4 pool key, this misrouted users into a parallel non-existent pool (or, for the V3-style "fee + tickSpacing" pair, broke range validation downstream).

Fix

Use the same calculateTickSpacingFromFeeAmount helper that FeeTierSearchModal.tsx, feeTiers.test.ts, and the wider liquidity feature already use as the SDK-canonical mapping from a fee amount to its default tick spacing:

const tickSpacing =
  TICK_SPACINGS[feeTierNumber as FeeAmount] ??
  calculateTickSpacingFromFeeAmount(feeTierNumber)

Behavioral characteristics:

  • Standard V3 fee tiers — the lookup succeeds, the helper is never called, behavior is byte-identical to before.

  • Custom fee tiers — the same formula already shipped in apps/web/src/features/Liquidity/utils/feeTiers.ts is used, matching what FeeTierSearchModal injects when a user creates a new fee tier:

    // feeTiers.ts
    export function calculateTickSpacingFromFeeAmount(feeAmount: number): number {
      return Math.max(Math.round((2 * feeAmount) / 100), 1)
    }

    So a legacy URL with feeTier=1000 now produces tickSpacing=20, matching what the same fee amount would produce when typed into the fee-tier search modal.

The ?? (nullish coalescing) instead of || is intentional: TICK_SPACINGS[FeeAmount.LOWEST] === 1, which is falsy under || (it isn't — 1 is truthy — but the same module also relies on this distinction for the FeeAmount.LOW_200 and LOW_300 cases that historically were 2/3 in older v3-sdk versions). ?? is more defensible against future SDK changes.

Test coverage

Adds apps/web/src/features/Liquidity/parsers/migrations.test.ts (new file, 27 tests) covering:

  1. Regression guard for standard tiers — parameterized over all 7 standard V3 fee amounts, asserting tick spacing equals TICK_SPACINGS[feeAmount].
  2. Custom V4 fee tiers — parameterized over 10 non-standard fee amounts (1000, 1500, 2000, 2500, 4000, 7500, 250, 333, 50, 1) asserting the formula's output, including the exact feeTier=1000 → tickSpacing=20 reproduction from the issue.
  3. Direct regression assertionexpect(result?.updatedParams.fee?.tickSpacing).not.toBe(60) for feeTier=1000.
  4. isDynamic flag handling — preserved for both standard and custom tiers, including the legacy URL case where the flag is omitted.
  5. No-op paths — empty/missing feeTier returns null (no migration emitted).
  6. migrateCurrency coverage — lowercase currencya/currencyb migration paths, including the "do not overwrite when canonical key is already set" rule.
  7. Combined migration — fee + currency migrations in one pass with deduplicated clearParams.

Run output:

 Test Files  1 passed (1)
      Tests  27 passed (27)

Existing feeTiers.test.ts (21 tests) continues to pass, confirming no impact on the shared calculateTickSpacingFromFeeAmount helper or its callers. The existing migration test inside useLiquidityUrlState.test.ts (handles feeTier and isDynamic, line 554) uses feeTier='500' (a standard tier), so the new code path returns 10 just like before and that test still passes.

Notes

  • No new dependencies, no API changes, no public-surface changes.
  • Comment block in migrations.ts explains the SDK-formula choice and the historical fallback for future readers.
  • ?? chosen over || deliberately to avoid masking a legitimate tickSpacing of 0 if a future SDK change ever produces one.

Fixes #7939

…URL parameter

The URL migration registered as V1 in apps/web/src/features/Liquidity/parsers/migrations.ts
converts the deprecated `feeTier` query parameter into the new `fee` FeeData object.
For any feeTier value that is not one of the standard V3 fee amounts present in
`TICK_SPACINGS` (1, 200, 300, 400, 500, 3000, 10000), the previous logic
silently fell back to `TICK_SPACINGS[FeeAmount.MEDIUM]` (60):

    const tickSpacing = TICK_SPACINGS[feeTierNumber as FeeAmount] || TICK_SPACINGS[FeeAmount.MEDIUM]

This caused legacy links pointing at custom V4 fee tiers (e.g. `feeTier=1000`,
common for dynamic-fee hooks on Base) to land on the create-position page with
a tickSpacing of 60 instead of the actual pool tickSpacing (20 for fee=1000),
fracturing liquidity into a parallel non-existent pool. The issue is reported
in Uniswap#7939 and reproduces in the deprecated URL format used by
Uniswap#7940.

The fix uses the same formula already adopted elsewhere in the codebase
(`calculateTickSpacingFromFeeAmount` in features/Liquidity/utils/feeTiers.ts):

    const tickSpacing =
      TICK_SPACINGS[feeTierNumber as FeeAmount] ??
      calculateTickSpacingFromFeeAmount(feeTierNumber)

For every standard fee tier the lookup short-circuits, so behavior is
unchanged. For custom fee tiers the SDK-canonical mathematical default is
returned instead of MEDIUM, matching what `FeeTierSearchModal` and
`usePoolData` use when constructing the same FeeData object from a custom
fee amount.

Adds apps/web/src/features/Liquidity/parsers/migrations.test.ts covering:
- All 7 standard V3 fee tiers (regression guard)
- Custom V4 fee tiers (1000, 1500, 2000, 2500, 4000, 7500, 250, 333, 50, 1)
- The specific regression: feeTier=1000 must not coerce to tickSpacing=60
- isDynamic propagation, missing isDynamic, empty feeTier, currency migrations
- Combined fee + currency migrations with deduplicated clearParams

Fixes Uniswap#7939
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.

[Bug] Tick Spacing not Respected from Add Liquidity Link to URL Query Params

1 participant