Skip to content

feat(kyc-controller): Iron Money KYC path + status polling - #9852

Open
amitabh94 wants to merge 62 commits into
mainfrom
feat/iron-kyc-controller-m2
Open

feat(kyc-controller): Iron Money KYC path + status polling#9852
amitabh94 wants to merge 62 commits into
mainfrom
feat/iron-kyc-controller-m2

Conversation

@amitabh94

Copy link
Copy Markdown
Contributor

Summary

  • Adds vendor: 'iron' KYC flow for MetaMask Money: idle → terms → session → submit(SumSub) → done/error with no MoonPay Check/Auth frames.
  • Extends KycService with Milestone 1 contract clients (mocked in tests): POST /vendors/iron/customers, GET /vendors/iron/disclaimers, POST /vendors/iron/kyc-required, POST /consents, GET /kyc/status, plus createUkycSession({ vendorId: 'iron' }) without MoonPay metadata.
  • Adds createIronCustomer, refreshKycStatus, KycController:statusChanged, persisted userStatus* fields, and maps session_not_in_valid_statecompleted.

Stacking

  • Base: feat/kyc-controller (feat: KYC controller #9615)
  • Depends on Milestone 1 API endpoints landing on va-mmcx-kyc-api (not merged yet). Clients are implemented against the planned contract; tests mock fetch.

Deferred (follow-ups)

  • Full 3-state error UX wiring beyond status fields (need-more-information / terminal-failure / retryable SumSub session id)
  • Mobile Money navigator + toast rendering (Milestone 3)
  • Live integration once Milestone 1 is on shared-dev

Test plan

  • yarn workspace @metamask/kyc-controller run test (100% coverage)
  • yarn workspace @metamask/kyc-controller run messenger-action-types:check
  • Smoke against local/dev KYC API once Iron routes + /consents + /kyc/status exist

Made with Cursor

Akaryatrh and others added 30 commits July 24, 2026 12:26
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
…b flow opening

Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
… been reset

Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Akaryatrh and others added 4 commits August 8, 2026 01:43
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Introduce vendor:'iron' flow (no MoonPay Check/Auth frames) with KycService clients for Iron customers/disclaimers/consents and user-keyed GET /kyc/status, so Money can drive toast state against the planned Milestone 1 API contract.
@amitabh94
amitabh94 requested review from a team as code owners August 12, 2026 21:18
Comment thread packages/kyc-controller/src/KycController.ts Outdated
Comment thread packages/kyc-controller/src/KycController.ts
Comment thread packages/kyc-controller/src/KycController.ts
amitabh94 and others added 2 commits August 12, 2026 15:37
Add PR link and formatting for changelog, fix JSDoc/restricted-syntax/jest matcher lint errors, and apply Prettier to the package.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use oxfmt (not Prettier) so import order and markdown alignment match the monorepo misc formatter.

Co-authored-by: Cursor <cursoragent@cursor.com>
@amitabh94

Copy link
Copy Markdown
Contributor Author

Demo topology update (for reviewers): Iron feature commit a40fa2fc3f is already on neobank-demo (merged by @georgeweiler into the demo integration branch; umbrella #9853). Remaining CI lint/oxfmt fixes from this PR are synced via #9855neobank-demo.

This PR (#9852) should stay targeting feat/kyc-controller for the mainline stack (#9615). Do not retarget to main / neobank-demo.

amitabh94 added a commit that referenced this pull request Aug 12, 2026
## Summary
- `neobank-demo` already includes Sébastien’s `feat/kyc-controller`
stack and the Iron feature commit from #9852 (`a40fa2fc3f`, merged by
George).
- This PR cherry-picks the remaining #9852 CI fixes (`lint` / changelog
PR link / `oxfmt`) so the demo branch matches the Iron tip for package
publish/preview and mobile consumption.
- Keeps #9852 targeting `feat/kyc-controller` for the eventual mainline
path via #9615. Does **not** merge to `main`.

## Context (demo topology)
- Integration branch: `neobank-demo` (owner: @georgeweiler) — umbrella
PR #9853 → `main`
- Iron feature PR (mainline stack): #9852 → `feat/kyc-controller`
- KYC controller PR: #9615 → `main`

## Test plan
- [ ] Confirm diff is formatting/lint/changelog only (no Iron behavior
change)
- [ ] CI green on this PR
- [ ] After merge, `@metamaskbot publish-preview` on #9853 (or this PR)
if mobile needs a preview pin of `@metamask/kyc-controller`


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
// Ignore; disclaimers loading will surface a country error if needed.
}

// Iron: create the empty-shell customer before T&C (offsite decision).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the reasoning for the KycController to explicitly handle this bit of the vendor onboarding lifecycle?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to be able to create a customer before signing the T&C.
Where do you think this logic is best placed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I meant more that it is odd for the KycController to have to concern itself with specifics of Iron onboarding, in this case creation of a customer with the user's email. Previously this was combined with the Moonpay flow under the notion of a "session". I agree that this step still must happen before Iron signings can be submitted, but i think we should abstract this into the same idea of a "session", hiding the fact that that really just means a customer must be created/exist to the KycController itself

Comment on lines +805 to +806
sumsubTncSigned: params?.sumsubTncSigned ?? true,
idosTncSigned: params?.idosTncSigned ?? true,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaulting to true seems to make it more possible that the disclaimers are not actually shown to the user

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that sounds correct. Should we default it to false then ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +1432 to +1440
vendorId: isIron ? 'iron' : 'moonpay',
...(isIron
? {}
: {
vendorMetadata: {
moonPayAccessToken: this.state.accessToken,
moonPayUserId: this.state.moonpayCustomerId,
},
}),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels written a little oddly. Perhaps we just pass state.activeVendor straight through to vendorId and then add a helper to generate the extra vendorMetadata bit

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a definitely a clean and better approach.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return result;
} catch (error) {
// Applicant already finished KYC — treat as completed for Money toast.
if (String(error).includes(SESSION_NOT_IN_VALID_STATE)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to abstract this into a helper

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 1842 to +1843
this.#stopPolling();
this.#stopUserStatusPolling();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO(jiexi): verify the difference between these two poll loops

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not certain we need both polling loops

/**
* Creates (or resumes) an Iron empty-shell customer for the authenticated
* canonical user. Must run before showing Iron T&C so the customer exists in
* `SigningsRequired` and resume logic can key off Iron status.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't need to exist before showing T&C, only before submitting signing contentIds to Iron

Base automatically changed from feat/kyc-controller to main August 17, 2026 23:45

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 47840d7. Configure here.

Comment thread packages/kyc-controller/src/KycController.ts
Comment thread packages/kyc-controller/ARCHITECTURE.md Outdated
@georgeweiler

georgeweiler commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

An architectural suggestion: right now Iron Money onboarding is exposed as public KYC APIs (createIronCustomer, Iron-named KycService methods, vendor: 'iron' on the consumer surface). It is a bit awkward IMO.

I am imagining something like this instead, where ramps-controller can pass a "vendor" (like iron) to some generic methods:

createVendorCustomer({ vendor, email })
fetchDisclaimers({ vendor })
checkKycRequired({ vendor })

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.

5 participants