fix(frontend): map full beneficiary list and enforce bps allocation validation - #1053
Merged
ONEONUORA merged 2 commits intoAug 24, 2026
Conversation
anonfedora
force-pushed
the
fix/beneficiary-allocation-validation-1031
branch
from
August 23, 2026 20:59
5fc68f1 to
bbf1d2d
Compare
…alidation CreateInheritancePlanPanel and EditInheritancePlanPanel only transmitted beneficiaries[0]?.name to the backend, silently dropping every other beneficiary's wallet address, allocation, and fiat off-ramp config. Separately, plansAPI.createPlan/updatePlan sent a request shape (title, fee, net_amount, bank_account_number, two_fa_code, ...) that never matched the real backend's Plan/UpdatePlanRequest contract at all. - plans.ts / inheritance.ts: CreatePlanRequest/UpdatePlanRequest now mirror the backend's actual Plan/UpdatePlanRequest structs (owner, token, amount, beneficiaries: PlanBeneficiaryRequest[], grace_period, earn_yield, yield_rate_bps); added the missing updatePlan() to inheritance.ts. - New BeneficiaryAllocationRow component centralizes beneficiary row UI, strict basis-point validation (must total exactly 10,000 bps), Stellar StrKey address validation, duplicate-address detection, and fiat anchor/daily-limit encoding shared by both panels. - Both panels now build and submit the full beneficiary list, surface clear per-field and total-allocation error messages, and correctly round-trip the backend's PlanResponse back into the UI. - Updated MSW mocks/tests to match the corrected contract. Closes Fracverse#1031
…ate plan update call Rebased fix/beneficiary-allocation-validation-1031 onto upstream/master to pick up Fracverse#1049 (wire frontend plans to the live API) and Fracverse#1052 (wallet-signed pings), which landed after this branch was created. Fracverse#1049 changed EditPlanPage.handleSaved to persist the panel's callback value via plansAPI.updatePlan(planId, updated) a second time, passing the full Plan object as if it were an UpdatePlanRequest. That no longer type-checks now that UpdatePlanRequest is a proper DTO, and it was always redundant: EditInheritancePlanPanel already calls plansAPI.updatePlan itself and hands the backend-confirmed plan back via onSaved. The page now just syncs local state with that result.
anonfedora
force-pushed
the
fix/beneficiary-allocation-validation-1031
branch
from
August 23, 2026 21:05
bbf1d2d to
28e6051
Compare
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.
Summary
CreateInheritancePlanPanelandEditInheritancePlanPanelonly sentbeneficiaries[0]?.nametoplansAPI.createPlan, silently discarding every other beneficiary's wallet address, allocation percentage, and fiat off-ramp config on submission.Digging further, the problem went deeper than the beneficiary array:
plansAPI.createPlan/updatePlansent a request shape (title,fee,net_amount,bank_account_number,bank_name,currency_preference,two_fa_code) that never matched the backend's realPlan/UpdatePlanRequestcontract (seebackend/src/api.rs, and the MSW mock explicitly commented "matches the Axum POST /api/plans signature"), which actually requiresowner,token,amount,beneficiaries: [{address, name, allocation_bps, fiat_anchor_info}],grace_period,earn_yield,yield_rate_bps. Beneficiaries were never going to survive submission regardless of how the array was built, andinheritance.tswas missing anupdatePlan()method entirely.This PR aligns the frontend with the real backend contract end-to-end and fixes the beneficiary data loss.
Changes
app/lib/api/inheritance.ts— addedUpdatePlanRequest+updatePlan(), matching the backend's realUpdatePlanRequest(beneficiaries,grace_period?,earn_yield?,yield_rate_bps?).app/lib/api/plans.ts— rewroteCreatePlanRequest/UpdatePlanRequestto match the backend's actualPlan/UpdatePlanRequeststructs, reusingPlanBeneficiaryRequest;createPlan/updatePlannow return the real unwrappedPlanResponseinstead of assuming an{status, data}envelope that the backend never sends.components/plans/BeneficiaryAllocationRow.tsx(new, shared by both panels) — beneficiary row UI plus:validateBeneficiaryDrafts— strict basis-point validation (allocations must total exactly 10,000 bps), Stellar StrKey checksum address validation, duplicate wallet-address detection, and fiat-field completeness checks, each with a specific error message.beneficiaryDraftToRequest— builds thefiat_anchor_infoJSON payload (name/currency/bank/account/daily_limit) the backend already parses on payout (parse_fiat_anchor_info).CreateInheritancePlanPanel.tsx:PlanBeneficiaryRequest[](address, name, allocation_bps, fiat_anchor_info) instead of dropping everything but the first name.owner/tokenand the panel previously collected neither.EditInheritancePlanPanel.tsx:beneficiariesarray (wallet address, bps, parsed fiat info) instead of only a beneficiary name.grace_period/earn_yield/yield_rate_bpson save, and merges the backend's response back into the UI'sPlanshape.tests/mocks/handlers.ts) and two test files to match the corrected, real backend contract.Test plan
npm test— all 202 tests pass (npx vitest --run)npx tsc --noEmit— no new type errors (one pre-existing, unrelated error intests/components/InactivityTimerCard.test.tsxpredates this branch)npx eslinton all touched files — 0 errors (only pre-existing-styleanywarnings)npm run build(next build) — compiles and type-checks cleanly, matching CI'sFrontend CIworkflow gateCloses #1031
🤖 Generated with Claude Code