Skip to content

feat(liquidity-launcher-sdk)!: derive permanence from the time horizon only (LP-1362) - #704

Open
brnunes wants to merge 1 commit into
mainfrom
bruno/lp-1362-permanence-sentinel-floor
Open

brnunes wants to merge 1 commit into
mainfrom
bruno/lp-1362-permanence-sentinel-floor

Conversation

@brnunes

@brnunes brnunes commented Aug 21, 2026 •

Copy link
Copy Markdown
Collaborator

Not for release this week. Batches with further SDK changes before we cut a version. Note the changeset is major — see Breaking below.

What

isPermanentTimelock now always judges permanence from the real time horizon past the auction end. The chain-agnostic raw-block form is gone.

-  if (params.chainId !== undefined && params.endBlock !== undefined) {
-    const horizonSeconds = Number(params.unlockBlock - params.endBlock) * getBlockTimeSeconds(params.chainId)
-    return horizonSeconds >= PERMANENT_TIMELOCK_MIN_HORIZON_SECONDS
-  }
-  // Sentinel form: chain-agnostic raw-block approximation.
-  return params.unlockBlock >= PERMANENT_UNLOCK_BLOCK_THRESHOLD
+  const horizonSeconds = Number(params.unlockBlock - params.endBlock) * getBlockTimeSeconds(params.chainId)
+  return horizonSeconds >= PERMANENT_TIMELOCK_MIN_HORIZON_SECONDS

Breaking:

  • PERMANENT_UNLOCK_BLOCK_THRESHOLD deleted.
  • PermanentTimelockParams no longer accepts {unlockBlock} alone; the block form requires chainId + endBlock.
  • Timestamp form and the structural ('burn' / 'creatorFees') short-circuit unchanged.

Why the raw-block form had to go

It was a single block count standing in for a duration, so its meaning changed per chain — 2e11 blocks is ~76,000 years at 12 s/block and ~634 years at 0.1 s. Two consequences:

  • It couldn't see slow-chain permanence at all. A 1000-year horizon on a 12 s chain is ~2.6e9 blocks, three orders of magnitude under the 2e11 bound. No raw-block threshold can catch that without also swallowing every finite lock on the fast chains.
  • It made two surfaces disagree. data-api's classifier already called isPermanentTimelock with chainId/endBlock; only the serving path used the raw form. Same lock, two answers.

And it had no caller left to protect. Across sdks, backend and universe there is exactly one production call site — data-api/src/bl/workers/auction/quickLaunch.ts:135 — and it already passes chainId, endBlock, unlockBlock. The serving path the raw form was introduced for has auction.end_block and auction.chain_id in scope at the call site.

The one row that changes

A stored unlock block encodes the block time believed when the lock was created. Chain 4663 has one row converted at the wrong cadence:

auction   4663_0xC5EdF109028fC48197aa0Ad4B750BdCBF9cab998   created 2026-07-08
horizon   262,800,000,000 blocks  ==  PERMANENT_TIMELOCK_REQUEST_SECONDS / 12  (exactly)
          → the 100,000-year Permanent preset, converted at 12 s/block
at 4663's real 0.1 s cadence  →  833 years  →  finite

The conversion predates 4663's 0.1 entry in BLOCK_TIME_SECONDS_BY_CHAIN by one day. Every 4663 permanent lock from 2026-07-13 on is 31,536,000,000,000 — exactly 1.0000× the 0.1 s conversion. This is the only row from the gap.

Critically, this is not a regression: the classifier already stored is_quick_launch = false for that auction, because it already used the chain-aware form. The raw-block threshold was what made serving claim locked_forever: true for a lock the classifier had already judged finite. Removing it makes the two agree.

Verified against all 2,642 production auction_liquidity_locks rows joined to their auctions: this is the only row where the raw-block and horizon rules differ.

Tests

The sentinel describe block is replaced by three cases on the real data: the mis-converted 4663 horizon reading finite at the chain it actually runs at, the same request converted at the correct cadence reading permanent, and a permanent mainnet lock whose raw block number (~2.6e9) is far too small for any raw-block bound to have seen.

