@b3dotfun/sdk 0.0.79 → 0.0.80-alpha.1

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 (53) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.js +23 -10
  2. package/dist/cjs/anyspend/react/components/AnySpendCollectorClubPurchase.js +9 -2
  3. package/dist/cjs/anyspend/react/components/AnySpendCustom.d.ts +2 -0
  4. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +75 -19
  5. package/dist/cjs/anyspend/react/components/AnySpendNFT.js +6 -9
  6. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +1 -3
  7. package/dist/cjs/anyspend/react/components/common/FiatPaymentMethod.d.ts +6 -1
  8. package/dist/cjs/anyspend/react/components/common/FiatPaymentMethod.js +40 -13
  9. package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +13 -7
  10. package/dist/cjs/anyspend/react/components/common/PanelOnrampPayment.js +5 -3
  11. package/dist/cjs/anyspend/react/hooks/useGeoOnrampOptions.d.ts +2 -8
  12. package/dist/cjs/anyspend/react/hooks/useGeoOnrampOptions.js +4 -2
  13. package/dist/cjs/anyspend/react/hooks/useStripeSupport.d.ts +1 -0
  14. package/dist/cjs/anyspend/react/hooks/useStripeSupport.js +1 -0
  15. package/dist/cjs/global-account/react/components/IPFSMediaRenderer/IPFSMediaRenderer.js +5 -3
  16. package/dist/cjs/shared/utils/ipfs.d.ts +3 -3
  17. package/dist/cjs/shared/utils/ipfs.js +8 -8
  18. package/dist/esm/anyspend/react/components/AnySpend.js +23 -10
  19. package/dist/esm/anyspend/react/components/AnySpendCollectorClubPurchase.js +9 -2
  20. package/dist/esm/anyspend/react/components/AnySpendCustom.d.ts +2 -0
  21. package/dist/esm/anyspend/react/components/AnySpendCustom.js +76 -20
  22. package/dist/esm/anyspend/react/components/AnySpendNFT.js +7 -10
  23. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +1 -3
  24. package/dist/esm/anyspend/react/components/common/FiatPaymentMethod.d.ts +6 -1
  25. package/dist/esm/anyspend/react/components/common/FiatPaymentMethod.js +39 -12
  26. package/dist/esm/anyspend/react/components/common/PanelOnramp.js +14 -8
  27. package/dist/esm/anyspend/react/components/common/PanelOnrampPayment.js +5 -3
  28. package/dist/esm/anyspend/react/hooks/useGeoOnrampOptions.d.ts +2 -8
  29. package/dist/esm/anyspend/react/hooks/useGeoOnrampOptions.js +4 -2
  30. package/dist/esm/anyspend/react/hooks/useStripeSupport.d.ts +1 -0
  31. package/dist/esm/anyspend/react/hooks/useStripeSupport.js +1 -0
  32. package/dist/esm/global-account/react/components/IPFSMediaRenderer/IPFSMediaRenderer.js +5 -3
  33. package/dist/esm/shared/utils/ipfs.d.ts +3 -3
  34. package/dist/esm/shared/utils/ipfs.js +8 -8
  35. package/dist/styles/index.css +1 -1
  36. package/dist/types/anyspend/react/components/AnySpendCustom.d.ts +2 -0
  37. package/dist/types/anyspend/react/components/common/FiatPaymentMethod.d.ts +6 -1
  38. package/dist/types/anyspend/react/hooks/useGeoOnrampOptions.d.ts +2 -8
  39. package/dist/types/anyspend/react/hooks/useStripeSupport.d.ts +1 -0
  40. package/dist/types/shared/utils/ipfs.d.ts +3 -3
  41. package/package.json +1 -1
  42. package/src/anyspend/react/components/AnySpend.tsx +24 -10
  43. package/src/anyspend/react/components/AnySpendCollectorClubPurchase.tsx +9 -1
  44. package/src/anyspend/react/components/AnySpendCustom.tsx +113 -59
  45. package/src/anyspend/react/components/AnySpendNFT.tsx +49 -48
  46. package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +1 -4
  47. package/src/anyspend/react/components/common/FiatPaymentMethod.tsx +53 -21
  48. package/src/anyspend/react/components/common/PanelOnramp.tsx +36 -40
  49. package/src/anyspend/react/components/common/PanelOnrampPayment.tsx +53 -16
  50. package/src/anyspend/react/hooks/useGeoOnrampOptions.ts +5 -2
  51. package/src/anyspend/react/hooks/useStripeSupport.ts +1 -0
  52. package/src/global-account/react/components/IPFSMediaRenderer/IPFSMediaRenderer.tsx +5 -7
  53. package/src/shared/utils/ipfs.ts +8 -8
