@b3dotfun/sdk 0.0.67 → 0.0.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.js +38 -26
  2. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +10 -6
  3. package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.js +1 -1
  4. package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +0 -7
  5. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.d.ts +2 -7
  6. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +3 -13
  7. package/dist/cjs/anyspend/react/hooks/index.d.ts +1 -0
  8. package/dist/cjs/anyspend/react/hooks/index.js +1 -0
  9. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.d.ts +1 -1
  10. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +12 -12
  11. package/dist/cjs/anyspend/react/hooks/useConnectedWalletDisplay.js +2 -1
  12. package/dist/cjs/anyspend/react/hooks/useRecipientAddressState.d.ts +52 -0
  13. package/dist/cjs/anyspend/react/hooks/useRecipientAddressState.js +52 -0
  14. package/dist/esm/anyspend/react/components/AnySpend.js +38 -26
  15. package/dist/esm/anyspend/react/components/AnySpendCustom.js +10 -6
  16. package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.js +1 -1
  17. package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +1 -8
  18. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +2 -7
  19. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +3 -13
  20. package/dist/esm/anyspend/react/hooks/index.d.ts +1 -0
  21. package/dist/esm/anyspend/react/hooks/index.js +1 -0
  22. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.d.ts +1 -1
  23. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +12 -12
  24. package/dist/esm/anyspend/react/hooks/useConnectedWalletDisplay.js +2 -1
  25. package/dist/esm/anyspend/react/hooks/useRecipientAddressState.d.ts +52 -0
  26. package/dist/esm/anyspend/react/hooks/useRecipientAddressState.js +49 -0
  27. package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +2 -7
  28. package/dist/types/anyspend/react/hooks/index.d.ts +1 -0
  29. package/dist/types/anyspend/react/hooks/useAnyspendFlow.d.ts +1 -1
  30. package/dist/types/anyspend/react/hooks/useRecipientAddressState.d.ts +52 -0
  31. package/package.json +1 -1
  32. package/src/anyspend/react/components/AnySpend.tsx +43 -31
  33. package/src/anyspend/react/components/AnySpendCustom.tsx +10 -7
  34. package/src/anyspend/react/components/AnySpendCustomExactIn.tsx +1 -1
  35. package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +1 -8
  36. package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +5 -27
  37. package/src/anyspend/react/hooks/index.ts +1 -0
  38. package/src/anyspend/react/hooks/useAnyspendFlow.ts +13 -12
  39. package/src/anyspend/react/hooks/useConnectedWalletDisplay.ts +4 -1
  40. package/src/anyspend/react/hooks/useRecipientAddressState.ts +78 -0
@@ -0,0 +1,52 @@
1
+ interface UseRecipientAddressStateProps {
2
+ /** Fixed recipient address from props (highest priority) */
3
+ recipientAddressFromProps?: string;
4
+ /** Connected wallet address from payment method */
5
+ walletAddress?: string;
6
+ /** Global account address */
7
+ globalAddress?: string;
8
+ }
9
+ interface UseRecipientAddressStateResult {
10
+ /** User explicitly selected recipient address (undefined means no explicit selection) */
11
+ selectedRecipientAddress: string | undefined;
12
+ /** Function to update the user-selected recipient address */
13
+ setSelectedRecipientAddress: (address: string | undefined) => void;
14
+ /** Effective recipient address (follows priority: props > user selection > wallet/global) */
15
+ effectiveRecipientAddress: string | undefined;
16
+ /** Reset recipient address state */
17
+ resetRecipientAddress: () => void;
18
+ }
19
+ /**
20
+ * Custom hook to manage recipient address state with automatic priority handling:
21
+ *
22
+ * **Priority System:**
23
+ * 1. `recipientAddressFromProps` - Fixed recipient from component props (highest priority)
24
+ * 2. `selectedRecipientAddress` - User's explicit manual selection
25
+ * 3. `walletAddress` or `globalAddress` - Auto-selected fallback
26
+ *
27
+ * **Key Features:**
28
+ * - Automatically manages recipient address based on priority
29
+ * - Preserves user's manual selections
30
+ * - Updates automatically when wallet/global address changes (if no manual selection)
31
+ * - Derived value approach - no useEffect needed, no stale state bugs
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * const {
36
+ * selectedRecipientAddress,
37
+ * setSelectedRecipientAddress,
38
+ * effectiveRecipientAddress,
39
+ * resetRecipientAddress
40
+ * } = useRecipientAddressState({
41
+ * recipientAddressFromProps,
42
+ * walletAddress,
43
+ * globalAddress,
44
+ * });
45
+ *
46
+ * // Use effectiveRecipientAddress for display and operations
47
+ * // Use setSelectedRecipientAddress when user explicitly selects
48
+ * // Call resetRecipientAddress when switching tabs or going back
49
+ * ```
50
+ */
51
+ export declare function useRecipientAddressState({ recipientAddressFromProps, walletAddress, globalAddress, }?: UseRecipientAddressStateProps): UseRecipientAddressStateResult;
52
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3dotfun/sdk",
3
- "version": "0.0.67",
3
+ "version": "0.0.68",
4
4
  "source": "src/index.ts",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "react-native": "./dist/cjs/index.native.js",
