@b3dotfun/sdk 0.0.10 → 0.0.11

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 (46) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +1 -1
  2. package/dist/cjs/anyspend/react/components/common/PaymentStripeWeb2.js +62 -25
  3. package/dist/cjs/anyspend/react/components/webview/WebviewOnrampPayment.js +29 -6
  4. package/dist/cjs/anyspend/react/providers/AnyspendProvider.d.ts +1 -0
  5. package/dist/cjs/anyspend/react/providers/AnyspendProvider.js +3 -1
  6. package/dist/cjs/anyspend/react/providers/StripeRedirectHandler.d.ts +8 -0
  7. package/dist/cjs/anyspend/react/providers/StripeRedirectHandler.js +41 -0
  8. package/dist/cjs/global-account/react/components/B3DynamicModal.js +2 -2
  9. package/dist/cjs/global-account/react/components/ui/dialog.js +1 -1
  10. package/dist/cjs/global-account/react/hooks/index.d.ts +1 -0
  11. package/dist/cjs/global-account/react/hooks/index.js +4 -1
  12. package/dist/cjs/global-account/react/hooks/useAccountWallet.js +0 -4
  13. package/dist/cjs/global-account/react/hooks/useProfile.d.ts +38 -0
  14. package/dist/cjs/global-account/react/hooks/useProfile.js +72 -0
  15. package/dist/esm/anyspend/react/components/AnySpendCustom.js +1 -1
  16. package/dist/esm/anyspend/react/components/common/PaymentStripeWeb2.js +64 -27
  17. package/dist/esm/anyspend/react/components/webview/WebviewOnrampPayment.js +30 -7
  18. package/dist/esm/anyspend/react/providers/AnyspendProvider.d.ts +1 -0
  19. package/dist/esm/anyspend/react/providers/AnyspendProvider.js +4 -2
  20. package/dist/esm/anyspend/react/providers/StripeRedirectHandler.d.ts +8 -0
  21. package/dist/esm/anyspend/react/providers/StripeRedirectHandler.js +38 -0
  22. package/dist/esm/global-account/react/components/B3DynamicModal.js +2 -2
  23. package/dist/esm/global-account/react/components/ui/dialog.js +1 -1
  24. package/dist/esm/global-account/react/hooks/index.d.ts +1 -0
  25. package/dist/esm/global-account/react/hooks/index.js +1 -0
  26. package/dist/esm/global-account/react/hooks/useAccountWallet.js +0 -4
  27. package/dist/esm/global-account/react/hooks/useProfile.d.ts +38 -0
  28. package/dist/esm/global-account/react/hooks/useProfile.js +68 -0
  29. package/dist/styles/index.css +1 -1
  30. package/dist/types/anyspend/react/providers/AnyspendProvider.d.ts +1 -0
  31. package/dist/types/anyspend/react/providers/StripeRedirectHandler.d.ts +8 -0
  32. package/dist/types/global-account/react/hooks/index.d.ts +1 -0
  33. package/dist/types/global-account/react/hooks/useProfile.d.ts +38 -0
  34. package/package.json +24 -23
  35. package/src/anyspend/react/components/AnySpendCustom.tsx +1 -1
  36. package/src/anyspend/react/components/common/PaymentStripeWeb2.tsx +86 -35
  37. package/src/anyspend/react/components/webview/WebviewOnrampPayment.tsx +39 -7
  38. package/src/anyspend/react/providers/AnyspendProvider.tsx +6 -1
  39. package/src/anyspend/react/providers/StripeRedirectHandler.tsx +45 -0
  40. package/src/global-account/react/components/B3DynamicModal.tsx +2 -2
  41. package/src/global-account/react/components/ui/dialog.tsx +3 -4
  42. package/src/global-account/react/components/ui/drawer.tsx +0 -2
  43. package/src/global-account/react/hooks/index.ts +7 -0
  44. package/src/global-account/react/hooks/useAccountWallet.tsx +0 -5
  45. package/src/global-account/react/hooks/useProfile.ts +141 -0
  46. package/src/styles/index.css +1 -1
