Skip to content

fix(settlement): use bigint contract units for payout and invoice math - #1527

Merged
greatest0fallt1me merged 2 commits into
CalloraOrg:mainfrom
olubukolatanko209-doc:fix/settlement-bigint-amounts
Oct 24, 2026
Merged

greatest0fallt1me merged 2 commits into
CalloraOrg:mainfrom
olubukolatanko209-doc:fix/settlement-bigint-amounts

Conversation

@olubukolatanko209-doc

Copy link
Copy Markdown
Contributor

Summary

Closes #1512

Replaces all floating-point USDC arithmetic in settlement, invoicing and usage tracking with exact bigint contract units (1 USDC = 10,000,000 units). This guarantees that payout amount == Σ event amounts == Σ invoice line items, to the stroop, for any set of events.


Problem

RevenueSettlementService summed e.amountUsdc floats; convertUsdcToStroops used Math.round(... + Number.EPSILON); InvoiceService totalled with sum + Number(item.amount). With many micro-charges these sums drift, and amounts above 2^53 stroops lose precision entirely.

Regression proof: 10,000 charges of 0.0000001 USDC as floats gives 0.0010000000000000002 drift. As bigint units (1n each): exactly 10000n. Test plan row 1 covers this.


Changes

New shared library — src/lib/usdcUnits.ts

Function Direction Used in
parseUsdcToUnits(str) decimal string → bigint SQL read, priceUsdc ingest
formatUnitsToUsdc(units) bigint → minimal decimal SQL write
formatUnitsToUsdcFixed(units) bigint → 7-decimal string API / webhook

Domain types changed

  • UsageEvent.amountUsdc: number → bigint
  • Settlement.amount: number → bigint
  • RevenueSummary fields: number → bigint
  • new_api_call webhook amountUsdc: number → string (external boundary)

Service files

File Change
usageStore.ts bigint sums, parseUsdcToUnits on SQL read, formatUnitsToUsdc on write
settlementStore.ts mapSettlementRow parses string→bigint; create writes decimal string
sorobanSettlement.ts removed convertUsdcToStroops; distribute(bigint); i128 = String(units)
revenueSettlementService.ts bigint reduce sum; minPayout via parseUsdcToUnits; emit formatUnitsToUsdcFixed
InvoiceService.ts SUM::text cast + parseUsdcToUnits; insert formatUnitsToUsdc; webhook fixed
billing.ts delegates helpers to usdcUnits

Boundary consumers

  • proxyRoutes.ts / gatewayRoutes.ts: parseUsdcToUnits(priceUsdc.toFixed(7)) at ingest
  • developerRoutes.ts: bigint sums; Number(units) / 10_000_000 for JSON (OpenAPI type: number unchanged)

Lint guard

eslint.config.js adds no-restricted-syntax banning parseFloat, Number( and .toFixed( in the five USDC-sensitive service files. Any reintroduction is a build-time error.


Test plan coverage

# Scenario Status
1 10,000 events of 0.0000001 → payout exactly 10000 units ✅ new test
2 fast-check property: Σ units == formatted total parsed back ✅ usdcUnits.test.ts
3 Payout amount passed to client is bigint == Σ claimed rows ✅ new test
4 minPayout boundary equal to total → payout fires ✅ new test
5 Invoice with 3 line items → total is exact sum ✅ InvoiceService.test.ts
6 Amount above 2^53 units → exact ✅ new test
7 settlement_completed webhook amount is decimal string ✅ new test
8 Lint guard flags reintroduced parseFloat ✅ eslint rule

Files changed

33 files changed, 879 insertions(+), 415 deletions(-)

Closes CalloraOrg#1512

## What

Replace all float-point USDC arithmetic in settlement, invoicing, and
usage tracking with exact bigint contract units (1 USDC = 10_000_000).

### New shared library
- src/lib/usdcUnits.ts — parseUsdcToUnits / formatUnitsToUsdc /
  formatUnitsToUsdcFixed + fast-check property tests

### Domain type changes
- UsageEvent.amountUsdc: number → bigint
- Settlement.amount: number → bigint
- RevenueSummary fields: number → bigint
- new_api_call webhook amountUsdc: number → string (boundary)

### Service changes
- usageStore.ts: bigint sums, SQL read boundary via parseUsdcToUnits,
  write via formatUnitsToUsdc, snapshot totals bigint
- settlementStore.ts: mapSettlementRow parses amount_usdc string→bigint,
  create writes formatUnitsToUsdc decimal string
- sorobanSettlement.ts: removed convertUsdcToStroops; distribute() and
  buildSorobanSettlementInvocation accept bigint; i128 = String(units)
- revenueSettlementService.ts: bigint reduce sum, minPayout converted via
  parseUsdcToUnits, webhook amount via formatUnitsToUsdcFixed
- InvoiceService.ts: SUM(amount_usdc)::text→parseUsdcToUnits, insert
  formatUnitsToUsdc, webhook formatUnitsToUsdcFixed
- billing.ts: delegates to usdcUnits helpers

### Boundary consumers
- proxyRoutes.ts, gatewayRoutes.ts: parseUsdcToUnits(priceUsdc.toFixed(7))
  at ingest; SSE payload uses formatUnitsToUsdc string
- developerRoutes.ts: bigint sums, Number(units)/10_000_000 for JSON

### Lint guard
- eslint.config.js: no-restricted-syntax bans parseFloat / Number( /
  .toFixed( in the five USDC-sensitive service files

### Tests added / updated
- New: src/lib/usdcUnits.test.ts (unit + fast-check property tests)
- New: src/services/usageStore.test.ts
- Updated: revenueSettlementService, sorobanSettlement, InvoiceService,
  settlementStore ×2, settlementStatusSyncJob, developerRevenue,
  persistentStores, usageMetering, complete-integration, proxy.integration,
  developerData, developerRoutes, event.emitter

### Docs
- docs/guides/SETTLEMENT_STORE_DOCUMENTATION.md: USDC contract units
  section, boundary rules table, lint guard note, conservation invariant
@drips-wave

drips-wave Bot commented Oct 19, 2026

Copy link
Copy Markdown

@olubukolatanko209-doc Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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

# Conflicts:
#	src/__tests__/revenueSettlementService.test.ts
#	src/lib/usdcUnits.test.ts
#	src/lib/usdcUnits.ts
#	src/services/InvoiceService.ts
#	src/services/billing.ts
#	src/services/revenueSettlementService.ts
#	src/services/usageStore.ts
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.

Replace floating-point USDC arithmetic in settlement and invoicing with bigint contract units

3 participants