Skip to content

feat: Resource Credits pre-publish check + RC top-up purchase client - #978

Merged
feruzm merged 3 commits into
developfrom
feature/rc-precheck
Jun 20, 2026
Merged

feat: Resource Credits pre-publish check + RC top-up purchase client#978
feruzm merged 3 commits into
developfrom
feature/rc-precheck

Conversation

@feruzm

@feruzm feruzm commented Jun 20, 2026

Copy link
Copy Markdown
Member

Summary

Two related Resource Credits (RC) features for the editor surfaces.

  1. Non-blocking RC pre-check. Before a user publishes, comments, or votes, we now estimate whether their RC is likely too low to broadcast and show an inline warning with a top-up CTA. This replaces the experience of submitting and only then hitting the chain's cryptic "Please wait to transact" failure. The check is a hint, never a gate: the publish/comment/vote action stays fully enabled and the banner renders nothing when the user is logged out, the estimate is not ready, or RC is sufficient.

  2. RC top-up purchase client. Adds the client side of a Points-funded, short-term, RC-only delegation to the user's own account (distinct from Boost+, which delegates Hive Power). When the new rcTopup flag is enabled the pre-check CTA opens an in-app top-up dialog; until the backend ships, the flag is off and the CTA falls back to the existing Boost+ purchase page. The dialog and pricing are wired up but inert in production until the backend endpoints and the relay account exist.

The on-chain delegation itself is broadcast by the Ecency relay account, not by the client. The client only signs a Points-spend custom_json (active authority) that the backend acts on.

Changes

SDK (@ecency/sdk)

  • estimateRcPrecheck(...) - new pure helper in resource-credits/utils. Given an RC account and the network-wide rc_api.get_rc_stats averages, returns { ready, willLikelyFail, currentMana, maxMana, avgCost, estimatedCost, deficit, remaining }. Uses a configurable safety buffer (default 1.2x) since on-chain cost varies with load. Returns a non-ready result when inputs are missing, and does not flag failure when the operation's average cost is unknown. Exposes RcPrecheckOperation / RcPrecheckInput / RcPrecheckResult types.
  • buildRcDelegationOp(user, duration) - new custom_json op builder (id ecency_rc_delegation, active authority, payload { user, duration }). Throws on missing user or non-finite duration.
  • useRcDelegation(...) - new mutation (in promotions/mutations) built on useBroadcastMutation, RC-only counterpart to useBoostPlus. Invalidates the account-full and RC account caches on success so the new RC appears once the relay delegation lands.
  • getRcDelegationPricesQueryOptions(accessToken) - new pricing query (in promotions/queries) hitting the private API /private-api/rc-delegation-price, reusing the existing PromotePrice shape. Returns an empty list and is disabled without a token.
  • getRcDelegationActiveQueryOptions(username, accessToken) - new query returning the user's current active RC top-up (if any), so the dialog can block a duplicate purchase up front. Hits /private-api/rc-delegation-active.
  • Barrels updated to export the above; package dist rebuilt (tracked build output).
  • Specs: build-rc-delegation-op.spec.ts, estimate-rc-precheck.spec.ts.

Web (@ecency/web)

  • RcPrecheckBanner + useRcPrecheck - new features/shared/rc-precheck module. The hook reuses the same RC queries the credits widget already loads (cache hit, no extra fetches) and feeds estimateRcPrecheck. The banner has a full layout and a compact single-line variant for tight surfaces; CTA opens the top-up dialog when rcTopup is enabled, otherwise opens the Boost+ purchase page in a new tab.
  • Banner wired into: submit EditorActions (above the action row on new posts), the comment box, and the vote dialog (compact, hidden on paid-out posts).
  • RcTopupDialog - new features/shared/rc-topup module. Two-step Points-spend flow (choose duration, confirm) mirroring the Boost dialog but self-only and RC-scoped, with insufficient-Points validation, a guard that disables the buy when the user already has an active top-up (only one is allowed at a time), and a submit guard against double-broadcast.
  • useRcDelegationMutation - web wrapper in api/sdk-mutations binding the SDK mutation to the active user and web broadcast adapter.
  • Config: new visionFeatures.rcTopup flag (default off in config.ts).
  • i18n: new rc-precheck.* and rc-topup.* strings in en-US.json.

Flags / dependencies / staged status

  • visionFeatures.rcTopup is off by default (config.ts ships enabled: false; the template is true only as documentation of the key). With the flag off, the only user-visible change is the pre-check banner, whose CTA points at the existing Boost+ purchase page. The pre-check itself ships on by default and is purely client-side and non-blocking.
  • Nothing here is deployed. No infrastructure changes.
  • Backend dependency. The RC top-up path needs the private API endpoints (/private-api/rc-delegation-price and /private-api/rc-delegation-active) and a relay account that watches for the ecency_rc_delegation custom_json and performs the actual delegate_rc. Keep the flag off until both exist.
  • SDK publish dependency. The new SDK helpers/mutations/queries must be published (and the web app consumes the rebuilt workspace dist in the meantime). The committed packages/sdk/dist/** changes are rebuilt package output only.

Testing

  • SDK unit specs added and cover the op builder (id, auths, payload, validation errors) and the pre-check estimator (not-ready on missing inputs, fail flagged below buffered cost with correct deficit/remaining, pass above cost, no false-positive when average cost is unknown).
  • Typecheck and lint pass for the changed SDK and web sources; SDK dist regenerated via the package build so the web app resolves the new exports.
  • The pre-check is non-blocking by construction (the action button is never disabled by it), and the top-up dialog is gated behind an off-by-default flag, so the live path is limited to the warning banner plus the existing Boost+ fallback.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added Resource Credits (RC) pre-check banners to the comment, voting, and publish/editor flows to warn users ahead of time when RC may be insufficient.
    • Introduced an in-app RC top-up dialog (“RC topup”) with a two-step flow and success confirmation.
    • Added new RC-related i18n messages for pre-check and top-up UI.
  • Configuration
    • Added an rcTopup feature flag (currently disabled).

@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds two tightly scoped Resource Credits features: a non-blocking pre-check banner that warns users before they broadcast with insufficient RC, and the client side of a Points-funded RC-only top-up delegation (gated behind an off-by-default rcTopup flag). The implementation is well-structured, follows the existing broadcast-adapter and mutation-wrapper patterns, and ships with unit specs for both the op builder and the pre-check estimator.

  • RC pre-check (estimateRcPrecheck, useRcPrecheck, RcPrecheckBanner) is purely advisory and non-blocking, wired into the editor, comment box, and vote dialog. It reuses already-loaded RC queries, so it is effectively free for logged-in users.
  • RC top-up dialog (RcTopupDialog, useRcDelegation, pricing/active queries) mirrors the existing Boost+ dialog pattern; the on-chain delegate_rc is broadcast by a relay account, not the client. The dialog remains inert in production until the backend endpoints and rcTopup flag are live.

Confidence Score: 5/5

Safe to merge — the pre-check banner is purely advisory and non-blocking, and the top-up dialog is gated behind an off-by-default flag with no backend yet deployed.

The two new features are well-isolated: the banner renders null when RC is sufficient or the user is logged out, and the top-up dialog is unreachable in production. The broadcast path follows the established active-key custom_json pattern used by Boost+. The only fresh concerns are the unconditional getRcStatsQueryOptions call for logged-out visitors and the missing error display in the top-up dialog when the broadcast throws — neither affects correctness of the on-chain action or the non-blocking guarantee.

use-rc-precheck.ts for the logged-out stats query; rc-topup-dialog.tsx for broadcast error feedback.

Important Files Changed

Filename Overview
packages/sdk/src/modules/resource-credits/utils/estimate-rc-precheck.ts New pure helper implementing the RC pre-check estimate; well-designed with a configurable buffer, EMPTY sentinel, and deterministic spec coverage. Logic is correct.
packages/sdk/src/modules/promotions/mutations/use-rc-delegation.ts Mutation mirrors useBoostPlus and invalidates account, RC, and rc-delegation-active caches on success. Uses hardcoded query key arrays instead of QueryKeys entries per CLAUDE.md.
packages/sdk/src/modules/promotions/queries/get-rc-delegation-prices-query-options.ts New pricing query with staleTime: Infinity and no user identifier in the key. Hardcoded key array violates QueryKeys policy per CLAUDE.md.
apps/web/src/features/shared/rc-precheck/use-rc-precheck.ts Reuses cached RC queries efficiently; getRcStatsQueryOptions fires unconditionally, causing a Hive RPC call for logged-out visitors on pages with the banner.
apps/web/src/features/shared/rc-precheck/rc-precheck-banner.tsx Well-structured non-blocking banner with compact variant; early-returns null when user is logged out, estimate not ready, or RC is sufficient.
apps/web/src/features/shared/rc-topup/rc-topup-dialog.tsx Two-step purchase dialog with double-submit guard and isAlreadyActive check. handleSubmit has no catch clause — broadcast errors are silently swallowed.
apps/web/src/app/submit/_components/editor-actions.tsx Adds the pre-check banner above the action row for new posts; operation="comment_operation" is correct since Hive post creation uses the comment op.
apps/web/src/api/sdk-mutations/use-rc-delegation-mutation.ts Standard web wrapper pattern; correctly wires active username and web broadcast adapter to the SDK mutation.

Reviews (3): Last reviewed commit: "chore: apply changeset versioning for PR..." | Re-trigger Greptile

Comment thread packages/sdk/src/modules/promotions/mutations/use-rc-delegation.ts
Comment on lines +11 to +14
return queryOptions({
queryKey: ["promotions", "rc-delegation-prices"],
queryFn: async () => {
if (!accessToken) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Stale price data visible to a different user on account switch

The query key ["promotions", "rc-delegation-prices"] contains no user identifier, yet the fetch is authenticated with a per-user accessToken. With staleTime: Infinity the cached response is never evicted. When user A is logged in and the prices are fetched, then user A logs out and user B logs in, useQuery will immediately return user A's cached data without refetching — because the key is identical. If pricing is ever account-specific (e.g., tiers, discounts), user B sees the wrong prices and could submit a purchase with an incorrect expectation of the cost.

Fix in Claude Code

Comment on lines +11 to +32
return queryOptions({
queryKey: ["promotions", "rc-delegation-prices"],
queryFn: async () => {
if (!accessToken) {
return [];
}

const response = await fetch(CONFIG.privateApiHost + "/private-api/rc-delegation-price", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ code: accessToken }),
});

if (!response.ok) {
throw new Error(`Failed to fetch RC delegation prices: ${response.status}`);
}

return (await response.json()) as PromotePrice[];
},
staleTime: Infinity,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 New query keys missing from centralized QueryKeys

Per CLAUDE.md, all new queries must add entries to QueryKeys in query-keys.ts as the single source of truth for cache references — "Use hardcoded query key arrays" is listed under ❌ DO NOT. Both get-rc-delegation-prices-query-options.ts (["promotions", "rc-delegation-prices"]) and get-rc-delegation-active-query-options.ts (["promotions", "rc-delegation-active", username]) define their keys inline without corresponding QueryKeys.promotions.rcDelegationPrices / rcDelegationActive entries. The absence of a QueryKeys entry for rc-delegation-active is also the direct cause of the invalidation gap above — there's no canonical key to pass to auth.adapter.invalidateQueries in use-rc-delegation.ts.

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2549824f38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

<EcencyConfigManager.Conditional
condition={({ visionFeatures }) => visionFeatures.drafts.enabled}
>
<RcPrecheckBanner operation="comment_operation" className="mx-2 mt-2" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Account for comment_options in publish RC check

When a post uses non-default rewards or beneficiaries, usePublishApi passes options to useCommentMutation, which broadcasts both comment and comment_options; this banner only checks comment_operation. For accounts with enough RC for the comment but not the extra options operation, the new pre-publish warning stays hidden and publishing still fails with the RC error, so the submit flow should estimate the full operation set or include comment_options_operation when options are present.

Useful? React with 👍 / 👎.

[activeTopup]
);
const canSubmit = useMemo(
() => !balanceError && !isAlreadyActive && duration > 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Disable top-up while active status is unresolved

If a user already has an active RC top-up, activeTopup is undefined until /rc-delegation-active returns, and canSubmit treats that as not active. When prices/points are cached or load first, the Next button is enabled during that window, so a duplicate purchase can be broadcast before the guard displays; include the active-status loading/fetching state in canSubmit or treat the unresolved state as blocked.

Useful? React with 👍 / 👎.

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5261d7ad-5da1-48f7-8e57-83b90fea023d

📥 Commits

Reviewing files that changed from the base of the PR and between 2549824 and 31769b7.

⛔ Files ignored due to path filters (6)
  • packages/sdk/dist/browser/index.js is excluded by !**/dist/**
  • packages/sdk/dist/browser/index.js.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/index.cjs is excluded by !**/dist/**
  • packages/sdk/dist/node/index.cjs.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/index.mjs is excluded by !**/dist/**
  • packages/sdk/dist/node/index.mjs.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (13)
  • apps/web/src/config/config.template.ts
  • apps/web/src/features/shared/rc-precheck/rc-precheck-banner.tsx
  • apps/web/src/features/shared/rc-topup/rc-topup-dialog.tsx
  • apps/web/src/specs/features/shared/comment.spec.tsx
  • apps/web/src/specs/setup-any-spec.ts
  • packages/sdk/CHANGELOG.md
  • packages/sdk/package.json
  • packages/sdk/src/modules/operations/builders/ecency.ts
  • packages/sdk/src/modules/promotions/mutations/use-rc-delegation.ts
  • packages/sdk/src/modules/promotions/queries/get-rc-delegation-active-query-options.ts
  • packages/sdk/src/modules/resource-credits/utils/estimate-rc-precheck.ts
  • packages/wallets/CHANGELOG.md
  • packages/wallets/package.json

📝 Walkthrough

Walkthrough

Adds a Resource Credits (RC) pre-check and top-up feature. A new SDK utility estimates RC sufficiency for given operations, a custom_json delegation operation builder is introduced, and promotion mutation/query hooks handle RC top-up purchases. A pre-check banner and a two-step top-up dialog are integrated into the editor, comment, and vote UI surfaces, with feature flags and i18n strings added.

Changes

RC Precheck and Top-up Feature

Layer / File(s) Summary
RC precheck estimation utility (SDK)
packages/sdk/src/modules/resource-credits/utils/estimate-rc-precheck.ts, packages/sdk/src/modules/resource-credits/utils/estimate-rc-precheck.spec.ts, packages/sdk/src/modules/resource-credits/utils/index.ts, packages/sdk/src/modules/resource-credits/index.ts
Defines RcPrecheckOperation, RcPrecheckInput, RcPrecheckResult, and estimateRcPrecheck that computes buffered mana cost, willLikelyFail, deficit, and remaining. Covered by a Vitest suite and wired into barrel exports.
RC delegation operation builder (SDK)
packages/sdk/src/modules/operations/builders/ecency.ts, packages/sdk/src/modules/operations/builders/build-rc-delegation-op.spec.ts, packages/sdk/src/modules/operations/builders/index.ts
Adds buildRcDelegationOp(user, duration) producing a validated custom_json ecency_rc_delegation operation, with a Vitest spec for payload shape and error cases.
RC delegation mutation hook and promotion queries (SDK)
packages/sdk/src/modules/promotions/mutations/use-rc-delegation.ts, packages/sdk/src/modules/promotions/queries/get-rc-delegation-prices-query-options.ts, packages/sdk/src/modules/promotions/queries/get-rc-delegation-active-query-options.ts, packages/sdk/src/modules/promotions/mutations/index.ts, packages/sdk/src/modules/promotions/queries/index.ts
Introduces useRcDelegation with cache invalidation on success, getRcDelegationPricesQueryOptions (POSTs to /private-api/rc-delegation-price), and getRcDelegationActiveQueryOptions (POSTs to /private-api/rc-delegation-active, returns RcDelegationActive or null).
Web RC precheck hook and banner component
apps/web/src/features/shared/rc-precheck/use-rc-precheck.ts, apps/web/src/features/shared/rc-precheck/rc-precheck-banner.tsx, apps/web/src/features/shared/rc-precheck/index.ts
useRcPrecheck wires two React Query calls into estimateRcPrecheck. RcPrecheckBanner renders in compact or full layout, conditionally shows RcTopupDialog or opens the Boost purchase page, and gates on the rcTopup feature flag.
RC top-up dialog and web mutation adapter
apps/web/src/features/shared/rc-topup/rc-topup-dialog.tsx, apps/web/src/features/shared/rc-topup/index.ts, apps/web/src/api/sdk-mutations/use-rc-delegation-mutation.ts, apps/web/src/api/sdk-mutations/index.ts
RcTopupDialog is a two-step modal (duration selection with affordability/active-delegation guards → success confirmation) backed by useRcDelegationMutation, which composes useActiveUsername + getWebBroadcastAdapter into useRcDelegation.
Banner integration, config, and i18n
apps/web/src/app/submit/_components/editor-actions.tsx, apps/web/src/features/shared/comment/index.tsx, apps/web/src/features/shared/entry-vote-btn/entry-vote-dialog.tsx, apps/web/src/config/config.ts, apps/web/src/config/config.template.ts, apps/web/src/features/i18n/locales/en-US.json
RcPrecheckBanner is inserted into editor actions (new entries), comment component, and vote dialog (unpaid entries). rcTopup feature flag is added to both config files. rc-precheck and rc-topup translation keys are added.
Test fixtures, setup, and specs
apps/web/src/specs/features/shared/comment.spec.tsx, apps/web/src/specs/setup-any-spec.ts
Updates test environment setup with reformatted TextEncoder/TextDecoder definitions and expanded SDK mock definitions; adds mock for RcPrecheckBanner in comment spec.
Package versioning and changelog
packages/sdk/CHANGELOG.md, packages/sdk/package.json, packages/wallets/CHANGELOG.md, packages/wallets/package.json
Updates SDK and wallets package versions (SDK 2.3.20 → 2.3.21, wallets 5.0.19 → 5.0.20) and adds changelog entries documenting the RC precheck and top-up purchase client feature.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant RcPrecheckBanner
  participant useRcPrecheck
  participant RcTopupDialog
  participant useRcDelegationMutation
  participant PrivateAPI as Private API

  User->>RcPrecheckBanner: views editor/comment/vote
  RcPrecheckBanner->>useRcPrecheck: useRcPrecheck(username, operation)
  useRcPrecheck->>PrivateAPI: fetch RC account + RC stats
  PrivateAPI-->>useRcPrecheck: rcAccount, rcStats
  useRcPrecheck-->>RcPrecheckBanner: RcPrecheckResult { willLikelyFail, deficit }
  RcPrecheckBanner-->>User: renders low-RC warning banner
  User->>RcPrecheckBanner: clicks "Top up RC"
  RcPrecheckBanner->>RcTopupDialog: showTopup=true
  RcTopupDialog->>PrivateAPI: fetch delegation prices + active delegation
  PrivateAPI-->>RcTopupDialog: prices[], activeRcDelegation
  User->>RcTopupDialog: selects duration, clicks Next
  RcTopupDialog->>useRcDelegationMutation: mutate({ duration })
  useRcDelegationMutation->>PrivateAPI: broadcast ecency_rc_delegation custom_json
  PrivateAPI-->>useRcDelegationMutation: success
  useRcDelegationMutation-->>RcTopupDialog: step 2 (success)
  RcTopupDialog-->>User: success confirmation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 When your Resource Credits run dry,
A little banner catches your eye!
"Top up your RC," it gently pleads,
A two-step modal fulfills your needs.
With points exchanged and mana restored,
The rabbit hops on — fully powered! ⚡

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: introducing RC pre-publish checks and an RC top-up purchase dialog for the client, which aligns with the PR's core objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/rc-precheck

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/sdk/src/modules/operations/builders/ecency.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

YAMLException: Cannot read config file: /packages/sdk/eslint.config.mjs
Error: end of the stream or a document separator is expected (5:12)

2 |
3 | export default tseslint.config(
4 | {
5 | ignores: ["dist", "node_modules", "tsup ...
----------------^
6 | },
7 | ...tseslint.configs.recommended,
at generateError (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:199:10)
at throwError (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:203:9)
at readDocument (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1651:5)
at loadDocuments (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1694:5)
at Object.load (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1720:19)
at loadLegacyConfigFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2565:21)
at loadConfigFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2680:20)
at ConfigArrayFactory._loadConfigData (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:42)
at ConfigArrayFactory.loadFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
at createCLIConfigArray (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)

packages/sdk/src/modules/promotions/mutations/use-rc-delegation.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

YAMLException: Cannot read config file: /packages/sdk/eslint.config.mjs
Error: end of the stream or a document separator is expected (5:12)

2 |
3 | export default tseslint.config(
4 | {
5 | ignores: ["dist", "node_modules", "tsup ...
----------------^
6 | },
7 | ...tseslint.configs.recommended,
at generateError (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:199:10)
at throwError (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:203:9)
at readDocument (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1651:5)
at loadDocuments (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1694:5)
at Object.load (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1720:19)
at loadLegacyConfigFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2565:21)
at loadConfigFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2680:20)
at ConfigArrayFactory._loadConfigData (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:42)
at ConfigArrayFactory.loadFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
at createCLIConfigArray (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)

packages/sdk/src/modules/promotions/queries/get-rc-delegation-active-query-options.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

YAMLException: Cannot read config file: /packages/sdk/eslint.config.mjs
Error: end of the stream or a document separator is expected (5:12)

2 |
3 | export default tseslint.config(
4 | {
5 | ignores: ["dist", "node_modules", "tsup ...
----------------^
6 | },
7 | ...tseslint.configs.recommended,
at generateError (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:199:10)
at throwError (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:203:9)
at readDocument (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1651:5)
at loadDocuments (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1694:5)
at Object.load (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1720:19)
at loadLegacyConfigFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2565:21)
at loadConfigFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2680:20)
at ConfigArrayFactory._loadConfigData (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:42)
at ConfigArrayFactory.loadFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
at createCLIConfigArray (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)

  • 1 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 8

🧹 Nitpick comments (1)
packages/sdk/src/modules/resource-credits/utils/estimate-rc-precheck.spec.ts (1)

7-17: ⚡ Quick win

Replace any in test doubles to keep strict typing effective.

Lines 7, 15, and 17 use any, which bypasses type checks and can hide contract changes in estimateRcPrecheck inputs.

As per coding guidelines, **/*.{ts,tsx} requires strict-mode-safe typing for all new TypeScript code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sdk/src/modules/resource-credits/utils/estimate-rc-precheck.spec.ts`
around lines 7 - 17, Replace the `any` type assertions in the test doubles to
enforce strict typing and catch contract changes in estimateRcPrecheck inputs.
For the calculateRCMana mock function, define a proper return type instead of
relying on type inference. For the stats function, replace the `as any`
assertion with the actual type that represents the resource credits stats
structure. Similarly, for the account function, replace the `as any` assertion
with the proper type that represents the account object expected by
estimateRcPrecheck. This ensures TypeScript's type checker can validate that the
test doubles match the actual function contracts.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/config/config.template.ts`:
- Around line 92-94: The rcTopup configuration in the template config file has
enabled set to true, but it should be set to false to align with the actual
config.ts file and the PR's stated intent of having this feature disabled by
default. Change the enabled property within the rcTopup object in visionFeatures
from true to false to prevent the incomplete feature from being accidentally
enabled in environments initialized from this template.