@@ -510,7 +510,8 @@ function AnySpendInner({
510
510
  });
511
511
 
512
512
  // Get geo-based onramp options for fiat payments
513
- const { geoData, coinbaseAvailablePaymentMethods, stripeWeb2Support } = useGeoOnrampOptions(srcAmountOnRamp);
513
+ const { geoData, coinbaseAvailablePaymentMethods, stripeOnrampSupport, stripeWeb2Support } =
514
+ useGeoOnrampOptions(srcAmountOnRamp);
514
515
 
515
516
  // Helper function to map payment method to onramp vendor
516
517
  const getOnrampVendor = (paymentMethod: FiatPaymentMethod): "coinbase" | "stripe" | "stripe-web2" | undefined => {
@@ -518,11 +519,11 @@ function AnySpendInner({
518
519
  case FiatPaymentMethod.COINBASE_PAY:
519
520
  return "coinbase";
520
521
  case FiatPaymentMethod.STRIPE:
521
- // Determine if it's stripe onramp or stripe-web2 based on support
522
- if (stripeWeb2Support?.isSupport) {
523
- return "stripe-web2";
524
- }
525
- return undefined;
522
+ // Stripe redirect flow (one-click URL)
523
+ return stripeOnrampSupport ? "stripe" : undefined;
524
+ case FiatPaymentMethod.STRIPE_WEB2:
525
+ // Stripe embedded payment form
526
+ return stripeWeb2Support?.isSupport ? "stripe-web2" : undefined;
526
527
  default:
527
528
  return undefined;
528
529
  }
@@ -683,7 +684,10 @@ function AnySpendInner({
683
684
 
684
685
  // Determine button state and text
685
686
  const btnInfo: { text: string; disable: boolean; error: boolean; loading: boolean } = useMemo(() => {
686
- if (activeInputAmountInWei === "0") return { text: "Enter an amount", disable: true, error: false, loading: false };
687
+ // For fiat tab, check srcAmountOnRamp; for crypto tab, check activeInputAmountInWei
688
+ const hasAmount =
689
+ activeTab === "fiat" ? srcAmountOnRamp && parseFloat(srcAmountOnRamp) > 0 : activeInputAmountInWei !== "0";
690
+ if (!hasAmount) return { text: "Enter an amount", disable: true, error: false, loading: false };
687
691
  if (isSameChainSameToken)
688
692
  return { text: "Select a different token or chain", disable: true, error: false, loading: false };
689
693
  if (isLoadingAnyspendQuote) return { text: "Loading quote...", disable: true, error: false, loading: true };
@@ -739,6 +743,7 @@ function AnySpendInner({
739
743
  activeTab,
740
744
  effectiveCryptoPaymentMethod,
741
745
  selectedFiatPaymentMethod,
746
+ srcAmountOnRamp,
742
747
  ]);
743
748
 
744
749
  // Handle main button click
@@ -870,11 +875,20 @@ function AnySpendInner({
870
875
  vendor = "coinbase";
871
876
  paymentMethodString = coinbaseAvailablePaymentMethods[0]?.id || ""; // Use first available payment method ID
872
877
  } else if (paymentMethod === FiatPaymentMethod.STRIPE) {
873
- if (!stripeWeb2Support || !stripeWeb2Support.isSupport) {
874
- toast.error("Stripe not available");
878
+ // Stripe redirect flow (one-click URL)
879
+ if (!stripeOnrampSupport) {
880
+ toast.error("Credit/Debit Card not available");
881
+ return;
882
+ }
883
+ vendor = "stripe";
884
+ paymentMethodString = "";
885
+ } else if (paymentMethod === FiatPaymentMethod.STRIPE_WEB2) {
886
+ // Stripe embedded payment form
887
+ if (!stripeWeb2Support.isSupport) {
888
+ toast.error("Pay with Card not available");
875
889
  return;
876
890
  }
877
- vendor = stripeWeb2Support && stripeWeb2Support.isSupport ? "stripe-web2" : "stripe";
891
+ vendor = "stripe-web2";
878
892
  paymentMethodString = "";
879
893
  } else {
880
894
  toast.error("Please select a payment method");
@@ -27,12 +27,13 @@
27
27
  import { USDC_BASE } from "@b3dotfun/sdk/anyspend/constants";
28
28
  import { components } from "@b3dotfun/sdk/anyspend/types/api";
29
29
  import { GetQuoteResponse } from "@b3dotfun/sdk/anyspend/types/api_req_res";
30
+ import { formatUnits } from "@b3dotfun/sdk/shared/utils/number";
30
31
  import React, { useMemo } from "react";
31
32
  import { encodeFunctionData } from "viem";
32
33
  import { AnySpendCustom } from "./AnySpendCustom";
33
34
 
34
35
  // Collector Club Shop contract on Base
35
- const CC_SHOP_ADDRESS = "0x4eE0190e37E8A13740c4777D1c9de65E79D8f751";
36
+ const CC_SHOP_ADDRESS = "0x23887D10c81118A9a2E3Af59C423e2f4ee4Cc7Cf";
36
37
  const BASE_CHAIN_ID = 8453;
37
38
 
38
39
  // ABI for buyPacksFor function only
@@ -136,6 +137,12 @@ export function AnySpendCollectorClubPurchase({
136
137
  }
137
138
  }, [pricePerPack, packAmount]);
138
139
 
140
+ // Calculate fiat amount (totalAmount in USD, assuming USDC with 6 decimals)
141
+ const srcFiatAmount = useMemo(() => {
142
+ if (!totalAmount || totalAmount === "0") return "0";
143
+ return formatUnits(totalAmount, USDC_BASE.decimals);
144
+ }, [totalAmount]);
145
+
139
146
  // Encode the buyPacksFor function call
140
147
  const encodedData = useMemo(() => {
141
148
  try {
@@ -185,6 +192,7 @@ export function AnySpendCollectorClubPurchase({
185
192
  header={header || defaultHeader}
186
193
  onSuccess={onSuccess}
187
194
  showRecipient={showRecipient}
195
+ srcFiatAmount={srcFiatAmount}
188
196
  />
189
197
  );
190
198
  }
@@ -44,14 +44,13 @@ import { motion } from "motion/react";
44
44
  import React, { useCallback, useEffect, useMemo, useState } from "react";
45
45
 
46
46
  import { base } from "viem/chains";
47
- import { useFeatureFlags } from "../contexts/FeatureFlagsContext";
48
47
  import { useAutoSetActiveWalletFromWagmi } from "../hooks/useAutoSetActiveWalletFromWagmi";
49
48
  import { useCryptoPaymentMethodState } from "../hooks/useCryptoPaymentMethodState";
50
49
  import { useRecipientAddressState } from "../hooks/useRecipientAddressState";
51
50
  import { AnySpendFingerprintWrapper, getFingerprintConfig } from "./AnySpendFingerprintWrapper";
52
51
  import { CryptoPaymentMethod, CryptoPaymentMethodType } from "./common/CryptoPaymentMethod";
53
52
  import { FeeBreakDown } from "./common/FeeBreakDown";
54
- import { FiatPaymentMethod, FiatPaymentMethodComponent } from "./common/FiatPaymentMethod";
53
+ import { FIAT_PAYMENT_METHOD_DISPLAY, FiatPaymentMethod, FiatPaymentMethodComponent } from "./common/FiatPaymentMethod";
55
54
  import { OrderDetails } from "./common/OrderDetails";
56
55
  import { OrderHistory } from "./common/OrderHistory";
57
56
  import { OrderToken } from "./common/OrderToken";
@@ -188,6 +187,8 @@ export function AnySpendCustom(props: {
188
187
  onSuccess?: (txHash?: string) => void;
189
188
  showRecipient?: boolean;
190
189
  onShowPointsDetail?: () => void;
190
+ /** Fiat amount in USD for fiat payments */
191
+ srcFiatAmount?: string;
191
192
  }) {
192
193
  const fingerprintConfig = getFingerprintConfig();
193
194
 
@@ -215,6 +216,7 @@ function AnySpendCustomInner({
215
216
  onSuccess,
216
217
  showRecipient = true,
217
218
  onShowPointsDetail,
219
+ srcFiatAmount: srcFiatAmountProps,
218
220
  }: {
219
221
  loadOrder?: string;
220
222
  mode?: "modal" | "page";
@@ -238,9 +240,9 @@ function AnySpendCustomInner({
238
240
  onSuccess?: (txHash?: string) => void;
239
241
  showRecipient?: boolean;
240
242
  onShowPointsDetail?: () => void;
243
+ srcFiatAmount?: string;
241
244
  }) {
242
245
  const hasMounted = useHasMounted();
243
- const featureFlags = useFeatureFlags();
244
246
 
245
247
  const searchParams = useSearchParamsSSR();
246
248
  const router = useRouter();
@@ -353,7 +355,12 @@ function AnySpendCustomInner({
353
355
  contractType: orderType === "mint_nft" ? metadata?.nftContract?.type : undefined,
354
356
  encodedData: encodedData,
355
357
  spenderAddress: spenderAddress,
356
- onrampVendor: selectedFiatPaymentMethod === FiatPaymentMethod.STRIPE ? "stripe-web2" : undefined,
358
+ onrampVendor:
359
+ selectedFiatPaymentMethod === FiatPaymentMethod.STRIPE
360
+ ? "stripe"
361
+ : selectedFiatPaymentMethod === FiatPaymentMethod.STRIPE_WEB2
362
+ ? "stripe-web2"
363
+ : undefined,
357
364
  });
358
365
  }, [
359
366
  activeTab,
@@ -402,14 +409,31 @@ function AnySpendCustomInner({
402
409
  }
403
410
  }, [activeTab, anyspendQuote?.data]);
404
411
  const formattedSrcAmount = srcAmount ? formatTokenAmount(srcAmount, srcToken.decimals, 6, false) : null;
405
- const srcFiatAmount = useMemo(
406
- () => (activeTab === "fiat" && srcAmount ? formatUnits(srcAmount.toString(), USDC_BASE.decimals) : "0"),
407
- [activeTab, srcAmount],
408
- );
412
+ // Calculate fiat amount for geo check (regardless of activeTab) to determine tab availability
413
+ const srcFiatAmountForGeoCheck = useMemo(() => {
414
+ // Use prop if provided
415
+ if (srcFiatAmountProps) {
416
+ return srcFiatAmountProps;
417
+ }
418
+ // Fallback to dstAmount if destination token is USDC
419
+ if (dstAmount && dstToken.address.toLowerCase() === USDC_BASE.address.toLowerCase()) {
420
+ return formatUnits(dstAmount, USDC_BASE.decimals);
421
+ }
422
+ // Use srcAmount if available (from quote)
423
+ if (srcAmount) {
424
+ return formatUnits(srcAmount.toString(), USDC_BASE.decimals);
425
+ }
426
+ return "0";
427
+ }, [srcAmount, srcFiatAmountProps, dstAmount, dstToken.address]);
409
428
 
410
- // Get geo data and onramp options (after quote is available)
411
- const { geoData, isOnrampSupported, coinbaseAvailablePaymentMethods, stripeWeb2Support } =
412
- useGeoOnrampOptions(srcFiatAmount);
429
+ const srcFiatAmount = useMemo(() => {
430
+ if (activeTab !== "fiat") return "0";
431
+ return srcFiatAmountForGeoCheck;
432
+ }, [activeTab, srcFiatAmountForGeoCheck]);
433
+
434
+ // Get geo data and onramp options (use srcFiatAmountForGeoCheck to check availability regardless of activeTab)
435
+ const { geoData, isOnrampSupported, coinbaseAvailablePaymentMethods, stripeOnrampSupport, stripeWeb2Support } =
436
+ useGeoOnrampOptions(srcFiatAmountForGeoCheck);
413
437
 
414
438
  useEffect(() => {
415
439
  if (oat?.data?.order.status === "executed" && !onSuccessCalled.current) {
@@ -521,9 +545,14 @@ function AnySpendCustomInner({
521
545
  // Get the current geo data from the hook
522
546
  const currentGeoData = geoData;
523
547
 
548
+ // Use total amount from quote (includes fees) for onramp, fallback to srcFiatAmount
549
+ const onrampAmount = anyspendQuote?.data?.currencyIn?.amountUsd
550
+ ? anyspendQuote.data.currencyIn.amountUsd.toString()
551
+ : srcFiatAmount;
552
+
524
553
  void createOnrampOrder({
525
554
  ...createOrderParams,
526
- srcFiatAmount: srcFiatAmount,
555
+ srcFiatAmount: onrampAmount,
527
556
  onramp: {
528
557
  vendor: onramp.vendor,
529
558
  paymentMethod: onramp.paymentMethod,
@@ -601,12 +630,21 @@ function AnySpendCustomInner({
601
630
  }
602
631
  vendor = "coinbase";
603
632
  paymentMethodString = coinbaseAvailablePaymentMethods[0]?.id || "";
604
- } else if (paymentMethod === FiatPaymentMethod.STRIPE) {
633
+ } else if (paymentMethod === FiatPaymentMethod.STRIPE_WEB2) {
634
+ // Stripe Web2 embedded payment
605
635
  if (!stripeWeb2Support || !stripeWeb2Support.isSupport) {
606
- toast.error("Stripe not available");
636
+ toast.error("Stripe embedded payment not available");
637
+ return;
638
+ }
639
+ vendor = "stripe-web2";
640
+ paymentMethodString = "";
641
+ } else if (paymentMethod === FiatPaymentMethod.STRIPE) {
642
+ // Stripe redirect (one-click buy URL)
643
+ if (!stripeOnrampSupport) {
644
+ toast.error("Stripe redirect payment not available");
607
645
  return;
608
646
  }
609
- vendor = stripeWeb2Support && stripeWeb2Support.isSupport ? "stripe-web2" : "stripe";
647
+ vendor = "stripe";
610
648
  paymentMethodString = "";
611
649
  } else {
612
650
  toast.error("Please select a payment method");
@@ -803,7 +841,7 @@ function AnySpendCustomInner({
803
841
 
804
842
  // Render points badge if conditions are met
805
843
  const renderPointsBadge = () => {
806
- if (featureFlags.showPoints && anyspendQuote?.data?.pointsAmount && anyspendQuote.data.pointsAmount > 0) {
844
+ if (anyspendQuote?.data?.pointsAmount && anyspendQuote.data.pointsAmount > 0) {
807
845
  return (
808
846
  <PointsBadge
809
847
  pointsAmount={anyspendQuote.data.pointsAmount}
@@ -1094,32 +1132,28 @@ function AnySpendCustomInner({
1094
1132
  className="text-as-tertiarry flex flex-wrap items-center justify-end gap-2 text-sm transition-colors hover:text-blue-700"
1095
1133
  onClick={() => setActivePanel(PanelView.FIAT_PAYMENT_METHOD)}
1096
1134
  >
1097
- {selectedFiatPaymentMethod === FiatPaymentMethod.COINBASE_PAY ? (
1098
- <>
1099
- <div className="flex items-center gap-2 whitespace-nowrap">
1100
- <div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-blue-600">
1101
- <span className="text-xs font-bold text-white">C</span>
1102
- </div>
1103
- Coinbase Pay
1104
- </div>
1105
- <ChevronRight className="h-4 w-4 shrink-0" />
1106
- </>
1107
- ) : selectedFiatPaymentMethod === FiatPaymentMethod.STRIPE ? (
1108
- <>
1109
- <div className="flex items-center gap-2 whitespace-nowrap">
1110
- <div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-blue-600">
1111
- <span className="text-xs font-bold text-white">S</span>
1112
- </div>
1113
- Credit/Debit Card
1114
- </div>
1115
- <ChevronRight className="h-4 w-4 shrink-0" />
1116
- </>
1117
- ) : (
1118
- <>
1119
- <span className="whitespace-nowrap">Select payment method</span>
1120
- <ChevronRight className="h-4 w-4 shrink-0" />
1121
- </>
1122
- )}
1135
+ {(() => {
1136
+ const config = FIAT_PAYMENT_METHOD_DISPLAY[selectedFiatPaymentMethod];
1137
+ if (config) {
1138
+ return (
1139
+ <>
1140
+ <div className="flex items-center gap-2 whitespace-nowrap">
1141
+ <div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-blue-600">
1142
+ <span className="text-xs font-bold text-white">{config.icon}</span>
1143
+ </div>
1144
+ {config.label}
1145
+ </div>
1146
+ <ChevronRight className="h-4 w-4 shrink-0" />
1147
+ </>
1148
+ );
1149
+ }
1150
+ return (
1151
+ <>
1152
+ <span className="whitespace-nowrap">Select payment method</span>
1153
+ <ChevronRight className="h-4 w-4 shrink-0" />
1154
+ </>
1155
+ );
1156
+ })()}
1123
1157
  </button>
1124
1158
  </motion.div>
1125
1159
 
@@ -1162,20 +1196,32 @@ function AnySpendCustomInner({
1162
1196
  <div className="flex flex-col items-end gap-0.5">
1163
1197
  <div className="flex flex-wrap items-center justify-end gap-2">
1164
1198
  {renderPointsBadge()}
1165
- <span className="text-as-primary whitespace-nowrap text-xl font-semibold">
1166
- ${srcFiatAmount || "0.00"}
1167
- </span>
1199
+ {isLoadingAnyspendQuote ? (
1200
+ <div className="bg-as-surface-secondary h-7 w-16 animate-pulse rounded" />
1201
+ ) : (
1202
+ <span className="text-as-primary whitespace-nowrap text-xl font-semibold">
1203
+ {anyspendQuote?.data?.currencyIn?.amountUsd ? (
1204
+ `$${Number(anyspendQuote.data.currencyIn.amountUsd).toFixed(2)}`
1205
+ ) : (
1206
+ <>
1207
+ ${parseFloat(srcFiatAmount || "0").toFixed(2)}
1208
+ <span className="text-as-tertiarry text-base">+</span>
1209
+ </>
1210
+ )}
1211
+ </span>
1212
+ )}
1168
1213
  </div>
1169
- {anyspendQuote?.data?.fee?.type === "stripeweb2_fee" && anyspendQuote.data.fee.originalAmount && (
1170
- <span className="text-as-secondary text-xs">
1171
- incl. $
1172
- {(
1214
+ {(() => {
1215
+ if (anyspendQuote?.data?.fee?.type === "stripeweb2_fee" && anyspendQuote.data.fee.originalAmount) {
1216
+ const fee =
1173
1217
  (Number(anyspendQuote.data.fee.originalAmount) - Number(anyspendQuote.data.fee.finalAmount)) /
1174
- 1e6
1175
- ).toFixed(2)}{" "}
1176
- fee
1177
- </span>
1178
- )}
1218
+ 1e6;
1219
+ if (fee > 0) {
1220
+ return <span className="text-as-secondary text-xs">incl. ${fee.toFixed(2)} fee</span>;
1221
+ }
1222
+ }
1223
+ return null;
1224
+ })()}
1179
1225
  </div>
1180
1226
  </motion.div>
1181
1227
  </div>
@@ -1273,17 +1319,25 @@ function AnySpendCustomInner({
1273
1319
  </div>
1274
1320
  );
1275
1321
 
1322
+ // Stable callback for fiat payment method selection
1323
+ const handleFiatPaymentMethodSelect = useCallback((method: FiatPaymentMethod) => {
1324
+ setSelectedFiatPaymentMethod(method);
1325
+ setActivePanel(PanelView.CONFIRM_ORDER);
1326
+ }, []);
1327
+
1328
+ // Stable callback for navigating back to confirm order
1329
+ const handleBackToConfirmOrder = useCallback(() => {
1330
+ setActivePanel(PanelView.CONFIRM_ORDER);
1331
+ }, []);
1332
+
1276
1333
  // Fiat payment method view
1277
1334
  const fiatPaymentMethodView = (
1278
1335
  <div className={cn("bg-as-surface-primary mx-auto w-[460px] max-w-full rounded-xl p-4")}>
1279
1336
  <FiatPaymentMethodComponent
1280
1337
  selectedPaymentMethod={selectedFiatPaymentMethod}
1281
1338
  setSelectedPaymentMethod={setSelectedFiatPaymentMethod}
1282
- onBack={() => setActivePanel(PanelView.CONFIRM_ORDER)}
1283
- onSelectPaymentMethod={(method: FiatPaymentMethod) => {
1284
- setSelectedFiatPaymentMethod(method);
1285
- setActivePanel(PanelView.CONFIRM_ORDER);
1286
- }}
1339
+ onBack={handleBackToConfirmOrder}
1340
+ onSelectPaymentMethod={handleFiatPaymentMethodSelect}
1287
1341
  srcAmountOnRamp={srcFiatAmount}
1288
1342
  />
1289
1343
  </div>
@@ -7,7 +7,7 @@ import { getIpfsUrl } from "@b3dotfun/sdk/shared/utils/ipfs";
7
7
  import { formatDisplayNumber, formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
8
8
  import { MoreVertical } from "lucide-react";
9
9
  import { AnimatePresence } from "motion/react";
10
- import { useEffect, useState } from "react";
10
+ import { useCallback, useEffect, useRef, useState } from "react";
11
11
  import { b3 } from "viem/chains";
12
12
  import { GetQuoteResponse } from "../../types/api_req_res";
13
13
  import { AnySpendCustom } from "./AnySpendCustom";
@@ -46,19 +46,19 @@ export function AnySpendNFT({
46
46
  onShowPointsDetail?: () => void;
47
47
  }) {
48
48
  const [imageUrlWithFallback, setFallbackImageUrl] = useState<string | null>(nftContract.imageUrl);
49
- const [isLoadingFallback, setIsLoadingFallback] = useState(false);
49
+ const hasFetchedRef = useRef(false);
50
50
 
51
51
  // Fetch contract metadata when imageUrl is empty
52
52
  useEffect(() => {
53
53
  async function fetchContractMetadata() {
54
54
  // fetch image Uri if not provided
55
- if (nftContract.imageUrl || isLoadingFallback) {
55
+ if (nftContract.imageUrl || hasFetchedRef.current) {
56
56
  return;
57
57
  }
58
58
 
59
- try {
60
- setIsLoadingFallback(true);
59
+ hasFetchedRef.current = true;
61
60
 
61
+ try {
62
62
  // Use the chainIdToPublicClient utility function
63
63
  const publicClient = chainIdToPublicClient(nftContract.chainId);
64
64
 
@@ -96,56 +96,57 @@ export function AnySpendNFT({
96
96
  }
97
97
  } catch (error) {
98
98
  console.error("Error fetching contract metadata:", error);
99
- } finally {
100
- setIsLoadingFallback(false);
101
99
  }
102
100
  }
103
101
 
104
102
  fetchContractMetadata();
105
- }, [nftContract.contractAddress, nftContract.chainId, nftContract.imageUrl, nftContract.tokenId, isLoadingFallback]);
106
-
107
- const header = ({
108
- anyspendPrice,
109
- isLoadingAnyspendPrice,
110
- }: {
111
- anyspendPrice: GetQuoteResponse | undefined;
112
- isLoadingAnyspendPrice: boolean;
113
- }) => (
114
- <>
115
- <div className="relative size-[200px]">
116
- <div className="absolute inset-0 scale-95 bg-black/30 blur-md"></div>
117
- <GlareCard className="overflow-hidden">
118
- {imageUrlWithFallback && (
119
- <img src={imageUrlWithFallback} alt={nftContract.name} className="size-full object-cover" />
120
- )}
121
- <div className="absolute inset-0 rounded-xl border border-white/10"></div>
122
- </GlareCard>
123
-
124
- <DropdownMenu nftContract={nftContract} />
125
- </div>
126
- <div className="from-b3-react-background to-as-on-surface-1 -mb-5 mt-[-100px] w-full rounded-t-lg bg-gradient-to-t">
127
- <div className="h-[100px] w-full" />
128
- <div className="mb-1 flex w-full flex-col items-center gap-2 p-5">
129
- <span className="font-sf-rounded text-2xl font-semibold">{nftContract.name}</span>
130
-
131
- <div className="flex w-fit items-center gap-1">
132
- {anyspendPrice ? (
133
- <AnimatePresence mode="wait">
134
- <div
135
- className={cn("text-as-primary group flex items-center text-3xl font-semibold transition-all", {
136
- "opacity-0": isLoadingAnyspendPrice,
137
- })}
138
- >
139
- {formatDisplayNumber(anyspendPrice?.data?.currencyIn?.amountUsd, { style: "currency" })}
140
- </div>
141
- </AnimatePresence>
142
- ) : (
143
- <div className="h-[36px] w-full" />
103
+ }, [nftContract.contractAddress, nftContract.chainId, nftContract.imageUrl, nftContract.tokenId]);
104
+
105
+ const header = useCallback(
106
+ ({
107
+ anyspendPrice,
108
+ isLoadingAnyspendPrice,
109
+ }: {
110
+ anyspendPrice: GetQuoteResponse | undefined;
111
+ isLoadingAnyspendPrice: boolean;
112
+ }) => (
113
+ <>
114
+ <div className="relative size-[200px]">
115
+ <div className="absolute inset-0 scale-95 bg-black/30 blur-md"></div>
116
+ <GlareCard className="overflow-hidden">
117
+ {imageUrlWithFallback && (
118
+ <img src={imageUrlWithFallback} alt={nftContract.name} className="size-full object-cover" />
144
119
  )}
120
+ <div className="absolute inset-0 rounded-xl border border-white/10"></div>
121
+ </GlareCard>
122
+
123
+ <DropdownMenu nftContract={nftContract} />
124
+ </div>
125
+ <div className="from-b3-react-background to-as-on-surface-1 -mb-5 mt-[-100px] w-full rounded-t-lg bg-gradient-to-t">
126
+ <div className="h-[100px] w-full" />
127
+ <div className="mb-1 flex w-full flex-col items-center gap-2 p-5">
128
+ <span className="font-sf-rounded text-2xl font-semibold">{nftContract.name}</span>
129
+
130
+ <div className="flex w-fit items-center gap-1">
131
+ {anyspendPrice ? (
132
+ <AnimatePresence mode="wait">
133
+ <div
134
+ className={cn("text-as-primary group flex items-center text-3xl font-semibold transition-all", {
135
+ "opacity-0": isLoadingAnyspendPrice,
136
+ })}
137
+ >
138
+ {formatDisplayNumber(anyspendPrice?.data?.currencyIn?.amountUsd, { style: "currency" })}
139
+ </div>
140
+ </AnimatePresence>
141
+ ) : (
142
+ <div className="h-[36px] w-full" />
143
+ )}
144
+ </div>
145
145
  </div>
146
146
  </div>
147
- </div>
148
- </>
147
+ </>
148
+ ),
149
+ [imageUrlWithFallback, nftContract],
149
150
  );
150
151
 
151
152
  return (
@@ -5,7 +5,6 @@ import { formatDisplayNumber } from "@b3dotfun/sdk/shared/utils/number";
5
5
  import { ChevronRight, Info } from "lucide-react";
6
6
  import { motion } from "motion/react";
7
7
  import { components } from "../../../types/api";
8
- import { useFeatureFlags } from "../../contexts/FeatureFlagsContext";
9
8
  import { OrderTokenAmount } from "./OrderTokenAmount";
10
9
  import { PointsBadge } from "./PointsBadge";
11
10
 
@@ -55,8 +54,6 @@ export function CryptoReceiveSection({
55
54
  onShowPointsDetail,
56
55
  onShowFeeDetail,
57
56
  }: CryptoReceiveSectionProps) {
58
- const featureFlags = useFeatureFlags();
59
-
60
57
  return (
61
58
  <motion.div
62
59
  initial={{ opacity: 0, y: 20, filter: "blur(10px)" }}
@@ -184,7 +181,7 @@ export function CryptoReceiveSection({
184
181
  );
185
182
  })()}
186
183
  </div>
187
- {featureFlags.showPoints && anyspendQuote?.data?.pointsAmount > 0 && (
184
+ {anyspendQuote?.data?.pointsAmount > 0 && (
188
185
  <PointsBadge
189
186
  pointsAmount={anyspendQuote.data.pointsAmount}
190
187
  pointsMultiplier={anyspendQuote.data.pointsMultiplier}