Skip to content

Non-Custodial Partner Program — partner statistic completion funnels - #4539

Closed
joshuakrueger-dfx wants to merge 2 commits into
feat/partner-statisticsfrom
feat/partner-statistics-completion
Closed

Non-Custodial Partner Program — partner statistic completion funnels#4539
joshuakrueger-dfx wants to merge 2 commits into
feat/partner-statisticsfrom
feat/partner-statistics-completion

Conversation

@joshuakrueger-dfx

@joshuakrueger-dfx joshuakrueger-dfx commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Why

Stacked on #4538merge that one first, otherwise this PR's diff pulls the aggregates in with it.

Adds the two completion funnels: how many payment-info requests turn into an actual payment, and how
many received payments are delivered rather than stalling.

Honest scope note

Cake did not ask for this. Their request named volume, user counts and popular coins — all covered
by #4538. These metrics were requested internally, and the only figures I have are global rather
than per partner: in 2026, 222,400 payment-info requests against 17,179 actual payments. Whether
that ratio looks different for any individual partner cannot be answered until this endpoint exists.
Reviewers should weigh the review cost against that.

What

completion: {
  paymentInfoRequests: { buy|sell|swap: { requested, paymentReceived, waitingForPayment,
                                          noPaymentReceived, receivedRate } },
  settlement:          { buy|sell|swap: { received, delivered, rejected, inProgress,
                                          deliveredRate } }
}

Two funnels, deliberately not multiplied into one number — they have different denominators.
Globally stage A sits near 7% and stage B near 96%; a single combined figure would mean nothing.

Stage A is not a conversion rate and is not labelled as one. A transaction_request row is
created on every payment-info call, so on every amount change in the frontend — not once per
purchase intent. The field names and @ApiProperty descriptions say so explicitly.

Both funnels are additive by construction (requested is the sum of the three statuses;
inProgress is defined as received − delivered − rejected), so a per-cell suppression would
disclose the hidden member by subtraction. They therefore use the block suppression introduced in
#4538: if any member falls below the threshold, the whole funnel object is suppressed.

settlement.received counts all rows while totals.transactions in #4538 counts only AML-passed
ones. That difference is intentional and documented on the DTO — a dashboard summing them will not
reconcile.

Verification

  • Suite in four shards on the combined stack: 333 suites, 6113 tests, 0 failures. lint empty,
    format:check clean, tsc --noEmit clean.
  • Statistic specs green under four process timezones.
  • Mutation probes: block suppression removed → red; per-direction timeline threshold reverted → red;
    distinct-user threshold removed → red.
  • The base commit in Non-Custodial Partner Program — aggregated partner statistics endpoints #4538 builds and passes on its own, verified by checking it out in isolation.

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.
Add stage A (payment-info request → payment received) and stage B
(settlement received → delivered / rejected / in progress) to the partner
summary. Funnel members form additive groups and use the same block
suppression as period totals: if any counter is under the threshold the
whole direction funnel and its rate are null, so rejected = received −
delivered − inProgress cannot be reconstructed from visible fields.
@joshuakrueger-dfx
joshuakrueger-dfx force-pushed the feat/partner-statistics branch from 5a188c3 to 49894e7 Compare August 1, 2026 20:36
@joshuakrueger-dfx
joshuakrueger-dfx deleted the branch feat/partner-statistics August 1, 2026 22:30
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.

1 participant