Skip to content

feat: buy cooldown, key simulation, slippage tolerance, deprecation notice - #898

Merged
Chucks1093 merged 7 commits into
accesslayerorg:devfrom
davedumto:feat/buy-cooldown-key-simulation-slippage-tolerance-deprecation-notice
Aug 31, 2026
Merged

feat: buy cooldown, key simulation, slippage tolerance, deprecation notice#898
Chucks1093 merged 7 commits into
accesslayerorg:devfrom
davedumto:feat/buy-cooldown-key-simulation-slippage-tolerance-deprecation-notice

Conversation

@davedumto

@davedumto davedumto commented Aug 31, 2026

Copy link
Copy Markdown

Summary

Implements 4 key detail / portfolio buy-UX issues: a per-user buy cooldown countdown, a key buy simulation tool, slippage tolerance on buy/sell modals, and a deprecation notice + redeem flow for deprecated keys.

#873 — Buy cooldown countdown on the key detail page

No buy-cooldown concept existed anywhere in the repo before this change (no lastBuyAt/cooldown field, no contract ABI or read method — src/contracts/abis only has a README and src/types/contracts/index.ts is empty). The closest existing analog, last_buy_timestamp / LockupCountdown, drives a sell-side lockup, the opposite of what this issue asks for.

  • Added HeldKeyPosition.nextBuyAllowedAt (per-user) and Course.nextBuyAllowedAt (creator-wide fallback) as the wiring point for this data once the backend/contract returns it.
  • Added buyCooldown.utils.ts (computeRemainingCooldownSeconds, formatCooldownDuration) and BuyCooldownCountdown, a self-ticking countdown mirroring LockupCountdown's setInterval/onExpire pattern, rendering "Next buy available in 4m 32s" style text.
  • Mounted on CreatorDetailPage for authenticated users.
  • Renders nothing when no cooldown data is present or it has expired — this reflects real state only, it does not fabricate a client-only timer disconnected from backend data.

#875 — Key simulation tool on the key detail page

  • Added keySimulation.utils.ts's simulateKeyBuy, which reuses the existing bonding-curve primitives (computeBondingCurvePrice / computeBuyCost) for the curve-aware gross cost and start/end price, plus the same bps fee math used by pricePreview.utils, so simulation numbers stay consistent with the real buy flow instead of duplicating pricing logic.
  • Added KeySimulationTool: an input for a hypothetical buy quantity showing projected start/end price, price impact %, average price paid, fees, and total cost.
  • Mounted on CreatorDetailPage between the price chart and holder concentration sections.

#872 — Slippage tolerance in buy/sell modals

Checked whether mawuli's slippage work (issue #877, PR #895SlippageToleranceSelector.tsx / slippageTolerance.utils.ts) had landed on upstream/dev. It had not (PR #895 is still open; upstream/dev was unchanged at the time this branch was rebased), so this is a from-scratch implementation matching the same computation contract described in #872.

  • Added slippageTolerance.utils.ts computing max_price (buy) and min_price (sell) from a preview price and tolerance percentage: max_price = preview_price * (1 + tolerance), min_price = preview_price * (1 - tolerance), floored at 0.
  • Added SlippageToleranceSelector with 0.5% / 1% / 5% presets and a custom input validated to [0, 50]%.
  • Wired the selector into TradeDialog for both buy and sell; the computed bound is forwarded through onConfirm.
  • Extended TradeVariables / useTradeMutation with maxPriceStroops / minPriceStroops so the (simulated) contract call carries the slippage bound.
  • Routed LandingPage's sell confirmation through the same useTradeMutation path as buy (previously a disconnected inline setTimeout stub), so slippage protection, optimistic updates, and rollback behave consistently for both trade directions.