In `@apps/web/src/features/shared/rc-precheck/rc-precheck-banner.tsx`:
- Around line 46-51: In the onTopUp function, the window.open call that opens
the purchase page with _blank target is missing the noopener security attribute,
which exposes window.opener to the destination page and creates a security
vulnerability. Add noopener to the window.open call by including it in the
second parameter string or as part of window features parameter to prevent the
opened page from accessing or manipulating the opener window.

In `@apps/web/src/features/shared/rc-topup/rc-topup-dialog.tsx`:
- Around line 38-41: The select function in the useQuery call with
getRcDelegationPricesQueryOptions is mutating the source array in place by
calling sort directly on the data parameter, which modifies the cached data and
can cause side effects for other consumers. Fix this by creating a shallow copy
of the data array using the spread operator before calling sort, so the original
cached array remains unchanged.
- Line 149: The onChange handler for the duration input field uses
ChangeEvent<any> which violates strict typing requirements. Replace
ChangeEvent<any> with ChangeEvent<HTMLSelectElement> in the onChange callback to
provide proper type safety and maintain consistency with TypeScript strict mode
guidelines. This change should be made in the setDuration onChange handler to
ensure the event type matches the actual HTML element being used.

In `@packages/sdk/src/modules/operations/builders/ecency.ts`:
- Around line 51-53: The validation in the buildRcDelegationOp function for the
duration parameter is incomplete. It currently only checks if the value is
finite using Number.isFinite(duration), but this allows zero, negative numbers,
and decimal values to pass through. Update the validation condition to
additionally verify that duration is a positive integer (greater than zero and a
whole number without decimal places). This will ensure invalid delegation
durations fail at the SDK boundary before serialization.

