Skip to content

fix(account): send parses plain integer as wei, not GEN#294

Merged
MuncleUscles merged 5 commits intomainfrom
fix/send-parse-amount
Apr 22, 2026
Merged

fix(account): send parses plain integer as wei, not GEN#294
MuncleUscles merged 5 commits intomainfrom
fix/send-parse-amount

Conversation

@MuncleUscles
Copy link
Copy Markdown
Member

@MuncleUscles MuncleUscles commented Apr 22, 2026

Summary

  • `account send` previously parsed any bare number as GEN (via `parseEther`), so `send 100` sent 100 GEN — surprising when the user meant 100 wei.
  • Now bare integers are parsed as wei (`BigInt(trimmed)`) and only amounts with a `GEN` suffix go through `parseEther`.
  • Matches the convention used by `cast send --value` in Foundry and by the staking subcommands downstream.

Test plan

  • `genlayer account send 100` transfers 100 wei
  • `genlayer account send "0.5 GEN"` transfers 0.5 GEN (5e17 wei)
  • `genlayer account send "1GEN"` also works (no space)
  • Existing unit tests pass

Summary by CodeRabbit

  • New Features

    • Amount input now supports an explicit "gen" suffix (case-insensitive) for clearer intent.
  • Bug Fixes

    • Amount parsing is now stricter and more predictable; whitespace is automatically trimmed, and ambiguous decimal inputs are rejected.

The old parseAmount had a bias: integers below 10^12 wei were
silently interpreted as GEN, integers above as wei. That makes
`account send 1000` attempt a 1000 GEN transfer, not 1000 wei —
a footgun that's inconsistent with how parseStakingAmount and the
SDK's parseStakingAmount handle the same units.

Align with the symmetric convention used elsewhere in the codebase:
- 'Ngen' / 'N gen' suffix  → N GEN via parseEther
- plain integer            → wei (BigInt, no conversion)
- plain decimal without suffix → rejected (ambiguous, was silently
  GEN for small values and wei for large)
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Warning

Rate limit exceeded

@MuncleUscles has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 11 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 4 minutes and 11 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b5113ea7-bbd3-471d-a914-0eaeabf11691

📥 Commits

Reviewing files that changed from the base of the PR and between 7d963e3 and 86aece9.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • .github/workflows/smoke.yml
  • package.json
  • tests/smoke.test.ts
📝 Walkthrough

Walkthrough

The SendAction.parseAmount method now strictly interprets amount inputs with explicit normalization. It trims whitespace, supports an optional case-insensitive "gen" suffix parsed via parseEther, otherwise treats input as an integer in wei via BigInt, and removes the previous size-based heuristic for inferring units.

Changes

