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-L83 —
Math.max(0, 100 − providerShareForService − supplierShareForService − delegatorFee)
providerShare → average across all services: #L89 (avgProviderShare = totalProviderShare / services.length)
supplierShare → average 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.
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.tsonstaging):Math.max(0, 100 − providerShareForService − supplierShareForService − delegatorFee)providerShare→ average across all services: #L89 (avgProviderShare = totalProviderShare / services.length)supplierShare→ average across only services withaddSupplierShare: #L91-L98 (servicesWithSupplierSharedenominator)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):
Each row sums to 100. But what gets displayed:
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 − delegatorFeedirectly.)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. NoteclientShareis 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
clientShareis 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_THRESHOLDconstant and theshares.clientShare <= THRESHOLDwarning block live inReviewStep/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.tsxwhile the underlying field isdelegatorShare. Align the label, or keep it intentionally since it is a fee.Non-goal
Not blocking PR #300.