Repository navigation
Parse commitment amounts as exact fixed-point values - #2121
Merged
1nonlypiece merged 2 commits intoOct 24, 2026
Merged
Conversation
Add parseAmount/parseDays that reject malformed input with specific reasons, keep amounts as bigint base units (7 decimals, max 1e12 like the contract), compute penalty and yield with basis point math, and convert legacy numeric records on load. The wizard and dashboard use them.
|
@Abioladory123 is attempting to deploy a commit to the 1nonly's projects Team on Vercel. A member of the Team first needs to authorize it. |
Contributor
Author
|
@1nonlypiece please review pr and merge. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The wizard parsed the amount with
Number(), so1e3,0x10and" 5 "were accepted and amounts were stored and summed as floats. This adds a strict decimal parser that produces exact base-unit bigints (7 decimals) and uses it for validation, display, storage and the penalty/yield math.Closes #2081
What changed
src/lib/amount.ts(new)parseAmount(input, decimals = 7)accepts only^\d+(\.\d+)?$after trimming. Rejections carry a code (empty,invalid_characters,too_many_decimals,zero,above_max) with a user-facing message map.parseDays(input, { min, max })is digits only (1e1,14.0,-3rejected) and range-checked (empty,invalid_characters,below_min,above_max).formatAmount(value, decimals, { maxFraction })renders bigints with grouping.penaltyOfandyieldOfdo BigInt basis-point math.src/lib/commitments.tsCommitment.amountis now a base-unit decimal string.normalizeStoredAmountis the single conversion used on load.src/app/create/page.tsxpattern/inputModeon the inputs.src/app/dashboard/page.tsxDecisions
" 5 "is 5); whitespace inside the number ("1 000") is rejected. Documented in theparseAmountcomment and covered in tests..5and5.are rejected, as the issue's regex implies. Leading zeros are allowed (007is 7).MAX_AMOUNT = 1_000_000_000_000nbase units (100,000 XLM), a named constant mirroringMAX_AMOUNTincontracts/escrow/src/lib.rs.above_maxis checked afterzero.compute_refund_amountandcalculate_accrued_yieldin the escrow contract, so the UI never shows more than the contract would pay. Refund isamount - penalty, so no stroop is lost. Example: 33 stroops at 5% is 1.65, shown as penalty 1 and refund 32.maxFractiontruncates rather than rounds. A non-zero value that would truncate to zero shows as<0.01, so 1 stroop no longer displays as0.toFixed(7)and then scaled, which removes float noise (0.1 + 0.2becomes0.3). Negative, NaN, exponent-form and non-digit values are dropped through the existingdroppedcounter and the dashboard notice; nothing throws. Legacy values above the max are kept, not dropped, so existing records stay visible.Math.round(percent * 100), valid for the 2-decimal percentages inTYPES(4.5 becomes 450).Not changed
fmt(Number(...))stays for the wallet XLM balance and for on-chain event amounts. The balance is a Horizon display string. The event amount is raw stroops that the dashboard shows as XLM, which looks like a separate unit bug, so I left it for its own issue.TYPES(4.5/9/16) differ from the contract'sannual_yield_bps(500/700/1000). Pre-existing and out of scope.package.json, lockfile, workflow or script changes.One thing to be aware of: records are now written as schema v3 with string amounts. An older build reading that payload would drop them, so rolling back the deploy after users have saved would discard those local demo records.
Type of Change
Testing
npm run typecheckandnpm run circularare clean.npm test:The one failure is
src/lib/soroban/errors.test.ts > matches the Rust contract error variants and numeric codes(expects 15 contract errors, finds 17). It is pre-existing: I ran it on a clean checkout ofupstream/master(0f6f056) before making any changes and it failed the same way (1 failed, 89 passed, 90 total).New tests:
src/lib/__tests__/amount.test.ts(52): every rejected input with its reason, 1-stroop accept and 8-decimal reject, exactly max accepted and max + 1 stroop rejected,parseDayscases, format round trips and truncation, hand-calculated penalty and yield including non-whole-stroop cases.src/lib/__tests__/commitments.test.ts: legacy v1 and v2 numeric records converted and rewritten, malformed stored amounts dropped without throwing, and a string round trip.src/app/create/create.amount.test.tsx: the wizard shows the inline reason and blocks Review for bad input, trims valid input, shows exact review values, and stores'1'for 1 stroop.dashboard.a11y.test.tsx: seed amounts changed to base-unit strings to match the new type. Assertions are unchanged.The checklist's
test:coverage/lintscripts don't exist inpackage.json, so I could not run coverage or lint.Checklist
lintscript available).