@@ -1,15 +1,15 @@
1
1
  import { USDC_BASE } from "@b3dotfun/sdk/anyspend";
2
- import { useStripeClientSecret } from "@b3dotfun/sdk/anyspend/react";
3
2
  import { STRIPE_CONFIG } from "@b3dotfun/sdk/anyspend/constants";
4
- import { ShinyButton, useB3 } from "@b3dotfun/sdk/global-account/react";
3
+ import { useStripeClientSecret } from "@b3dotfun/sdk/anyspend/react";
4
+ import { components } from "@b3dotfun/sdk/anyspend/types/api";
5
+ import { ShinyButton, useB3, useModalStore } from "@b3dotfun/sdk/global-account/react";
5
6
  import { formatStripeAmount } from "@b3dotfun/sdk/shared/utils/payment.utils";
6
- import { Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
7
- import { loadStripe } from "@stripe/stripe-js";
7
+ import { AddressElement, Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
8
+ import { loadStripe, PaymentIntentResult, StripePaymentElementOptions } from "@stripe/stripe-js";
8
9
  import { HelpCircle, Info, X } from "lucide-react";
9
10
  import { useEffect, useState } from "react";
10
11
  import HowItWorks from "./HowItWorks";
11
12
  import PaymentMethodIcons from "./PaymentMethodIcons";
12
- import { components } from "@b3dotfun/sdk/anyspend/types/api";
13
13
 
14
14
  const stripePromise = loadStripe(STRIPE_CONFIG.publishableKey);
15
15
 
@@ -90,12 +90,14 @@ function StripePaymentForm({
90
90
  }) {
91
91
  const stripe = useStripe();
92
92
  const elements = useElements();
93
+ const setB3ModalOpen = useModalStore(state => state.setB3ModalOpen);
93
94
 
94
95
  const [loading, setLoading] = useState<boolean>(false);
95
96
  const [message, setMessage] = useState<string | null>(null);
96
97
  const [amount, setAmount] = useState<string | null>(null);
97
98
  const [stripeReady, setStripeReady] = useState<boolean>(false);
98
99
  const [showHowItWorks, setShowHowItWorks] = useState<boolean>(false);
100
+ const [showAddressElement, setShowAddressElement] = useState<boolean>(false);
99
101
 
100
102
  useEffect(() => {
101
103
  if (stripe && elements) {
@@ -122,6 +124,13 @@ function StripePaymentForm({
122
124
  fetchPaymentIntent();
123
125
  }, [clientSecret, stripe]);
124
126
 
127
+ // Handle payment element changes
128
+ const handlePaymentElementChange = (event: any) => {
129
+ // Show address element only for card payments
130
+ console.log("@@stripe-web2-payment:payment-element-change:", JSON.stringify(event, null, 2));
131
+ setShowAddressElement(event.value.type === "card");
132
+ };
133
+
125
134
  const handleSubmit = async (e: React.FormEvent) => {
126
135
  e.preventDefault();
127
136
 
@@ -135,30 +144,38 @@ function StripePaymentForm({
135
144
 
136
145
  try {
137
146
  console.log("@@stripe-web2-payment:confirming-payment:", JSON.stringify({ orderId: order.id }, null, 2));
138
- const { error, paymentIntent } = await stripe.confirmPayment({
147
+
148
+ const result = (await stripe.confirmPayment({
139
149
  elements,
140
- redirect: "if_required",
141
- });
142
-
143
- if (error) {
144
- console.error("@@stripe-web2-payment:error:", JSON.stringify(error, null, 2));
145
- setMessage(error.message || "An unexpected error occurred.");
146
- } else if (paymentIntent && paymentIntent.status === "succeeded") {
147
- console.log(
148
- "@@stripe-web2-payment:success:",
149
- JSON.stringify({ orderId: order.id, paymentIntentId: paymentIntent.id }, null, 2),
150
- );
151
- // Payment succeeded without redirect - handle success in the modal
152
- setMessage(null);
153
-
154
- // Add waitingForDeposit=true to query params
155
- const currentUrl = new URL(window.location.href);
156
- currentUrl.searchParams.set("waitingForDeposit", "true");
157
- window.history.replaceState(null, "", currentUrl.toString());
158
-
159
- // Call the success callback if provided
160
- onPaymentSuccess?.(paymentIntent);
150
+ confirmParams: {
151
+ return_url: `${window.location.origin}/?orderId=${order.id}&waitingForDeposit=true&fromStripe=true`,
152
+ },
153
+ })) as PaymentIntentResult;
154
+
155
+ if (result.error) {
156
+ // This point will only be reached if there is an immediate error.
157
+ // Otherwise, the customer will be redirected to the `return_url`.
158
+ console.error("@@stripe-web2-payment:error:", JSON.stringify(result.error, null, 2));
159
+ setMessage(result.error.message || "An unexpected error occurred.");
160
+ return;
161
161
  }
162
+
163
+ // At this point TypeScript knows result.paymentIntent exists and error is undefined
164
+ console.log(
165
+ "@@stripe-web2-payment:success:",
166
+ JSON.stringify({ orderId: order.id, paymentIntentId: result.paymentIntent.id }, null, 2),
167
+ );
168
+
169
+ // Payment succeeded without redirect - handle success in the modal
170
+ setMessage(null);
171
+
172
+ // Add waitingForDeposit=true to query params
173
+ const currentUrl = new URL(window.location.href);
174
+ currentUrl.searchParams.set("waitingForDeposit", "true");
175
+ window.history.replaceState(null, "", currentUrl.toString());
176
+
177
+ // Call the success callback if provided
178
+ onPaymentSuccess?.(result.paymentIntent);
162
179
  } catch (error) {
163
180
  console.error("@@stripe-web2-payment:confirmation-error:", JSON.stringify(error, null, 2));
164
181
  setMessage("Payment failed. Please try again.");
@@ -167,18 +184,32 @@ function StripePaymentForm({
167
184
  }
168
185
  };
169
186
 
187
+ // Handle 3DS redirect
188
+ useEffect(() => {
189
+ // Check if we're returning from Stripe
190
+ const url = new URL(window.location.href);
191
+ const fromStripe = url.searchParams.get("fromStripe");
192
+ const paymentIntent = url.searchParams.get("payment_intent");
193
+
194
+ console.log("@@stripe-web2-payment:fromStripe:", fromStripe);
195
+ console.log("@@stripe-web2-payment:paymentIntent:", paymentIntent);
196
+
197
+ if (fromStripe && paymentIntent) {
198
+ // Close the modal as we're returning from 3DS
199
+ setB3ModalOpen(true);
200
+
201
+ // Clean up URL params
202
+ url.searchParams.delete("fromStripe");
203
+ window.history.replaceState({}, "", url.toString());
204
+ }
205
+ }, [setB3ModalOpen]);
206
+
170
207
  if (!stripeReady) {
171
208
  return <StripeLoadingState />;
172
209
  }
173
210
 
174
- const stripeElementOptions = {
211
+ const stripeElementOptions: StripePaymentElementOptions = {
175
212
  layout: "tabs" as const,
176
- defaultValues: {
177
- billingDetails: {
178
- name: "",
179
- email: "",
180
- },
181
- },
182
213
  fields: {
183
214
  billingDetails: "auto" as const,
184
215
  },
@@ -282,7 +313,27 @@ function StripePaymentForm({
282
313
  {/* Simplified Payment Form */}
283
314
  <div className="bg-as-on-surface-1 w-full rounded-2xl p-6">
284
315
  <div className="text-as-primary mb-4 text-lg font-semibold">Payment Details</div>
285
- <PaymentElement options={stripeElementOptions} />
316
+ <PaymentElement onChange={handlePaymentElementChange} options={stripeElementOptions} />
317
+ {showAddressElement && (
318
+ <AddressElement
319
+ options={{
320
+ mode: "billing",
321
+ fields: {
322
+ phone: "always",
323
+ },
324
+ // More granular control
325
+ display: {
326
+ name: "split", // or 'split' for first/last name separately
327
+ },
328
+ // Validation
329
+ validation: {
330
+ phone: {
331
+ required: "auto", // or 'always', 'never'
332
+ },
333
+ },
334
+ }}
335
+ />
336
+ )}
286
337
  </div>
287
338
 
288
339
  {/* Error Message */}
@@ -3,7 +3,7 @@ import { useAnyspendCreateOnrampOrder, useGeoOnrampOptions, useStripeClientSecre
3
3
  import { components } from "@b3dotfun/sdk/anyspend/types/api";
4
4
  import { GetQuoteResponse } from "@b3dotfun/sdk/anyspend/types/api_req_res";
5
5
  import centerTruncate from "@b3dotfun/sdk/shared/utils/centerTruncate";
6
- import { Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
6
+ import { AddressElement, Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
7
7
  import { loadStripe } from "@stripe/stripe-js";
8
8
  import { motion } from "framer-motion";
9
9
  import { Loader2 } from "lucide-react";
@@ -35,6 +35,7 @@ function StripePaymentForm({
35
35
  const elements = useElements();
36
36
  const [isProcessing, setIsProcessing] = useState(false);
37
37
  const [error, setError] = useState<string | null>(null);
38
+ const [showAddressElement, setShowAddressElement] = useState<boolean>(false);
38
39
 
39
40
  const handleSubmit = async (e: React.FormEvent) => {
40
41
  e.preventDefault();
@@ -67,13 +68,21 @@ function StripePaymentForm({
67
68
  }
68
69
  };
69
70
 
71
+ // Handle payment element changes
72
+ const handlePaymentElementChange = (event: any) => {
73
+ // Show address element only for card payments
74
+ console.log("@@stripe-web2-payment:payment-element-change:", JSON.stringify(event, null, 2));
75
+ setShowAddressElement(event.value.type === "card");
76
+ };
77
+
70
78
  const stripeElementOptions = {
71
79
  layout: "tabs" as const,
72
- defaultValues: {
73
- billingDetails: {
74
- name: "",
75
- email: "",
76
- },
80
+ fields: {
81
+ billingDetails: "auto" as const,
82
+ },
83
+ wallets: {
84
+ applePay: "auto" as const,
85
+ googlePay: "auto" as const,
77
86
  },
78
87
  };
79
88
 
@@ -82,7 +91,30 @@ function StripePaymentForm({
82
91
  <div className="overflow-hidden rounded-xl bg-white">
83
92
  <div className="px-6 py-4">
84
93
  <h2 className="mb-4 text-lg font-semibold">Payment Details</h2>
85
- <PaymentElement options={stripeElementOptions} />
94
+ <PaymentElement options={stripeElementOptions} onChange={handlePaymentElementChange} />
95
+
96
+ {showAddressElement && (
97
+ <div className="mt-4">
98
+ <AddressElement
99
+ options={{
100
+ mode: "billing",
101
+ fields: {
102
+ phone: "always",
103
+ },
104
+ // More granular control
105
+ display: {
106
+ name: "split", // or 'split' for first/last name separately
107
+ },
108
+ // Validation
109
+ validation: {
110
+ phone: {
111
+ required: "auto", // or 'always', 'never'
112
+ },
113
+ },
114
+ }}
115
+ />
116
+ </div>
117
+ )}
86
118
 
87
119
  {error && (
88
120
  <div className="mt-4 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-600">{error}</div>
@@ -3,6 +3,7 @@
3
3
  import { TooltipProvider } from "@b3dotfun/sdk/global-account/react";
4
4
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5
5
  import { ReactNode, useState } from "react";
6
+ import { StripeRedirectHandler } from "./StripeRedirectHandler";
6
7
 
7
8
  interface AnyspendProviderProps {
8
9
  children: ReactNode;
@@ -27,6 +28,7 @@ const defaultQueryClientConfig = {
27
28
  * - Optimized for performance with React.memo
28
29
  * - Safe to use at the application root
29
30
  * - Configures sensible defaults for query caching
31
+ * - Handles Stripe payment redirects and modal state
30
32
  *
31
33
  * @example
32
34
  * ```tsx
@@ -44,7 +46,10 @@ export const AnyspendProvider = function AnyspendProvider({ children }: Anyspend
44
46
 
45
47
  return (
46
48
  <QueryClientProvider client={queryClient}>
47
- <TooltipProvider>{children}</TooltipProvider>
49
+ <TooltipProvider>
50
+ <StripeRedirectHandler />
51
+ {children}
52
+ </TooltipProvider>
48
53
  </QueryClientProvider>
49
54
  );
50
55
  };
@@ -0,0 +1,45 @@
1
+ "use client";
2
+
3
+ import { useModalStore } from "@b3dotfun/sdk/global-account/react";
4
+ import { useEffect } from "react";
5
+
6
+ /**
7
+ * Handles Stripe payment redirects by showing the order details modal
8
+ * when returning from Stripe's payment flow.
9
+ *
10
+ * This component should be mounted inside AnyspendProvider to ensure
11
+ * proper modal state management.
12
+ */
13
+ export function StripeRedirectHandler() {
14
+ const setB3ModalOpen = useModalStore(state => state.setB3ModalOpen);
15
+ const setB3ModalContentType = useModalStore(state => state.setB3ModalContentType);
16
+
17
+ useEffect(() => {
18
+ const url = new URL(window.location.href);
19
+ const fromStripe = url.searchParams.get("fromStripe");
20
+ const paymentIntent = url.searchParams.get("payment_intent");
21
+ const orderId = url.searchParams.get("orderId");
22
+
23
+ console.log("@@stripe-web2-payment:fromStripe:", fromStripe);
24
+ console.log("@@stripe-web2-payment:paymentIntent:", paymentIntent);
25
+ console.log("@@stripe-web2-payment:orderId:", orderId);
26
+
27
+ if (fromStripe && paymentIntent && orderId) {
28
+ // Re-open the modal with the order details
29
+ setB3ModalOpen(true);
30
+ setB3ModalContentType({
31
+ type: "anyspendOrderDetails",
32
+ orderId,
33
+ });
34
+
35
+ // Clean up URL params
36
+ url.searchParams.delete("fromStripe");
37
+ url.searchParams.delete("payment_intent");
38
+ url.searchParams.delete("payment_intent_client_secret");
39
+ url.searchParams.delete("redirect_status");
40
+ window.history.replaceState({}, "", url.toString());
41
+ }
42
+ }, [setB3ModalOpen, setB3ModalContentType]);
43
+
44
+ return null;
45
+ }
@@ -41,7 +41,7 @@ export function B3DynamicModal() {
41
41
  "anySpendBondKit",
42
42
  ].find(type => contentType?.type === type)
43
43
  ) {
44
- contentClass += " max-h-[90dvh] overflow-y-auto no-scrollbar w-full";
44
+ contentClass += " w-full";
45
45
  }
46
46
 
47
47
  if (
@@ -115,7 +115,7 @@ export function B3DynamicModal() {
115
115
  <ModalContent className={contentClass} hideCloseButton={hideCloseButton}>
116
116
  <ModalTitle className="sr-only hidden">{contentType?.type || "Modal"}</ModalTitle>
117
117
  <ModalDescription className="sr-only hidden">{contentType?.type || "Modal Body"}</ModalDescription>
118
- <div className="overflow-auto">
118
+ <div className="no-scrollbar max-h-[90dvh] overflow-auto sm:max-h-[80dvh]">
119
119
  {history.length > 0 && contentType?.showBackButton && (
120
120
  <button
121
121
  onClick={navigateBack}
@@ -50,13 +50,12 @@ const DialogContent: React.ForwardRefExoticComponent<DialogContentProps & React.
50
50
  <DialogPrimitive.Content
51
51
  ref={ref}
52
52
  className={cn(
53
- "bg-b3-react-background fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg !outline-none",
53
+ "bg-b3-react-background fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border p-6 shadow-lg !outline-none",
54
54
  "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 duration-500",
55
55
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
56
56
  "data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
57
- "data-[state=closed]:rotate-x-10 data-[state=open]:rotate-x-0",
58
57
  "[perspective:1200px] [transform-style:preserve-3d] sm:rounded-xl",
59
- "ease-spring transition-all",
58
+ "transition-all ease-out",
60
59
  className,
61
60
  )}
62
61
  {...props}
@@ -65,7 +64,7 @@ const DialogContent: React.ForwardRefExoticComponent<DialogContentProps & React.
65
64
  {!hideCloseButton && (
66
65
  <DialogPrimitive.Close
67
66
  className={cn(
68
- "data-[state=open]:bg-accent data-[state=open]:text-b3-react-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none dark:data-[state=open]:bg-gray-800 dark:data-[state=open]:text-gray-400",
67
+ "data-[state=open]:bg-b3-react-background data-[state=open]:text-b3-react-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none dark:data-[state=open]:bg-gray-800 dark:data-[state=open]:text-gray-400",
69
68
  closeBtnClassName,
70
69
  )}
71
70
  >
@@ -40,9 +40,7 @@ const DrawerContent = React.forwardRef<
40
40
  )}
41
41
  {...props}
42
42
  >
43
- {/* <div className="bg-b3-react-muted mx-auto mt-4 h-2 w-[100px] rounded-full" /> */}
44
43
  {children}
45
- {/* <div className="bg-b3-react-muted mx-auto mt-4 h-2 w-[100px] rounded-full" /> */}
46
44
  </DrawerPrimitive.Content>
47
45
  </DrawerPortal>
48
46
  );
@@ -20,6 +20,13 @@ export { useMediaQuery } from "./useMediaQuery";
20
20
  export { useNativeBalance, useNativeBalanceFromRPC } from "./useNativeBalance";
21
21
  export { useOnchainName } from "./useOnchainName";
22
22
  export { useOneBalance } from "./useOneBalance";
23
+ export {
24
+ useProfile,
25
+ useProfilePreference,
26
+ type Profile,
27
+ type CombinedProfile,
28
+ type PreferenceRequestBody,
29
+ } from "./useProfile";
23
30
  export { useQueryB3 } from "./useQueryB3";
24
31
  export { useQueryBSMNT } from "./useQueryBSMNT";
25
32
  export { useRemoveSessionKey } from "./useRemoveSessionKey";
@@ -101,10 +101,5 @@ export function useAccountWallet(): {
101
101
  ],
102
102
  );
103
103
 
104
- useEffect(() => {
105
- console.log(`account`, account);
106
- console.log(`useAccountWallet`, res);
107
- }, [account, res]);
108
-
109
104
  return res;
110
105
  }
@@ -0,0 +1,141 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+
3
+ // TypeScript interface for profile data
4
+ export interface Profile {
5
+ type: string;
6
+ address?: string;
7
+ name?: string;
8
+ avatar?: string | null;
9
+ bio?: string | null;
10
+ displayName?: string | null;
11
+ }
12
+
13
+ export interface CombinedProfile {
14
+ name: string | null;
15
+ address: string | null;
16
+ avatar: string | null;
17
+ bio: string | null;
18
+ displayName: string | null;
19
+ profiles: Profile[];
20
+ }
21
+
22
+ // TypeScript interface for preference request body
23
+ export interface PreferenceRequestBody {
24
+ key: string;
25
+ preferredType: string;
26
+ signature: string;
27
+ signer: string;
28
+ timestamp: number;
29
+ }
30
+
31
+ const PROFILES_API_URL = "https://profiles.b3.fun";
32
+
33
+ async function fetchProfile({
34
+ address,
35
+ name,
36
+ fresh = false,
37
+ }: {
38
+ address?: string;
39
+ name?: string;
40
+ fresh?: boolean;
41
+ }): Promise<CombinedProfile> {
42
+ if (!address && !name) {
43
+ throw new Error("Either address or name must be provided");
44
+ }
45
+
46
+ const params = new URLSearchParams();
47
+ if (address) params.append("address", address);
48
+ if (name) params.append("name", name);
49
+ if (fresh) params.append("fresh", "true");
50
+
51
+ const response = await fetch(`${PROFILES_API_URL}?${params.toString()}`);
52
+
53
+ if (!response.ok) {
54
+ throw new Error(`Failed to fetch profile: ${response.statusText}`);
55
+ }
56
+
57
+ return response.json();
58
+ }
59
+
60
+ async function setProfilePreference({
61
+ key,
62
+ preferredType,
63
+ signature,
64
+ signer,
65
+ timestamp,
66
+ }: PreferenceRequestBody): Promise<{ success: boolean }> {
67
+ const response = await fetch(`${PROFILES_API_URL}/preference`, {
68
+ method: "POST",
69
+ headers: {
70
+ "Content-Type": "application/json",
71
+ },
72
+ body: JSON.stringify({
73
+ key,
74
+ preferredType,
75
+ signature,
76
+ signer,
77
+ timestamp,
78
+ }),
79
+ });
80
+
81
+ if (!response.ok) {
82
+ throw new Error(`Failed to set preference: ${response.statusText}`);
83
+ }
84
+
85
+ return response.json();
86
+ }
87
+
88
+ export function useProfile(
89
+ {
90
+ address,
91
+ name,
92
+ fresh = false,
93
+ }: {
94
+ address?: string;
95
+ name?: string;
96
+ fresh?: boolean;
97
+ },
98
+ options?: {
99
+ enabled?: boolean;
100
+ refetchInterval?: number;
101
+ staleTime?: number;
102
+ },
103
+ ) {
104
+ return useQuery({
105
+ queryKey: ["profile", address || name, fresh],
106
+ queryFn: () => fetchProfile({ address, name, fresh }),
107
+ enabled: (options?.enabled ?? true) && (!!address || !!name),
108
+ refetchInterval: options?.refetchInterval,
109
+ staleTime: options?.staleTime ?? 5 * 60 * 1000, // 5 minutes default
110
+ });
111
+ }
112
+
113
+ export function useProfilePreference() {
114
+ const setPreference = async (
115
+ key: string,
116
+ preferredType: string,
117
+ signerAddress: string,
118
+ signMessage: (message: string) => Promise<string>,
119
+ ) => {
120
+ const timestamp = Math.floor(Date.now() / 1000);
121
+ const message = `SetProfilePreference:${key}:${preferredType}:${timestamp}`;
122
+
123
+ try {
124
+ const signature = await signMessage(message);
125
+
126
+ return setProfilePreference({
127
+ key,
128
+ preferredType,
129
+ signature,
130
+ signer: signerAddress,
131
+ timestamp,
132
+ });
133
+ } catch (error) {
134
+ throw new Error(`Failed to set profile preference: ${error}`);
135
+ }
136
+ };
137
+
138
+ return { setPreference };
139
+ }
140
+
141
+ export default useProfile;
@@ -169,7 +169,7 @@ html[data-theme="dark"] .b3-root,
169
169
  .b3-modal {
170
170
  @apply bg-b3-react-background text-b3-react-foreground rounded-2xl border;
171
171
  border: 1px solid var(--border);
172
- overflow: auto;
172
+ overflow: hidden;
173
173
  outline: none;
174
174
  }
175
175