@b3dotfun/sdk 0.0.40-alpha.10 → 0.0.40-alpha.12

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 (47) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.d.ts +2 -1
  2. package/dist/cjs/anyspend/react/components/AnySpend.js +6 -2
  3. package/dist/cjs/anyspend/react/components/AnyspendDepositHype.js +5 -2
  4. package/dist/cjs/anyspend/react/components/common/ConnectWalletPayment.js +5 -3
  5. package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +73 -3
  6. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.d.ts +2 -1
  7. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +3 -3
  8. package/dist/cjs/anyspend/react/components/common/OrderDetails.js +8 -8
  9. package/dist/cjs/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  10. package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +2 -2
  11. package/dist/cjs/anyspend/react/components/common/PointsDetailPanel.d.ts +6 -0
  12. package/dist/cjs/anyspend/react/components/common/PointsDetailPanel.js +14 -0
  13. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.d.ts +2 -1
  14. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +1 -0
  15. package/dist/cjs/global-account/react/hooks/useUnifiedChainSwitchAndExecute.js +22 -20
  16. package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -1
  17. package/dist/esm/anyspend/react/components/AnySpend.js +6 -2
  18. package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +5 -2
  19. package/dist/esm/anyspend/react/components/common/ConnectWalletPayment.js +6 -4
  20. package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +73 -3
  21. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +2 -1
  22. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +3 -3
  23. package/dist/esm/anyspend/react/components/common/OrderDetails.js +8 -8
  24. package/dist/esm/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  25. package/dist/esm/anyspend/react/components/common/PanelOnramp.js +2 -2
  26. package/dist/esm/anyspend/react/components/common/PointsDetailPanel.d.ts +6 -0
  27. package/dist/esm/anyspend/react/components/common/PointsDetailPanel.js +8 -0
  28. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.d.ts +2 -1
  29. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +1 -0
  30. package/dist/esm/global-account/react/hooks/useUnifiedChainSwitchAndExecute.js +22 -20
  31. package/dist/styles/index.css +1 -1
  32. package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -1
  33. package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +2 -1
  34. package/dist/types/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  35. package/dist/types/anyspend/react/components/common/PointsDetailPanel.d.ts +6 -0
  36. package/dist/types/anyspend/react/hooks/useAnyspendFlow.d.ts +2 -1
  37. package/package.json +1 -1
  38. package/src/anyspend/react/components/AnySpend.tsx +14 -0
  39. package/src/anyspend/react/components/AnyspendDepositHype.tsx +13 -0
  40. package/src/anyspend/react/components/common/ConnectWalletPayment.tsx +7 -4
  41. package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +124 -4
  42. package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +12 -4
  43. package/src/anyspend/react/components/common/OrderDetails.tsx +8 -9
  44. package/src/anyspend/react/components/common/PanelOnramp.tsx +10 -3
  45. package/src/anyspend/react/components/common/PointsDetailPanel.tsx +55 -0
  46. package/src/anyspend/react/hooks/useAnyspendFlow.ts +1 -0
  47. package/src/global-account/react/hooks/useUnifiedChainSwitchAndExecute.ts +23 -21
@@ -14,6 +14,7 @@ import { CryptoReceiveSection } from "./common/CryptoReceiveSection";
14
14
  import { ErrorSection } from "./common/ErrorSection";
15
15
  import { FiatPaymentMethod, FiatPaymentMethodComponent } from "./common/FiatPaymentMethod";
16
16
  import { OrderDetails } from "./common/OrderDetails";
17
+ import { PointsDetailPanel } from "./common/PointsDetailPanel";
17
18
  import { RecipientSelection } from "./common/RecipientSelection";
18
19
 
19
20
  import { ArrowDown } from "lucide-react";
@@ -224,6 +225,7 @@ function AnySpendDepositHypeInner({
224
225
  fiatPaymentMethodIndex={PanelView.FIAT_PAYMENT_METHOD}
225
226
  recipientSelectionPanelIndex={PanelView.RECIPIENT_SELECTION}
226
227
  anyspendQuote={anyspendQuote}
228
+ onShowPointsDetail={() => setActivePanel(PanelView.POINTS_DETAIL)}
227
229
  />
228
230
  </motion.div>
229
231
  )}
