feat: add investor eligibility explanation mapper (Closes #52) - #157
Open
panditdhamdhere wants to merge 1 commit into
Open
feat: add investor eligibility explanation mapper (Closes #52)#157panditdhamdhere wants to merge 1 commit into
panditdhamdhere wants to merge 1 commit into
Conversation
Explain why an investor is approved, blocked, revoked, unknown, or unavailable instead of surfacing a bare whitelist boolean, so dashboards can show a reason code and a next-step CTA. A bare whitelist false maps to blocked rather than revoked, because checkWhitelist cannot distinguish "never approved" from "approved then revoked". Revoked requires an explicit signal from an admin receipt, whitelist_remove event, or off-chain KYC system. Messages are fixed safe strings and every result carries a non-guarantee disclaimer with verified false, so no legal or regulatory determination is implied.
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.
Description
ComplianceModule.checkWhitelist()returns a bare boolean, so a dashboard showing"not eligible" cannot tell the user why: never approved, KYC revoked, or the
compliance query simply failed. Each of those needs a different UI response, and a
falsetoday collapses them into one.This PR adds a mapper that turns observable compliance signals into a UI-friendly
explanation carrying a stable reason code, a safe message, and a suggested next
action.
Why this approach: the mapper is a pure function (following the
buildNetworkFailureDiagnosticandbuildAdminActionReceiptpatterns) with a thinInvestorModulewrapper for the live path, rather than a new client module. Thatkeeps it usable both with and without RPC — dashboards that already loaded a
portfolio or role result can explain eligibility without a second round trip.
Two decisions worth reviewer attention:
falsemaps toblocked, notrevoked.checkWhitelistcannot distinguish "never approved" from "approved then revoked", so inferring a
revoke from
falsewould be inventing information.revokedis only emitted whena caller supplies
isKycRevoked: truefrom a source that actually knows — an adminwhitelist-removereceipt, a decodedwhitelist_removeevent, or an off-chain KYCsystem.
result carries a fixed
disclaimerfield andverified: false, so thenon-guarantee language travels with the object and cannot be dropped when
serialising for UI or support tooling.
Files added
src/types/eligibility.ts— status union, reason codes, next actions, input/result typessrc/investor/eligibility.ts—buildInvestorEligibilityExplanation,explainWhitelistResult,normalizeInvestorEligibilityStatus,ELIGIBILITY_DISCLAIMERsrc/errors/eligibility.ts—EligibilityExplanationErrordocs/investor-eligibility.mdtests/investor-eligibility.test.tsFiles modified
src/investor/portfolio.ts— addsexplainEligibility(live compliance check) andexplainEligibilityFromSignals(pure, no RPC)src/index.ts— public exports for helpers, types, and errordocs/api-reference.md—InvestorModulemethods, standalone helpers, and an open note on the boolean limitationdocs/investor-portfolio.md— cross-linkREADME.md— quickstart sectionCONTRIBUTING.md— doc-update rule and review checklist itemCloses: #52
Evidence Checklist
1. Issue Reference
Closes #52).2. Implementation Summary
3. Tests
tests/investor-eligibility.test.tscovers all five states, alias normalisation,unrecognised future statuses resolving to
unknown(neverapproved), signalpriority when inputs conflict (revoke wins over a contradictory approved boolean),
frozen results, and invalid-timestamp rejection.
The live
InvestorModulepath is tested by spying onclient.compliance.checkWhitelist— approved, blocked, a rejected compliance query,and an invalid address that must not reach compliance at all. One test asserts a
thrown RPC error containing a token in its message never appears in the serialised
explanation.
4. Commands Run
Command output
5. CI Status
(Tick once CI finishes.)
6. Acceptance Criteria Coverage
InvestorEligibilityReasonCode:WHITELISTED,NOT_WHITELISTED,KYC_REVOKED,COMPLIANCE_QUERY_FAILED,INVALID_ADDRESS,INSUFFICIENT_DATA,UNRECOGNIZED_STATUS.InvestorEligibilityStatusunion, one mapping per state, each with its ownreason code and suggested next action.
errors, URLs, and credentials are never interpolated. A test asserts a token
present in a thrown error does not survive into the result.
statuses, conflicting-signal priority, and the live compliance paths.
docs/investor-eligibility.mdcovers thestatus model,
blockedvsrevoked, a reason-code/next-action table, pure andlive usage, and UI guidance.
ELIGIBILITY_DISCLAIMERand
verified: false. Docs state explicitly this is not legal, financial, orregulatory advice and does not guarantee a transaction will succeed. A test
asserts the disclaimer is present on every state.
Reviewer Notes
Protocol limitation, documented rather than worked around: the live
explainEligibilitypath cannot emitrevokedon its own, becausecheckWhitelistonly returns a boolean. Emitting
revokedrequires a caller-supplied signal viaexplainEligibilityFromSignals. This is called out as an open note indocs/api-reference.md. If the contract later exposes a richer compliance query(e.g. distinguishing revoke from never-approved), the mapper can consume it without
changing its public shape.
npm run lintandnpm run formatcannot run on this branch or onmain. Bothscripts invoke
eslintandprettier, but neither package is indevDependencies,so
npm run verifyexits ateslint: command not found. Pre-existing and unrelatedto this change; CI runs
npm run check, which passes.Naming:
InvestorEligibilityStatusis intentionally separate from the existingPortfolioStatus(active/empty/blocked/unavailable). Portfolio statusdescribes holdings; eligibility status describes whitelist standing. Both use
blocked, but they answer different questions and are documented as such.Relationship to the existing
TransferEligibility: that type stays untouched —it is per-asset and factors in balance. This mapper is address-level and
compliance-only. No behaviour change to
getPortfolio.