fix(connect): harden intent handling — validate params, implement receive, scope DM auto-approve (A1-A3, #609)#381
Merged
Conversation
… of hang/crash - Add centralized validateIntent + resolveCoinId helpers in ConnectIntentHandler. - Reject INVALID_PARAMS for malformed send/payment_request/dm/sign_message instead of opening a modal that silently hangs or (sign_message) crashes the render. - Resolve coinId symbol->64-hex via TokenRegistry so the default 'UCT' no longer fails to match held assets (was a guaranteed silent hang). - Unsupported intents (receive + the 9 invoice intents) now return METHOD_NOT_FOUND immediately instead of granting then rejecting with USER_REJECTED. Part of #609 (track A1): wallet-side intent-handling bugs W1/W3/W4/W5/W6.
…sleading 'UCT' fallback The `resolveCoinId(...) ?? 'UCT'` tail could (only in theory — validateIntent already rejects an unresolvable coinId upstream) ship a non-canonical symbol as coinId. Replace it with a single resolve into a local + null guard, and extract DEFAULT_COIN.
…ution & default coinId is always 64-hex (same contract as the mint intent, and the form Asset.coinId uses), so the symbol-resolution helper and the 'UCT' symbol default were unnecessary and a foot-gun. validateIntent now rejects a non-hex/missing coinId with INVALID_PARAMS (matching mint), and send/payment_request pass params.coinId through unchanged. Removes resolveCoinId and DEFAULT_COIN.
…proved recipient - A2: implement the `receive` intent via sphere.payments.receive() with a confirmation modal. - A3: DM auto-approve is now scoped to the recipient the user approved; a DM to any other recipient falls back to the normal confirmation modal (the auto-handler returns null) instead of being sent silently, and the message param is validated. Adds a null (declined) return to the auto-intent handler contract (ConnectProvider/Context). Part of #609 (tracks A2, A3).
igmahl
approved these changes
Jun 18, 2026
… intents Connect `send` and `payment_request` intents now take `amount` in BASE UNITS (smallest indivisible unit) — a positive integer string, like the `mint` intent and the token engine/SDK — instead of a whole-token decimal. This matches every major wallet (MetaMask, Phantom, WalletConnect): a dApp-requested transfer amount travels in base units (exact integers, no float), with human<->base conversion done at the dApp's UI edge. Both intents are now CONFIRM-ONLY (approve/reject), modeled on the `mint` intent, instead of reusing the editable manual SendModal/SendPaymentRequestModal: - IntentConfirmModal — shared confirm-only shell (BaseModal + header + buttons) - SendIntentModal / PaymentRequestIntentModal — the base-unit amount is shown via formatAmount (one-way, display only) and handed to the SDK verbatim; the amount is fixed (not user-editable) — a different amount would be a different request - SendIntentModal shows an insufficient-balance warning and disables confirm - validateIntent now rejects non-integer / non-positive amounts for send/payment_request The manual SendModal/SendPaymentRequestModal are no longer used by the intent handler; they remain for the wallet's own send / payment-request flows.
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.
What
Hardens the wallet's Connect intent handling so dApp intents are validated and handled correctly instead of silently hanging, crashing, granting-then-rejecting, or over-permitting.
Part of #609 — tracks A1 / A2 / A3.
A1 — param validation & clean rejection (
ConnectIntentHandler.tsx)validateIntent()+ a one-shot effect that rejects the pending intent before any modal renders.sign_messageno longer crashes the render whenmessageis missing (wasundefined.match()in the render body); missing/invalidmessage→INVALID_PARAMS.send/payment_requestnow require a canonical 64-hexcoinId(same check asmint); missing/non-hex →INVALID_PARAMS. The old default symbol'UCT'never matched the 64-hexAsset.coinId, causing a silent hang — removed (no symbol resolution, no symbol default).dmvalidatesto/message→INVALID_PARAMS.create_invoice…set_auto_return) reject immediately withMETHOD_NOT_FOUND("not supported by this wallet") instead of granting thenUSER_REJECTED, matching the gate decision that invoicing is experimental and not enabled (#609).A2 — implement
receive(ConnectIntentHandler.tsx)receiveintent now fetches pending incoming transfers viasphere.payments.receive()behind a confirmation modal, and resolves with{ transfers }(previously rejected as unsupported).A3 — scope DM auto-approve to the approved recipient (
ConnectIntentHandler.tsx,ConnectProvider.tsx,ConnectContext.ts)null) instead of being sent silently; the message param is validated.null("declined → show modal") return to the auto-intent handler contract.Behavior change
send/payment_requestnow require a canonical 64-hexcoinId(consistent withmint). Omitting it or passing a symbol like'UCT'returnsINVALID_PARAMSinstead of the previous silent hang. The dApp-facing docs (#609 track B1) will be updated to statecoinIdis the 64-hex id.Not in this PR (tracked separately)
setStateduring render) → A4 (separate PR).coinIdstill opens the send modal on the asset-selection step (the user can cancel →USER_REJECTED).Verification
tsc -b→ clean (exit 0).eslint .→ 0 errors (only 3 pre-existing warnings in an unrelated fileExplorePage.tsx).