Skip to content

Reconcile stake-review rev-share breakdown so the rows sum to 100% #305

@jorgecuesta

Description

@jorgecuesta

Context

Non-blocking follow-up from PR #300, which added Provider/Supplier/Delegator rows next to Client Share in the stake review (PlanDetailsSection) and a warning when client share is 0%. The feature is good to ship — these are refinements to revisit later.

1. The displayed shares don't always reconcile to 100%

Per service the model is sound: client = 100 − provider − supplier − delegator (the client/owner gets whatever is left), so each service sums to 100.

The issue is in how the services are aggregated into one number per row in calculateShares. Each row uses a different statistic:

Code refs (apps/middleman/src/lib/utils/shareCalculations.ts on staging):

  • Per-service client share, the correct clamped remainder: shareCalculations.ts#L73-L83Math.max(0, 100 − providerShareForService − supplierShareForService − delegatorFee)
  • providerShareaverage across all services: #L89 (avgProviderShare = totalProviderShare / services.length)
  • supplierShareaverage across only services with addSupplierShare: #L91-L98 (servicesWithSupplierShare denominator)
  • The mismatch is in the returned object: #L101-L104
    providerShare: avgProviderShare,            // mean over all services
    supplierShare: avgSupplierShare,            // mean over addSupplierShare subset
    delegatorShare: delegatorFee,               // flat fee (currently always 0)
    clientShare: median(perServiceClientShares) // <-- median, computed independently

The displayed Client Share is computed independently as a median (#L104), not as the remainder of the other three displayed rows. So when a plan has services with differing shares, the four rows can fail to add up.

Example — 3 services, delegator = 0, supplier = 5 each, provider varies (20 / 20 / 80):

Service provider supplier delegator client (= remainder)
S1 20 5 0 75
S2 20 5 0 75
S3 80 5 0 15

Each row sums to 100. But what gets displayed:

  • Provider = avg(20, 20, 80) = 40
  • Supplier = 5
  • Delegator = 0
  • Client = median(75, 75, 15) = 75 (the true average remainder is 55)
  • Displayed total = 120

The Client row uses a median while the others use averages, so a user reading the rows top-to-bottom sees numbers that don't sum to 100. (The empty-services fallback at #L63-L69 does reconcile, since it sets clientShare = 100 − delegatorFee directly.)

These values are rendered as the four rows in PlanDetailsSection.tsx (the Provider/Supplier/Delegator rows added in #300, alongside the existing Client Share row).

Suggested direction: display Client = 100 − Provider − Supplier − Delegator (the actual remainder), or aggregate all four rows on the same basis, so the breakdown always reconciles by construction. Note clientShare is also consumed by the 0% warning and the effective-yield calc (calculateEffectiveYield), so check those call sites when changing it.

2. Revisit the 0% warning signal + copy

Because clientShare is a median (#L104), a plan with per-service client shares like [0, 0, 40] yields a median of 0 and triggers "You will receive 0% of rewards from this provider" — even though staking could still earn on the 40% service.

The CLIENT_SHARE_WARNING_THRESHOLD constant and the shares.clientShare <= THRESHOLD warning block live in ReviewStep/index.tsx (added in #300). Worth deciding whether the median is the right trigger for an absolute "0%" claim, and softening the copy accordingly (the line above already shows the actual percentage).

3. Minor: label consistency

"Delegator Fee" sits next to "Provider / Supplier / Client Share" in PlanDetailsSection.tsx while the underlying field is delegatorShare. Align the label, or keep it intentionally since it is a fee.

Non-goal

Not blocking PR #300.

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions