Skip to content

feat(swap-widget): overhaul receive address UI - #12407

Merged
kaladinlight merged 4 commits into
developfrom
feat/swap-widget-receive-address-row
Jun 2, 2026
Merged

feat(swap-widget): overhaul receive address UI#12407
kaladinlight merged 4 commits into
developfrom
feat/swap-widget-receive-address-row

Conversation

@kaladinlight

@kaladinlight kaladinlight commented Jun 2, 2026

Copy link
Copy Markdown
Member

Description

Started from SS-5661 ("manual address only editable in one spot") and ran with it into a full overhaul of the swap-widget receive-address UX, plus the surrounding internals.

Receive address UX

  • Replaced the receive-address modal with a dedicated, always-visible row after the Buy card that is directly editable inline (the old grey "enter receive address" text is now an actionable input, addressing the original issue).
  • A custom address can be entered even when one is derived from the connected wallet, with inline accept (✓) / reset (✗) controls.
  • Adaptive states: compact "resolved" view, input view, and a skeleton while the wallet connection is still resolving — no on-load layout shift. Soft attention glow only when an address is genuinely needed.
  • The row is scoped to the buy chain: an in-progress draft resets across namespaces (e.g. EVM → UTXO) while a still-valid custom address is kept across same-namespace chains.
  • The row is hidden until a wallet is connected (or resolving), so there's no perpetual skeleton.

Internals refactor (taking the issue as a jumping-off point)

  • Input/step/modal components now read machine + wallet state from context instead of prop-drilling (e.g. InputStep went from ~35 props to a handful); the AddressInputModal is deleted.
  • The action-button label and disabled state are unified into a single source of truth (Connect Wallet → receive address → amount), eliminating a label/disabled drift bug and removing unreachable "Executing…" branches (execution is shown by ExecutionStep). Connect labels are now uniformly "Connect Wallet" (chain is irrelevant to the user).
  • The connect flow opens AppKit per-namespace for every chain (previously a no-op for UTXO/Solana); the EVM connect guard keys off isConnected.
  • useSwapDisplayValues and useEvmSigning now return memoized objects for stable references.

Issue (if applicable)

closes #12322

Linear: SS-5661 — Manual address only editable in one spot

Risk

Low–Medium. Scope is contained to the @shapeshiftoss/swap-widget package (embeddable SDK), not the main web app. No new or modified on-chain transaction types. Behavioral touch points worth a look: the wallet connect flow (now opens AppKit per namespace) and the action-button gating (label/enabled state).

Protocols/wallets affected: wallet connect via Reown AppKit across EVM (eip155), UTXO (bip122), and Solana (solana) namespaces, within the swap widget only.

Testing

Engineering

Run the widget demo (pnpm --filter @shapeshiftoss/swap-widget dev) and verify:

  • Receive row shows the wallet-derived address once connected; shows a skeleton while resolving; is hidden before any wallet is connected.
  • Enter a custom receive address inline → ✓ accepts, ✗ resets back to the derived address.
  • Switch buy asset across namespaces (EVM → BTC): a stale EVM draft/custom does not persist; it resets / falls back to the derived address.
  • Switch buy asset within EVM (e.g. ETH → ARB): a valid custom EVM address is kept.
  • Action button reads correctly through the flow: Connect Wallet (enabled) → Enter receive addressEnter an amountFinding rates…Swap; label always matches enabled/disabled state.
  • Clicking Connect Wallet opens AppKit for each sell chain type (EVM / Bitcoin / Solana).
  • pnpm exec tsc --noEmit and eslint pass (verified locally).

Operations

  • 🏁 Contained to the @shapeshiftoss/swap-widget SDK package; not a user-facing feature in the main app and not gated by an app feature flag — no operations testing required.

Screenshots (if applicable)

connectWallet customReceiveAddressEmpty enterReceiveAddress invalidEvmAddress invalidUtxoAddress validEvmAddress validUtxoAddress walletAddress

Summary by CodeRabbit

New Features

  • Added inline receive address editing capabilities with real-time validation feedback

Refactor

  • Improved internal state management by refactoring components to use context-driven architecture
  • Enhanced wallet connection integration with improved multi-chain support

