Skip to content

feat: add getInvoiceAge and getFundingVelocity helpers - #704

Open
henrypeters wants to merge 1 commit into
Stellar-split:mainfrom
henrypeters:feature/issue-611-add-getInvoiceAge-and-getFundingVelocity
Open

feat: add getInvoiceAge and getFundingVelocity helpers#704
henrypeters wants to merge 1 commit into
Stellar-split:mainfrom
henrypeters:feature/issue-611-add-getInvoiceAge-and-getFundingVelocity

Conversation

@henrypeters

Copy link
Copy Markdown

Closes #611

What changed

Adds the two missing derived invoice metrics to src/invoiceStats.ts. Both are computed locally from existing Invoice fields with no network calls.

  • getInvoiceAge(invoice): { days, hours, minutes } — age since createdAt, using Date.now() internally. The result is a calendar-style breakdown: hours is the 0–23 remainder after whole days, minutes the 0–59 remainder after whole hours.
  • getFundingVelocity(invoice): number — funded per day since creation. Returns 0 when createdAt falls within the same second as Date.now(), which prevents the division by zero.
  • Both auto-detect the createdAt unit by magnitude: values > 1e12 are milliseconds, everything else is Unix seconds.
  • Both are exported from src/index.ts, along with the InvoiceAge return type.

Supporting changes:

  • src/types.ts: added createdAt?: number to Invoice. Kept optional deliberately — making it required would be a breaking API change and would break the many existing Invoice literals across src/ and the test suite.
  • Absent, 0, negative, and non-finite createdAt values are treated as unknown (zero age, 0 velocity) rather than as epoch 1970. Without this guard, the 0 that a Soroban u64 decoder produces for an unset field would report a ~20,500-day age.

Why

invoiceStats already exposed aggregate analytics but had no way to answer "how old is this invoice?" or "how fast is it being funded?", both of which callers were deriving by hand from createdAt.

Notes for reviewers

  • The returned velocity is in the same base units as invoice.fundedstroops per day, not USDC per day. This matches the existing computeInvoiceStats().fundingVelocity convention; the JSDoc now states the unit explicitly so callers know to divide by 10_000_000 for a USDC figure.
  • getFundingVelocity is intentionally a different metric from InvoiceStats.fundingVelocity: the latter is a payment-window rate (payments over the span between first and last payment), the new function is a lifetime average since creation. Both are now cross-documented so the two are not mistaken for each other.
  • hashInvoice() canonicalises every key present on the invoice object, so populating the new createdAt field changes an invoice's contentHash. This is documented on the field; no current code in src/ populates it, so no existing hash changes today.

How it was tested

New test/invoiceStats.test.ts — 22 tests, all passing. Coverage includes the 2-day-old age case, the day/hour/minute breakdown, sub-minute truncation, zero and future ages, missing createdAt, age growth across Date.now() via fake timers, seconds↔milliseconds detection parity for both functions, a known 500-funded-over-4-days → 125/day ratio, linear scaling, large bigint amounts, the same-second zero guard (999 ms and exact-now), the 0/negative/NaN/±Infinity sentinel guards, and a regression check that computeInvoiceStats is unchanged.

  • npm test — 98/98 pass.
  • Hash/diff/export-adjacent suites (invoiceHashVerifier, invoiceDiff, invoiceVersionTracker, invoiceFilter, receipt, proof, sdkExports) — 122/122 pass, confirming the new Invoice field breaks no existing canonicalisation or diffing behaviour.
  • npm run lint (tsc --noEmit) reports 217 errors both with and without this branch — verified by diffing the error list against a pristine checkout of main. None are in invoiceStats.ts; they are pre-existing repo-wide failures (missing type exports such as PathQuery, TokenGatePolicy, xdrDecoder issues) that also make npm run build fail on main. This PR adds no new type errors.

…#611)

Adds two derived invoice metrics to invoiceStats, both computed locally from
existing Invoice fields with no network calls:

- getInvoiceAge(invoice) returns { days, hours, minutes } since createdAt as a
  calendar-style breakdown, using Date.now() internally.
- getFundingVelocity(invoice) returns stroops funded per day since creation,
  and 0 when createdAt is within the same second as now to avoid dividing by a
  zero age.

Both accept createdAt as Unix seconds or milliseconds, auto-detected by
magnitude (> 1e12 means ms), and treat absent, zero, negative, or non-finite
values as unknown rather than as epoch 1970. Adds the optional createdAt field
to Invoice, notes that populating it changes hashInvoice output, and documents
how the new lifetime velocity differs from InvoiceStats.fundingVelocity.

Closes Stellar-split#611
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.

Add getInvoiceAge and getFundingVelocity to invoiceStats

1 participant