feat(kyc-controller): register Money Account wallets - #9847
feat(kyc-controller): register Money Account wallets#9847saustrie-consensys wants to merge 58 commits into
Conversation
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>
…ay session creation 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>
Registering a self-hosted wallet with MoonPay Iron requires signing a proof-of-ownership sentence and POSTing it byte-identically. Three pieces land here, each independently testable: - buildOwnershipMessage: produces the exact sentence Iron expects, with the date always derived in UTC so a signature made just before midnight is not reused against a stale date after rollover. - WalletRegistrationService: talks to the MetaMask proxy for status lookup and registration, and maps proxy/Iron failures onto typed WalletRegistrationError kinds so callers can branch without parsing strings. - wallet-registration-machine: a pure transition reducer covering the signing step, including the ambiguous 409 (address already exists vs idempotency-key reuse) which is only resolvable by re-reading the address list, transient failures, and the UTC-rollover re-sign. The machine follows the hand-rolled FSM convention already used in this repo rather than pulling in a state-chart dependency.
Limit the public request contract to Monad and keep product eligibility outside the Iron protocol machine. This removes speculative multi-chain, conflict-classification, and prerequisite state surfaces while retaining 409 and transient-write reconciliation.
Expose an address-only controller action so Ramps can register a Monad wallet without handling Iron customer IDs, signing, or retry state.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Caution MetaMask internal reviewing guidelines:
|
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.moonpayCustomerIdreceived from MoonPay's hosted frame.GET /vendors/moonpay/customerproxy endpoint.KeyringController:signPersonalMessage.KycServiceand 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.
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
References
https://api.sandbox.iron.xyz/spec/2026-08-01Checklist