feat: add getInvoiceAge and getFundingVelocity helpers - #704
Open
henrypeters wants to merge 1 commit into
Open
Conversation
…#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
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.
Closes #611
What changed
Adds the two missing derived invoice metrics to
src/invoiceStats.ts. Both are computed locally from existingInvoicefields with no network calls.getInvoiceAge(invoice): { days, hours, minutes }— age sincecreatedAt, usingDate.now()internally. The result is a calendar-style breakdown:hoursis the 0–23 remainder after whole days,minutesthe 0–59 remainder after whole hours.getFundingVelocity(invoice): number— funded per day since creation. Returns0whencreatedAtfalls within the same second asDate.now(), which prevents the division by zero.createdAtunit by magnitude: values> 1e12are milliseconds, everything else is Unix seconds.src/index.ts, along with theInvoiceAgereturn type.Supporting changes:
src/types.ts: addedcreatedAt?: numbertoInvoice. Kept optional deliberately — making it required would be a breaking API change and would break the many existingInvoiceliterals acrosssrc/and the test suite.0, negative, and non-finitecreatedAtvalues are treated as unknown (zero age,0velocity) rather than as epoch 1970. Without this guard, the0that a Sorobanu64decoder produces for an unset field would report a ~20,500-day age.Why
invoiceStatsalready 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 fromcreatedAt.Notes for reviewers
invoice.funded— stroops per day, not USDC per day. This matches the existingcomputeInvoiceStats().fundingVelocityconvention; the JSDoc now states the unit explicitly so callers know to divide by10_000_000for a USDC figure.getFundingVelocityis intentionally a different metric fromInvoiceStats.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 newcreatedAtfield changes an invoice'scontentHash. This is documented on the field; no current code insrc/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, missingcreatedAt, age growth acrossDate.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), the0/negative/NaN/±Infinitysentinel guards, and a regression check thatcomputeInvoiceStatsis unchanged.npm test— 98/98 pass.invoiceHashVerifier,invoiceDiff,invoiceVersionTracker,invoiceFilter,receipt,proof,sdkExports) — 122/122 pass, confirming the newInvoicefield 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 ofmain. None are ininvoiceStats.ts; they are pre-existing repo-wide failures (missing type exports such asPathQuery,TokenGatePolicy,xdrDecoderissues) that also makenpm run buildfail onmain. This PR adds no new type errors.