Style

  • Redesigned receive address row UI with improved layout and interactive controls

kaladinlight and others added 2 commits June 1, 2026 14:28
Reduce asset data regeneration from daily to once per week (Monday 9AM
UTC), as daily regen is excessive given current web release cadence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the receive-address modal with a dedicated, always-visible row after
the Buy card that is directly editable inline. A custom address can be entered
even when one is derived from the connected wallet, with inline accept/reset
controls, adaptive resolved/input/skeleton states, and a soft attention glow
only when an address is genuinely needed. The row is scoped to the buy chain,
so an in-progress draft resets across namespaces (e.g. EVM -> UTXO).

Taking the issue as a starting point, this also overhauls the surrounding
widget internals:

- Input/step/modal components read machine and wallet state from context
  instead of prop-drilling (InputStep ~35 props -> a handful).
- Action-button label and disabled state are unified into a single source of
  truth (Connect Wallet -> receive address -> amount), removing drift and
  dead "Executing..." branches.
- Connect flow opens AppKit per-namespace for every chain (not just EVM), and
  the EVM connect guard keys off isConnected.
- Display-values and EVM signing hooks are memoized for stable references.

Closes SS-5661

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kaladinlight
kaladinlight requested a review from a team as a code owner June 2, 2026 18:38
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ec26af44-7f41-48f0-b62c-449a0f1d0510

📥 Commits

Reviewing files that changed from the base of the PR and between 1b89458 and e5d1d57.

📒 Files selected for processing (1)
  • packages/swap-widget/src/components/ReceiveAddressRow.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/swap-widget/src/components/ReceiveAddressRow.tsx

📝 Walkthrough

Walkthrough

This PR refactors the swap widget's address input flow and component architecture, replacing a modal-based address input with an inline editable component and migrating multiple step components to context-driven patterns using SwapMachineCtx.

Changes

Swap Widget Address Input and Context Refactor

Layer / File(s) Summary
ReceiveAddressRow Component
packages/swap-widget/src/components/ReceiveAddressRow.tsx
New component replaces modal flow; renders resolved truncated address or editable input with validation, edit/reset handlers, focus management on mode changes, and input state reset when buyChainId changes.
Receive Address Styling
packages/swap-widget/src/components/SwapWidget.css
Removes .ssw-wallet-badge styles; adds .ssw-receive-row component styles including resolved display, address text, edit button, input wrapper with invalid state, accept/reset buttons, error text, and attention glow variant.
Step Components Context Migration
packages/swap-widget/src/components/ApprovalStep.tsx, ExecutionStep.tsx, StatusStep.tsx, SettingsModal.tsx
Converts ApprovalStep, ExecutionStep, StatusStep, and SettingsModal from props-driven to zero-argument context-driven components; derive state directly from SwapMachineCtx via useSelector and useActorRef instead of receiving context/send/match-state as props.
InputStep Refactoring
packages/swap-widget/src/components/InputStep.tsx
Simplifies props from large context/address/rate/balance set to displayValues, callbacks, and flags; removes inline wallet badge and address button UI; integrates new ReceiveAddressRow component with conditional rendering based on chain support and wallet context.
SwapWidget Orchestration and AppKit Integration
packages/swap-widget/src/components/SwapWidget.tsx
Wires context-driven step components with fewer props; adds AppKit account hooks for EVM/UTXO/Solana; removes address modal flow; updates SwapWalletContextValue to track isReceiveAddressResolving instead of isCustomReceiveAddress; eliminates AddressInputModal rendering.
Token Selection and Wallet Context
packages/swap-widget/src/components/TokenSelectModal.tsx, src/contexts/SwapWalletContext.tsx, src/hooks/useSwapHandlers.ts
TokenSelectModal derives addresses internally via signing hooks and asset ids from SwapMachineCtx; removes evmAddress and currentAssetIds props; updates context field to isReceiveAddressResolving; refines useSwapHandlers wallet connection with chain-specific AppKit namespaces (eip155, bip122, solana).
Hook Memoization and Exports
packages/swap-widget/src/hooks/useSwapDisplayValues.ts, useEvmSigning.ts
Exports SwapDisplayValues type; wraps useSwapDisplayValues and useEvmSigning return values in useMemo with explicit dependency arrays to prevent re-renders from fresh object creation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • shapeshift/web#12353: The main PR replaces the old AddressInputModal flow with the new ReceiveAddressRow, which performs validateAddress(..., buyChainId) and resets editing/state when buyChainId changes—directly mirroring the retrieved PR's goal of clearing/ignoring stale manual receive addresses on chain switch.
  • shapeshift/web#11865: Main PR refactors step components like ApprovalStep.tsx and ExecutionStep.tsx to be context-driven (zero-arg/use SwapMachineCtx), which overlaps directly with the retrieved PR's work on the same step components and their prop surfaces/signatures.
  • shapeshift/web#12346: Both PRs are tightly connected around the widget's receive-address flow: the main PR replaces the buy-address UI with the new ReceiveAddressRow (and updates InputStep/SwapWidget.css/wallet context receive-address state), which builds directly on the retrieved PR's rename/model shift to receiveAddress (replacing effectiveReceiveAddress) and related address handling in InputStep/SwapWalletContext.

