Skip to content

feat: replace floating-point monetary storage with exact BigInt stroops - #145

Merged
3m1n3nc3 merged 1 commit into
learnault:mainfrom
Muhammadjazuli:fix/123-exact-monetary-storage
Aug 21, 2026
Merged

feat: replace floating-point monetary storage with exact BigInt stroops#145
3m1n3nc3 merged 1 commit into
learnault:mainfrom
Muhammadjazuli:fix/123-exact-monetary-storage

Conversation

@Muhammadjazuli

Copy link
Copy Markdown
Contributor

Summary

Closes #123

Replaces all binary floating-point monetary fields with exact-integer stroop values (1 XLM = 10,000,000 stroops) across the Prisma schema, service layer, controller, API types, and tests. This eliminates silent IEEE-754 precision loss in reward calculations and makes asset identity explicit on every monetary record.


Changes

Prisma schema (prisma/schema.prisma)

Table Old field New field
Module reward Float rewardStroops BigInt + assetCode, assetIssuer, assetNetwork
Transaction amount Float amountStroops BigInt + assetCode, assetIssuer, assetNetwork
Referral bonusAmount Float? bonusAmountStroops BigInt?

Migration (prisma/migrations/20260821000000_exact_monetary_storage/migration.sql)

  • Converts legacy float values using ROUND(old * 10_000_000)::BIGINT (nearest stroop)
  • Adds CHECK (col >= 0) constraints on all stroop columns
  • Wrapped in BEGIN / COMMIT — atomic
  • Rollback notes documented in SQL comments

New utility module (src/utils/money.ts)

  • xlmToStroops, stroopsToXlmString, xlmStringToStroops
  • legacyFloatXlmToStroops — explicit rounding, throws on NaN / Infinity / negative
  • addStroops, subtractStroops (throws on underflow), multiplyStroops (rational BigInt), clampStroops
  • formatStroops, assertInRange, isValidStroopAmount, MoneyError

Service (src/services/reward.service.ts)

  • All internal amounts use bigint stroops throughout
  • Difficulty multipliers expressed as [numerator, denominator] rational pairs — no floats
  • Stellar SDK sendPayment receives 7-decimal XLM strings via stroopsToXlmString

Controller (src/controllers/reward.controller.ts)

  • Parses incoming amounts via xlmStringToStroops (rejects > 7 decimal places)
  • Serialises bigint → XLM string at every API response boundary

Types (src/types/reward.types.ts)

  • Transaction.amount / Balance fields changed from number to string (7-decimal XLM)

Tests

File Tests What is covered
tests/unit/money.test.ts 83 Conversion, rounding, overflow, boundary, arithmetic, integration
tests/unit/reward.service.test.ts 48 BigInt stroop arithmetic, Stellar receives XLM strings not numbers
tests/unit/reward.controller.test.ts 22 Mock data updated to BigInt/XLM string; all 22 pass

Verification

pnpm lint          → clean (0 errors, 0 warnings)
pnpm test:coverage → 741 passed | 3 skipped (DB-dependent, no Postgres in CI)

The 3 skipped tests are DB integration tests that have always been skipped in the test environment without a running Postgres instance — unrelated to this change.

…ault#123)

Replace all binary floating-point monetary fields with exact-integer stroop
values (1 XLM = 10,000,000 stroops) across the Prisma schema, services,
controller, and tests.

Schema changes (prisma/schema.prisma):
- Module.reward Float → rewardStroops BigInt
- Transaction.amount Float → amountStroops BigInt
- Referral.bonusAmount Float? → bonusAmountStroops BigInt?
- Add assetCode / assetIssuer / assetNetwork to Module and Transaction

Migration (prisma/migrations/20260821000000_exact_monetary_storage):
- Converts legacy float values using ROUND(old * 10_000_000)::BIGINT
- Adds CHECK constraints (non-negative stroops)
- Wrapped in a BEGIN/COMMIT transaction
- Rollback notes documented in SQL comments

New utility module (src/utils/money.ts):
- xlmToStroops, stroopsToXlmString, xlmStringToStroops
- legacyFloatXlmToStroops (explicit rounding, throws on NaN/Infinity/negative)
- addStroops, subtractStroops (throws on underflow), multiplyStroops (rational),
  clampStroops, formatStroops, assertInRange, isValidStroopAmount, MoneyError

Service (src/services/reward.service.ts):
- All internal amounts use bigint stroops
- Difficulty multipliers expressed as [numerator, denominator] rational pairs
- Stellar SDK receives 7-decimal XLM strings via stroopsToXlmString

Controller (src/controllers/reward.controller.ts):
- Parses incoming XLM string amounts via xlmStringToStroops
- Serialises bigint → XLM string at every API response boundary

Types (src/types/reward.types.ts):
- Transaction.amount / Balance fields changed to string (7-decimal XLM)

Tests:
- tests/unit/money.test.ts: 83 tests covering conversion, rounding, overflow,
  boundary, arithmetic, and integration scenarios
- tests/unit/reward.service.test.ts: fully updated to use BigInt stroops
- tests/unit/reward.controller.test.ts: mock data updated to BigInt/XLM string

CI: pnpm lint clean, pnpm test:coverage 741 passed / 3 skipped (DB tests)

Closes learnault#123
@3m1n3nc3
3m1n3nc3 merged commit bf4d99c into learnault:main Aug 21, 2026
1 check passed
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.

Feature: Replace Floating-Point Monetary Storage

2 participants