Two pre-existing tests used the removed {lockMode, unlockBlock} shape and now pass chainId/endBlock — the block-form equivalents were already asserted alongside them, so no coverage is lost.

  • bun test → 230 pass / 0 fail (15 files)
  • tsc --noEmit -p tsconfig.types.json → clean

Follow-up (not in this PR)

On the version bump, Uniswap/backend#12485's serving call site moves from {lockMode, unlockBlock} to {lockMode, chainId, endBlock, unlockBlock}, and its boundary test is rewritten against the horizon. Part of LP-1362.

🤖 Generated with Claude Code

@brnunes
brnunes requested a review from a team as a code owner August 21, 2026 22:47
@linear-code

linear-code Bot commented Aug 21, 2026

Copy link
Copy Markdown

LP-1362

@github-actions

github-actions Bot commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

● Reviewed · against fbd2a87 · 2026-08-21 23:07 UTC · 2 reviews · view run ↗

Note

Approved.

Removes the chain-agnostic raw-block sentinel from isPermanentTimelock, so permanence is always derived from the chain-aware time horizon past the auction end.

Assessment

This collapses a two-branch predicate into one canonical definition of permanence, deleting PERMANENT_UNLOCK_BLOCK_THRESHOLD and the divergence where the serving path and the data-api classifier could answer differently for the same lock. The type union now forces chainId + endBlock for the block form, so the old unknown-chain path is gone by construction; the block-form math is identical to the prior code when chainId was supplied, so no new NaN path is introduced. The single production row that flips to finite (chain 4663, mis-converted at 12 s/block) aligns serving with the classifier that already stored is_quick_launch = false — not a regression. It's a major breaking change; the follow-up backend PR migrates the last remaining serving call site.

Iteration history · 2 reviews
2026-08-21 23:07 UTC · ✅ approved · 0 findings · fbd2a87 · run ↗

(no findings)

2026-08-21 22:49 UTC · ✅ approved · 0 findings · 9e91f6f · run ↗

(no findings)

Tip

Teach the reviewer. React 👍 on findings that helped, 👎 on false positives. Reply to push back or add context — we aggregate this weekly to tune the bot.

Comment @request-claude-review to re-run.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

✅ Approved — see full review in the sticky comment ↑

@graphite-app
graphite-app Bot requested review from a team August 21, 2026 22:51
@graphite-app

graphite-app Bot commented Aug 21, 2026

Copy link
Copy Markdown

Graphite Automations

"Request reviewers once CI passes on sdks monorepo" took an action on this PR • (08/21/26)

1 assignee was added and 3 reviewers were added to this PR based on Siyu Jiang (See-You John)'s automation.

…n only (LP-1362)

BREAKING CHANGE: removes PERMANENT_UNLOCK_BLOCK_THRESHOLD and the raw-block
form of isPermanentTimelock. The block form now requires chainId + endBlock.

The chain-agnostic threshold could not express "1000 years" on every chain
(~76,000 years at 12 s/block, ~634 at 0.1 s), so which locks it caught depended
on the chain it was applied to, and it could not see a permanent lock on a slow
chain at all (~2.6e9 blocks at 12 s, three orders under the 2e11 bound). It had
no remaining caller: the only production call site already passed chainId and
endBlock, and the serving path the raw form was added for has both in scope.

One row changes in the field. 4663_0xC5EdF1… (2026-07-08) carries a horizon of
PERMANENT_TIMELOCK_REQUEST_SECONDS / 12 = 262,800,000,000 blocks — the 100k-year
Permanent preset converted at 12 s/block, a day before 4663's 0.1 s cadence was
registered. At the real cadence that is 833 years, so it is now finite. That
agrees with the classifier, which already stored is_quick_launch = false for it;
the raw-block form was what made serving disagree. Checked against all 2,642
prod auction_liquidity_locks rows — it is the only row the two rules differ on.

230 SDK tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@brnunes
brnunes force-pushed the bruno/lp-1362-permanence-sentinel-floor branch from 9e91f6f to fbd2a87 Compare August 21, 2026 23:05
@brnunes brnunes changed the title feat(liquidity-launcher-sdk): make the permanence sentinel a floor under the chain-aware form (LP-1362) feat(liquidity-launcher-sdk)!: derive permanence from the time horizon only (LP-1362) Aug 21, 2026

This branch has not been deployed

No deployments
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.

1 participant