Suggested reviewers

  • gomesalexandre

Poem

🐰 A rabbit's refactor dance,
Context flows, no props by chance,
Modals gone—inline we go,
Address row steals the show,
App-Kit chains now sing their tune,
Swap flows smooth 'neath the moon. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(swap-widget): overhaul receive address UI' accurately describes the main change in the PR, which is a comprehensive refactor of the receive address UI component.
Linked Issues check ✅ Passed The PR successfully addresses the objective from #12322 by making the grey placeholder text in the receive address field actionable and directly editable, eliminating the need for a separate 'enter address' control.
Out of Scope Changes check ✅ Passed All changes are scoped to the swap-widget package and directly support the receive address UI overhaul objective. Changes to prop drilling, hooks memoization, and context consumption are necessary refactorings to support the new inline receive-address row.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/swap-widget-receive-address-row

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
.github/workflows/generate.yml (2)

72-76: ⚡ Quick win

Remove duplicate branch checkout.

Line 76 repeats the git checkout -B feat_regenerate_asset_data command that was already executed on line 72. The second checkout is redundant.

♻️ Proposed fix
       - name: Commit changes
         run: |
-          git checkout -B feat_regenerate_asset_data
           git config --local user.email "action@github.com"
           git config --local user.name "asset-generation-bot"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/generate.yml around lines 72 - 76, The workflow contains a
duplicated git checkout command ("git checkout -B feat_regenerate_asset_data") —
remove the redundant second occurrence inside the "Commit changes" step so the
branch is only created/checked out once; keep the first checkout (the one before
the "Commit changes" step) and ensure the "Commit changes" step only runs the
commit/add/push commands, not another git checkout.

44-44: ⚡ Quick win

Update pnpm pin for CI maintenance (security risk looks unlikely for 10.30.3)