In
`@packages/sdk/src/modules/promotions/queries/get-rc-delegation-active-query-options.ts`:
- Around line 34-38: In the return statement of the query helper, the condition
only checks that responseData and responseData.expires exist before creating the
return object, but it does not verify that responseData.user is defined. Since
the return type contract specifies user as a string (not optional), add
responseData.user to the conditional check so that the object is only returned
when both user and expires are present in the response, preventing undefined
values from being cast as strings.

In
`@packages/sdk/src/modules/promotions/queries/get-rc-delegation-prices-query-options.ts`:
- Around line 12-33: The queryKey in getRcDelegationPricesQueryOptions is
globally static and doesn't include user identity, causing stale prices to be
cached across user switches since TanStack Query v5 only refetches when the
queryKey changes, not when closure variables like accessToken change. Add the
username or activeUser identifier to the queryKey array alongside the existing
string literals, following the same pattern used in other queries like
getPointsQueryOptions and getRcDelegationActiveQueryOptions in the same
component. Ensure the queryKey changes whenever the user changes to properly
isolate the cache per user.

In `@packages/sdk/src/modules/resource-credits/utils/estimate-rc-precheck.ts`:
- Around line 69-83: The `buffer` parameter in the `estimateRcPrecheck` function
is not validated before being used in the `estimatedCost` calculation, allowing
invalid values like 0, negative numbers, NaN, or Infinity to produce unreliable
estimates. Add validation to ensure `buffer` is a finite positive number before
the calculation on line 82. Insert this validation early in the function, after
the initial checks for `rcAccount` and `rcStats`, and ensure that `buffer` is
greater than 0 and not NaN or Infinity. Either normalize invalid values to the
default value of 1.2 or throw an error to prevent silent failures.

