@b3dotfun/sdk 0.0.49 → 0.0.50-test.0

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 (164) hide show
  1. package/dist/cjs/anyspend/constants/index.d.ts +1 -0
  2. package/dist/cjs/anyspend/constants/index.js +12 -2
  3. package/dist/cjs/anyspend/react/components/AnySpend.d.ts +2 -1
  4. package/dist/cjs/anyspend/react/components/AnySpend.js +18 -5
  5. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +12 -3
  6. package/dist/cjs/anyspend/react/components/AnyspendDepositHype.js +11 -4
  7. package/dist/cjs/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  8. package/dist/cjs/anyspend/react/components/common/CryptoPaySection.js +2 -2
  9. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  10. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +17 -11
  11. package/dist/cjs/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
  12. package/dist/cjs/anyspend/react/components/common/FeeBreakDown.js +19 -0
  13. package/dist/cjs/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
  14. package/dist/cjs/anyspend/react/components/common/FeeDetailPanel.js +116 -0
  15. package/dist/cjs/anyspend/react/components/common/OrderDetails.d.ts +1 -0
  16. package/dist/cjs/anyspend/react/components/common/OrderDetails.js +6 -6
  17. package/dist/cjs/anyspend/react/components/common/OrderDetailsCollapsible.d.ts +1 -0
  18. package/dist/cjs/anyspend/react/components/common/OrderDetailsCollapsible.js +3 -2
  19. package/dist/cjs/anyspend/react/components/common/OrderToken.js +1 -1
  20. package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.js +1 -1
  21. package/dist/cjs/anyspend/react/components/common/OrderTokenAmountFiat.js +1 -1
  22. package/dist/cjs/anyspend/react/components/common/OrderTokenAmountNew.js +1 -1
  23. package/dist/cjs/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  24. package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +36 -21
  25. package/dist/cjs/anyspend/react/components/common/PanelOnrampPayment.js +2 -1
  26. package/dist/cjs/anyspend/react/components/common/PointsDetailPanel.js +1 -2
  27. package/dist/cjs/anyspend/react/contexts/FeatureFlagsContext.js +1 -1
  28. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
  29. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +1 -0
  30. package/dist/cjs/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +48 -12
  31. package/dist/cjs/anyspend/types/api.d.ts +133 -178
  32. package/dist/cjs/anyspend/utils/chain.js +4 -4
  33. package/dist/cjs/bondkit/bondkitToken.d.ts +3 -1
  34. package/dist/cjs/bondkit/bondkitToken.js +19 -0
  35. package/dist/cjs/bondkit/components/TradingView.d.ts +1 -1
  36. package/dist/cjs/bondkit/components/TradingView.js +14 -3
  37. package/dist/cjs/bondkit/components/index.d.ts +1 -1
  38. package/dist/cjs/bondkit/components/index.js +1 -1
  39. package/dist/cjs/bondkit/components/types.d.ts +1 -0
  40. package/dist/cjs/bondkit/config.d.ts +1 -0
  41. package/dist/cjs/bondkit/config.js +1 -0
  42. package/dist/cjs/bondkit/index.d.ts +1 -1
  43. package/dist/cjs/bondkit/index.js +2 -6
  44. package/dist/cjs/bondkit/types.d.ts +15 -0
  45. package/dist/cjs/global-account/react/components/B3Provider/B3Provider.d.ts +1 -1
  46. package/dist/cjs/global-account/react/components/B3Provider/B3Provider.js +1 -1
  47. package/dist/cjs/global-account/react/components/B3Provider/RelayKitProviderWrapper.js +2 -2
  48. package/dist/cjs/global-account/react/components/ui/tooltip.js +1 -1
  49. package/dist/cjs/global-account/react/hooks/useAuthentication.js +11 -0
  50. package/dist/cjs/global-account/react/hooks/useFirstEOA.d.ts +7670 -1
  51. package/dist/cjs/global-account/react/hooks/useFirstEOA.js +21 -1
  52. package/dist/cjs/shared/constants/chains/b3Chain.d.ts +9 -3
  53. package/dist/cjs/shared/generated/chain-networks.json +40 -7
  54. package/dist/esm/anyspend/constants/index.d.ts +1 -0
  55. package/dist/esm/anyspend/constants/index.js +11 -1
  56. package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -1
  57. package/dist/esm/anyspend/react/components/AnySpend.js +18 -5
  58. package/dist/esm/anyspend/react/components/AnySpendCustom.js +14 -5
  59. package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +11 -4
  60. package/dist/esm/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  61. package/dist/esm/anyspend/react/components/common/CryptoPaySection.js +4 -4
  62. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  63. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +19 -13
  64. package/dist/esm/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
  65. package/dist/esm/anyspend/react/components/common/FeeBreakDown.js +16 -0
  66. package/dist/esm/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
  67. package/dist/esm/anyspend/react/components/common/FeeDetailPanel.js +113 -0
  68. package/dist/esm/anyspend/react/components/common/OrderDetails.d.ts +1 -0
  69. package/dist/esm/anyspend/react/components/common/OrderDetails.js +6 -6
  70. package/dist/esm/anyspend/react/components/common/OrderDetailsCollapsible.d.ts +1 -0
  71. package/dist/esm/anyspend/react/components/common/OrderDetailsCollapsible.js +4 -3
  72. package/dist/esm/anyspend/react/components/common/OrderToken.js +1 -1
  73. package/dist/esm/anyspend/react/components/common/OrderTokenAmount.js +1 -1
  74. package/dist/esm/anyspend/react/components/common/OrderTokenAmountFiat.js +1 -1
  75. package/dist/esm/anyspend/react/components/common/OrderTokenAmountNew.js +1 -1
  76. package/dist/esm/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  77. package/dist/esm/anyspend/react/components/common/PanelOnramp.js +38 -23
  78. package/dist/esm/anyspend/react/components/common/PanelOnrampPayment.js +2 -1
  79. package/dist/esm/anyspend/react/components/common/PointsDetailPanel.js +2 -3
  80. package/dist/esm/anyspend/react/contexts/FeatureFlagsContext.js +1 -1
  81. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
  82. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +1 -0
  83. package/dist/esm/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +48 -12
  84. package/dist/esm/anyspend/types/api.d.ts +133 -178
  85. package/dist/esm/anyspend/utils/chain.js +4 -4
  86. package/dist/esm/bondkit/bondkitToken.d.ts +3 -1
  87. package/dist/esm/bondkit/bondkitToken.js +19 -0
  88. package/dist/esm/bondkit/components/TradingView.d.ts +1 -1
  89. package/dist/esm/bondkit/components/TradingView.js +14 -3
  90. package/dist/esm/bondkit/components/index.d.ts +1 -1
  91. package/dist/esm/bondkit/components/index.js +1 -1
  92. package/dist/esm/bondkit/components/types.d.ts +1 -0
  93. package/dist/esm/bondkit/config.d.ts +1 -0
  94. package/dist/esm/bondkit/config.js +1 -0
  95. package/dist/esm/bondkit/index.d.ts +1 -1
  96. package/dist/esm/bondkit/index.js +1 -1
  97. package/dist/esm/bondkit/types.d.ts +15 -0
  98. package/dist/esm/global-account/react/components/B3Provider/B3Provider.d.ts +1 -1
  99. package/dist/esm/global-account/react/components/B3Provider/B3Provider.js +1 -1
  100. package/dist/esm/global-account/react/components/B3Provider/RelayKitProviderWrapper.js +2 -2
  101. package/dist/esm/global-account/react/components/ui/tooltip.js +1 -1
  102. package/dist/esm/global-account/react/hooks/useAuthentication.js +11 -0
  103. package/dist/esm/global-account/react/hooks/useFirstEOA.d.ts +7670 -1
  104. package/dist/esm/global-account/react/hooks/useFirstEOA.js +22 -2
  105. package/dist/esm/shared/constants/chains/b3Chain.d.ts +9 -3
  106. package/dist/esm/shared/generated/chain-networks.json +40 -7
  107. package/dist/styles/index.css +1 -1
  108. package/dist/types/anyspend/constants/index.d.ts +1 -0
  109. package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -1
  110. package/dist/types/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  111. package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  112. package/dist/types/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
  113. package/dist/types/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
  114. package/dist/types/anyspend/react/components/common/OrderDetails.d.ts +1 -0
  115. package/dist/types/anyspend/react/components/common/OrderDetailsCollapsible.d.ts +1 -0
  116. package/dist/types/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  117. package/dist/types/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
  118. package/dist/types/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +48 -12
  119. package/dist/types/anyspend/types/api.d.ts +133 -178
  120. package/dist/types/bondkit/bondkitToken.d.ts +3 -1
  121. package/dist/types/bondkit/components/TradingView.d.ts +1 -1
  122. package/dist/types/bondkit/components/index.d.ts +1 -1
  123. package/dist/types/bondkit/components/types.d.ts +1 -0
  124. package/dist/types/bondkit/config.d.ts +1 -0
  125. package/dist/types/bondkit/index.d.ts +1 -1
  126. package/dist/types/bondkit/types.d.ts +15 -0
  127. package/dist/types/global-account/react/components/B3Provider/B3Provider.d.ts +1 -1
  128. package/dist/types/global-account/react/hooks/useFirstEOA.d.ts +7670 -1
  129. package/dist/types/shared/constants/chains/b3Chain.d.ts +9 -3
  130. package/package.json +5 -5
  131. package/src/anyspend/constants/index.ts +12 -1
  132. package/src/anyspend/react/components/AnySpend.tsx +33 -0
  133. package/src/anyspend/react/components/AnySpendCustom.tsx +69 -7
  134. package/src/anyspend/react/components/AnyspendDepositHype.tsx +24 -0
  135. package/src/anyspend/react/components/common/CryptoPaySection.tsx +14 -2
  136. package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +31 -11
  137. package/src/anyspend/react/components/common/FeeBreakDown.tsx +105 -0
  138. package/src/anyspend/react/components/common/FeeDetailPanel.tsx +334 -0
  139. package/src/anyspend/react/components/common/OrderDetails.tsx +7 -0
  140. package/src/anyspend/react/components/common/OrderDetailsCollapsible.tsx +16 -0
  141. package/src/anyspend/react/components/common/OrderToken.tsx +1 -1
  142. package/src/anyspend/react/components/common/OrderTokenAmount.tsx +1 -1
  143. package/src/anyspend/react/components/common/OrderTokenAmountFiat.tsx +1 -1
  144. package/src/anyspend/react/components/common/OrderTokenAmountNew.tsx +1 -1
  145. package/src/anyspend/react/components/common/PanelOnramp.tsx +58 -27
  146. package/src/anyspend/react/components/common/PanelOnrampPayment.tsx +18 -6
  147. package/src/anyspend/react/components/common/PointsDetailPanel.tsx +1 -13
  148. package/src/anyspend/react/contexts/FeatureFlagsContext.tsx +2 -2
  149. package/src/anyspend/react/hooks/useAnyspendFlow.ts +1 -0
  150. package/src/anyspend/types/api.ts +135 -178
  151. package/src/anyspend/utils/chain.ts +4 -4
  152. package/src/bondkit/bondkitToken.ts +24 -0
  153. package/src/bondkit/components/TradingView.tsx +15 -3
  154. package/src/bondkit/components/index.ts +1 -1
  155. package/src/bondkit/components/types.ts +1 -0
  156. package/src/bondkit/config.ts +2 -0
  157. package/src/bondkit/index.ts +1 -1
  158. package/src/bondkit/types.ts +19 -0
  159. package/src/global-account/react/components/B3Provider/B3Provider.tsx +1 -1
  160. package/src/global-account/react/components/B3Provider/RelayKitProviderWrapper.tsx +2 -2
  161. package/src/global-account/react/components/ui/tooltip.tsx +11 -9
  162. package/src/global-account/react/hooks/useAuthentication.ts +13 -1
  163. package/src/global-account/react/hooks/useFirstEOA.tsx +20 -2
  164. package/src/shared/generated/chain-networks.json +40 -7
@@ -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")}
@@ -118,7 +130,7 @@ export function CryptoReceiveSection({
118
130
  (() => {
119
131
  const calculatePriceImpact = (inputUsd?: string | number, outputUsd?: string | number) => {
120
132
  if (!inputUsd || !outputUsd) {
121
- return { percentage: "0.00", isNegative: false };
133
+ return { percentage: "0.00", percentageNum: 0, isNegative: false };
122
134
  }
123
135
 
124
136
  const input = Number(inputUsd);
@@ -126,38 +138,46 @@ export function CryptoReceiveSection({
126
138
 
127
139
  // Handle edge cases
128
140
  if (input === 0 || isNaN(input) || isNaN(output) || input <= output) {
129
- return { percentage: "0.00", isNegative: false };
141
+ return { percentage: "0.00", percentageNum: 0, isNegative: false };
130
142
  }
131
143
 
132
144
  const percentageValue = ((output - input) / input) * 100;
133
145
 
134
146
  // Handle the -0.00% case
135
147
  if (percentageValue > -0.005 && percentageValue < 0) {
136
- return { percentage: "0.00", isNegative: false };
148
+ return { percentage: "0.00", percentageNum: 0, isNegative: false };
137
149
  }
138
150
 
139
151
  return {
140
152
  percentage: Math.abs(percentageValue).toFixed(2),
153
+ percentageNum: Math.abs(percentageValue),
141
154
  isNegative: percentageValue < 0,
142
155
  };
143
156
  };
144
157
 
145
- const { percentage, isNegative } = calculatePriceImpact(
158
+ const { percentage, percentageNum, isNegative } = calculatePriceImpact(
146
159
  anyspendQuote.data.currencyIn.amountUsd,
147
160
  anyspendQuote.data.currencyOut.amountUsd,
148
161
  );
149
162
 
150
- // Parse the percentage as a number for comparison
151
- const percentageNum = parseFloat(percentage);
163
+ // Get the fee percentage if available
164
+ const feePercent = anyspendQuote.data.fee?.finalFeeBps ? anyspendQuote.data.fee.finalFeeBps / 100 : 0;
165
+
166
+ // Calculate actual slippage (price impact minus fee)
167
+ const actualSlippage = percentageNum - feePercent;
168
+
169
+ // Show warning based on actual slippage, not total impact
170
+ const yellowThreshold = 1; // 1% actual slippage
171
+ const redThreshold = 2; // 2% actual slippage
152
172
 
153
- // Don't show if less than 1%
154
- if (percentageNum < 1) {
173
+ // Don't show if actual slippage is less than yellow threshold
174
+ if (actualSlippage < yellowThreshold) {
155
175
  return null;
156
176
  }
157
177
 
158
178
  // Using inline style to ensure color displays
159
179
  return (
160
- <span className="ml-2" style={{ color: percentageNum >= 10 ? "red" : "#FFD700" }}>
180
+ <span className="ml-2" style={{ color: actualSlippage >= redThreshold ? "red" : "#FFD700" }}>
161
181
  ({isNegative ? "-" : ""}
162
182
  {percentage}%)
163
183
  </span>
@@ -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
+ }
@@ -0,0 +1,334 @@
1
+ import { ShinyButton } from "@b3dotfun/sdk/global-account/react";
2
+ import { cn } from "@b3dotfun/sdk/shared/utils/cn";
3
+ import { ChevronDown } from "lucide-react";
4
+ import { useState } from "react";
5
+ import { components } from "../../../types/api";
6
+
7
+ interface FeeDetailPanelProps {
8
+ fee: components["schemas"]["Fee"];
9
+ transactionAmountUsd?: number;
10
+ onBack: () => void;
11
+ }
12
+
13
+ // Fee tier definitions
14
+ const CRYPTO_FEE_TIERS = [
15
+ { min: 0, max: 10, bps: 120, label: "$0 – $10" },
16
+ { min: 10, max: 500, bps: 80, label: "$10 – $500" },
17
+ { min: 500, max: 5000, bps: 60, label: "$500 – $5,000" },
18
+ { min: 5000, max: 50000, bps: 40, label: "$5,000 – $50,000" },
19
+ { min: 50000, max: Infinity, bps: 28, label: "$50,000+" },
20
+ ];
21
+
22
+ const FIAT_FEE_TIERS = [
23
+ { min: 0.01, max: 25, fee: "$1", label: "$0.01 – $25" },
24
+ { min: 25.01, max: 250, fee: "2%", label: "$25.01 – $250" },
25
+ { min: 251, max: Infinity, fee: "3%", label: "$251+" },
26
+ ];
27
+
28
+ // Whale discount tiers based on $ANY holdings
29
+ const WHALE_DISCOUNT_TIERS = [
30
+ { minAny: 100000, discountPercent: 50, label: "Tier 1: 100k $ANY" },
31
+ { minAny: 500000, discountPercent: 75, label: "Tier 2: 500k $ANY" },
32
+ { minAny: 1000000, discountPercent: 100, label: "Tier 3: 1M+ $ANY" },
33
+ ];
34
+
35
+ export function FeeDetailPanel({ fee, transactionAmountUsd, onBack }: FeeDetailPanelProps) {
36
+ // Detect if this is a fiat onramp order (Stripe) vs regular crypto swap
37
+ // stripeweb2_fee = Stripe/fiat onramp (uses FIAT_FEE_TIERS)
38
+ // standard_fee = Regular crypto swap (uses CRYPTO_FEE_TIERS)
39
+ const isStripeFee = fee.type === "stripeweb2_fee";
40
+
41
+ // Convert basis points to percentage
42
+ const bpsToPercent = (bps: number) => (bps / 100).toFixed(2);
43
+
44
+ // Check if discount is active
45
+ const hasWhaleDiscount = fee.anyspendWhaleDiscountBps > 0;
46
+ const hasPartnerDiscount = fee.anyspendPartnerDiscountBps > 0;
47
+
48
+ // Find current tier based on transaction amount
49
+ const getCurrentCryptoTier = (amount?: number) => {
50
+ if (!amount) return null;
51
+ return CRYPTO_FEE_TIERS.find(tier => amount >= tier.min && amount < tier.max);
52
+ };
53
+
54
+ const getCurrentFiatTier = (amount?: number) => {
55
+ if (!amount) return null;
56
+ return FIAT_FEE_TIERS.find(tier => amount >= tier.min && amount < tier.max);
57
+ };
58
+
59
+ const currentCryptoTier = getCurrentCryptoTier(transactionAmountUsd);
60
+ const currentFiatTier = getCurrentFiatTier(transactionAmountUsd);
61
+
62
+ // The whale discount is a percentage discount on the base fee itself
63
+ // Example: 50% discount on 80 bps fee = 40 bps discount, final fee = 40 bps
64
+ // So: finalFee = baseFee - (baseFee * discountPercent / 100)
65
+ // Which means: discountPercent = ((baseFee - finalFee) / baseFee) * 100
66
+ const baseFee = fee.type === "standard_fee" ? fee.anyspendFeeBps : 0;
67
+
68
+ // The whale discount percentage (50%, 75%, or 100%)
69
+ const whaleDiscountPercent =
70
+ baseFee > 0 && hasWhaleDiscount ? Math.round(((baseFee - fee.finalFeeBps) / baseFee) * 100) : 0;
71
+
72
+ // Determine which whale tier based on the discount percentage
73
+ const currentWhaleTier = WHALE_DISCOUNT_TIERS.find(
74
+ tier => Math.abs(whaleDiscountPercent - tier.discountPercent) <= 5,
75
+ );
76
+
77
+ // Calculate partner discount percentage
78
+ const partnerDiscountPercent =
79
+ baseFee > 0 && hasPartnerDiscount ? Math.round((fee.anyspendPartnerDiscountBps / baseFee) * 100) : 0;
80
+
81
+ // State for expanding tier lists
82
+ const [showAllFeeTiers, setShowAllFeeTiers] = useState(false);
83
+ const [showAllDiscountTiers, setShowAllDiscountTiers] = useState(false);
84
+
85
+ return (
86
+ <div className="mx-auto flex w-[460px] max-w-full flex-col items-center gap-3">
87
+ <div className="flex w-full flex-col gap-3">
88
+ <div className="text-center">
89
+ <h3 className="text-as-primary text-lg font-bold">Fee Breakdown</h3>
90
+ </div>
91
+
92
+ {/* Base Fee Schedule Section */}
93
+ <div className="bg-as-surface-secondary border-as-border-secondary rounded-2xl border p-4">
94
+ <h4 className="text-as-primary mb-3 text-sm font-semibold">
95
+ {isStripeFee ? "Fiat Fee Schedule" : "Base Fee Schedule"}
96
+ </h4>
97
+ <div className="space-y-1.5">
98
+ {isStripeFee
99
+ ? FIAT_FEE_TIERS.map((tier, idx) => {
100
+ const isCurrentTier = currentFiatTier?.label === tier.label;
101
+ const currentTierIndex = FIAT_FEE_TIERS.findIndex(t => t.label === currentFiatTier?.label);
102
+
103
+ // Show all tiers if expanded, otherwise show up to current tier
104
+ if (!showAllFeeTiers && currentTierIndex !== -1 && idx > currentTierIndex) {
105
+ return null;
106
+ }
107
+
108
+ return (
109
+ <div
110
+ key={idx}
111
+ className={cn(
112
+ "flex items-center justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
113
+ isCurrentTier ? "bg-as-brand/10 text-as-brand font-semibold" : "text-as-primary/60",
114
+ )}
115
+ >
116
+ <span>{tier.label}</span>
117
+ <span>Credit Card Fee + {tier.fee}</span>
118
+ </div>
119
+ );
120
+ })
121
+ : CRYPTO_FEE_TIERS.map((tier, idx) => {
122
+ const isCurrentTier = currentCryptoTier?.label === tier.label;
123
+ const currentTierIndex = CRYPTO_FEE_TIERS.findIndex(t => t.label === currentCryptoTier?.label);
124
+
125
+ // Show all tiers if expanded, otherwise show up to current tier
126
+ if (!showAllFeeTiers && currentTierIndex !== -1 && idx > currentTierIndex) {
127
+ return null;
128
+ }
129
+
130
+ return (
131
+ <div
132
+ key={idx}
133
+ className={cn(
134
+ "flex items-center justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
135
+ isCurrentTier ? "bg-as-brand/10 text-as-brand font-semibold" : "text-as-primary/60",
136
+ )}
137
+ >
138
+ <span>{tier.label}</span>
139
+ <span>{bpsToPercent(tier.bps)}%</span>
140
+ </div>
141
+ );
142
+ })}
143
+ </div>
144
+
145
+ {/* Show expand button if there are lower tiers */}
146
+ {(() => {
147
+ const currentTierIndex = isStripeFee
148
+ ? FIAT_FEE_TIERS.findIndex(t => t.label === currentFiatTier?.label)
149
+ : CRYPTO_FEE_TIERS.findIndex(t => t.label === currentCryptoTier?.label);
150
+ const totalTiers = isStripeFee ? FIAT_FEE_TIERS.length : CRYPTO_FEE_TIERS.length;
151
+ const hasMoreTiers = currentTierIndex !== -1 && currentTierIndex < totalTiers - 1;
152
+
153
+ if (hasMoreTiers) {
154
+ return (
155
+ <button
156
+ onClick={() => setShowAllFeeTiers(!showAllFeeTiers)}
157
+ className="text-as-primary/60 hover:text-as-primary mt-2 flex w-full items-center justify-center gap-1 text-xs transition-colors"
158
+ >
159
+ <span>{showAllFeeTiers ? "Show less" : "Show higher tiers"}</span>
160
+ <ChevronDown className={cn("h-3.5 w-3.5 transition-transform", showAllFeeTiers && "rotate-180")} />
161
+ </button>
162
+ );
163
+ }
164
+ return null;
165
+ })()}
166
+ </div>
167
+
168
+ {/* Whale Discount Tiers - Always show for crypto (not fiat) */}
169
+ {!isStripeFee && (
170
+ <div className="bg-as-surface-secondary border-as-border-secondary rounded-2xl border p-4">
171
+ <h4 className="text-as-primary mb-3 text-sm font-semibold">
172
+ {hasWhaleDiscount ? "Whale Discount Tiers" : hasPartnerDiscount ? "Partner Discount" : "Discount Tiers"}
173
+ </h4>
174
+ <div className="space-y-1.5">
175
+ {hasPartnerDiscount && !hasWhaleDiscount ? (
176
+ <div className="flex items-center justify-between rounded-lg bg-green-500/10 px-3 py-2.5 text-sm font-semibold text-green-600">
177
+ <span>Partner Discount</span>
178
+ <span>{partnerDiscountPercent}% discount</span>
179
+ </div>
180
+ ) : (
181
+ <>
182
+ {WHALE_DISCOUNT_TIERS.map((tier, idx) => {
183
+ const isCurrentTier = currentWhaleTier?.label === tier.label;
184
+ const currentTierIndex = WHALE_DISCOUNT_TIERS.findIndex(t => t.label === currentWhaleTier?.label);
185
+
186
+ // If no whale discount, show only first tier; otherwise show up to current tier
187
+ if (!showAllDiscountTiers) {
188
+ if (!hasWhaleDiscount && idx > 0) {
189
+ return null;
190
+ }
191
+ if (hasWhaleDiscount && currentTierIndex !== -1 && idx > currentTierIndex) {
192
+ return null;
193
+ }
194
+ }
195
+
196
+ return (
197
+ <div
198
+ key={idx}
199
+ className={cn(
200
+ "flex items-center justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
201
+ isCurrentTier ? "bg-green-500/10 font-semibold text-green-600" : "text-as-primary/60",
202
+ )}
203
+ >
204
+ <span>{tier.label}</span>
205
+ <span>{tier.discountPercent}% discount</span>
206
+ </div>
207
+ );
208
+ })}
209
+
210
+ {/* Show expand button */}
211
+ {(() => {
212
+ const currentTierIndex = WHALE_DISCOUNT_TIERS.findIndex(t => t.label === currentWhaleTier?.label);
213
+ const hasMoreTiers = hasWhaleDiscount
214
+ ? currentTierIndex !== -1 && currentTierIndex < WHALE_DISCOUNT_TIERS.length - 1
215
+ : WHALE_DISCOUNT_TIERS.length > 1;
216
+
217
+ if (hasMoreTiers) {
218
+ return (
219
+ <button
220
+ onClick={() => setShowAllDiscountTiers(!showAllDiscountTiers)}
221
+ className="text-as-primary/60 hover:text-as-primary mt-2 flex w-full items-center justify-center gap-1 text-xs transition-colors"
222
+ >
223
+ <span>{showAllDiscountTiers ? "Show less" : "Show all tiers"}</span>
224
+ <ChevronDown
225
+ className={cn("h-3.5 w-3.5 transition-transform", showAllDiscountTiers && "rotate-180")}
226
+ />
227
+ </button>
228
+ );
229
+ }
230
+ return null;
231
+ })()}
232
+ </>
233
+ )}
234
+ </div>
235
+ </div>
236
+ )}
237
+
238
+ {/* Transaction Summary */}
239
+ {transactionAmountUsd && (
240
+ <div className="bg-as-surface-secondary border-as-border-secondary rounded-2xl border p-4">
241
+ <div className="space-y-2 text-sm">
242
+ <div className="flex items-center justify-between">
243
+ <span className="text-as-secondary">Transaction</span>
244
+ <span className="text-as-primary font-semibold">${transactionAmountUsd.toFixed(2)}</span>
245
+ </div>
246
+
247
+ {isStripeFee && (
248
+ <>
249
+ <div className="flex items-center justify-between">
250
+ <span className="text-as-secondary">
251
+ Credit Card Fee ({fee.stripeFeeBps ? `${bpsToPercent(fee.stripeFeeBps)}%` : "0%"} + $
252
+ {fee.stripeFeeUsd?.toFixed(2) || "0.00"})
253
+ </span>
254
+ <span className="text-as-primary font-medium">
255
+ ${((transactionAmountUsd * (fee.stripeFeeBps || 0)) / 10000 + (fee.stripeFeeUsd || 0)).toFixed(2)}
256
+ </span>
257
+ </div>
258
+ <div className="flex items-center justify-between">
259
+ <span className="text-as-secondary">
260
+ AnySpend Fee ({fee.anyspendFeeBps ? `${bpsToPercent(fee.anyspendFeeBps)}%` : "0%"}
261
+ {fee.anyspendFeeUsd && fee.anyspendFeeUsd > 0 ? ` + $${fee.anyspendFeeUsd.toFixed(2)}` : ""})
262
+ </span>
263
+ <span className="text-as-primary font-medium">
264
+ $
265
+ {((transactionAmountUsd * (fee.anyspendFeeBps || 0)) / 10000 + (fee.anyspendFeeUsd || 0)).toFixed(
266
+ 2,
267
+ )}
268
+ </span>
269
+ </div>
270
+ <div className="border-as-border-secondary border-t pt-2">
271
+ <div className="flex items-center justify-between">
272
+ <span className="text-as-primary font-semibold">Total Fee</span>
273
+ <span className="text-as-brand font-semibold">
274
+ ${((transactionAmountUsd * (fee.finalFeeBps || 0)) / 10000 + (fee.finalFeeUsd || 0)).toFixed(2)}
275
+ </span>
276
+ </div>
277
+ </div>
278
+ </>
279
+ )}
280
+
281
+ {!isStripeFee && currentCryptoTier && (
282
+ <>
283
+ <div className="flex items-center justify-between">
284
+ <span className="text-as-secondary">Base Fee ({bpsToPercent(currentCryptoTier.bps)}%)</span>
285
+ <span className="text-as-primary font-medium">
286
+ ${((transactionAmountUsd * currentCryptoTier.bps) / 10000).toFixed(2)}
287
+ </span>
288
+ </div>
289
+
290
+ {hasWhaleDiscount && currentWhaleTier && (
291
+ <div className="flex items-center justify-between">
292
+ <span className="text-green-600">Discount ({currentWhaleTier.discountPercent}% off)</span>
293
+ <span className="font-medium text-green-600">
294
+ -$
295
+ {((transactionAmountUsd * baseFee * currentWhaleTier.discountPercent) / 100 / 10000).toFixed(2)}
296
+ </span>
297
+ </div>
298
+ )}
299
+
300
+ {hasPartnerDiscount && (
301
+ <div className="flex items-center justify-between">
302
+ <span className="text-green-600">Partner Discount ({partnerDiscountPercent}% off)</span>
303
+ <span className="font-medium text-green-600">
304
+ -${((transactionAmountUsd * baseFee * partnerDiscountPercent) / 100 / 10000).toFixed(2)}
305
+ </span>
306
+ </div>
307
+ )}
308
+
309
+ <div className="border-as-border-secondary border-t pt-2">
310
+ <div className="flex items-center justify-between">
311
+ <span className="text-as-primary font-semibold">Total Fee</span>
312
+ <span className="text-as-brand font-semibold">
313
+ ${((transactionAmountUsd * fee.finalFeeBps) / 10000).toFixed(2)}
314
+ </span>
315
+ </div>
316
+ </div>
317
+ </>
318
+ )}
319
+ </div>
320
+ </div>
321
+ )}
322
+
323
+ <ShinyButton
324
+ accentColor={"hsl(var(--as-brand))"}
325
+ onClick={onBack}
326
+ className={cn("as-main-button !bg-as-brand relative w-full")}
327
+ textClassName={cn("text-white")}
328
+ >
329
+ Back to {isStripeFee ? "Payment" : "Swap"}
330
+ </ShinyButton>
331
+ </div>
332
+ </div>
333
+ );
334
+ }
@@ -68,6 +68,7 @@ interface OrderDetailsProps {
68
68
  onPaymentMethodChange?: (method: CryptoPaymentMethodType) => void; // Callback for payment method switching
69
69
  onBack?: () => void;
70
70
  disableUrlParamManagement?: boolean; // When true, will not modify URL parameters
71
+ points?: number | undefined; // Points earned from the transaction
71
72
  }
72
73
 
73
74
  // Add this helper function near the top or just above the component
@@ -212,6 +213,7 @@ export const OrderDetails = memo(function OrderDetails({
212
213
  onPaymentMethodChange,
213
214
  onBack,
214
215
  disableUrlParamManagement = false,
216
+ points,
215
217
  }: OrderDetailsProps) {
216
218
  const router = useRouter();
217
219
  const searchParams = useSearchParams();
@@ -576,6 +578,7 @@ export const OrderDetails = memo(function OrderDetails({
576
578
  nft={nft}
577
579
  recipientName={recipientName}
578
580
  formattedExpectedDstAmount={formattedExpectedDstAmount}
581
+ points={points}
579
582
  />
580
583
  <Accordion type="single" collapsible className="order-details-accordion w-full">
581
584
  <AccordionItem value="refund-details" className="order-details-refund-item">
@@ -654,6 +657,7 @@ export const OrderDetails = memo(function OrderDetails({
654
657
  nft={nft}
655
658
  recipientName={recipientName}
656
659
  formattedExpectedDstAmount={formattedExpectedDstAmount}
660
+ points={points}
657
661
  />
658
662
  <Accordion type="single" collapsible className="order-details-accordion w-full">
659
663
  <AccordionItem value="execute-details" className="order-details-execute-item">
@@ -781,6 +785,7 @@ export const OrderDetails = memo(function OrderDetails({
781
785
  nft={nft}
782
786
  recipientName={recipientName}
783
787
  formattedExpectedDstAmount={formattedExpectedDstAmount}
788
+ points={points}
784
789
  />
785
790
  <Accordion type="single" collapsible className="order-details-accordion w-full">
786
791
  <AccordionItem value="more-details" className="order-details-more-item">
@@ -910,6 +915,7 @@ export const OrderDetails = memo(function OrderDetails({
910
915
  nft={nft}
911
916
  recipientName={recipientName}
912
917
  formattedExpectedDstAmount={formattedExpectedDstAmount}
918
+ points={points}
913
919
  />
914
920
  <Accordion type="single" collapsible className="order-details-accordion w-full">
915
921
  <AccordionItem value="deposit-details" className="order-details-deposit-item">
@@ -1190,6 +1196,7 @@ export const OrderDetails = memo(function OrderDetails({
1190
1196
  nft={nft}
1191
1197
  recipientName={recipientName}
1192
1198
  formattedExpectedDstAmount={formattedExpectedDstAmount}
1199
+ points={points}
1193
1200
  />
1194
1201
  )}
1195
1202
 
@@ -5,6 +5,7 @@ import { components } from "@b3dotfun/sdk/anyspend/types/api";
5
5
  import { CopyToClipboard } from "@b3dotfun/sdk/global-account/react";
6
6
  import { cn } from "@b3dotfun/sdk/shared/utils";
7
7
  import centerTruncate from "@b3dotfun/sdk/shared/utils/centerTruncate";
8
+ import { formatNumber } from "@b3dotfun/sdk/shared/utils/formatNumber";
8
9
  import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
9
10
  import { ChevronDown, Copy } from "lucide-react";
10
11
  import { motion } from "motion/react";
@@ -27,6 +28,7 @@ interface OrderDetailsCollapsibleProps {
27
28
  className?: string;
28
29
  showTotal?: boolean;
29
30
  totalAmount?: string;
31
+ points?: number;
30
32
  }
31
33
 
32
34
  export const OrderDetailsCollapsible = memo(function OrderDetailsCollapsible({
@@ -39,6 +41,7 @@ export const OrderDetailsCollapsible = memo(function OrderDetailsCollapsible({
39
41
  className,
40
42
  showTotal = false,
41
43
  totalAmount,
44
+ points,
42
45
  }: OrderDetailsCollapsibleProps) {
43
46
  const [showOrderDetails, setShowOrderDetails] = useState(true);
44
47
 
@@ -150,6 +153,19 @@ export const OrderDetailsCollapsible = memo(function OrderDetailsCollapsible({
150
153
  </div>
151
154
  </div>
152
155
 
156
+ {points !== undefined && points !== null && (
157
+ <>
158
+ <div className="order-details-divider divider w-full" />
159
+ {/* Points Section */}
160
+ <div className="order-details-points-section flex w-full justify-between gap-4">
161
+ <div className="order-details-points-label text-as-tertiarry">Points</div>
162
+ <div className="order-details-points-value text-as-brand font-semibold">
163
+ +{formatNumber(points)} pts
164
+ </div>
165
+ </div>
166
+ </>
167
+ )}
168
+
153
169
  <div className="order-details-divider divider w-full" />
154
170
 
155
171
  {/* Order ID / Total Section */}
@@ -5,7 +5,7 @@ import { Button, useAccountWallet, useTokenBalancesByChain } from "@b3dotfun/sdk
5
5
  import { cn } from "@b3dotfun/sdk/shared/utils";
6
6
  import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
7
7
  import { simpleHashChainToChainName } from "@b3dotfun/sdk/shared/utils/simplehash";
8
- import { TokenSelector } from "@reservoir0x/relay-kit-ui";
8
+ import { TokenSelector } from "@relayprotocol/relay-kit-ui";
9
9
  import { CheckCircle2, ChevronsUpDown } from "lucide-react";
10
10
  import { useMemo } from "react";
11
11
  import { ChainTokenIcon } from "./ChainTokenIcon";