.github/workflows/generate.yml pins pnpm@10.30.3, but the latest published pnpm is 11.5.1. Current GitHub advisory data shows pnpm vulnerabilities patched at/affecting versions like < 10.28.1 / < 10.28.2 and other ranges below 10.27.0, with nothing in the returned advisories indicating impact for 10.30.3. Consider bumping the pin to a newer 10.x/11.x release to stay current.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/generate.yml at line 44, Update the pnpm version pin in
the workflow's run command (the line containing "corepack enable && corepack
prepare pnpm@10.30.3 --activate") to a newer, maintained release (for example
pnpm@11.5.1 or the latest 11.x) to keep CI current; modify that command to
reference the chosen newer version and ensure CI runs successfully after the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/generate.yml:
- Line 7: The change to the scheduled cron entry ("cron: '0 9 * * 1' # 9AM UTC
every Monday") is unrelated to the swap widget UI work and should not be bundled
here; either revert this cron line back to its previous value in this branch or
remove the change and open a separate PR that only updates the workflow schedule
(file containing the cron entry) so asset data generation scheduling is tracked
independently from the swap widget refactor.

In `@packages/swap-widget/src/components/ApprovalStep.tsx`:
- Line 6: The destructuring of send from SwapMachineCtx.useActorRef() in
ApprovalStep is safe—keep using const { send } = SwapMachineCtx.useActorRef() as
is (same pattern used in StatusStep), or if you want explicitness/consistency
change to const actorRef = SwapMachineCtx.useActorRef() and call
actorRef.send(...) in your click handlers; update ApprovalStep's handlers to use
the chosen form (send or actorRef.send) so calls reference the correct symbol.

In `@packages/swap-widget/src/components/ReceiveAddressRow.tsx`:
- Around line 121-139: The input in ReceiveAddressRow currently uses a span for
"Receive address" so screen readers rely on the placeholder; update the
component to give the input an accessible name by either converting the
span.ssw-receive-label into a <label> and linking it to the input via an
id/htmlFor pair, or by adding an explicit aria-label (e.g. aria-label="Receive
address") on the input element (ssw-receive-input) while keeping existing
refs/handlers (inputRef, handleChange, draft, formatHint) unchanged.

---

Nitpick comments:
In @.github/workflows/generate.yml:
- Around line 72-76: The workflow contains a duplicated git checkout command
("git checkout -B feat_regenerate_asset_data") — remove the redundant second
occurrence inside the "Commit changes" step so the branch is only
created/checked out once; keep the first checkout (the one before the "Commit
changes" step) and ensure the "Commit changes" step only runs the
commit/add/push commands, not another git checkout.
- Line 44: Update the pnpm version pin in the workflow's run command (the line
containing "corepack enable && corepack prepare pnpm@10.30.3 --activate") to a
newer, maintained release (for example pnpm@11.5.1 or the latest 11.x) to keep
CI current; modify that command to reference the chosen newer version and ensure
CI runs successfully after the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dbfdaf8a-ddd9-4ac1-9f54-59eef37dce7a

📥 Commits

Reviewing files that changed from the base of the PR and between 76aac24 and 1b89458.

📒 Files selected for processing (16)
  • .github/workflows/generate.yml
  • packages/swap-widget/src/components/AddressInputModal.css
  • packages/swap-widget/src/components/AddressInputModal.tsx
  • packages/swap-widget/src/components/ApprovalStep.tsx
  • packages/swap-widget/src/components/ExecutionStep.tsx
  • packages/swap-widget/src/components/InputStep.tsx
  • packages/swap-widget/src/components/ReceiveAddressRow.tsx
  • packages/swap-widget/src/components/SettingsModal.tsx
  • packages/swap-widget/src/components/StatusStep.tsx
  • packages/swap-widget/src/components/SwapWidget.css
  • packages/swap-widget/src/components/SwapWidget.tsx
  • packages/swap-widget/src/components/TokenSelectModal.tsx
  • packages/swap-widget/src/contexts/SwapWalletContext.tsx
  • packages/swap-widget/src/hooks/useEvmSigning.ts
  • packages/swap-widget/src/hooks/useSwapDisplayValues.ts
  • packages/swap-widget/src/hooks/useSwapHandlers.ts
💤 Files with no reviewable changes (2)
  • packages/swap-widget/src/components/AddressInputModal.tsx
  • packages/swap-widget/src/components/AddressInputModal.css

Comment thread .github/workflows/generate.yml
Comment thread packages/swap-widget/src/components/ApprovalStep.tsx
Comment thread packages/swap-widget/src/components/ReceiveAddressRow.tsx
kaladinlight and others added 2 commits June 2, 2026 15:32
The "Receive address" caption is a span, not a label tied to the input, so
the field's only accessible name was the placeholder format hint. Add an
explicit aria-label so screen readers announce the field purpose.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kaladinlight
kaladinlight enabled auto-merge (squash) June 2, 2026 21:39
@kaladinlight
kaladinlight merged commit 86e098f into develop Jun 2, 2026
4 checks passed
@kaladinlight
kaladinlight deleted the feat/swap-widget-receive-address-row branch June 2, 2026 21:46
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.

Manual address only editable in one spot

1 participant