---

Nitpick comments:
In
`@packages/sdk/src/modules/resource-credits/utils/estimate-rc-precheck.spec.ts`:
- Around line 7-17: Replace the `any` type assertions in the test doubles to
enforce strict typing and catch contract changes in estimateRcPrecheck inputs.
For the calculateRCMana mock function, define a proper return type instead of
relying on type inference. For the stats function, replace the `as any`
assertion with the actual type that represents the resource credits stats
structure. Similarly, for the account function, replace the `as any` assertion
with the proper type that represents the account object expected by
estimateRcPrecheck. This ensures TypeScript's type checker can validate that the
test doubles match the actual function contracts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a5a80827-6165-4f11-9c7a-2a9bbed5c975

📥 Commits

Reviewing files that changed from the base of the PR and between 63e9336 and 2549824.

⛔ Files ignored due to path filters (7)
  • packages/sdk/dist/browser/index.d.ts is excluded by !**/dist/**
  • packages/sdk/dist/browser/index.js is excluded by !**/dist/**
  • packages/sdk/dist/browser/index.js.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/index.cjs is excluded by !**/dist/**
  • packages/sdk/dist/node/index.cjs.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/index.mjs is excluded by !**/dist/**
  • packages/sdk/dist/node/index.mjs.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (25)
  • apps/web/src/api/sdk-mutations/index.ts
  • apps/web/src/api/sdk-mutations/use-rc-delegation-mutation.ts
  • apps/web/src/app/submit/_components/editor-actions.tsx
  • apps/web/src/config/config.template.ts
  • apps/web/src/config/config.ts
  • apps/web/src/features/i18n/locales/en-US.json
  • apps/web/src/features/shared/comment/index.tsx
  • apps/web/src/features/shared/entry-vote-btn/entry-vote-dialog.tsx
  • apps/web/src/features/shared/rc-precheck/index.ts
  • apps/web/src/features/shared/rc-precheck/rc-precheck-banner.tsx
  • apps/web/src/features/shared/rc-precheck/use-rc-precheck.ts
  • apps/web/src/features/shared/rc-topup/index.ts
  • apps/web/src/features/shared/rc-topup/rc-topup-dialog.tsx
  • packages/sdk/src/modules/operations/builders/build-rc-delegation-op.spec.ts
  • packages/sdk/src/modules/operations/builders/ecency.ts
  • packages/sdk/src/modules/operations/builders/index.ts
  • packages/sdk/src/modules/promotions/mutations/index.ts
  • packages/sdk/src/modules/promotions/mutations/use-rc-delegation.ts
  • packages/sdk/src/modules/promotions/queries/get-rc-delegation-active-query-options.ts
  • packages/sdk/src/modules/promotions/queries/get-rc-delegation-prices-query-options.ts
  • packages/sdk/src/modules/promotions/queries/index.ts
  • packages/sdk/src/modules/resource-credits/index.ts
  • packages/sdk/src/modules/resource-credits/utils/estimate-rc-precheck.spec.ts
  • packages/sdk/src/modules/resource-credits/utils/estimate-rc-precheck.ts
  • packages/sdk/src/modules/resource-credits/utils/index.ts

Comment thread apps/web/src/config/config.template.ts
Comment thread apps/web/src/features/shared/rc-precheck/rc-precheck-banner.tsx
Comment thread apps/web/src/features/shared/rc-topup/rc-topup-dialog.tsx
Comment thread apps/web/src/features/shared/rc-topup/rc-topup-dialog.tsx Outdated
Comment thread packages/sdk/src/modules/operations/builders/ecency.ts Outdated
Comment on lines +12 to +33
queryKey: ["promotions", "rc-delegation-prices"],
queryFn: async () => {
if (!accessToken) {
return [];
}

const response = await fetch(CONFIG.privateApiHost + "/private-api/rc-delegation-price", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ code: accessToken }),
});

if (!response.ok) {
throw new Error(`Failed to fetch RC delegation prices: ${response.status}`);
}

return (await response.json()) as PromotePrice[];
},
staleTime: Infinity,
enabled: !!accessToken,

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In TanStack Query v5, if queryKey stays the same and staleTime is Infinity, does changing only a queryFn closure variable (like accessToken) trigger an automatic refetch?

💡 Result:

In TanStack Query v5, if the queryKey remains the same and staleTime is set to Infinity, changing a variable captured in your queryFn closure will NOT trigger an automatic refetch [1][2][3]. TanStack Query manages caching and refetching primarily based on the queryKey [1][3]. Because the queryKey is used to uniquely identify and cache the data, TanStack Query only considers the data "outdated" or in need of a re-run when the queryKey changes or when the query is manually invalidated [4][1][2]. If the queryKey is unchanged, TanStack Query treats the cache as fresh (due to staleTime: Infinity) and will not automatically re-execute the query function [4]. To ensure that your query function uses the latest variable (such as an updated accessToken), you must follow the idiomatic pattern of including that variable in the queryKey [1][5][3]: 1. Include the variable in the queryKey array (e.g., ['todos', accessToken]) [1][3]. 2. This ensures the query cache is invalidated and the queryFn is re-run whenever that variable changes [1][6]. If you omit the variable from the queryKey, you are creating a stale closure issue where the query function continues to use the initial value it was created with [5][7]. While you could manually call refetch to execute the function with the latest variable, relying on this is generally discouraged for data-dependent fetching because it bypasses the library's automatic caching mechanisms [5][7][2]. Using the queryKey as a dependency array is the recommended approach to keep your data in sync with your application state [1][3][6].

Citations:


🏁 Script executed:

cat -n packages/sdk/src/modules/promotions/queries/get-rc-delegation-prices-query-options.ts

Repository: ecency/vision-next

Length of output: 1361


🏁 Script executed:

rg "getRcDelegationPricesQueryOptions" --type ts --type tsx -B 2 -A 2

Repository: ecency/vision-next

Length of output: 90


🏁 Script executed:

rg "getRcDelegationPricesQueryOptions" -B 2 -A 2

Repository: ecency/vision-next

Length of output: 50376


🏁 Script executed:

rg "getRcDelegationPricesQueryOptions" --type ts -B 2 -A 2

Repository: ecency/vision-next

Length of output: 20957


🏁 Script executed:

cat -n apps/web/src/features/shared/rc-topup/rc-topup-dialog.tsx | head -100

Repository: ecency/vision-next

Length of output: 3871


🏁 Script executed:

grep -n "accessToken\|username\|useAccount\|useUser" apps/web/src/features/shared/rc-topup/rc-topup-dialog.tsx | head -20

Repository: ecency/vision-next

Length of output: 339


Auth-scoped fetch is cached under a global key, causing stale prices across user switches.

The queryKey at line 12 (["promotions", "rc-delegation-prices"]) doesn't include user identity, but the queryFn uses accessToken to fetch user-specific prices. With staleTime: Infinity (line 32), switching accounts will reuse the cached result from the previous user instead of refetching.

TanStack Query v5 only refetches when the queryKey changes or the query is manually invalidated—a closure variable change (accessToken) alone does NOT trigger automatic refetch.

Notice that other queries in the same component already follow the correct pattern:

  • getPointsQueryOptions(activeUser?.username) (line 51)
  • getRcDelegationActiveQueryOptions(activeUser?.username ?? "", accessToken) (line 56)

Include username in the queryKey array to fix the cache isolation:

Suggested fix
-export function getRcDelegationPricesQueryOptions(accessToken: string) {
+export function getRcDelegationPricesQueryOptions(
+  username: string | undefined,
+  accessToken: string
+) {
   return queryOptions({
-    queryKey: ["promotions", "rc-delegation-prices"],
+    queryKey: ["promotions", "rc-delegation-prices", username],
     queryFn: async () => {

Update the call site to pass activeUser?.username.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/sdk/src/modules/promotions/queries/get-rc-delegation-prices-query-options.ts`
around lines 12 - 33, The queryKey in getRcDelegationPricesQueryOptions is
globally static and doesn't include user identity, causing stale prices to be
cached across user switches since TanStack Query v5 only refetches when the
queryKey changes, not when closure variables like accessToken change. Add the
username or activeUser identifier to the queryKey array alongside the existing
string literals, following the same pattern used in other queries like
getPointsQueryOptions and getRcDelegationActiveQueryOptions in the same
component. Ensure the queryKey changes whenever the user changes to properly
isolate the cache per user.

- lazy-load the RC top-up dialog so its SDK/mutation chain is not pulled into every comment/editor/vote render (fixes comment.spec build failure)
- invalidate the rc-delegation-active query after a successful top-up
- disable the buy while the active-top-up check is still loading (avoid duplicate)
- copy prices before sorting in the query select (no cache mutation)
- harden new-tab open with noopener; type the duration select event
- validate buildRcDelegationOp duration (positive integer) and estimateRcPrecheck buffer
- guard the rc-delegation-active response user shape
- default visionFeatures.rcTopup to disabled in the config template
- add the new RC SDK exports to the test mock
@feruzm feruzm added the patch Bug fixes and patches (1.0.0 → 1.0.1) label Jun 20, 2026
@feruzm
feruzm merged commit 611db08 into develop Jun 20, 2026
3 of 4 checks passed
@feruzm
feruzm deleted the feature/rc-precheck branch June 20, 2026 20:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch Bug fixes and patches (1.0.0 → 1.0.1)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant