feat(kyc-controller): register Money Account wallets - #9850
Draft
saustrie-consensys wants to merge 2 commits into
Draft
feat(kyc-controller): register Money Account wallets#9850saustrie-consensys wants to merge 2 commits into
saustrie-consensys wants to merge 2 commits into
Conversation
Add an address-only KycController action that resolves the MoonPay customer, signs a Monad ownership proof, and registers the wallet through the MetaMask proxy.
4 tasks
Contributor
Author
|
Backend ownership update: wallet signing moved from kyc-api to Money Movement neobank-proxy.
This Core branch still uses the old |
Retarget Money Account self-hosted wallet registration from kyc-api /vendors/moonpay/* to onramp-api neobank-proxy /neobank/... so Core matches Money Movement ownership and transparent proxy semantics.
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.
Explanation
This stacked PR adds MoonPay Iron self-hosted wallet registration to the unreleased
@metamask/kyc-controllerpackage from #9615. The POC is limited to Money Account on Monad.Ramps and other consumers call one messenger action:
The consumer does not pass an Iron customer ID, construct a registration service, build the ownership message, sign it, or manage retry state.
KycControllerhandles those details internally:this.state.moonpayCustomerIdfrom MoonPay's hosted frame; otherwise resolve Iron's internal customer id via neobank-proxyGET /neobank/customers/{external_id}/external(MetaMaskcanonicalProfileIdasexternal_id).GET /neobank/addresses/crypto/{customer_id}?filter=SelfHosted(Monad filtered client-side).KeyringController:signPersonalMessage.POST /neobank/addresses/crypto/selfhostedwith a client-generatedIdempotency-Key, and drive the internal state machine for conflict reconciliation, transient retries, and UTC rollover re-signing.The low-level registration service, message builder, and state machine are implementation details. They are not exported as alternate consumer APIs. Typed registration errors remain exported so clients can classify failures if needed.
This replaces #9847. That PR targeted
mainand could not cleanly stack onto #9615 because the branches diverged from different points onmain.Backend contract (synced with onramp-api #1126)
Wallet signing is owned by Money Movement on onramp-api neobank-proxy (not kyc-api). This Core PR now targets the transparent neobank routes:
GET /neobank/customers/{external_id}/externalCustomer.idis Iron's customer UUIDGET /neobank/addresses/crypto/{customer_id}?filter=SelfHostedcustomer_id; Monad filtered in CorePOST /neobank/addresses/crypto/selfhostedIdempotency-KeyneobankBaseUrlonKycService, falls back to KYCbaseUrl)https://on-ramp.dev-api.cx.metamask.io{ code: 'iron_error', message }envelopeexternal_id/customer_idcome from the clientBackend PRs:
Recommended call site (vs #9848 NeoBankService)
Prefer the UI / product entrypoint calling
KycController:registerMoneyAccountWallet, notNeoBankService.Rationale:
#9848'sNeoBankServiceis a thin Ramp API HTTP client (autoramp fetch + bearer). It does not own KYC session state, signing, or Travel Rule orchestration.signPersonalMessage, retries,moonpayCustomerId). Putting that inside NeoBank would double-orchestrate and place signing in the wrong layer.RampsControllerproduct method) once the user has a Money Account address and KYC context, not inside NeoBank's transport methods.moonpayCustomerIdis only reliably inKycControllerstate after the MoonPay frame flow; NeoBank refresh/sync paths often run without that session.Do not wire this into #9848's branch. Call from mobile/extension UI (or a later RampsController Money Account helper) with:
State machine
stateDiagram-v2 [*] --> idle idle --> preparing: START preparing --> alreadyRegistered: LOOKUP_ACTIVE preparing --> registeredDisabled: LOOKUP_DISABLED preparing --> signing: LOOKUP_ABSENT preparing --> lookupUnavailable: LOOKUP_FAILED signing --> awaitingUnlock: WALLET_LOCKED awaitingUnlock --> signing: WALLET_UNLOCKED signing --> submitting: SIGN_OK signing --> cancelled: SIGN_REJECTED / CANCEL signing --> failedRetryable: SIGN_FAILED(retryable) signing --> failedTerminal: SIGN_FAILED(!retryable) submitting --> registered: SUBMIT_OK submitting --> disambiguate409: SUBMIT_CONFLICT submitting --> checkThenRetry: SUBMIT_TRANSIENT submitting --> signing: SUBMIT_VALIDATION(utcRollover & attempts < max) submitting --> failedTerminal: SUBMIT_VALIDATION(otherwise) / SUBMIT_TERMINAL submitting --> failedRetryable: SUBMIT_RATE_LIMITED disambiguate409 --> alreadyRegistered: LOOKUP_ACTIVE disambiguate409 --> registeredDisabled: LOOKUP_DISABLED disambiguate409 --> failedRetryable: LOOKUP_ABSENT disambiguate409 --> lookupUnavailable: LOOKUP_FAILED checkThenRetry --> alreadyRegistered: LOOKUP_ACTIVE checkThenRetry --> registeredDisabled: LOOKUP_DISABLED checkThenRetry --> signing: LOOKUP_ABSENT & attempts < max checkThenRetry --> failedRetryable: LOOKUP_ABSENT & attempts >= max checkThenRetry --> lookupUnavailable: LOOKUP_FAILED failedRetryable --> preparing: RETRY lookupUnavailable --> preparing: RETRY cancelled --> preparing: RETRY registered --> [*] alreadyRegistered --> [*] registeredDisabled --> [*] failedTerminal --> [*]Contract confirmations from MoonPay/Iron
blockchain: "Monad"works in sandbox and is present in the pinned2026-08-01contract.SELF_ATTESTEDaddress satisfies proof of ownership and is treated as registered.Verification
Remaining client wiring blockers
neobankBaseUrl(on-ramp host) when constructingKycServiceso wallet routes do not hit the KYC API host.AuthenticationController:getSessionProfilemust be delegated toKycServicemessengers so fallback customer lookup can readcanonicalProfileId.References
https://api.sandbox.iron.xyz/spec/2026-08-01Checklist