Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/thirdweb/src/utils/units.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,19 @@ describe("toUnits", () => {
expect(toUnits("69.59000000059", 9)).toMatchInlineSnapshot("69590000001n");
expect(toUnits("69.59000002359", 9)).toMatchInlineSnapshot("69590000024n");
});

it("scientific notation", () => {
// negative exponents
expect(toUnits("1e-18", 18)).toMatchInlineSnapshot("1n");
expect(toUnits("1e-16", 18)).toMatchInlineSnapshot("100n");
expect(toUnits("1.23456789012345e-7", 18)).toMatchInlineSnapshot(
"123456789012n",
);
// positive exponents
expect(toUnits("1.234e3", 18)).toMatchInlineSnapshot(
"1234000000000000000000n",
);
});
});

describe("toWei", () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/thirdweb/src/utils/units.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export function toEther(wei: bigint) {
* @utils
*/
export function toUnits(tokens: string, decimals: number): bigint {
if (tokens.includes("e")) {
tokens = Number(tokens).toFixed(decimals);
}

Comment on lines +76 to +79
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid Number/toFixed for scientific notation; handle 'E' and prevent RangeError (>100 decimals).

Using Number(...).toFixed(decimals) can:

  • Lose precision for large/small magnitudes.
  • Throw RangeError when decimals > 100.
  • Miss inputs using uppercase 'E'.

Recommend parsing scientific notation purely with string ops and supporting both 'e'/'E'.

Apply this diff to replace the current block:

-  if (tokens.includes("e")) {
-    tokens = Number(tokens).toFixed(decimals);
-  }
+  if (/[eE]/.test(tokens)) {
+    tokens = normalizeScientific(tokens);
+  }

Add this helper (non-exported) elsewhere in the file:

// Normalizes scientific notation to a plain decimal string without using Number()
// Preserves sign and avoids precision/RangeError issues.
// @internal
function normalizeScientific(input: string): string {
  const str = input.trim();
  const match = /^([+-]?)(\d+(?:\.\d+)?|\.\d+)[eE]([+-]?\d+)$/.exec(str);
  if (!match) return input;
  const [, sign, coeff, expStr] = match;
  const exp = Number(expStr);

  const [intPart, fracPart = ""] = coeff.split(".");
  const digits = `${intPart}${fracPart}`.replace(/^0+(?=\d)/, "") || "0";
  const originalIndex = intPart.length;
  const newIndex = originalIndex + exp;

  if (newIndex <= 0) {
    return `${sign}0.${"0".repeat(-newIndex)}${digits}`;
  }
  if (newIndex >= digits.length) {
    return `${sign}${digits}${"0".repeat(newIndex - digits.length)}`;
  }
  return `${sign}${digits.slice(0, newIndex)}.${digits.slice(newIndex)}`;
}
🤖 Prompt for AI Agents
In packages/thirdweb/src/utils/units.ts around lines 76 to 79, the current code
uses Number(tokens).toFixed(decimals) which fails for uppercase 'E', can lose
precision and throws RangeError for decimals > 100; replace that block to first
normalize scientific notation via a new non-exported normalizeScientific(input:
string) helper (as provided in the review) that converts any 'e' or 'E'
formatted string into a plain decimal string without using Number(), then use
that normalized string for subsequent processing instead of
Number(...).toFixed(...). Add the normalizeScientific helper elsewhere in this
file (not exported) and ensure the replacement handles sign, preserves digits,
and avoids Number/toFixed entirely.

let [integerPart, fractionPart = ""] = tokens.split(".") as [string, string];
const prefix = integerPart.startsWith("-") ? "-" : "";
if (prefix) {
Expand Down
Loading