#871 — Deprecation notice + redeem button on the portfolio page

  • Added Course.deprecated / deprecationReason as the status field marking a key deprecated.
  • Added keyDeprecation.utils.ts: isKeyDeprecated and estimateRedeemValue (quantity × current per-key price, reusing resolveCreatorKeyPriceStroops the same way reinvestDividend.utils reuses it for its own estimate).
  • Added DeprecationNotice badge and RedeemKeyDialog (mirrors ReinvestDividendDialog's structure/testids) showing held quantity, per-key price, and total redemption value.
  • Added useRedeemDeprecatedKeyMutation, mirroring useReinvestDividendMutation's optimistic-update/rollback/invalidation shape; on success it removes the position entirely.
  • Wired into PortfolioHoldingRow: deprecated keys show the notice badge, hide Buy/Sell and the sell lockup countdown, and show a Redeem button opening the confirmation dialog.

Test plan

  • Added focused unit/component tests for every new util and component (slippage math, key simulation math, cooldown math, deprecation/redeem math, plus RTL tests for SlippageToleranceSelector, KeySimulationTool, BuyCooldownCountdown, DeprecationNotice, RedeemKeyDialog, and TradeDialog's new slippage wiring) — 137 tests across 16 new/changed test files, all passing.
  • Updated two pre-existing TradeDialog tests that the new slippage selector legitimately changed: TradeDialog.a11y.test.tsx's sell-modal Tab-order test (now passes through the new preset/custom-input controls before reaching Confirm) and TradeDialog.sellPayoutDisplay.test.tsx's onConfirm assertion (now receives a third slippage argument).
  • npx tsc -b — clean.
  • npx eslint on all touched files — clean.
  • npx vite build — succeeds (pre-existing bundle-size and third-party /*#__PURE__*/ comment warnings only, unrelated to this change).
  • Full npx vitest run compared against the pre-existing base commit (6f05e2e, before this branch's work): both show the same ~49 pre-existing failing test files from environment-level issues (WagmiProvider/localStorage mocking gaps when the full suite runs together, and file-order-dependent flakiness — e.g. LandingPage.buyFlowEndToEnd.integration.test.tsx passes individually but is order-sensitive in a full run on both branches). No new regressions were introduced beyond the two TradeDialog tests intentionally updated above.

Caveats

  • #873 and #871 add new optional fields (nextBuyAllowedAt, deprecated, deprecationReason) to Course/HeldKeyPosition as the client-side wiring point; neither the mock API nor a contract currently populates them, so the countdown/notice will activate automatically once the backend or contract starts returning real values.
  • #872's contract call is still the existing simulated setTimeout stub in useTradeMutation/useRedeemDeprecatedKeyMutation (matching the rest of the repo's trading hooks) — maxPriceStroops/minPriceStroops are threaded all the way to the mutation variables with a comment marking where the real max_price/min_price contract args would go.

closes #873
closes #875
closes #872
closes #871

- Add slippageTolerance.utils.ts computing max_price (buy) and
  min_price (sell) from a preview price and tolerance percentage:
  max_price = preview_price * (1 + tolerance), min_price =
  preview_price * (1 - tolerance), floored at 0.
- Add SlippageToleranceSelector with 0.5%/1%/5% presets and a
  custom input validated to [0, 50]%.
- Wire the selector into TradeDialog for both buy and sell, and
  forward the computed bound through onConfirm.
- Extend TradeVariables/useTradeMutation with maxPriceStroops/
  minPriceStroops so the (simulated) contract call carries the
  slippage bound.
- Route LandingPage's sell confirmation through the same
  useTradeMutation path as buy (previously a disconnected inline
  stub) so slippage protection, optimistic updates, and rollback
  behave consistently for both trade directions.
- Update TradeDialog.a11y/.sellPayoutDisplay tests for the new
  focusable controls and onConfirm signature.

No prior slippage implementation had landed on upstream/dev at the
time of this work (PR accesslayerorg#895 with SlippageToleranceSelector/
slippageTolerance.utils was still open), so this is a from-scratch
implementation matching the computation contract described in accesslayerorg#872.
)

- Add keySimulation.utils.ts's simulateKeyBuy, which reuses the
  existing bonding-curve primitives (computeBondingCurvePrice /
  computeBuyCost) for the curve-aware gross cost and start/end
  price, plus the same bps fee math used by pricePreview.utils, so
  simulation numbers stay consistent with the real buy flow instead
  of duplicating pricing logic.
- Add KeySimulationTool: an input for a hypothetical buy quantity
  showing projected start/end price, price impact %, average price
  paid, fees, and total cost.
- Mount it on CreatorDetailPage between the price chart and holder
  concentration sections.
- Label the tool's fee rows 'Simulated protocol/creator fee' to
  avoid colliding with the page's existing Fee Structure section
  text (fixed a pre-existing-test collision this introduced).
No per-user buy cooldown concept existed anywhere in this repo prior
to this change — no lastBuyAt/cooldown field on user-key state, and
no ABI/contract read method for it (src/contracts/abis only has a
README; src/types/contracts/index.ts is empty). The closest analog,
last_buy_timestamp, drives a *sell*-side lockup (LockupCountdown),
which is the opposite of a buy cooldown.

- Add HeldKeyPosition.nextBuyAllowedAt and Course.nextBuyAllowedAt
  as the wiring points for this data once the backend/contract
  starts returning it: per-user position value preferred, falling
  back to a creator-wide value on Course.
- Add buyCooldown.utils.ts (computeRemainingCooldownSeconds,
  formatCooldownDuration) and BuyCooldownCountdown, a self-ticking
  countdown mirroring LockupCountdown's setInterval/onExpire
  pattern, rendering 'Next buy available in 4m 32s' style text.
- Mount it on CreatorDetailPage for authenticated users, sourced
  from userPosition.nextBuyAllowedAt (falling back to
  creator.nextBuyAllowedAt).
- Renders nothing when no cooldown data is present or the cooldown
  has expired — this reflects real state only, it does not
  fabricate a client-only timer disconnected from backend data.
…esslayerorg#871)

- Add Course.deprecated/deprecationReason as the status field marking
  a key deprecated (e.g. creator left the platform, key superseded).
- Add keyDeprecation.utils.ts: isKeyDeprecated and
  estimateRedeemValue (quantity * current per-key price, reusing
  resolveCreatorKeyPriceStroops the same way reinvestDividend.utils
  reuses it for its own estimate).
- Add DeprecationNotice badge and RedeemKeyDialog (mirrors
  ReinvestDividendDialog's structure/testids) showing the held
  quantity, per-key price, and total redemption value.
- Add useRedeemDeprecatedKeyMutation to useWallet.ts, mirroring
  useReinvestDividendMutation's optimistic-update/rollback/
  invalidation shape; on success it removes the position entirely.
- Wire into PortfolioHoldingRow: deprecated keys show the notice
  badge, hide Buy/Sell/the sell lockup countdown, and show a Redeem
  button opening the confirmation dialog; wire onRedeem through
  LandingPage's portfolio section.

Full vitest run comparison against the pre-existing base commit
(6f05e2e) shows the same pre-existing environment-level failures
(WagmiProvider/localStorage mocking issues when the full suite runs
together) on both branches — no new regressions from this change
beyond what was already fixed in the accesslayerorg#872 commit (a11y focus order,
sellPayoutDisplay assertion).
@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@davedumto Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Chucks1093
Chucks1093 merged commit c6a7ff8 into accesslayerorg:dev Aug 31, 2026
1 check failed
freebuff-web Bot pushed a commit to Neziahtech/accesslayer-client that referenced this pull request Aug 31, 2026
The merge commit e498adc (PR accesslayerorg#898) corrupted KeySimulationTool.tsx,
SlippageToleranceSelector.tsx, their tests, and slippageTolerance.utils.ts
by duplicating code blocks. Restore working versions from pre-merge commits.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
Seunfunmi-319509 pushed a commit to Seunfunmi-319509/accesslayer-client that referenced this pull request Sep 1, 2026
…org#898)

PR accesslayerorg#898 merge into feat/846-watchlist concatenated two versions of
KeySimulationTool, SlippageToleranceSelector, slippageTolerance.utils,
and their test files, producing broken syntax and duplicate declarations.
- KeySimulationTool.tsx: removed stale courseService-based duplicate, kept
  simulateKeyBuy version used by CreatorDetailPage
- SlippageToleranceSelector.tsx: kept only the accesslayerorg#877 version with
  previewPrice/side/onConfirm props
- slippageTolerance.utils.ts: added missing closing brace for
  computeSlippageBounds, removed duplicate SLIPPAGE_TOLERANCE_PRESETS
- Merged concatenated test files for SlippageToleranceSelector and
  slippageTolerance.utils with combined imports
- CreatorDetailPage.tsx: removed unused WatchlistButton import
- BatchBuyModal.tsx: fixed react-hooks/exhaustive-deps warning by
  capturing debounceTimers.current ref before cleanup

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
Seunfunmi-319509 added a commit to Seunfunmi-319509/accesslayer-client that referenced this pull request Sep 4, 2026
…#900)

The last dev merge into feat/recently-viewed-keys-864 carried over
dev's own badly-resolved merge (e498adc in accesslayerorg#898), which left several
files with duplicate, unparseable content that broke `pnpm lint` and
`tsc`:

- slippageTolerance.utils.ts / SlippageToleranceSelector.tsx /
  KeySimulationTool.tsx and their tests were each stitched from two
  generations of the API (accesslayerorg#872-era and accesslayerorg#877/accesslayerorg#887-era). Restore the
  implementations the tree's consumers (TradeDialog, LandingPage,
  CreatorDetailPage) actually use, from the clean pre-merge state.
- WatchlistToggle.tsx and its test (dev accesslayerorg#861 artifacts) cannot compile
  against the wallet-scoped zustand watchlist store used by this
  branch's components; drop them and restore the store from the
  branch's own last good state, matching the accesslayerorg#870 resolution.

Verified locally: pnpm lint clean, pnpm build green, 46 watchlist /
slippage / recently-viewed tests pass.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
freebuff-web Bot pushed a commit to DanbabaJr/accesslayer-client that referenced this pull request Sep 7, 2026
The accesslayerorg#898 merge appended a second implementation to
KeySimulationTool.tsx, SlippageToleranceSelector.tsx and
slippageTolerance.utils.ts (and spliced imports into interfaces),
leaving the tree unbuildable. Keep the implementation each production
consumer is wired to and drop the orphaned duplicates and their test
suites so tsc and the test runner pass again.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment