@b3dotfun/sdk 0.0.48 → 0.0.49-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 (82) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.d.ts +2 -1
  2. package/dist/cjs/anyspend/react/components/AnySpend.js +8 -4
  3. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +12 -3
  4. package/dist/cjs/anyspend/react/components/AnyspendDepositHype.js +10 -3
  5. package/dist/cjs/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  6. package/dist/cjs/anyspend/react/components/common/CryptoPaySection.js +2 -2
  7. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  8. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +2 -2
  9. package/dist/cjs/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
  10. package/dist/cjs/anyspend/react/components/common/FeeBreakDown.js +19 -0
  11. package/dist/cjs/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
  12. package/dist/cjs/anyspend/react/components/common/FeeDetailPanel.js +116 -0
  13. package/dist/cjs/anyspend/react/components/common/OrderDetailsCollapsible.js +6 -8
  14. package/dist/cjs/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  15. package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +36 -21
  16. package/dist/cjs/anyspend/react/components/common/PanelOnrampPayment.js +2 -1
  17. package/dist/cjs/anyspend/react/components/common/PointsDetailPanel.js +1 -2
  18. package/dist/cjs/anyspend/react/components/common/TokenBalance.js +20 -3
  19. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
  20. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +1 -0
  21. package/dist/cjs/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +24 -0
  22. package/dist/cjs/anyspend/types/api.d.ts +119 -176
  23. package/dist/cjs/anyspend/types/chain.d.ts +1 -1
  24. package/dist/cjs/anyspend/utils/chain.d.ts +1 -0
  25. package/dist/cjs/anyspend/utils/chain.js +23 -0
  26. package/dist/cjs/global-account/react/components/ui/tooltip.js +1 -1
  27. package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -1
  28. package/dist/esm/anyspend/react/components/AnySpend.js +8 -4
  29. package/dist/esm/anyspend/react/components/AnySpendCustom.js +14 -5
  30. package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +10 -3
  31. package/dist/esm/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  32. package/dist/esm/anyspend/react/components/common/CryptoPaySection.js +4 -4
  33. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  34. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +4 -4
  35. package/dist/esm/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
  36. package/dist/esm/anyspend/react/components/common/FeeBreakDown.js +16 -0
  37. package/dist/esm/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
  38. package/dist/esm/anyspend/react/components/common/FeeDetailPanel.js +113 -0
  39. package/dist/esm/anyspend/react/components/common/OrderDetailsCollapsible.js +6 -8
  40. package/dist/esm/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  41. package/dist/esm/anyspend/react/components/common/PanelOnramp.js +38 -23
  42. package/dist/esm/anyspend/react/components/common/PanelOnrampPayment.js +2 -1
  43. package/dist/esm/anyspend/react/components/common/PointsDetailPanel.js +2 -3
  44. package/dist/esm/anyspend/react/components/common/TokenBalance.js +20 -3
  45. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
  46. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +1 -0
  47. package/dist/esm/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +24 -0
  48. package/dist/esm/anyspend/types/api.d.ts +119 -176
  49. package/dist/esm/anyspend/types/chain.d.ts +1 -1
  50. package/dist/esm/anyspend/utils/chain.d.ts +1 -0
  51. package/dist/esm/anyspend/utils/chain.js +23 -1
  52. package/dist/esm/global-account/react/components/ui/tooltip.js +1 -1
  53. package/dist/styles/index.css +1 -1
  54. package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -1
  55. package/dist/types/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  56. package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  57. package/dist/types/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
  58. package/dist/types/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
  59. package/dist/types/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  60. package/dist/types/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
  61. package/dist/types/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +24 -0
  62. package/dist/types/anyspend/types/api.d.ts +119 -176
  63. package/dist/types/anyspend/types/chain.d.ts +1 -1
  64. package/dist/types/anyspend/utils/chain.d.ts +1 -0
  65. package/package.json +1 -1
  66. package/src/anyspend/react/components/AnySpend.tsx +20 -0
  67. package/src/anyspend/react/components/AnySpendCustom.tsx +69 -7
  68. package/src/anyspend/react/components/AnyspendDepositHype.tsx +23 -0
  69. package/src/anyspend/react/components/common/CryptoPaySection.tsx +14 -2
  70. package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +14 -2
  71. package/src/anyspend/react/components/common/FeeBreakDown.tsx +105 -0
  72. package/src/anyspend/react/components/common/FeeDetailPanel.tsx +307 -0
  73. package/src/anyspend/react/components/common/OrderDetailsCollapsible.tsx +6 -8
  74. package/src/anyspend/react/components/common/PanelOnramp.tsx +58 -27
  75. package/src/anyspend/react/components/common/PanelOnrampPayment.tsx +18 -6
  76. package/src/anyspend/react/components/common/PointsDetailPanel.tsx +1 -13
  77. package/src/anyspend/react/components/common/TokenBalance.tsx +19 -3
  78. package/src/anyspend/react/hooks/useAnyspendFlow.ts +1 -0
  79. package/src/anyspend/types/api.ts +121 -176
  80. package/src/anyspend/types/chain.ts +1 -1
  81. package/src/anyspend/utils/chain.ts +27 -0
  82. package/src/global-account/react/components/ui/tooltip.tsx +11 -9
@@ -22,6 +22,7 @@ import {
22
22
  TextShimmer,
23
23
  Tooltip,
24
24
  TooltipContent,
25
+ TooltipProvider,
25
26
  TooltipTrigger,
26
27
  TransitionPanel,
27
28
  useAccountWallet,
@@ -37,7 +38,7 @@ import { shortenAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
37
38
  import { formatTokenAmount, formatUnits } from "@b3dotfun/sdk/shared/utils/number";
38
39
  import { simpleHashChainToChainName } from "@b3dotfun/sdk/shared/utils/simplehash";
39
40
  import invariant from "invariant";
40
- import { ChevronRight, ChevronRightCircle, Loader2 } from "lucide-react";
41
+ import { ChevronRight, ChevronRightCircle, Info, Loader2 } from "lucide-react";
41
42
  import { motion } from "motion/react";
42
43
  import React, { useCallback, useEffect, useMemo, useState } from "react";
43
44
  import { toast } from "sonner";
@@ -45,6 +46,7 @@ import { base } from "viem/chains";
45
46
  import { useFeatureFlags } from "../contexts/FeatureFlagsContext";
46
47
  import { AnySpendFingerprintWrapper, getFingerprintConfig } from "./AnySpendFingerprintWrapper";
47
48
  import { CryptoPaymentMethod, CryptoPaymentMethodType } from "./common/CryptoPaymentMethod";
49
+ import { FeeBreakDown } from "./common/FeeBreakDown";
48
50
  import { FiatPaymentMethod, FiatPaymentMethodComponent } from "./common/FiatPaymentMethod";
49
51
  import { OrderDetails } from "./common/OrderDetails";
50
52
  import { OrderHistory } from "./common/OrderHistory";
@@ -77,6 +79,7 @@ function generateGetRelayQuoteRequest({
77
79
  contractType,
78
80
  encodedData,
79
81
  spenderAddress,
82
+ onrampVendor,
80
83
  }: {
81
84
  orderType: components["schemas"]["Order"]["type"];
82
85
  srcChainId: number;
@@ -90,6 +93,7 @@ function generateGetRelayQuoteRequest({
90
93
  contractType?: components["schemas"]["NftContract"]["type"];
91
94
  encodedData: string;
92
95
  spenderAddress?: string;
96
+ onrampVendor?: components["schemas"]["OnrampMetadata"]["vendor"];
93
97
  }): GetQuoteRequest {
94
98
  switch (orderType) {
95
99
  case "mint_nft": {
@@ -105,6 +109,7 @@ function generateGetRelayQuoteRequest({
105
109
  contractAddress: contractAddress,
106
110
  tokenId: tokenId,
107
111
  contractType: contractType,
112
+ onrampVendor,
108
113
  };
109
114
  }
110
115
  case "join_tournament": {
@@ -117,6 +122,7 @@ function generateGetRelayQuoteRequest({
117
122
  recipientAddress,
118
123
  price: dstAmount,
119
124
  contractAddress: contractAddress,
125
+ onrampVendor,
120
126
  };
121
127
  }
122
128
  case "fund_tournament": {
@@ -129,6 +135,7 @@ function generateGetRelayQuoteRequest({
129
135
  recipientAddress,
130
136
  fundAmount: dstAmount,
131
137
  contractAddress: contractAddress,
138
+ onrampVendor,
132
139
  };
133
140
  }
134
141
  case "custom": {
@@ -145,6 +152,7 @@ function generateGetRelayQuoteRequest({
145
152
  to: contractAddress,
146
153
  spenderAddress: spenderAddress,
147
154
  },
155
+ onrampVendor,
148
156
  };
149
157
  }
150
158
  default: {
@@ -336,6 +344,7 @@ function AnySpendCustomInner({
336
344
  contractType: orderType === "mint_nft" ? metadata?.nftContract?.type : undefined,
337
345
  encodedData: encodedData,
338
346
  spenderAddress: spenderAddress,
347
+ onrampVendor: selectedFiatPaymentMethod === FiatPaymentMethod.STRIPE ? "stripe-web2" : undefined,
339
348
  });
340
349
  }, [
341
350
  activeTab,
@@ -351,6 +360,7 @@ function AnySpendCustomInner({
351
360
  spenderAddress,
352
361
  srcChainId,
353
362
  srcToken,
363
+ selectedFiatPaymentMethod,
354
364
  ]);
355
365
  const { anyspendQuote, isLoadingAnyspendQuote } = useAnyspendQuote(getRelayQuoteRequest);
356
366
 
@@ -960,14 +970,40 @@ function AnySpendCustomInner({
960
970
  className="relative flex w-full items-center justify-between"
961
971
  >
962
972
  <div className="flex items-center gap-2">
963
- <span className="text-as-tertiarry text-sm">
973
+ <span className="text-as-tertiarry flex items-center gap-1.5 text-sm">
964
974
  Total <span className="text-as-tertiarry">(with fee)</span>
975
+ {anyspendQuote?.data?.fee && (
976
+ <TooltipProvider>
977
+ <Tooltip>
978
+ <TooltipTrigger asChild>
979
+ <button className="text-as-primary/40 hover:text-as-primary/60 transition-colors">
980
+ <Info className="h-4 w-4" />
981
+ </button>
982
+ </TooltipTrigger>
983
+ <TooltipContent side="top">
984
+ <FeeBreakDown fee={anyspendQuote.data.fee} />
985
+ </TooltipContent>
986
+ </Tooltip>
987
+ </TooltipProvider>
988
+ )}
965
989
  </span>
966
990
  {renderPointsBadge()}
967
991
  </div>
968
- <span className="text-as-primary font-semibold">
969
- {formattedSrcAmount || "--"} {srcToken.symbol}
970
- </span>
992
+ <div className="flex flex-col items-end gap-0.5">
993
+ <span className="text-as-primary font-semibold">
994
+ {formattedSrcAmount || "--"} {srcToken.symbol}
995
+ </span>
996
+ {anyspendQuote?.data?.fee?.type === "standard_fee" && anyspendQuote.data.currencyIn?.amountUsd && (
997
+ <span className="text-as-secondary text-xs">
998
+ incl. $
999
+ {(
1000
+ (Number(anyspendQuote.data.currencyIn.amountUsd) * anyspendQuote.data.fee.finalFeeBps) /
1001
+ 10000
1002
+ ).toFixed(2)}{" "}
1003
+ fee
1004
+ </span>
1005
+ )}
1006
+ </div>
971
1007
  </motion.div>
972
1008
  </div>
973
1009
  </div>
@@ -1086,12 +1122,38 @@ function AnySpendCustomInner({
1086
1122
  className="relative flex w-full items-center justify-between"
1087
1123
  >
1088
1124
  <div className="flex items-center gap-2">
1089
- <span className="text-as-tertiarry text-sm">
1125
+ <span className="text-as-tertiarry flex items-center gap-1.5 text-sm">
1090
1126
  Total <span className="text-as-tertiarry">(USD)</span>
1127
+ {anyspendQuote?.data?.fee && (
1128
+ <TooltipProvider>
1129
+ <Tooltip>
1130
+ <TooltipTrigger asChild>
1131
+ <button className="text-as-primary/40 hover:text-as-primary/60 transition-colors">
1132
+ <Info className="h-4 w-4" />
1133
+ </button>
1134
+ </TooltipTrigger>
1135
+ <TooltipContent side="top">
1136
+ <FeeBreakDown fee={anyspendQuote.data.fee} />
1137
+ </TooltipContent>
1138
+ </Tooltip>
1139
+ </TooltipProvider>
1140
+ )}
1091
1141
  </span>
1092
1142
  {renderPointsBadge()}
1093
1143
  </div>
1094
- <span className="text-as-primary text-xl font-semibold">${srcFiatAmount || "0.00"}</span>
1144
+ <div className="flex flex-col items-end gap-0.5">
1145
+ <span className="text-as-primary text-xl font-semibold">${srcFiatAmount || "0.00"}</span>
1146
+ {anyspendQuote?.data?.fee?.type === "stripeweb2_fee" && anyspendQuote.data.fee.originalAmount && (
1147
+ <span className="text-as-secondary text-xs">
1148
+ incl. $
1149
+ {(
1150
+ (Number(anyspendQuote.data.fee.originalAmount) - Number(anyspendQuote.data.fee.finalAmount)) /
1151
+ 1e6
1152
+ ).toFixed(2)}{" "}
1153
+ fee
1154
+ </span>
1155
+ )}
1156
+ </div>
1095
1157
  </motion.div>
1096
1158
  </div>
1097
1159
 
@@ -12,6 +12,7 @@ import { AnySpendFingerprintWrapper, getFingerprintConfig } from "./AnySpendFing
12
12
  import { CryptoPaySection } from "./common/CryptoPaySection";
13
13
  import { CryptoPaymentMethod, CryptoPaymentMethodType } from "./common/CryptoPaymentMethod";
14
14
  import { CryptoReceiveSection } from "./common/CryptoReceiveSection";
15
+ import { FeeDetailPanel } from "./common/FeeDetailPanel";
15
16
  import { FiatPaymentMethod, FiatPaymentMethodComponent } from "./common/FiatPaymentMethod";
16
17
  import { OrderDetails } from "./common/OrderDetails";
17
18
  import { PointsDetailPanel } from "./common/PointsDetailPanel";
@@ -80,6 +81,7 @@ function AnySpendDepositHypeInner({
80
81
  srcAmount,
81
82
  setSrcAmount,
82
83
  dstAmount,
84
+ isSrcInputDirty,
83
85
  setIsSrcInputDirty,
84
86
  selectedCryptoPaymentMethod,
85
87
  setSelectedCryptoPaymentMethod,
@@ -207,6 +209,7 @@ function AnySpendDepositHypeInner({
207
209
  setSelectedSrcToken={setSelectedSrcToken}
208
210
  srcAmount={srcAmount}
209
211
  setSrcAmount={setSrcAmount}
212
+ isSrcInputDirty={isSrcInputDirty}
210
213
  setIsSrcInputDirty={setIsSrcInputDirty}
211
214
  selectedCryptoPaymentMethod={selectedCryptoPaymentMethod}
212
215
  onSelectCryptoPaymentMethod={() => setActivePanel(PanelView.CRYPTO_PAYMENT_METHOD)}
@@ -236,6 +239,7 @@ function AnySpendDepositHypeInner({
236
239
  recipientSelectionPanelIndex={PanelView.RECIPIENT_SELECTION}
237
240
  anyspendQuote={anyspendQuote}
238
241
  onShowPointsDetail={() => setActivePanel(PanelView.POINTS_DETAIL)}
242
+ onShowFeeDetail={() => setActivePanel(PanelView.FEE_DETAIL)}
239
243
  customUsdInputValues={customUsdInputValues}
240
244
  />
241
245
  </motion.div>
@@ -272,12 +276,14 @@ function AnySpendDepositHypeInner({
272
276
  selectedDstChainId={base.id}
273
277
  setSelectedDstChainId={() => {}}
274
278
  setSelectedDstToken={() => {}}
279
+ isSrcInputDirty={isSrcInputDirty}
275
280
  onChangeDstAmount={value => {
276
281
  setIsSrcInputDirty(false);
277
282
  setSrcAmount(value);
278
283
  }}
279
284
  anyspendQuote={anyspendQuote}
280
285
  onShowPointsDetail={() => setActivePanel(PanelView.POINTS_DETAIL)}
286
+ onShowFeeDetail={() => setActivePanel(PanelView.FEE_DETAIL)}
281
287
  />
282
288
  )}
283
289
  </div>
@@ -470,6 +476,20 @@ function AnySpendDepositHypeInner({
470
476
  />
471
477
  );
472
478
 
479
+ const feeDetailView = anyspendQuote?.data?.fee ? (
480
+ <FeeDetailPanel
481
+ fee={anyspendQuote.data.fee}
482
+ transactionAmountUsd={
483
+ paymentType === "fiat"
484
+ ? parseFloat(srcAmount)
485
+ : anyspendQuote.data.currencyIn?.amountUsd
486
+ ? Number(anyspendQuote.data.currencyIn.amountUsd)
487
+ : undefined
488
+ }
489
+ onBack={() => setActivePanel(PanelView.MAIN)}
490
+ />
491
+ ) : null;
492
+
473
493
  // If showing token selection, render with panel transitions
474
494
  return (
475
495
  <StyleRoot>
@@ -522,6 +542,9 @@ function AnySpendDepositHypeInner({
522
542
  <div key="points-detail-view" className={cn(mode === "page" && "p-6")}>
523
543
  {pointsDetailView}
524
544
  </div>,
545
+ <div key="fee-detail-view" className={cn(mode === "page" && "p-6")}>
546
+ {feeDetailView}
547
+ </div>,
525
548
  ]}
526
549
  </TransitionPanel>
527
550
  </div>
@@ -2,7 +2,7 @@ import { useAccountWallet, useProfile, useTokenData } from "@b3dotfun/sdk/global
2
2
  import { formatUsername } from "@b3dotfun/sdk/shared/utils";
3
3
  import { shortenAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
4
4
  import { formatDisplayNumber } from "@b3dotfun/sdk/shared/utils/number";
5
- import { ChevronRight } from "lucide-react";
5
+ import { ChevronRight, Info } from "lucide-react";
6
6
  import { motion } from "motion/react";
7
7
  import { useEffect, useRef } from "react";
8
8
  import { components } from "../../../types/api";
@@ -18,6 +18,7 @@ interface CryptoPaySectionProps {
18
18
  setSelectedSrcToken: (token: components["schemas"]["Token"]) => void;
19
19
  srcAmount: string;
20
20
  setSrcAmount: (amount: string) => void;
21
+ isSrcInputDirty: boolean;
21
22
  setIsSrcInputDirty: (dirty: boolean) => void;
22
23
  // Payment method state
23
24
  selectedCryptoPaymentMethod: CryptoPaymentMethodType;
@@ -26,6 +27,8 @@ interface CryptoPaySectionProps {
26
27
  anyspendQuote?: any;
27
28
  // Token selection callback
28
29
  onTokenSelect?: (token: components["schemas"]["Token"], event: { preventDefault: () => void }) => void;
30
+ // Fee detail callback
31
+ onShowFeeDetail?: () => void;
29
32
  }
30
33
 
31
34
  export function CryptoPaySection({
@@ -35,11 +38,13 @@ export function CryptoPaySection({
35
38
  setSelectedSrcToken,
36
39
  srcAmount,
37
40
  setSrcAmount,
41
+ isSrcInputDirty,
38
42
  setIsSrcInputDirty,
39
43
  selectedCryptoPaymentMethod,
40
44
  onSelectCryptoPaymentMethod,
41
45
  anyspendQuote,
42
46
  onTokenSelect,
47
+ onShowFeeDetail,
43
48
  }: CryptoPaySectionProps) {
44
49
  const { connectedSmartWallet, connectedEOAWallet } = useAccountWallet();
45
50
  const { data: srcTokenMetadata } = useTokenData(selectedSrcToken?.chainId, selectedSrcToken?.address);
@@ -89,7 +94,14 @@ export function CryptoPaySection({
89
94
  className="pay-section bg-as-surface-secondary border-as-border-secondary relative flex w-full flex-col gap-2 rounded-2xl border p-4 sm:p-6"
90
95
  >
91
96
  <div className="flex items-center justify-between">
92
- <div className="text-as-primary/50 flex h-7 items-center text-sm">Pay</div>
97
+ <div className="text-as-primary/50 flex h-7 items-center gap-1.5 text-sm">
98
+ Pay
99
+ {!isSrcInputDirty && anyspendQuote?.data?.fee && onShowFeeDetail && (
100
+ <button onClick={onShowFeeDetail} className="text-as-primary/40 hover:text-as-primary/60 transition-colors">
101
+ <Info className="h-4 w-4" />
102
+ </button>
103
+ )}
104
+ </div>
93
105
  <button
94
106
  className="text-as-tertiarry flex h-7 items-center gap-2 text-sm transition-colors focus:!outline-none"
95
107
  onClick={onSelectCryptoPaymentMethod}
@@ -2,7 +2,7 @@ import { formatUsername } from "@b3dotfun/sdk/shared/utils";
2
2
  import { cn } from "@b3dotfun/sdk/shared/utils/cn";
3
3
  import { shortenAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
4
4
  import { formatDisplayNumber } from "@b3dotfun/sdk/shared/utils/number";
5
- import { ChevronRight } from "lucide-react";
5
+ import { ChevronRight, Info } from "lucide-react";
6
6
  import { motion } from "motion/react";
7
7
  import { components } from "../../../types/api";
8
8
  import { useFeatureFlags } from "../../contexts/FeatureFlagsContext";
@@ -23,6 +23,7 @@ interface CryptoReceiveSectionProps {
23
23
  selectedDstChainId?: number;
24
24
  setSelectedDstChainId?: (chainId: number) => void;
25
25
  setSelectedDstToken?: (token: components["schemas"]["Token"]) => void;
26
+ isSrcInputDirty: boolean;
26
27
  onChangeDstAmount?: (value: string) => void;
27
28
  // Quote data
28
29
  anyspendQuote?: any;
@@ -31,6 +32,8 @@ interface CryptoReceiveSectionProps {
31
32
  dstTokenLogoURI?: string;
32
33
  // Points navigation
33
34
  onShowPointsDetail?: () => void;
35
+ // Fee detail navigation
36
+ onShowFeeDetail?: () => void;
34
37
  }
35
38
 
36
39
  export function CryptoReceiveSection({
@@ -44,11 +47,13 @@ export function CryptoReceiveSection({
44
47
  selectedDstChainId,
45
48
  setSelectedDstChainId,
46
49
  setSelectedDstToken,
50
+ isSrcInputDirty,
47
51
  onChangeDstAmount,
48
52
  anyspendQuote,
49
53
  dstTokenSymbol,
50
54
  dstTokenLogoURI,
51
55
  onShowPointsDetail,
56
+ onShowFeeDetail,
52
57
  }: CryptoReceiveSectionProps) {
53
58
  const featureFlags = useFeatureFlags();
54
59
 
@@ -60,7 +65,14 @@ export function CryptoReceiveSection({
60
65
  className="receive-section bg-as-surface-secondary border-as-border-secondary relative flex w-full flex-col gap-2 rounded-2xl border p-4 sm:p-6"
61
66
  >
62
67
  <div className="flex w-full items-center justify-between">
63
- <div className="text-as-primary/50 flex h-7 items-center text-sm">{isDepositMode ? "Deposit" : "Receive"}</div>
68
+ <div className="text-as-primary/50 flex h-7 items-center gap-1.5 text-sm">
69
+ {isDepositMode ? "Deposit" : "Receive"}
70
+ {isSrcInputDirty && anyspendQuote?.data?.fee && onShowFeeDetail && (
71
+ <button onClick={onShowFeeDetail} className="text-as-primary/40 hover:text-as-primary/60 transition-colors">
72
+ <Info className="h-4 w-4" />
73
+ </button>
74
+ )}
75
+ </div>
64
76
  {selectedRecipientAddress ? (
65
77
  <button
66
78
  className={cn("text-as-tertiarry flex h-7 items-center gap-2 rounded-lg")}
@@ -0,0 +1,105 @@
1
+ import { components } from "../../../types/api";
2
+
3
+ interface FeeBreakDownProps {
4
+ fee: components["schemas"]["Fee"];
5
+ /** Number of decimals for amount display (default: 6 for USDC) */
6
+ decimals?: number;
7
+ /** Show currency symbol for amounts (default: true) */
8
+ showCurrency?: boolean;
9
+ /** Custom className for the container */
10
+ className?: string;
11
+ }
12
+
13
+ export function FeeBreakDown({ fee, decimals = 6, showCurrency = true, className = "" }: FeeBreakDownProps) {
14
+ const isStripeFee = fee.type === "stripeweb2_fee";
15
+
16
+ // Convert basis points to percentage
17
+ const bpsToPercent = (bps: number) => (bps / 100).toFixed(2);
18
+
19
+ // Format amount with optional currency
20
+ const formatAmount = (amount: string) => {
21
+ const divisor = Math.pow(10, decimals);
22
+ const formatted = (Number(amount) / divisor).toFixed(2);
23
+ return showCurrency ? `$${formatted}` : formatted;
24
+ };
25
+
26
+ // Check if discount is active
27
+ const hasWhaleDiscount = fee.anyspendWhaleDiscountBps > 0;
28
+ const hasPartnerDiscount = fee.anyspendPartnerDiscountBps > 0;
29
+
30
+ return (
31
+ <div className={`min-w-[240px] ${className}`}>
32
+ {/* Fee Breakdown Section */}
33
+ <div className="mb-4">
34
+ <h3 className="text-as-primary mb-2 text-sm font-semibold">Fee Breakdown</h3>
35
+ <table className="w-full">
36
+ <tbody className="text-as-secondary text-xs">
37
+ {isStripeFee && (
38
+ <tr>
39
+ <td className="py-1">Stripe Fee</td>
40
+ <td className="py-1 text-right">
41
+ {bpsToPercent(fee.stripeFeeBps)}% + ${fee.stripeFeeUsd.toFixed(2)}
42
+ </td>
43
+ </tr>
44
+ )}
45
+ <tr>
46
+ <td className="py-1">AnySpend Fee</td>
47
+ <td className="py-1 text-right">
48
+ {bpsToPercent(fee.anyspendFeeBps)}%
49
+ {isStripeFee && fee.anyspendFeeUsd > 0 && ` + $${fee.anyspendFeeUsd.toFixed(2)}`}
50
+ </td>
51
+ </tr>
52
+ {hasWhaleDiscount && (
53
+ <tr className="text-green-600">
54
+ <td className="py-1">Whale Discount</td>
55
+ <td className="py-1 text-right">-{bpsToPercent(fee.anyspendWhaleDiscountBps)}%</td>
56
+ </tr>
57
+ )}
58
+ {hasPartnerDiscount && (
59
+ <tr className="text-green-600">
60
+ <td className="py-1">Partner Discount</td>
61
+ <td className="py-1 text-right">-{bpsToPercent(fee.anyspendPartnerDiscountBps)}%</td>
62
+ </tr>
63
+ )}
64
+ <tr className="border-as-border-secondary border-t">
65
+ <td className="text-as-primary py-1.5 pt-2 font-semibold">Total Fee</td>
66
+ <td className="text-as-primary py-1.5 pt-2 text-right font-semibold">
67
+ {bpsToPercent(fee.finalFeeBps)}%{isStripeFee && ` + $${fee.finalFeeUsd.toFixed(2)}`}
68
+ </td>
69
+ </tr>
70
+ </tbody>
71
+ </table>
72
+ </div>
73
+
74
+ {/* Amount Calculation Section (Stripe only) */}
75
+ {isStripeFee && (
76
+ <>
77
+ <div className="border-as-border-secondary my-3 border-t"></div>
78
+ <div>
79
+ <h3 className="text-as-primary mb-2 text-sm font-semibold">Amount Calculation</h3>
80
+ <table className="w-full">
81
+ <tbody className="text-as-secondary text-xs">
82
+ <tr>
83
+ <td className="py-1">Original Amount</td>
84
+ <td className="py-1 text-right font-medium">{formatAmount(fee.originalAmount)}</td>
85
+ </tr>
86
+ <tr>
87
+ <td className="py-1">Total Fee</td>
88
+ <td className="py-1 text-right text-red-600">
89
+ -{formatAmount((Number(fee.originalAmount) - Number(fee.finalAmount)).toString())}
90
+ </td>
91
+ </tr>
92
+ <tr className="border-as-border-secondary border-t">
93
+ <td className="text-as-primary py-1.5 pt-2 font-semibold">You Receive</td>
94
+ <td className="text-as-primary py-1.5 pt-2 text-right font-semibold">
95
+ {formatAmount(fee.finalAmount)}
96
+ </td>
97
+ </tr>
98
+ </tbody>
99
+ </table>
100
+ </div>
101
+ </>
102
+ )}
103
+ </div>
104
+ );
105
+ }