Skip to content

fix(keychain): float→BigInt crash in token send + HMR provider exports#2623

Open
tarrencev wants to merge 2 commits into
mainfrom
fix/inventory-bigint-and-hmr-provider-exports
Open

fix(keychain): float→BigInt crash in token send + HMR provider exports#2623
tarrencev wants to merge 2 commits into
mainfrom
fix/inventory-bigint-and-hmr-provider-exports

Conversation

@tarrencev

Copy link
Copy Markdown
Contributor

From a PostHog error triage of the Controller. Two fixes:

C1 — real prod bug: float passed to BigInt() in token send

SendTokenDrawer computed the base-unit amount as amount * 10 ** decimals and passed it to BigInt(). A fractional amount yields a non-integer float — e.g. 0.0045 * 10**18 = 4499999999999999.5 — which throws cannot be converted to a BigInt because it is not an integer. Observed in production (x.cartridge.gg, /inventory/token/…). Fix: parse the raw decimal input string into base units exactly with bigint math (parseTokenAmount), thread the input string through amount.tsx, and treat invalid/over-precision input as no-amount. Regression test added (0.0045 @ 18 decimals).

C2 — dev HMR error class: react-refresh/only-export-components

Provider modules exported createContext()/hooks/constants alongside the Provider, breaking Vite Fast Refresh and invalidating context identity on hot-reload — cause of the ~100 dev-only useX must be used within XProvider errors. Enabled react-refresh/only-export-components (warn) and split the offenders (starterpack, onchain-purchase, credit-purchase, upgrade, wallets) into context/provider/hook modules.

Verification

tsc -b, lint (0 errors), format:check, keychain tests (579 passing) green. Triaged + implemented with Codex; the "credits" PostHog errors were an unmerged local WIP branch (not on main) — nothing to fix there.

🤖 Generated with Claude Code

C1: SendTokenDrawer computed base units as amount * 10**decimals and passed the
result to BigInt(), so a fractional amount produced a non-integer float
(0.0045 * 10**18 = 4499999999999999.5) and threw 'cannot be converted to a
BigInt because it is not an integer' (seen in prod on /inventory/token). Parse
the raw decimal input string into base units exactly with bigint math
(parseTokenAmount), thread the input string through amount.tsx, and treat
invalid/over-precision input as no-amount. Adds a regression test (0.0045 @ 18).

C2: provider modules exported createContext()/hooks/constants alongside the
Provider, breaking Vite Fast Refresh and invalidating context identity on
hot-reload (cause of dev-only 'useX must be used within XProvider' errors).
Enable react-refresh/only-export-components (warn) and split the offenders
(starterpack, onchain-purchase, credit-purchase, upgrade, wallets) into
context/provider/hook modules. Targeted modules warning-clean; tsc, lint
(0 errors), format, and keychain tests (579 passing) green.

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

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
controller-example-next Ready Ready Preview Jun 19, 2026 12:40am
controller-ui Ready Ready Preview Jun 19, 2026 12:40am
keychain Ready Ready Preview Jun 19, 2026 12:40am
keychain-storybook Ready Ready Preview Jun 19, 2026 12:40am

Request Review

The C2 split moved CONTROLLER_VERSIONS/UpgradeContext/UpgradeInterface/
UpgradeProviderProps out of provider/upgrade.tsx into upgrade-context.ts.
.storybook/ is outside the package tsconfig (so tsc -b didn't catch it), but the
Vercel keychain-storybook build compiles it — fixing the stale import path.

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

@gillen-pitchclick gillen-pitchclick 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.

Static guard found blocking identifier/type-safety issues: removed symbols still referenced, or unsafe BigInt/number conversion.

Automated review could not parse the agent response.

UpgradeInterface,
UpgradeProviderProps,
} from "../src/components/provider/upgrade";
} from "../src/components/provider/upgrade-context";

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.

Blocking: context is still referenced here, but this diff removes its definition/export.

@@ -1,8 +1,8 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { STABLE_CONTROLLER } from "@/components/provider/upgrade";
import { STABLE_CONTROLLER } from "@/components/provider/upgrade-context";

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.