Cohort / File(s) Summary
Amount Parsing Logic
src/commands/account/send.ts
Refactored parseAmount to normalize whitespace, explicitly handle "gen" suffix (case-insensitive) via parseEther, treat non-suffixed inputs as wei integers via BigInt, and remove numeric threshold heuristic. Decimal inputs like "1.5" now fail validation instead of being coerced.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A parsing rabbit hops with glee,
No more ambiguous amounts to see!
With "gen" explicit, wei so true,
The method knows just what to do. 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: plain integers are now parsed as wei instead of GEN.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/send-parse-amount

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/account/send.ts`:
- Around line 34-47: The amount parser currently lets BigInt(trimmed) accept
signed and base-prefixed values; change the wei branch in send.ts to only accept
an unsigned decimal integer by validating trimmed against a whitelist regex
(e.g., /^[0-9]+$/) and then converting with BigInt, throwing a clear error for
invalid input; keep the existing GEN-suffix handling (parseEther) for decimal
GEN amounts. Also update the help text in account/index.ts to clearly document
the amount grammar: explain that trailing "gen" (case-insensitive) denotes
decimal GEN (supports fractions) and plain unsigned integers (no sign or 0x
prefix) denote wei. Ensure error messages reference the same grammar so users
know to use e.g. "1gen" or "1000" (wei).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 041c10d4-a79d-468d-8a08-aae981df3103

📥 Commits

Reviewing files that changed from the base of the PR and between 0b32bff and 7d963e3.

📒 Files selected for processing (1)
  • src/commands/account/send.ts

Comment on lines +34 to +47
// Symmetric with genlayer-js `parseStakingAmount`:
// "Ngen" → N GEN (parseEther)
// integer → wei (as-is)
// decimal without suffix → rejected (ambiguous; was previously silently
// interpreted as GEN for small values and as wei for large — a footgun
// that made `send 1000` attempt to transfer 1000 GEN, not 1000 wei).
const trimmed = amount.trim();
const lower = trimmed.toLowerCase();
if (lower.endsWith("gen")) {
return parseEther(lower.slice(0, -3).trim());
}
// If it's a large number (likely wei), use as-is
if (BigInt(amount) > 1_000_000_000_000n) {
return BigInt(amount);
}
// Otherwise assume it's in GEN
return parseEther(amount);
// Plain integer → wei. BigInt() throws on decimals, which is the intended
// failure mode for ambiguous input like "1.5".
return BigInt(trimmed);
Copy link
Copy Markdown

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:

Does JavaScript BigInt(string) accept signed and base-prefixed string inputs such as "-1" and "0x10", and does viem parseEther accept signed decimal strings?

💡 Result:

Yes, JavaScript's BigInt(string) accepts signed strings like "-1" and base-prefixed strings like "0x10". No, viem's parseEther does not accept signed decimal strings; while its underlying parseUnits supports negatives technically, parseEther is designed for positive ether amounts in Ethereum transactions, and negative values are invalid for such contexts (e.g., uint256 types). Using negatives would produce a negative BigInt, which cannot be encoded as unsigned transaction data.

Citations:


🏁 Script executed:

# Check the actual content of send.ts around lines 34-47
cat -n src/commands/account/send.ts | sed -n '30,50p'

Repository: genlayerlabs/genlayer-cli

Length of output: 1900


🏁 Script executed:

# Check the command definition and help text in index.ts
cat -n src/commands/account/index.ts | sed -n '90,115p'

Repository: genlayerlabs/genlayer-cli

Length of output: 1603


🏁 Script executed:

# Also verify that SendAction extends BaseAction as stated
cat -n src/commands/account/send.ts | sed -n '1,25p'

Repository: genlayerlabs/genlayer-cli

Length of output: 973


🏁 Script executed:

# Get the actual parseAmount implementation at lines 34-47
cat -n src/commands/account/send.ts | sed -n '34,47p'

Repository: genlayerlabs/genlayer-cli

Length of output: 854


Whitelist the amount grammar and update help text.

BigInt(trimmed) accepts signed values (e.g., "-1") and base-prefixed strings (e.g., "0x10"), which contradicts the stated "plain integer → wei" contract. Additionally, the help text at line 99 of src/commands/account/index.ts only says "Send GEN to an address" without documenting that plain integers are wei and the GEN suffix is required for decimal amounts.

Update the validation to explicitly accept only plain positive integers for wei, and update the help text to document the amount format.

🛡️ Proposed validation and help text updates

For src/commands/account/send.ts lines 40–47:

     const trimmed = amount.trim();
     const lower = trimmed.toLowerCase();
     if (lower.endsWith("gen")) {
-      return parseEther(lower.slice(0, -3).trim());
+      const genAmount = lower.slice(0, -3).trim();
+      if (!/^\d+(?:\.\d+)?$/.test(genAmount)) {
+        throw new Error("Invalid amount. Use an integer wei value or a decimal GEN value with a GEN suffix.");
+      }
+      return parseEther(genAmount);
     }
     // Plain integer → wei. BigInt() throws on decimals, which is the intended
     // failure mode for ambiguous input like "1.5".
+    if (!/^\d+$/.test(trimmed)) {
+      throw new Error("Invalid amount. Use an integer wei value or add a GEN suffix for GEN amounts.");
+    }
     return BigInt(trimmed);

For src/commands/account/index.ts line 99:

-    .description("Send GEN to an address")
+    .description("Send GEN or wei to an address (amount: integer wei or decimal GEN with 'GEN' suffix)")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Symmetric with genlayer-js `parseStakingAmount`:
// "Ngen" → N GEN (parseEther)
// integer → wei (as-is)
// decimal without suffix → rejected (ambiguous; was previously silently
// interpreted as GEN for small values and as wei for large — a footgun
// that made `send 1000` attempt to transfer 1000 GEN, not 1000 wei).
const trimmed = amount.trim();
const lower = trimmed.toLowerCase();
if (lower.endsWith("gen")) {
return parseEther(lower.slice(0, -3).trim());
}
// If it's a large number (likely wei), use as-is
if (BigInt(amount) > 1_000_000_000_000n) {
return BigInt(amount);
}
// Otherwise assume it's in GEN
return parseEther(amount);
// Plain integer → wei. BigInt() throws on decimals, which is the intended
// failure mode for ambiguous input like "1.5".
return BigInt(trimmed);
// Symmetric with genlayer-js `parseStakingAmount`:
// "Ngen" → N GEN (parseEther)
// integer → wei (as-is)
// decimal without suffix → rejected (ambiguous; was previously silently
// interpreted as GEN for small values and as wei for large — a footgun
// that made `send 1000` attempt to transfer 1000 GEN, not 1000 wei).
const trimmed = amount.trim();
const lower = trimmed.toLowerCase();
if (lower.endsWith("gen")) {
const genAmount = lower.slice(0, -3).trim();
if (!/^\d+(?:\.\d+)?$/.test(genAmount)) {
throw new Error("Invalid amount. Use an integer wei value or a decimal GEN value with a GEN suffix.");
}
return parseEther(genAmount);
}
// Plain integer → wei. BigInt() throws on decimals, which is the intended
// failure mode for ambiguous input like "1.5".
if (!/^\d+$/.test(trimmed)) {
throw new Error("Invalid amount. Use an integer wei value or add a GEN suffix for GEN amounts.");
}
return BigInt(trimmed);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/account/send.ts` around lines 34 - 47, The amount parser
currently lets BigInt(trimmed) accept signed and base-prefixed values; change
the wei branch in send.ts to only accept an unsigned decimal integer by
validating trimmed against a whitelist regex (e.g., /^[0-9]+$/) and then
converting with BigInt, throwing a clear error for invalid input; keep the
existing GEN-suffix handling (parseEther) for decimal GEN amounts. Also update
the help text in account/index.ts to clearly document the amount grammar:
explain that trailing "gen" (case-insensitive) denotes decimal GEN (supports
fractions) and plain unsigned integers (no sign or 0x prefix) denote wei. Ensure
error messages reference the same grammar so users know to use e.g. "1gen" or
"1000" (wei).

Picks up the validatorDeposit/Exit routing fix (genlayer-js#155) which
routes those two calls through the ValidatorWallet so Staking sees the
wallet as msg.sender (previously reverted).

The CLI's own staking subcommands already route through
VALIDATOR_WALLET_ABI directly (walletClient.writeContract on the
validator address), so no call-site changes are needed — this is just
keeping the bundled SDK current.
The smoke suite shells out to `node dist/index.js` to exercise the
compiled CLI end-to-end (the `genlayer staking validators` assertion
in tests/smoke.test.ts). Without a build step that entry point
doesn't exist and the test fails with MODULE_NOT_FOUND before it can
hit the network. Pre-existing bug — every PR has been red on this
check; it's only green on main because the scheduled run happens
after a release built dist/.
@MuncleUscles MuncleUscles merged commit 660e745 into main Apr 22, 2026
5 of 6 checks passed
@MuncleUscles MuncleUscles deleted the fix/send-parse-amount branch April 22, 2026 14:35
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