@@ -264,6 +266,7 @@ function AnySpendDepositHypeInner({
264
266
  setSrcAmount(value);
265
267
  }}
266
268
  anyspendQuote={anyspendQuote}
269
+ onShowPointsDetail={() => setActivePanel(PanelView.POINTS_DETAIL)}
267
270
  />
268
271
  )}
269
272
  </div>
@@ -449,6 +452,13 @@ function AnySpendDepositHypeInner({
449
452
  />
450
453
  );
451
454
 
455
+ const pointsDetailView = (
456
+ <PointsDetailPanel
457
+ pointsAmount={anyspendQuote?.data?.pointsAmount || 0}
458
+ onBack={() => setActivePanel(PanelView.MAIN)}
459
+ />
460
+ );
461
+
452
462
  // If showing token selection, render with panel transitions
453
463
  return (
454
464
  <StyleRoot>
@@ -498,6 +508,9 @@ function AnySpendDepositHypeInner({
498
508
  <div key="loading-view" className={cn(mode === "page" && "p-6")}>
499
509
  {loadingView}
500
510
  </div>,
511
+ <div key="points-detail-view" className={cn(mode === "page" && "p-6")}>
512
+ {pointsDetailView}
513
+ </div>,
501
514
  ]}
502
515
  </TransitionPanel>
503
516
  </div>
@@ -2,12 +2,11 @@
2
2
 
3
3
  import { RELAY_SOLANA_MAINNET_CHAIN_ID } from "@b3dotfun/sdk/anyspend";
4
4
  import { components } from "@b3dotfun/sdk/anyspend/types/api";
5
- import { ShinyButton, useProfile } from "@b3dotfun/sdk/global-account/react";
5
+ import { ShinyButton, useAccountWallet, useProfile } from "@b3dotfun/sdk/global-account/react";
6
6
  import centerTruncate from "@b3dotfun/sdk/shared/utils/centerTruncate";
7
7
  import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
8
8
  import { motion } from "framer-motion";
9
9
  import { ChevronRight, Loader2 } from "lucide-react";
10
- import { useAccount } from "wagmi";
11
10
  import { CryptoPaymentMethodType } from "./CryptoPaymentMethod";
12
11
  import { OrderDetailsCollapsible } from "./OrderDetailsCollapsible";
13
12
  import { PaymentMethodSwitch } from "./PaymentMethodSwitch";
@@ -41,7 +40,11 @@ export default function ConnectWalletPayment({
41
40
  }: ConnectWalletPaymentProps) {
42
41
  const profile = useProfile({ address: order.recipientAddress });
43
42
  const recipientName = profile.data?.name?.replace(/\.b3\.fun/g, "");
44
- const { address: connectedAddress } = useAccount();
43
+ const { connectedEOAWallet, connectedSmartWallet } = useAccountWallet();
44
+ const connectedEvmAddress =
45
+ cryptoPaymentMethod === CryptoPaymentMethodType.GLOBAL_WALLET
46
+ ? connectedSmartWallet?.getAccount()?.address
47
+ : connectedEOAWallet?.getAccount()?.address;
45
48
 
46
49
  const srcToken = order.metadata.srcToken;
47
50
  const dstToken = order.metadata.dstToken;
@@ -97,7 +100,7 @@ export default function ConnectWalletPayment({
97
100
  Connected to:{" "}
98
101
  {order.srcChain === RELAY_SOLANA_MAINNET_CHAIN_ID && phantomWalletAddress
99
102
  ? centerTruncate(phantomWalletAddress, 6)
100
- : centerTruncate(connectedAddress || "")}
103
+ : centerTruncate(connectedEvmAddress || "")}
101
104
  </span>
102
105
 
103
106
  <PaymentMethodSwitch currentMethod={cryptoPaymentMethod} onMethodChange={onPaymentMethodChange} />
@@ -3,11 +3,14 @@
3
3
  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
+ import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
6
7
  import { WalletCoinbase, WalletMetamask, WalletPhantom, WalletRainbow, WalletWalletConnect } from "@web3icons/react";
7
8
  import { ChevronLeft, ChevronRightCircle, Wallet, X, ZapIcon } from "lucide-react";
8
9
  import { useState } from "react";
9
10
  import { createPortal } from "react-dom";
10
11
  import { toast } from "sonner";
12
+ import { useSetActiveWallet, useWalletInfo } from "thirdweb/react";
13
+ import { WalletId, createWallet } from "thirdweb/wallets";
11
14
  import { useAccount, useConnect, useDisconnect, useWalletClient } from "wagmi";
12
15
 
13
16
  export enum CryptoPaymentMethodType {
@@ -38,13 +41,69 @@ export function CryptoPaymentMethod({
38
41
  onBack,
39
42
  onSelectPaymentMethod,
40
43
  }: CryptoPaymentMethodProps) {
41
- const { wallet: globalWallet, address: globalAddress } = useAccountWallet();
42
- const { address, isConnected, connector } = useAccount();
44
+ const {
45
+ wallet: globalWallet,
46
+ connectedEOAWallet: connectedEOAWallet,
47
+ connectedSmartWallet: connectedSmartWallet,
48
+ } = useAccountWallet();
49
+ const { connector, address, isConnected: wagmiWalletIsConnected } = useAccount();
43
50
  const { connect, connectors, isPending } = useConnect();
44
51
  const { disconnect } = useDisconnect();
45
52
  const { data: walletClient } = useWalletClient();
46
53
  const [showWalletModal, setShowWalletModal] = useState(false);
47
54
 
55
+ const setActiveWallet = useSetActiveWallet();
56
+ const { data: eoaWalletInfo } = useWalletInfo(connectedEOAWallet?.id);
57
+
58
+ const isConnected = !!connectedEOAWallet;
59
+ const globalAddress = connectedSmartWallet?.getAccount()?.address;
60
+
61
+ // Helper function to check if two addresses are the same
62
+ const isSameAddress = (addr1?: string, addr2?: string): boolean => {
63
+ if (!addr1 || !addr2) return false;
64
+ return addr1.toLowerCase() === addr2.toLowerCase();
65
+ };
66
+
67
+ // Check if connectedEOAWallet and wagmi wallet represent the same wallet
68
+ const connectedEOAAddress = connectedEOAWallet?.getAccount()?.address;
69
+ const wagmiAddress = address;
70
+ const isWalletDuplicated = isSameAddress(connectedEOAAddress, wagmiAddress);
71
+
72
+ // Determine which wallet to show (prefer connectedEOAWallet if both exist and are the same)
73
+ const shouldShowConnectedEOA = !!connectedEOAWallet;
74
+ const shouldShowWagmiWallet = wagmiWalletIsConnected && (!isWalletDuplicated || !connectedEOAWallet);
75
+
76
+ // Map wagmi connector names to thirdweb wallet IDs
77
+ const getThirdwebWalletId = (connectorName: string): WalletId | null => {
78
+ const walletMap: Record<string, WalletId> = {
79
+ MetaMask: "io.metamask",
80
+ "Coinbase Wallet": "com.coinbase.wallet",
81
+ Rainbow: "me.rainbow",
82
+ WalletConnect: "walletConnect",
83
+ Phantom: "app.phantom",
84
+ };
85
+ return walletMap[connectorName] || null;
86
+ };
87
+
88
+ // Create thirdweb wallet from wagmi connector
89
+ const createThirdwebWalletFromConnector = async (connectorName: string) => {
90
+ const walletId = getThirdwebWalletId(connectorName);
91
+ if (!walletId) {
92
+ console.warn(`No thirdweb wallet ID found for connector: ${connectorName}`);
93
+ return null;
94
+ }
95
+
96
+ try {
97
+ const thirdwebWallet = createWallet(walletId);
98
+ // Connect the wallet to sync with the existing wagmi connection
99
+ await thirdwebWallet.connect({ client });
100
+ return thirdwebWallet;
101
+ } catch (error) {
102
+ console.error(`Failed to create thirdweb wallet for ${connectorName}:`, error);
103
+ return null;
104
+ }
105
+ };
106
+
48
107
  // Define available wallet connectors
49
108
  const availableConnectors = connectors.filter(connector =>
50
109
  ["MetaMask", "WalletConnect", "Coinbase Wallet", "Rainbow", "Phantom"].includes(connector.name),
@@ -202,16 +261,77 @@ export function CryptoPaymentMethod({
202
261
  </button>
203
262
 
204
263
  {/* Installed Wallets Section */}
205
- {(isConnected || globalAddress) && (
264
+ {(shouldShowConnectedEOA || shouldShowWagmiWallet || globalAddress) && (
206
265
  <div className="installed-wallets">
207
266
  <h3 className="text-as-primary/80 mb-3 text-sm font-medium">Connected wallets</h3>
208
267
  <div className="space-y-2">
209
268
  {/* Current Connected Wallet */}
210
- {isConnected && (
269
+
270
+ {shouldShowConnectedEOA && (
211
271
  <button
212
272
  onClick={() => {
213
273
  setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
214
274
  onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
275
+ setActiveWallet(connectedEOAWallet);
276
+ toast.success(`Selected ${eoaWalletInfo?.name || connector?.name || "wallet"}`);
277
+ }}
278
+ className={cn(
279
+ "crypto-payment-method-connect-wallet w-full rounded-xl border p-4 text-left transition-all hover:shadow-md",
280
+ selectedPaymentMethod === CryptoPaymentMethodType.CONNECT_WALLET
281
+ ? "connected-wallet border-as-brand bg-as-brand/5"
282
+ : "border-as-border-secondary bg-as-surface-primary hover:border-as-secondary/80",
283
+ )}
284
+ >
285
+ <div className="flex items-center justify-between">
286
+ <div className="flex items-center gap-3">
287
+ <div className="wallet-icon flex h-10 w-10 items-center justify-center rounded-full bg-blue-100">
288
+ <Wallet className="h-5 w-5 text-blue-600" />
289
+ </div>
290
+ <div className="flex flex-col">
291
+ <span className="text-as-primary font-semibold">
292
+ {eoaWalletInfo?.name || connector?.name || "Connected Wallet"}
293
+ </span>
294
+ <span className="text-as-primary/60 text-sm">
295
+ {shortenAddress(connectedEOAWallet?.getAccount()?.address || "")}
296
+ </span>
297
+ </div>
298
+ </div>
299
+ <div className="flex items-center gap-2">
300
+ {selectedPaymentMethod === CryptoPaymentMethodType.CONNECT_WALLET && (
301
+ <div className="h-2 w-2 rounded-full bg-green-500"></div>
302
+ )}
303
+ <button
304
+ onClick={e => {
305
+ e.stopPropagation();
306
+ disconnect();
307
+ toast.success("Wallet disconnected");
308
+ if (selectedPaymentMethod === CryptoPaymentMethodType.CONNECT_WALLET) {
309
+ setSelectedPaymentMethod(CryptoPaymentMethodType.NONE);
310
+ }
311
+ }}
312
+ className="text-as-primary/60 hover:text-as-primary/80 rounded-lg p-1.5 transition-colors"
313
+ >
314
+ <X className="h-4 w-4" />
315
+ </button>
316
+ </div>
317
+ </div>
318
+ </button>
319
+ )}
320
+
321
+ {shouldShowWagmiWallet && (
322
+ <button
323
+ onClick={async () => {
324
+ setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
325
+ onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
326
+
327
+ // Create thirdweb wallet from wagmi connector
328
+ if (connector?.name) {
329
+ const thirdwebWallet = await createThirdwebWalletFromConnector(connector.name);
330
+ if (thirdwebWallet) {
331
+ setActiveWallet(thirdwebWallet);
332
+ }
333
+ }
334
+
215
335
  toast.success(`Selected ${connector?.name || "wallet"}`);
216
336
  }}
217
337
  className={cn(
@@ -28,6 +28,8 @@ interface CryptoReceiveSectionProps {
28
28
  // custom dst token data
29
29
  dstTokenSymbol?: string;
30
30
  dstTokenLogoURI?: string;
31
+ // Points navigation
32
+ onShowPointsDetail?: () => void;
31
33
  }
32
34
 
33
35
  export function CryptoReceiveSection({
@@ -45,6 +47,7 @@ export function CryptoReceiveSection({
45
47
  anyspendQuote,
46
48
  dstTokenSymbol,
47
49
  dstTokenLogoURI,
50
+ onShowPointsDetail,
48
51
  }: CryptoReceiveSectionProps) {
49
52
  const featureFlags = useFeatureFlags();
50
53
 
@@ -103,7 +106,7 @@ export function CryptoReceiveSection({
103
106
  setToken={setSelectedDstToken || (() => {})}
104
107
  />
105
108
  )}
106
- <div className="text-as-primary/50 flex h-5 items-center justify-between text-sm">
109
+ <div className="text-as-primary/50 flex h-5 items-center justify-start gap-2 text-sm">
107
110
  <div className="flex items-center gap-2">
108
111
  {formatDisplayNumber(anyspendQuote?.data?.currencyOut?.amountUsd, {
109
112
  style: "currency",
@@ -161,9 +164,14 @@ export function CryptoReceiveSection({
161
164
  })()}
162
165
  </div>
163
166
  {featureFlags.showPoints && anyspendQuote?.data?.pointsAmount && anyspendQuote.data.pointsAmount > 0 && (
164
- <div key={`points-${anyspendQuote.data.pointsAmount}`} className="flex items-center gap-1">
165
- <span className="text-as-brand font-medium">+{anyspendQuote.data.pointsAmount.toLocaleString()} pts</span>
166
- </div>
167
+ <button
168
+ key={`points-${anyspendQuote.data.pointsAmount}`}
169
+ className="bg-as-brand hover:scale-102 active:scale-98 active:scale-98 relative flex cursor-pointer items-center gap-1 rounded-lg px-2 py-1 transition-all"
170
+ onClick={() => onShowPointsDetail?.()}
171
+ >
172
+ <div className="pointer-events-none absolute inset-0 h-full w-full rounded-lg border border-white/10 border-t-white/20 bg-gradient-to-b from-white/10 to-white/0" />
173
+ <span className="text-xs text-white">+{anyspendQuote.data.pointsAmount.toLocaleString()} pts</span>
174
+ </button>
167
175
  )}
168
176
  </div>
169
177
  </motion.div>
@@ -223,10 +223,9 @@ export const OrderDetails = memo(function OrderDetails({
223
223
  // Read crypto payment method from URL parameters
224
224
  const cryptoPaymentMethodFromUrl = searchParams.get("cryptoPaymentMethod") as CryptoPaymentMethodType | null;
225
225
  const effectiveCryptoPaymentMethod =
226
- cryptoPaymentMethod || cryptoPaymentMethodFromUrl || CryptoPaymentMethodType.NONE;
227
-
228
- // Use selectedCryptoPaymentMethod for OrderStatus if provided, otherwise fall back to effective method
229
- const orderStatusPaymentMethod = selectedCryptoPaymentMethod || effectiveCryptoPaymentMethod;
226
+ selectedCryptoPaymentMethod !== CryptoPaymentMethodType.NONE
227
+ ? selectedCryptoPaymentMethod
228
+ : cryptoPaymentMethod || cryptoPaymentMethodFromUrl || CryptoPaymentMethodType.NONE;
230
229
 
231
230
  const setB3ModalOpen = useModalStore((state: any) => state.setB3ModalOpen);
232
231
 
@@ -575,7 +574,7 @@ export const OrderDetails = memo(function OrderDetails({
575
574
  if (refundTxs.length > 0) {
576
575
  return (
577
576
  <>
578
- <OrderStatus order={order} selectedCryptoPaymentMethod={orderStatusPaymentMethod} />
577
+ <OrderStatus order={order} selectedCryptoPaymentMethod={effectiveCryptoPaymentMethod} />
579
578
  <OrderDetailsCollapsible
580
579
  order={order}
581
580
  dstToken={dstToken}
@@ -653,7 +652,7 @@ export const OrderDetails = memo(function OrderDetails({
653
652
  if (executeTx) {
654
653
  return (
655
654
  <>
656
- <OrderStatus order={order} selectedCryptoPaymentMethod={orderStatusPaymentMethod} />
655
+ <OrderStatus order={order} selectedCryptoPaymentMethod={effectiveCryptoPaymentMethod} />
657
656
  <OrderDetailsCollapsible
658
657
  order={order}
659
658
  dstToken={dstToken}
@@ -780,7 +779,7 @@ export const OrderDetails = memo(function OrderDetails({
780
779
  if (relayTxs.length > 0 && relayTxs.every(tx => tx.status === "success")) {
781
780
  return (
782
781
  <>
783
- <OrderStatus order={order} selectedCryptoPaymentMethod={orderStatusPaymentMethod} />
782
+ <OrderStatus order={order} selectedCryptoPaymentMethod={effectiveCryptoPaymentMethod} />
784
783
  <OrderDetailsCollapsible
785
784
  order={order}
786
785
  dstToken={dstToken}
@@ -909,7 +908,7 @@ export const OrderDetails = memo(function OrderDetails({
909
908
  if (depositTxs?.length || waitingForDeposit) {
910
909
  return (
911
910
  <>
912
- <OrderStatus order={order} selectedCryptoPaymentMethod={orderStatusPaymentMethod} />
911
+ <OrderStatus order={order} selectedCryptoPaymentMethod={effectiveCryptoPaymentMethod} />
913
912
  <OrderDetailsCollapsible
914
913
  order={order}
915
914
  dstToken={dstToken}
@@ -1008,7 +1007,7 @@ export const OrderDetails = memo(function OrderDetails({
1008
1007
 
1009
1008
  return (
1010
1009
  <>
1011
- <OrderStatus order={order} selectedCryptoPaymentMethod={orderStatusPaymentMethod} />
1010
+ <OrderStatus order={order} selectedCryptoPaymentMethod={effectiveCryptoPaymentMethod} />
1012
1011
  {statusDisplay === "processing" && (
1013
1012
  <>
1014
1013
  {order.onrampMetadata ? (
@@ -28,6 +28,7 @@ export function PanelOnramp({
28
28
  dstTokenSymbol,
29
29
  hideDstToken = false,
30
30
  anyspendQuote,
31
+ onShowPointsDetail,
31
32
  }: {
32
33
  srcAmountOnRamp: string;
33
34
  setSrcAmountOnRamp: (amount: string) => void;
@@ -44,6 +45,7 @@ export function PanelOnramp({
44
45
  dstTokenSymbol?: string;
45
46
  hideDstToken?: boolean;
46
47
  anyspendQuote?: GetQuoteResponse;
48
+ onShowPointsDetail?: () => void;
47
49
  }) {
48
50
  const featureFlags = useFeatureFlags();
49
51
  // Get geo-based onramp options to access fee information
@@ -258,9 +260,14 @@ export function PanelOnramp({
258
260
  })()}
259
261
  </span>
260
262
  {featureFlags.showPoints && anyspendQuote?.data?.pointsAmount && anyspendQuote.data.pointsAmount > 0 && (
261
- <span key={`points-${anyspendQuote.data.pointsAmount}`} className="text-as-brand text-sm font-medium">
262
- +{anyspendQuote.data.pointsAmount.toLocaleString()} pts
263
- </span>
263
+ <button
264
+ key={`points-${anyspendQuote.data.pointsAmount}`}
265
+ className="bg-as-brand hover:scale-102 active:scale-98 relative flex cursor-pointer items-center gap-1 rounded-lg px-2 py-1 transition-all"
266
+ onClick={() => onShowPointsDetail?.()}
267
+ >
268
+ <div className="pointer-events-none absolute inset-0 h-full w-full rounded-lg border border-white/10 border-t-white/20 bg-gradient-to-b from-white/10 to-white/0" />
269
+ <span className="text-xs text-white">+{anyspendQuote.data.pointsAmount.toLocaleString()} pts</span>
270
+ </button>
264
271
  )}
265
272
  </div>
266
273
  <span className="text-as-primary font-semibold">
@@ -0,0 +1,55 @@
1
+ import { Button, ShinyButton } from "@b3dotfun/sdk/global-account/react";
2
+ import { cn } from "@b3dotfun/sdk/shared/utils/cn";
3
+ import { ArrowDown } from "lucide-react";
4
+ import Link from "next/link";
5
+
6
+ interface PointsDetailPanelProps {
7
+ pointsAmount?: number;
8
+ onBack: () => void;
9
+ }
10
+
11
+ export function PointsDetailPanel({ pointsAmount = 0, onBack }: PointsDetailPanelProps) {
12
+ return (
13
+ <div className="mx-auto flex w-[460px] max-w-full flex-col items-center gap-4">
14
+ <div className="flex w-full items-center justify-between">
15
+ <Button
16
+ variant="ghost"
17
+ onClick={onBack}
18
+ className="text-as-primary/70 hover:text-as-primary flex items-center gap-2"
19
+ >
20
+ <ArrowDown className="h-4 w-4 rotate-90" />
21
+ Back
22
+ </Button>
23
+ </div>
24
+
25
+ <div className="flex flex-col items-center gap-4 text-center">
26
+ <h3 className="text-as-primary text-xl font-bold">Earn Points with Every Swap</h3>
27
+ <p className="text-as-primary/70 text-balance text-sm leading-relaxed">
28
+ You'll earn <span className="text-as-brand font-semibold">+{pointsAmount.toLocaleString()} points</span>{" "}
29
+ towards the{" "}
30
+ <Link href="https://anyspend.com/points" target="_blank" className="text-as-brand underline">
31
+ next AnySpend airdrop
32
+ </Link>{" "}
33
+ when you complete this transaction.
34
+ </p>
35
+ <div className="bg-as-surface-primary border-as-border-secondary mt-2 w-full rounded-lg border p-4 text-left">
36
+ <h4 className="text-as-primary mb-2 font-semibold">How it works:</h4>
37
+ <ul className="text-as-primary/70 space-y-1 text-sm">
38
+ <li>• Points are earned based on transaction volume</li>
39
+ <li>• Higher volume = more points</li>
40
+ <li>• Points contribute to future airdrops</li>
41
+ <li>• Keep swapping to maximize your rewards</li>
42
+ </ul>
43
+ </div>
44
+ <ShinyButton
45
+ accentColor={"hsl(var(--as-brand))"}
46
+ onClick={onBack}
47
+ className={cn("as-main-button !bg-as-brand relative w-full")}
48
+ textClassName={cn("text-white")}
49
+ >
50
+ Back to Swap
51
+ </ShinyButton>
52
+ </div>
53
+ </div>
54
+ );
55
+ }
@@ -31,6 +31,7 @@ export enum PanelView {
31
31
  RECIPIENT_SELECTION,
32
32
  ORDER_DETAILS,
33
33
  LOADING,
34
+ POINTS_DETAIL,
34
35
  }
35
36
 
36
37
  interface UseAnyspendFlowProps {
@@ -6,10 +6,11 @@ import invariant from "invariant";
6
6
  import { useCallback, useState } from "react";
7
7
  import { toast } from "sonner";
8
8
  import { prepareTransaction, sendTransaction as twSendTransaction } from "thirdweb";
9
+ import { useActiveWallet } from "thirdweb/react";
10
+ import { isAddress } from "viem";
9
11
  import { useSwitchChain, useWalletClient } from "wagmi";
10
12
  import { useB3 } from "../components";
11
13
  import { useAccountWallet } from "./useAccountWallet";
12
- import { isAddress } from "viem";
13
14
 
14
15
  export interface UnifiedTransactionParams {
15
16
  to: string;
@@ -29,46 +30,53 @@ export function useUnifiedChainSwitchAndExecute() {
29
30
  const { data: walletClient } = useWalletClient();
30
31
  const { switchChainAsync } = useSwitchChain();
31
32
  const [isSwitchingOrExecuting, setIsSwitchingOrExecuting] = useState(false);
33
+ const activeWallet = useActiveWallet();
32
34
 
33
- const { isActiveSmartWallet, isActiveEOAWallet } = useAccountWallet();
35
+ const { isActiveSmartWallet, isActiveEOAWallet, connectedEOAWallet } = useAccountWallet();
34
36
  const { account: aaAccount } = useB3();
35
37
 
36
38
  // Handle EOA wallet chain switch and execute transaction
37
39
  const handleEOASwitchChainAndSendTransaction = useCallback(
38
40
  async (targetChainId: number, params: UnifiedTransactionParams): Promise<string | undefined> => {
39
- if (!walletClient) {
41
+ if (!connectedEOAWallet) {
40
42
  toast.error("Please connect your wallet");
41
43
  return;
42
44
  }
43
45
 
44
- const providerId = walletClient.chain.id;
45
- const onCorrectChain = providerId === targetChainId;
46
+ // Get target chain configuration once
47
+ const targetChain = supportedChains.find(chain => chain.id === targetChainId);
48
+ if (!targetChain) {
49
+ toast.error(`Chain ${targetChainId} is not supported`);
50
+ return;
51
+ }
52
+
53
+ const currentChainId = activeWallet?.getChain()?.id;
54
+ const onCorrectChain = currentChainId === targetChainId;
46
55
 
47
56
  // Helper function to execute the transaction
48
57
  const executeTransaction = async (): Promise<string> => {
49
- const signer = walletClient.account;
58
+ const signer = activeWallet?.getAccount();
50
59
  if (!signer) {
51
60
  throw new Error("No account connected");
52
61
  }
53
62
 
54
- // Get the target chain configuration instead of using potentially stale walletClient.chain
55
- const targetChain = supportedChains.find(chain => chain.id === targetChainId);
56
- if (!targetChain) {
57
- throw new Error(`Chain ${targetChainId} is not supported`);
63
+ // Coinbase Smart Wallet specific chain switching (different behavior from other wallets)
64
+ const walletChain = connectedEOAWallet.getChain();
65
+ if (walletChain?.id !== targetChainId) {
66
+ activeWallet?.switchChain(getThirdwebChain(targetChainId));
58
67
  }
59
68
 
60
69
  invariant(isAddress(params.to), "params.to is not a valid address");
61
70
 
62
- const hash = await walletClient.sendTransaction({
63
- account: signer,
64
- chain: targetChain,
71
+ const result = await signer.sendTransaction({
72
+ chainId: targetChainId,
65
73
  to: params.to,
66
74
  data: params.data as `0x${string}`,
67
75
  value: params.value,
68
76
  });
69
77
 
70
- toast.success(`Transaction sent: ${hash.slice(0, 10)}...`);
71
- return hash;
78
+ toast.success(`Transaction sent: ${result.transactionHash.slice(0, 10)}...`);
79
+ return result.transactionHash;
72
80
  };
73
81
 
74
82
  try {
@@ -80,12 +88,6 @@ export function useUnifiedChainSwitchAndExecute() {
80
88
 
81
89
  const switchingToastId = toast.info(`Switching to ${getChainName(targetChainId)}…`);
82
90
 
83
- const targetChain = supportedChains.find(chain => chain.id === targetChainId);
84
- if (!targetChain) {
85
- toast.error(`Chain ${targetChainId} is not supported`);
86
- return;
87
- }
88
-
89
91
  const blockExplorerUrl = targetChain.blockExplorers?.default.url;
90
92
  invariant(blockExplorerUrl, "Block explorer URL is required");
91
93
  const nativeCurrency = getNativeToken(targetChainId);