Blocking: context is still referenced here, but this diff removes its definition/export.

) {
return "";
} else {
const baseUnitAmount = parseTokenAmount(

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.

Blocking: parseTokenAmount is still referenced here, but this diff removes its definition/export.

export type ControllerVersionInfo = {
version: string;
hash: string;
outsideExecutionVersion: OutsideExecutionVersion;

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.

Blocking: OutsideExecutionVersion is still referenced here, but this diff removes its definition/export.

changes: string[];
};

export const CONTROLLER_VERSIONS: ControllerVersionInfo[] = [

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.

Blocking: ControllerVersionInfo is still referenced here, but this diff removes its definition/export.

children: ReactNode;
}

/** A chain whose account is behind the target version. */

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.

Blocking: is is still referenced here, but this diff removes its definition/export.

}

/** A chain whose account is behind the target version. */
export type OutdatedChain = { rpcUrl: string; version: ControllerVersionInfo };

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.

Blocking: ControllerVersionInfo is still referenced here, but this diff removes its definition/export.

determineUpgradePath,
useUpgrade,
} from "./upgrade";
} from "./upgrade-context";

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.

Blocking: context is still referenced here, but this diff removes its definition/export.

(v) => addAddressPadding(v.hash) === addAddressPadding(classHash),
);
import {
BETA_CONTROLLER,

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.

Blocking: BETA_CONTROLLER is still referenced here, but this diff removes its definition/export.

);
import {
BETA_CONTROLLER,
ControllerVersionInfo,

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.

Blocking: ControllerVersionInfo is still referenced here, but this diff removes its definition/export.

@gillen-pitchclick gillen-pitchclick 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.

Static guard found blocking identifier/type-safety issues: removed symbols still referenced, or unsafe BigInt/number conversion.

Automated review could not parse the agent response.

@@ -1,8 +1,8 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { STABLE_CONTROLLER } from "@/components/provider/upgrade";
import { STABLE_CONTROLLER } from "@/components/provider/upgrade-context";

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.

Blocking: context is still referenced here, but this diff removes its definition/export.

) {
return "";
} else {
const baseUnitAmount = parseTokenAmount(

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.

Blocking: parseTokenAmount is still referenced here, but this diff removes its definition/export.

export type ControllerVersionInfo = {
version: string;
hash: string;
outsideExecutionVersion: OutsideExecutionVersion;

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.

Blocking: OutsideExecutionVersion is still referenced here, but this diff removes its definition/export.

changes: string[];
};

export const CONTROLLER_VERSIONS: ControllerVersionInfo[] = [

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.

Blocking: ControllerVersionInfo is still referenced here, but this diff removes its definition/export.

{
version: "1.0.4",
hash: "0x24a9edbfa7082accfceabf6a92d7160086f346d622f28741bf1c651c412c9ab",
outsideExecutionVersion: OutsideExecutionVersion.V2,

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.

Blocking: OutsideExecutionVersion is still referenced here, but this diff removes its definition/export.

}

/** A chain whose account is behind the target version. */
export type OutdatedChain = { rpcUrl: string; version: ControllerVersionInfo };

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.

Blocking: ControllerVersionInfo is still referenced here, but this diff removes its definition/export.

determineUpgradePath,
useUpgrade,
} from "./upgrade";
} from "./upgrade-context";

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.

Blocking: context is still referenced here, but this diff removes its definition/export.

(v) => addAddressPadding(v.hash) === addAddressPadding(classHash),
);
import {
BETA_CONTROLLER,

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.

Blocking: BETA_CONTROLLER is still referenced here, but this diff removes its definition/export.

);
import {
BETA_CONTROLLER,
ControllerVersionInfo,

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.

Blocking: ControllerVersionInfo is still referenced here, but this diff removes its definition/export.

import {
BETA_CONTROLLER,
ControllerVersionInfo,
determineUpgradePath,

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.

Blocking: determineUpgradePath is still referenced here, but this diff removes its definition/export.

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