@@ -33,7 +33,9 @@ import { base, mainnet } from "viem/chains";
33
33
  import { components } from "../../types/api";
34
34
  import { useAutoSelectCryptoPaymentMethod } from "../hooks/useAutoSelectCryptoPaymentMethod";
35
35
  import { useAutoSetActiveWalletFromWagmi } from "../hooks/useAutoSetActiveWalletFromWagmi";
36
+ import { useConnectedWalletDisplay } from "../hooks/useConnectedWalletDisplay";
36
37
  import { useCryptoPaymentMethodState } from "../hooks/useCryptoPaymentMethodState";
38
+ import { useRecipientAddressState } from "../hooks/useRecipientAddressState";
37
39
  import { AnySpendFingerprintWrapper, getFingerprintConfig } from "./AnySpendFingerprintWrapper";
38
40
  import { CryptoPaymentMethod, CryptoPaymentMethodType } from "./common/CryptoPaymentMethod";
39
41
  import { CryptoPaySection } from "./common/CryptoPaySection";
@@ -444,16 +446,29 @@ function AnySpendInner({
444
446
  // [selectedDstChainId, newRecipientAddress, resolvedAddress]
445
447
  // );
446
448
 
447
- // State for recipient selection
448
- const [recipientAddress, setRecipientAddress] = useState<string | undefined>();
449
-
450
449
  const { address: globalAddress, wallet: globalWallet, connectedEOAWallet } = useAccountWallet();
451
- const recipientProfile = useProfile({ address: recipientAddress, fresh: true });
452
- const recipientName = recipientProfile.data?.name;
453
450
 
454
451
  // Auto-set active wallet from wagmi
455
452
  useAutoSetActiveWalletFromWagmi();
456
453
 
454
+ // Get wallet address based on selected payment method
455
+ const { walletAddress } = useConnectedWalletDisplay(effectiveCryptoPaymentMethod);
456
+
457
+ // Recipient address state with dual-state system (auto + explicit user selection)
458
+ // The hook automatically manages priority: props > user selection > wallet/global
459
+ const {
460
+ setSelectedRecipientAddress,
461
+ effectiveRecipientAddress,
462
+ // resetRecipientAddress, // Not used yet, but available for future use
463
+ } = useRecipientAddressState({
464
+ recipientAddressFromProps,
465
+ walletAddress,
466
+ globalAddress,
467
+ });
468
+
469
+ const recipientProfile = useProfile({ address: effectiveRecipientAddress, fresh: true });
470
+ const recipientName = recipientProfile.data?.name;
471
+
457
472
  // Check token balance for crypto payments
458
473
  const { rawBalance, isLoading: isBalanceLoading } = useTokenBalanceDirect({
459
474
  token: selectedSrcToken,
@@ -515,7 +530,7 @@ function AnySpendInner({
515
530
  type: "swap",
516
531
  tradeType: isSrcInputDirty ? "EXACT_INPUT" : "EXACT_OUTPUT",
517
532
  amount: activeInputAmountInWei,
518
- recipientAddress,
533
+ recipientAddress: effectiveRecipientAddress,
519
534
  }
520
535
  : {
521
536
  srcChain: base.id,
@@ -525,7 +540,7 @@ function AnySpendInner({
525
540
  type: "swap",
526
541
  tradeType: "EXACT_INPUT",
527
542
  amount: srcAmountOnrampInWei,
528
- recipientAddress,
543
+ recipientAddress: effectiveRecipientAddress,
529
544
  onrampVendor: getOnrampVendor(selectedFiatPaymentMethod),
530
545
  },
531
546
  );
@@ -546,15 +561,15 @@ function AnySpendInner({
546
561
  setCustomRecipients(parsedRecipients);
547
562
 
548
563
  // If no wallet is connected and no recipient is selected, select the first recipient
549
- if (!globalAddress && !recipientAddress && parsedRecipients.length > 0) {
550
- setRecipientAddress(parsedRecipients[0].address);
564
+ if (!globalAddress && !effectiveRecipientAddress && parsedRecipients.length > 0) {
565
+ setSelectedRecipientAddress(parsedRecipients[0].address);
551
566
  }
552
567
  }
553
568
  } catch (err) {
554
569
  console.error("Error loading recipients from local storage:", err);
555
570
  }
556
571
  // Only run this effect once on mount
557
- }, [globalAddress, recipientAddress, customRecipients.length]);
572
+ }, [globalAddress, effectiveRecipientAddress, customRecipients.length, setSelectedRecipientAddress]);
558
573
 
559
574
  // Update dependent amount when relay price changes
560
575
  useEffect(() => {
@@ -666,7 +681,7 @@ function AnySpendInner({
666
681
 
667
682
  if (activeTab === "fiat") {
668
683
  // For fiat: check recipient first, then payment method
669
- if (!recipientAddress) return { text: "Select recipient", disable: false, error: false, loading: false };
684
+ if (!effectiveRecipientAddress) return { text: "Select recipient", disable: false, error: false, loading: false };
670
685
 
671
686
  // If no fiat payment method selected, show "Select payment method"
672
687
  if (selectedFiatPaymentMethod === FiatPaymentMethod.NONE) {
@@ -685,7 +700,7 @@ function AnySpendInner({
685
700
  }
686
701
 
687
702
  // Check recipient after payment method
688
- if (!recipientAddress) return { text: "Select recipient", disable: false, error: false, loading: false };
703
+ if (!effectiveRecipientAddress) return { text: "Select recipient", disable: false, error: false, loading: false };
689
704
 
690
705
  // If payment method selected, show appropriate action
691
706
  if (
@@ -704,7 +719,7 @@ function AnySpendInner({
704
719
  activeInputAmountInWei,
705
720
  isSameChainSameToken,
706
721
  isLoadingAnyspendQuote,
707
- recipientAddress,
722
+ effectiveRecipientAddress,
708
723
  isCreatingOrder,
709
724
  isCreatingOnrampOrder,
710
725
  anyspendQuote,
@@ -722,12 +737,12 @@ function AnySpendInner({
722
737
 
723
738
  if (activeTab === "fiat") {
724
739
  // For fiat: check recipient first
725
- if (!recipientAddress) {
740
+ if (!effectiveRecipientAddress) {
726
741
  navigateToPanel(PanelView.RECIPIENT_SELECTION, "forward");
727
742
  return;
728
743
  }
729
744
 
730
- invariant(recipientAddress, "Recipient address is not found");
745
+ invariant(effectiveRecipientAddress, "Recipient address is not found");
731
746
 
732
747
  // If no fiat payment method selected, show payment method selection
733
748
  if (selectedFiatPaymentMethod === FiatPaymentMethod.NONE) {
@@ -750,12 +765,12 @@ function AnySpendInner({
750
765
  }
751
766
 
752
767
  // Check recipient after payment method
753
- if (!recipientAddress) {
768
+ if (!effectiveRecipientAddress) {
754
769
  navigateToPanel(PanelView.RECIPIENT_SELECTION, "forward");
755
770
  return;
756
771
  }
757
772
 
758
- invariant(recipientAddress, "Recipient address is not found");
773
+ invariant(effectiveRecipientAddress, "Recipient address is not found");
759
774
 
760
775
  // If payment method is selected, create order with payment method info
761
776
  if (
@@ -788,7 +803,7 @@ function AnySpendInner({
788
803
  const handleCryptoSwap = async (method: CryptoPaymentMethodType) => {
789
804
  try {
790
805
  invariant(anyspendQuote, "Relay price is not found");
791
- invariant(recipientAddress, "Recipient address is not found");
806
+ invariant(effectiveRecipientAddress, "Recipient address is not found");
792
807
 
793
808
  // Debug: Check payment method values
794
809
  console.log("handleCryptoSwap - method parameter:", method);
@@ -797,7 +812,7 @@ function AnySpendInner({
797
812
  const srcAmountBigInt = parseUnits(srcAmount.replace(/,/g, ""), selectedSrcToken.decimals);
798
813
 
799
814
  createOrder({
800
- recipientAddress,
815
+ recipientAddress: effectiveRecipientAddress,
801
816
  orderType: "swap",
802
817
  srcChain: selectedSrcChainId,
803
818
  dstChain: isBuyMode ? destinationTokenChainId : selectedDstChainId,
@@ -819,7 +834,7 @@ function AnySpendInner({
819
834
  const handleFiatOrder = async (paymentMethod: FiatPaymentMethod) => {
820
835
  try {
821
836
  invariant(anyspendQuote, "Relay price is not found");
822
- invariant(recipientAddress, "Recipient address is not found");
837
+ invariant(effectiveRecipientAddress, "Recipient address is not found");
823
838
 
824
839
  if (!srcAmountOnRamp || parseFloat(srcAmountOnRamp) <= 0) {
825
840
  toast.error("Please enter a valid amount");
@@ -862,7 +877,7 @@ function AnySpendInner({
862
877
  };
863
878
 
864
879
  createOnrampOrder({
865
- recipientAddress,
880
+ recipientAddress: effectiveRecipientAddress,
866
881
  orderType: "swap",
867
882
  dstChain: getDstToken().chainId,
868
883
  dstToken: getDstToken(),
@@ -1059,7 +1074,7 @@ function AnySpendInner({
1059
1074
  setActivePanel(panelIndex);
1060
1075
  }
1061
1076
  }}
1062
- _recipientAddress={recipientAddress}
1077
+ _recipientAddress={effectiveRecipientAddress}
1063
1078
  destinationToken={selectedDstToken}
1064
1079
  destinationChainId={selectedDstChainId}
1065
1080
  destinationAmount={dstAmount}
@@ -1118,12 +1133,9 @@ function AnySpendInner({
1118
1133
  <CryptoReceiveSection
1119
1134
  isDepositMode={false}
1120
1135
  isBuyMode={isBuyMode}
1121
- selectedRecipientAddress={recipientAddress}
1136
+ effectiveRecipientAddress={effectiveRecipientAddress}
1122
1137
  recipientName={recipientName || undefined}
1123
1138
  onSelectRecipient={() => navigateToPanel(PanelView.RECIPIENT_SELECTION, "forward")}
1124
- setRecipientAddress={setRecipientAddress}
1125
- recipientAddressFromProps={recipientAddressFromProps}
1126
- globalAddress={globalAddress}
1127
1139
  dstAmount={dstAmount}
1128
1140
  dstToken={selectedDstToken}
1129
1141
  selectedDstChainId={selectedDstChainId}
@@ -1137,7 +1149,6 @@ function AnySpendInner({
1137
1149
  anyspendQuote={anyspendQuote}
1138
1150
  onShowPointsDetail={() => navigateToPanel(PanelView.POINTS_DETAIL, "forward")}
1139
1151
  onShowFeeDetail={() => navigateToPanel(PanelView.FEE_DETAIL, "forward")}
1140
- selectedCryptoPaymentMethod={effectiveCryptoPaymentMethod}
1141
1152
  />
1142
1153
  )}
1143
1154
  </div>
@@ -1165,7 +1176,7 @@ function AnySpendInner({
1165
1176
  </div>
1166
1177
  </ShinyButton>
1167
1178
 
1168
- {!hideTransactionHistoryButton && (globalAddress || recipientAddress) ? (
1179
+ {!hideTransactionHistoryButton && (globalAddress || effectiveRecipientAddress) ? (
1169
1180
  <Button
1170
1181
  variant="link"
1171
1182
  onClick={onClickHistory}
@@ -1182,7 +1193,7 @@ function AnySpendInner({
1182
1193
  <PanelOnrampPayment
1183
1194
  srcAmountOnRamp={srcAmountOnRamp}
1184
1195
  recipientName={recipientName || undefined}
1185
- recipientAddress={recipientAddress}
1196
+ recipientAddress={effectiveRecipientAddress}
1186
1197
  isBuyMode={isBuyMode}
1187
1198
  destinationTokenChainId={destinationTokenChainId}
1188
1199
  destinationTokenAddress={destinationTokenAddress}
@@ -1213,10 +1224,11 @@ function AnySpendInner({
1213
1224
 
1214
1225
  const recipientSelectionView = (
1215
1226
  <RecipientSelection
1216
- initialValue={recipientAddress || ""}
1227
+ initialValue={effectiveRecipientAddress || ""}
1217
1228
  onBack={navigateBack}
1218
1229
  onConfirm={address => {
1219
- setRecipientAddress(address);
1230
+ // User manually selected a recipient
1231
+ setSelectedRecipientAddress(address);
1220
1232
  navigateBack();
1221
1233
  }}
1222
1234
  />
@@ -46,6 +46,7 @@ import { base } from "viem/chains";
46
46
  import { useFeatureFlags } from "../contexts/FeatureFlagsContext";
47
47
  import { useAutoSetActiveWalletFromWagmi } from "../hooks/useAutoSetActiveWalletFromWagmi";
48
48
  import { useCryptoPaymentMethodState } from "../hooks/useCryptoPaymentMethodState";
49
+ import { useRecipientAddressState } from "../hooks/useRecipientAddressState";
49
50
  import { AnySpendFingerprintWrapper, getFingerprintConfig } from "./AnySpendFingerprintWrapper";
50
51
  import { CryptoPaymentMethod, CryptoPaymentMethodType } from "./common/CryptoPaymentMethod";
51
52
  import { FeeBreakDown } from "./common/FeeBreakDown";
@@ -261,11 +262,12 @@ function AnySpendCustomInner({
261
262
  // Get current user's wallet
262
263
  const currentWallet = useAccountWallet();
263
264
 
264
- // Add state for custom recipient
265
- const [customRecipientAddress, setCustomRecipientAddress] = useState<string | undefined>(recipientAddressProps);
266
-
267
- // Update recipient logic to use custom recipient
268
- const recipientAddress = customRecipientAddress || currentWallet.address;
265
+ // Recipient address state with dual-state system (auto + explicit user selection)
266
+ // The hook automatically manages priority: props > user selection > global address
267
+ const { setSelectedRecipientAddress, effectiveRecipientAddress: recipientAddress } = useRecipientAddressState({
268
+ recipientAddressFromProps: recipientAddressProps,
269
+ globalAddress: currentWallet.address,
270
+ });
269
271
 
270
272
  const [orderId, setOrderId] = useState<string | undefined>(loadOrder);
271
273
 
@@ -1230,12 +1232,13 @@ function AnySpendCustomInner({
1230
1232
  const recipientSelectionView = (
1231
1233
  <div className={cn("bg-as-surface-primary mx-auto w-[460px] max-w-full rounded-xl p-4")}>
1232
1234
  <RecipientSelection
1233
- initialValue={customRecipientAddress || ""}
1235
+ initialValue={recipientAddress || ""}
1234
1236
  title="Add recipient address or ENS"
1235
1237
  description="Send tokens to another address"
1236
1238
  onBack={() => setActivePanel(PanelView.CONFIRM_ORDER)}
1237
1239
  onConfirm={address => {
1238
- setCustomRecipientAddress(address);
1240
+ // User manually selected a recipient
1241
+ setSelectedRecipientAddress(address);
1239
1242
  setActivePanel(PanelView.CONFIRM_ORDER);
1240
1243
  }}
1241
1244
  />
@@ -364,7 +364,7 @@ function AnySpendCustomExactInInner({
364
364
  <CryptoReceiveSection
365
365
  isDepositMode={false}
366
366
  isBuyMode={true}
367
- selectedRecipientAddress={selectedRecipientOrDefault}
367
+ effectiveRecipientAddress={selectedRecipientOrDefault}
368
368
  recipientName={recipientName || undefined}
369
369
  onSelectRecipient={() => setActivePanel(PanelView.RECIPIENT_SELECTION)}
370
370
  dstAmount={dstAmount}
@@ -4,7 +4,7 @@ import { useAccountWallet } from "@b3dotfun/sdk/global-account/react";
4
4
  import { cn } from "@b3dotfun/sdk/shared/utils/cn";
5
5
  import { shortenAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
6
6
  import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
7
- import { WalletCoinbase, WalletMetamask, WalletPhantom, WalletRainbow, WalletWalletConnect } from "@web3icons/react";
7
+ import { WalletCoinbase, WalletMetamask, WalletRainbow, WalletWalletConnect } from "@web3icons/react";
8
8
  import { ChevronLeft, ChevronRightCircle, Wallet, X, ZapIcon } from "lucide-react";
9
9
  import { useState } from "react";
10
10
  import { createPortal } from "react-dom";
@@ -127,13 +127,6 @@ export function CryptoPaymentMethod({
127
127
  description: "Connect using WalletConnect protocol",
128
128
  connector: availableConnectors.find(c => c.name === "WalletConnect"),
129
129
  },
130
- {
131
- id: "phantom",
132
- name: "Phantom",
133
- icon: <WalletPhantom size={48} />,
134
- description: "Connect using Phantom wallet",
135
- connector: availableConnectors.find(c => c.name === "Phantom"),
136
- },
137
130
  ].filter(wallet => wallet.connector); // Only show wallets that have available connectors
138
131
 
139
132
  // Reset modal state when closing
@@ -4,11 +4,8 @@ import { shortenAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
4
4
  import { formatDisplayNumber } from "@b3dotfun/sdk/shared/utils/number";
5
5
  import { ChevronRight, Info } from "lucide-react";
6
6
  import { motion } from "motion/react";
7
- import { useEffect } from "react";
8
7
  import { components } from "../../../types/api";
9
8
  import { useFeatureFlags } from "../../contexts/FeatureFlagsContext";
10
- import { useConnectedWalletDisplay } from "../../hooks/useConnectedWalletDisplay";
11
- import { CryptoPaymentMethodType } from "./CryptoPaymentMethod";
12
9
  import { OrderTokenAmount } from "./OrderTokenAmount";
13
10
  import { PointsBadge } from "./PointsBadge";
14
11
 
@@ -16,12 +13,9 @@ interface CryptoReceiveSectionProps {
16
13
  isDepositMode?: boolean;
17
14
  isBuyMode?: boolean;
18
15
  // Recipient data
19
- selectedRecipientAddress?: string;
16
+ effectiveRecipientAddress?: string;
20
17
  recipientName?: string;
21
18
  onSelectRecipient: () => void;
22
- setRecipientAddress?: (address: string | undefined) => void;
23
- recipientAddressFromProps?: string;
24
- globalAddress?: string;
25
19
  // Token data
26
20
  dstAmount: string;
27
21
  dstToken: components["schemas"]["Token"];
@@ -40,19 +34,14 @@ interface CryptoReceiveSectionProps {
40
34
  onShowPointsDetail?: () => void;
41
35
  // Fee detail navigation
42
36
  onShowFeeDetail?: () => void;
43
- // Payment method for wallet tracking
44
- selectedCryptoPaymentMethod?: CryptoPaymentMethodType;
45
37
  }
46
38
 
47
39
  export function CryptoReceiveSection({
48
40
  isDepositMode = false,
49
41
  isBuyMode = false,
50
- selectedRecipientAddress,
42
+ effectiveRecipientAddress,
51
43
  recipientName,
52
44
  onSelectRecipient,
53
- setRecipientAddress,
54
- recipientAddressFromProps,
55
- globalAddress,
56
45
  dstAmount,
57
46
  dstToken,
58
47
  selectedDstChainId,
@@ -65,20 +54,9 @@ export function CryptoReceiveSection({
65
54
  dstTokenLogoURI,
66
55
  onShowPointsDetail,
67
56
  onShowFeeDetail,
68
- selectedCryptoPaymentMethod,
69
57
  }: CryptoReceiveSectionProps) {
70
58
  const featureFlags = useFeatureFlags();
71
59
 
72
- // Get wallet address based on selected payment method
73
- const { walletAddress } = useConnectedWalletDisplay(selectedCryptoPaymentMethod);
74
-
75
- // Set default recipient address when wallet changes
76
- useEffect(() => {
77
- if (setRecipientAddress) {
78
- setRecipientAddress(recipientAddressFromProps || walletAddress || globalAddress);
79
- }
80
- }, [recipientAddressFromProps, walletAddress, globalAddress, setRecipientAddress]);
81
-
82
60
  return (
83
61
  <motion.div
84
62
  initial={{ opacity: 0, y: 20, filter: "blur(10px)" }}
@@ -95,14 +73,14 @@ export function CryptoReceiveSection({
95
73
  </button>
96
74
  )}
97
75
  </div>
98
- {selectedRecipientAddress ? (
76
+ {effectiveRecipientAddress ? (
99
77
  <button
100
78
  className={cn("text-as-tertiarry flex h-7 items-center gap-2 rounded-lg")}
101
79
  onClick={onSelectRecipient}
102
80
  >
103
81
  <>
104
82
  <span className="text-as-tertiarry flex items-center gap-1 text-sm">
105
- {recipientName ? formatUsername(recipientName) : shortenAddress(selectedRecipientAddress || "")}
83
+ {recipientName ? formatUsername(recipientName) : shortenAddress(effectiveRecipientAddress || "")}
106
84
  </span>
107
85
  <ChevronRight className="h-4 w-4" />
108
86
  </>
@@ -131,7 +109,7 @@ export function CryptoReceiveSection({
131
109
  ) : (
132
110
  // Token selection for regular swap mode
133
111
  <OrderTokenAmount
134
- address={selectedRecipientAddress}
112
+ address={effectiveRecipientAddress}
135
113
  context="to"
136
114
  inputValue={dstAmount}
137
115
  onChangeInput={onChangeDstAmount || (() => {})}
@@ -8,6 +8,7 @@ export * from "./useCoinbaseOnrampOptions";
8
8
  export * from "./useConnectedUserProfile";
9
9
  export * from "./useGeoOnrampOptions";
10
10
  export * from "./useGetGeo";
11
+ export * from "./useRecipientAddressState";
11
12
  export * from "./useSigMint";
12
13
  export * from "./useStripeClientSecret";
13
14
  export * from "./useStripeSupport";
@@ -26,6 +26,7 @@ import { useAutoSelectCryptoPaymentMethod } from "./useAutoSelectCryptoPaymentMe
26
26
  import { useAutoSetActiveWalletFromWagmi } from "./useAutoSetActiveWalletFromWagmi";
27
27
  import { useConnectedWalletDisplay } from "./useConnectedWalletDisplay";
28
28
  import { useCryptoPaymentMethodState } from "./useCryptoPaymentMethodState";
29
+ import { useRecipientAddressState } from "./useRecipientAddressState";
29
30
 
30
31
  export enum PanelView {
31
32
  MAIN,
@@ -105,22 +106,22 @@ export function useAnyspendFlow({
105
106
 
106
107
  const [selectedFiatPaymentMethod, setSelectedFiatPaymentMethod] = useState<FiatPaymentMethod>(FiatPaymentMethod.NONE);
107
108
 
108
- // Recipient state
109
+ // Recipient state with dual-state system (auto + explicit user selection)
109
110
  const { address: globalAddress } = useAccountWallet();
110
111
  const { walletAddress } = useConnectedWalletDisplay(effectiveCryptoPaymentMethod);
111
- const [selectedRecipientAddress, setSelectedRecipientAddress] = useState<string | undefined>(recipientAddress);
112
- const recipientProfile = useProfile({ address: selectedRecipientAddress, fresh: true });
113
- const recipientName = recipientProfile.data?.name;
114
112
 
115
113
  // Auto-set active wallet from wagmi
116
114
  useAutoSetActiveWalletFromWagmi();
117
115
 
118
- // Set default recipient address when wallet changes
119
- useEffect(() => {
120
- if (!selectedRecipientAddress && globalAddress) {
121
- setSelectedRecipientAddress(globalAddress);
122
- }
123
- }, [selectedRecipientAddress, globalAddress]);
116
+ // Recipient address state - hook automatically manages priority: props > user selection > wallet/global
117
+ const { setSelectedRecipientAddress, effectiveRecipientAddress } = useRecipientAddressState({
118
+ recipientAddressFromProps: recipientAddress,
119
+ walletAddress,
120
+ globalAddress,
121
+ });
122
+
123
+ const recipientProfile = useProfile({ address: effectiveRecipientAddress, fresh: true });
124
+ const recipientName = recipientProfile.data?.name;
124
125
 
125
126
  // Check token balance for crypto payments
126
127
  const { rawBalance, isLoading: isBalanceLoading } = useTokenBalance({
@@ -208,7 +209,7 @@ export function useAnyspendFlow({
208
209
  dstTokenAddress: selectedDstToken.address,
209
210
  type: orderType,
210
211
  amount: activeInputAmountInWei,
211
- recipientAddress: selectedRecipientAddress,
212
+ recipientAddress: effectiveRecipientAddress,
212
213
  onrampVendor: paymentType === "fiat" ? getOnrampVendor(selectedFiatPaymentMethod) : undefined,
213
214
  });
214
215
 
@@ -333,7 +334,7 @@ export function useAnyspendFlow({
333
334
  selectedFiatPaymentMethod,
334
335
  setSelectedFiatPaymentMethod,
335
336
  // Recipient
336
- selectedRecipientAddress,
337
+ selectedRecipientAddress: effectiveRecipientAddress,
337
338
  setSelectedRecipientAddress,
338
339
  recipientName,
339
340
  globalAddress,
@@ -20,6 +20,8 @@ export function useConnectedWalletDisplay(
20
20
  const { connectedEOAWallet, connectedSmartWallet } = useAccountWallet();
21
21
  const { address: wagmiAddress, isConnected: wagmiWalletIsConnected } = useAccount();
22
22
 
23
+ const globalWalletAddress = connectedSmartWallet?.getAccount()?.address;
24
+
23
25
  // Helper function to check if two addresses are the same
24
26
  const isSameAddress = (addr1?: string, addr2?: string): boolean => {
25
27
  if (!addr1 || !addr2) return false;
@@ -28,7 +30,8 @@ export function useConnectedWalletDisplay(
28
30
 
29
31
  // Check if connectedEOAWallet and wagmi wallet represent the same wallet
30
32
  const connectedEOAAddress = connectedEOAWallet?.getAccount()?.address;
31
- const isWalletDuplicated = isSameAddress(connectedEOAAddress, wagmiAddress);
33
+ const isWalletDuplicated =
34
+ isSameAddress(connectedEOAAddress, wagmiAddress) || isSameAddress(globalWalletAddress, wagmiAddress);
32
35
 
33
36
  // Determine which wallet to show (prefer connectedEOAWallet if both exist and are the same)
34
37
  const shouldShowConnectedEOA = !!connectedEOAWallet;
@@ -0,0 +1,78 @@
1
+ import { useState } from "react";
2
+
3
+ interface UseRecipientAddressStateProps {
4
+ /** Fixed recipient address from props (highest priority) */
5
+ recipientAddressFromProps?: string;
6
+ /** Connected wallet address from payment method */
7
+ walletAddress?: string;
8
+ /** Global account address */
9
+ globalAddress?: string;
10
+ }
11
+
12
+ interface UseRecipientAddressStateResult {
13
+ /** User explicitly selected recipient address (undefined means no explicit selection) */
14
+ selectedRecipientAddress: string | undefined;
15
+ /** Function to update the user-selected recipient address */
16
+ setSelectedRecipientAddress: (address: string | undefined) => void;
17
+ /** Effective recipient address (follows priority: props > user selection > wallet/global) */
18
+ effectiveRecipientAddress: string | undefined;
19
+ /** Reset recipient address state */
20
+ resetRecipientAddress: () => void;
21
+ }
22
+
23
+ /**
24
+ * Custom hook to manage recipient address state with automatic priority handling:
25
+ *
26
+ * **Priority System:**
27
+ * 1. `recipientAddressFromProps` - Fixed recipient from component props (highest priority)
28
+ * 2. `selectedRecipientAddress` - User's explicit manual selection
29
+ * 3. `walletAddress` or `globalAddress` - Auto-selected fallback
30
+ *
31
+ * **Key Features:**
32
+ * - Automatically manages recipient address based on priority
33
+ * - Preserves user's manual selections
34
+ * - Updates automatically when wallet/global address changes (if no manual selection)
35
+ * - Derived value approach - no useEffect needed, no stale state bugs
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * const {
40
+ * selectedRecipientAddress,
41
+ * setSelectedRecipientAddress,
42
+ * effectiveRecipientAddress,
43
+ * resetRecipientAddress
44
+ * } = useRecipientAddressState({
45
+ * recipientAddressFromProps,
46
+ * walletAddress,
47
+ * globalAddress,
48
+ * });
49
+ *
50
+ * // Use effectiveRecipientAddress for display and operations
51
+ * // Use setSelectedRecipientAddress when user explicitly selects
52
+ * // Call resetRecipientAddress when switching tabs or going back
53
+ * ```
54
+ */
55
+ export function useRecipientAddressState({
56
+ recipientAddressFromProps,
57
+ walletAddress,
58
+ globalAddress,
59
+ }: UseRecipientAddressStateProps = {}): UseRecipientAddressStateResult {
60
+ // selectedRecipientAddress: explicitly selected by user (undefined means no explicit selection)
61
+ const [selectedRecipientAddress, setSelectedRecipientAddress] = useState<string | undefined>(undefined);
62
+
63
+ // The effective recipient address, derived on each render, respecting priority.
64
+ const effectiveRecipientAddress =
65
+ recipientAddressFromProps || selectedRecipientAddress || walletAddress || globalAddress;
66
+
67
+ // Helper function to reset user's manual selection.
68
+ const resetRecipientAddress = () => {
69
+ setSelectedRecipientAddress(undefined);
70
+ };
71
+
72
+ return {
73
+ selectedRecipientAddress,
74
+ setSelectedRecipientAddress,
75
+ effectiveRecipientAddress,
76
+ resetRecipientAddress,
77
+ };
78
+ }