Non-Custodial Partner Program — aggregated partner statistics endpoints - #4528
Closed
joshuakrueger-dfx wants to merge 1 commit into
Closed
Non-Custodial Partner Program — aggregated partner statistics endpoints#4528joshuakrueger-dfx wants to merge 1 commit into
joshuakrueger-dfx wants to merge 1 commit into
Conversation
joshuakrueger-dfx
force-pushed
the
feat/partner-statistics
branch
from
July 30, 2026 21:09
71d1972 to
06c1896
Compare
Wallet partners integrating DFX have no way to see how their integration performs. The only partner-facing transaction endpoint returns per-transaction detail including customer IBANs, which is both more than a usage dashboard needs and more than a partner should hold. Add GET /statistic/partner and /statistic/partner/timeline, scoped to the wallet in the company JWT. There is deliberately no walletId parameter, so a partner cannot express a request for someone else's data. Both endpoints return aggregates only: volume and transaction counts by direction, active and new users, breakdowns by asset, fiat currency, blockchain and payment method, and the referral position in EUR. Aggregates alone are not anonymous. Counts below a threshold of five are suppressed; the effective count is min(transactions, distinct users). Additive groups (period totals by direction, timeline buckets) use block suppression — if any member is under the threshold the whole group is null — so a hidden value cannot be recovered via totals − visible. Period bounds snap to UTC day boundaries with a half-open interval and a one-day minimum. Referral open credit uses refCredit + partnerRefCredit − paidRefCredit on the wallet owner account; tradingUsers and referral balances are gated by the same threshold. Rate limits for these routes count by wallet id, not IP prefix.
joshuakrueger-dfx
force-pushed
the
feat/partner-statistics
branch
from
July 31, 2026 07:32
06c1896 to
9bf880c
Compare
Collaborator
Author
|
Superseded — reopened as a fresh PR on the corrected revision. |
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.
Why
Wallet partners integrating DFX have no way to see how their integration performs. The only
partner-facing transaction endpoint returns per-transaction detail — more than a usage dashboard
needs, and more than a partner should hold. This is the interface half of the Non-Custodial Partner
Program; #4508 is the lock that must ship first.
Generic by design: there is no partner-specific code anywhere. Any wallet gets its own numbers
through the same route.
What
GET /v1/statistic/partnerandGET /v1/statistic/partner/timeline, scoped to the wallet in thecompany JWT. There is deliberately no
walletIdparameter — a partner cannot express a requestfor someone else's data. Both return aggregates only: volume and transaction counts by direction,
active and new users, breakdowns by asset, fiat currency, blockchain and payment method, plus the
referral position in EUR.
Anonymity is the load-bearing part
Aggregates alone are not anonymous. Three independent reviews demonstrated, with executed output,
that a suppressed value could be recovered by subtraction — from period totals and from both
funnels, because a visible total was emitted alongside its parts.
What this PR does about it:
group is suppressed — members, total and derived rates. No more per-cell nulling.
{buy: 5, sell: 1}previously passed and disclosed the single sell in the clear.
differing by hours can no longer isolate a single transaction by subtraction.
to pass every gate.
and Azure addresses, counted in-memory per instance, and was off entirely unless
REQUEST_LIMIT_CHECKwas set.Verified by executed output rather than argument: with
sellbelow the threshold,total − buy − swapyieldsnull, not the hidden value. Same for both funnels.Correctness fixes found in review
referral.creditOpenusedpartnerRefCredit − paidRefCredit.paidRefCreditcovers both pots;the three sibling sites use
refCredit + partnerRefCredit − paidRefCredit. The old formulaunderstated the figure and could go negative.
bucket by a day. Truncation is now bound to UTC in SQL and the bucket keys follow.
map.seton bucket keys silently dropped a bucket on collision; it accumulates now.Verification
lintempty,format:checkclean,tsc --noEmitclean.America/Los_Angeles. Before the fix, Europe/Zurich produced 4 buckets where 3 were expected.
nothing: neutralising
mergeNamedRowsfailed 0 of 32, and dropping the wallet scope from asingle query failed 0 of 18 because the scope assertion used
someinstead ofevery. Bothare red now, alongside probes for block suppression, day snapping, the distinct-user threshold,
the
creditOpenformula and the per-direction timeline threshold.buckets against actual rows, following the repo's existing
MIGRATION_TEST_PGconvention.For reviewers
TZis not pinned anywhere in the repo — not in the Dockerfile, compose files or workflows. Thismodule no longer depends on it, but two existing modules build bucket keys from local date parts
on the assumption that "the app and DB both run UTC". Worth a separate look.
saturate the connection pool.
is not a formal query-budget guarantee.