@b3dotfun/sdk 0.0.49-alpha.0 → 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 (66) 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/PanelOnramp.d.ts +2 -1
  14. package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +36 -21
  15. package/dist/cjs/anyspend/react/components/common/PanelOnrampPayment.js +2 -1
  16. package/dist/cjs/anyspend/react/components/common/PointsDetailPanel.js +1 -2
  17. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
  18. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +1 -0
  19. package/dist/cjs/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +24 -0
  20. package/dist/cjs/anyspend/types/api.d.ts +119 -176
  21. package/dist/cjs/global-account/react/components/ui/tooltip.js +1 -1
  22. package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -1
  23. package/dist/esm/anyspend/react/components/AnySpend.js +8 -4
  24. package/dist/esm/anyspend/react/components/AnySpendCustom.js +14 -5
  25. package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +10 -3
  26. package/dist/esm/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  27. package/dist/esm/anyspend/react/components/common/CryptoPaySection.js +4 -4
  28. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  29. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +4 -4
  30. package/dist/esm/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
  31. package/dist/esm/anyspend/react/components/common/FeeBreakDown.js +16 -0
  32. package/dist/esm/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
  33. package/dist/esm/anyspend/react/components/common/FeeDetailPanel.js +113 -0
  34. package/dist/esm/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  35. package/dist/esm/anyspend/react/components/common/PanelOnramp.js +38 -23
  36. package/dist/esm/anyspend/react/components/common/PanelOnrampPayment.js +2 -1
  37. package/dist/esm/anyspend/react/components/common/PointsDetailPanel.js +2 -3
  38. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
  39. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +1 -0
  40. package/dist/esm/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +24 -0
  41. package/dist/esm/anyspend/types/api.d.ts +119 -176
  42. package/dist/esm/global-account/react/components/ui/tooltip.js +1 -1
  43. package/dist/styles/index.css +1 -1
  44. package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -1
  45. package/dist/types/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  46. package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  47. package/dist/types/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
  48. package/dist/types/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
  49. package/dist/types/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  50. package/dist/types/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
  51. package/dist/types/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +24 -0
  52. package/dist/types/anyspend/types/api.d.ts +119 -176
  53. package/package.json +1 -1
  54. package/src/anyspend/react/components/AnySpend.tsx +20 -0
  55. package/src/anyspend/react/components/AnySpendCustom.tsx +69 -7
  56. package/src/anyspend/react/components/AnyspendDepositHype.tsx +23 -0
  57. package/src/anyspend/react/components/common/CryptoPaySection.tsx +14 -2
  58. package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +14 -2
  59. package/src/anyspend/react/components/common/FeeBreakDown.tsx +105 -0
  60. package/src/anyspend/react/components/common/FeeDetailPanel.tsx +307 -0
  61. package/src/anyspend/react/components/common/PanelOnramp.tsx +58 -27
  62. package/src/anyspend/react/components/common/PanelOnrampPayment.tsx +18 -6
  63. package/src/anyspend/react/components/common/PointsDetailPanel.tsx +1 -13
  64. package/src/anyspend/react/hooks/useAnyspendFlow.ts +1 -0
  65. package/src/anyspend/types/api.ts +121 -176
  66. package/src/global-account/react/components/ui/tooltip.tsx +11 -9
@@ -0,0 +1,307 @@
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>CC 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 && currentFiatTier && (
248
+ <div className="flex items-center justify-between">
249
+ <span className="text-as-secondary">{currentFiatTier.label}</span>
250
+ <span className="text-as-primary">CC Fee + {currentFiatTier.fee}</span>
251
+ </div>
252
+ )}
253
+
254
+ {!isStripeFee && currentCryptoTier && (
255
+ <>
256
+ <div className="flex items-center justify-between">
257
+ <span className="text-as-secondary">Base Fee ({bpsToPercent(currentCryptoTier.bps)}%)</span>
258
+ <span className="text-as-primary font-medium">
259
+ ${((transactionAmountUsd * currentCryptoTier.bps) / 10000).toFixed(2)}
260
+ </span>
261
+ </div>
262
+
263
+ {hasWhaleDiscount && currentWhaleTier && (
264
+ <div className="flex items-center justify-between">
265
+ <span className="text-green-600">Discount ({currentWhaleTier.discountPercent}% off)</span>
266
+ <span className="font-medium text-green-600">
267
+ -$
268
+ {((transactionAmountUsd * baseFee * currentWhaleTier.discountPercent) / 100 / 10000).toFixed(2)}
269
+ </span>
270
+ </div>
271
+ )}
272
+
273
+ {hasPartnerDiscount && (
274
+ <div className="flex items-center justify-between">
275
+ <span className="text-green-600">Partner Discount ({partnerDiscountPercent}% off)</span>
276
+ <span className="font-medium text-green-600">
277
+ -${((transactionAmountUsd * baseFee * partnerDiscountPercent) / 100 / 10000).toFixed(2)}
278
+ </span>
279
+ </div>
280
+ )}
281
+
282
+ <div className="border-as-border-secondary border-t pt-2">
283
+ <div className="flex items-center justify-between">
284
+ <span className="text-as-primary font-semibold">Total Fee</span>
285
+ <span className="text-as-brand font-semibold">
286
+ ${((transactionAmountUsd * fee.finalFeeBps) / 10000).toFixed(2)}
287
+ </span>
288
+ </div>
289
+ </div>
290
+ </>
291
+ )}
292
+ </div>
293
+ </div>
294
+ )}
295
+
296
+ <ShinyButton
297
+ accentColor={"hsl(var(--as-brand))"}
298
+ onClick={onBack}
299
+ className={cn("as-main-button !bg-as-brand relative w-full")}
300
+ textClassName={cn("text-white")}
301
+ >
302
+ Back to {isStripeFee ? "Payment" : "Swap"}
303
+ </ShinyButton>
304
+ </div>
305
+ </div>
306
+ );
307
+ }
@@ -1,11 +1,11 @@
1
- import { useCoinbaseOnrampOptions, useGeoOnrampOptions } from "@b3dotfun/sdk/anyspend/react";
1
+ import { useCoinbaseOnrampOptions } from "@b3dotfun/sdk/anyspend/react";
2
2
  import { components } from "@b3dotfun/sdk/anyspend/types/api";
3
3
  import { GetQuoteResponse } from "@b3dotfun/sdk/anyspend/types/api_req_res";
4
4
  import { ALL_CHAINS } from "@b3dotfun/sdk/anyspend/utils/chain";
5
5
  import { Input, useGetGeo, useProfile } from "@b3dotfun/sdk/global-account/react";
6
6
  import { cn, formatUsername } from "@b3dotfun/sdk/shared/utils";
7
7
  import { formatAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
8
- import { ChevronRight, Wallet } from "lucide-react";
8
+ import { ChevronRight, Info, Wallet } from "lucide-react";
9
9
  import { useRef } from "react";
10
10
  import { toast } from "sonner";
11
11
  import { useFeatureFlags } from "../../contexts/FeatureFlagsContext";
@@ -30,6 +30,7 @@ export function PanelOnramp({
30
30
  hideDstToken = false,
31
31
  anyspendQuote,
32
32
  onShowPointsDetail,
33
+ onShowFeeDetail,
33
34
  customUsdInputValues = ["5", "10", "20", "25"],
34
35
  }: {
35
36
  srcAmountOnRamp: string;
@@ -48,39 +49,46 @@ export function PanelOnramp({
48
49
  hideDstToken?: boolean;
49
50
  anyspendQuote?: GetQuoteResponse;
50
51
  onShowPointsDetail?: () => void;
52
+ onShowFeeDetail?: () => void;
51
53
  customUsdInputValues?: string[];
52
54
  }) {
53
55
  const featureFlags = useFeatureFlags();
54
- // Get geo-based onramp options to access fee information
55
- const { stripeWeb2Support } = useGeoOnrampOptions(srcAmountOnRamp);
56
56
 
57
- // Helper function to get fees from API data
57
+ // Helper function to get fees from anyspend quote
58
58
  const getFeeFromApi = (paymentMethod: FiatPaymentMethod): number | null => {
59
+ // Try to get fee from anyspend quote first (most accurate)
60
+ if (anyspendQuote?.data?.fee) {
61
+ const fee = anyspendQuote.data.fee;
62
+ if (fee.type === "stripeweb2_fee") {
63
+ // Calculate total fee in USD from originalAmount - finalAmount
64
+ const originalAmount = Number(fee.originalAmount) / 1e6; // Convert from wei to USD
65
+ const finalAmount = Number(fee.finalAmount) / 1e6;
66
+ return originalAmount - finalAmount;
67
+ }
68
+ }
69
+
70
+ // Fallback to payment method defaults
59
71
  switch (paymentMethod) {
60
72
  case FiatPaymentMethod.COINBASE_PAY:
61
- // Coinbase doesn't provide fee info in API, return 0
62
- return 0;
73
+ return 0; // Coinbase has no additional fees
63
74
  case FiatPaymentMethod.STRIPE:
64
- // Get fee from Stripe API response
65
- if (stripeWeb2Support && "formattedFeeUsd" in stripeWeb2Support) {
66
- return parseFloat(stripeWeb2Support.formattedFeeUsd) || 0;
67
- }
68
- return null;
75
+ return null; // No quote available yet
69
76
  default:
70
- return null; // No fee when no payment method selected
77
+ return null;
71
78
  }
72
79
  };
73
80
 
74
81
  // Helper function to get total amount from API (for Stripe) or calculate it (for others)
75
82
  const getTotalAmount = (paymentMethod: FiatPaymentMethod): number => {
76
83
  const baseAmount = parseFloat(srcAmountOnRamp) || 5;
77
- const fee = getFeeFromApi(paymentMethod);
78
84
 
79
- if (paymentMethod === FiatPaymentMethod.STRIPE && stripeWeb2Support && "formattedTotalUsd" in stripeWeb2Support) {
80
- // Use the total from Stripe API if available
81
- return parseFloat(stripeWeb2Support.formattedTotalUsd) || baseAmount;
85
+ // Try to get from anyspend quote first (most accurate)
86
+ if (anyspendQuote?.data?.fee?.type === "stripeweb2_fee") {
87
+ return Number(anyspendQuote.data.fee.originalAmount) / 1e6; // Convert from wei to USD
82
88
  }
83
89
 
90
+ const fee = getFeeFromApi(paymentMethod);
91
+
84
92
  // For Coinbase or when fee is available, calculate manually
85
93
  if (fee !== null) {
86
94
  return baseAmount + fee;
@@ -257,13 +265,16 @@ export function PanelOnramp({
257
265
 
258
266
  <div className="">
259
267
  <div className="flex items-center justify-between">
260
- <div className="flex items-center gap-2">
261
- <span className="text-as-tertiarry text-sm">
262
- {(() => {
263
- const fee = getFeeFromApi(selectedPaymentMethod || FiatPaymentMethod.NONE);
264
- return fee !== null ? `Total (included $${fee.toFixed(2)} fee)` : "Total";
265
- })()}
266
- </span>
268
+ <div className="flex items-center gap-1.5">
269
+ <span className="text-as-tertiarry text-sm">Total</span>
270
+ {anyspendQuote?.data?.fee && onShowFeeDetail && (
271
+ <button
272
+ onClick={onShowFeeDetail}
273
+ className="text-as-primary/40 hover:text-as-primary/60 transition-colors"
274
+ >
275
+ <Info className="h-4 w-4" />
276
+ </button>
277
+ )}
267
278
  {featureFlags.showPoints &&
268
279
  anyspendQuote?.data?.pointsAmount &&
269
280
  anyspendQuote?.data?.pointsAmount > 0 && (
@@ -274,9 +285,29 @@ export function PanelOnramp({
274
285
  />
275
286
  )}
276
287
  </div>
277
- <span className="text-as-primary font-semibold">
278
- ${getTotalAmount(selectedPaymentMethod || FiatPaymentMethod.NONE).toFixed(2)}
279
- </span>
288
+ <div className="flex flex-col items-end gap-0.5">
289
+ <span className="text-as-primary font-semibold">
290
+ ${getTotalAmount(selectedPaymentMethod || FiatPaymentMethod.NONE).toFixed(2)}
291
+ </span>
292
+ {(() => {
293
+ // For fiat payments, show the fee from the payment method
294
+ const fiatFee = getFeeFromApi(selectedPaymentMethod || FiatPaymentMethod.NONE);
295
+ if (fiatFee !== null && fiatFee > 0) {
296
+ return <span className="text-as-secondary text-xs">incl. ${fiatFee.toFixed(2)} fee</span>;
297
+ }
298
+
299
+ // For crypto payments (standard_fee), calculate from the quote
300
+ if (anyspendQuote?.data?.fee?.type === "standard_fee" && anyspendQuote.data.currencyIn?.amountUsd) {
301
+ const cryptoFee =
302
+ (Number(anyspendQuote.data.currencyIn.amountUsd) * anyspendQuote.data.fee.finalFeeBps) / 10000;
303
+ if (cryptoFee > 0) {
304
+ return <span className="text-as-secondary text-xs">incl. ${cryptoFee.toFixed(2)} fee</span>;
305
+ }
306
+ }
307
+
308
+ return null;
309
+ })()}
310
+ </div>
280
311
  </div>
281
312
  </div>
282
313
  </div>
@@ -194,12 +194,24 @@ function PanelOnrampPaymentInner(props: PanelOnrampPaymentProps) {
194
194
  <div className="border-b3-react-border border-t pt-3">
195
195
  <div className="flex items-center justify-between">
196
196
  <p className="text-b3-react-foreground font-semibold">Amount</p>
197
- <p
198
- className="text-b3-react-foreground hover:text-b3-react-foreground/80 cursor-pointer text-xl font-semibold transition-colors"
199
- onClick={onBack}
200
- >
201
- ${parseFloat(srcAmountOnRamp).toFixed(2)}
202
- </p>
197
+ <div className="flex flex-col items-end gap-0.5">
198
+ <p
199
+ className="text-b3-react-foreground hover:text-b3-react-foreground/80 cursor-pointer text-xl font-semibold transition-colors"
200
+ onClick={onBack}
201
+ >
202
+ ${parseFloat(srcAmountOnRamp).toFixed(2)}
203
+ </p>
204
+ {anyspendQuote?.data?.fee?.type === "standard_fee" && anyspendQuote.data.currencyIn?.amountUsd && (
205
+ <p className="text-b3-react-foreground/60 text-xs">
206
+ incl. $
207
+ {(
208
+ (Number(anyspendQuote.data.currencyIn.amountUsd) * anyspendQuote.data.fee.finalFeeBps) /
209
+ 10000
210
+ ).toFixed(2)}{" "}
211
+ fee
212
+ </p>
213
+ )}
214
+ </div>
203
215
  </div>
204
216
  </div>
205
217
  </div>
@@ -1,6 +1,5 @@
1
- import { Button, ShinyButton } from "@b3dotfun/sdk/global-account/react";
1
+ import { ShinyButton } from "@b3dotfun/sdk/global-account/react";
2
2
  import { cn } from "@b3dotfun/sdk/shared/utils/cn";
3
- import { ArrowDown } from "lucide-react";
4
3
  import Link from "next/link";
5
4
 
6
5
  interface PointsDetailPanelProps {
@@ -11,17 +10,6 @@ interface PointsDetailPanelProps {
11
10
  export function PointsDetailPanel({ pointsAmount = 0, onBack }: PointsDetailPanelProps) {
12
11
  return (
13
12
  <div className="mx-auto flex w-[460px] max-w-full flex-col items-center gap-4">
14
- <div className="flex w-full items-center justify-between">
15
- <Button
16
- variant="ghost"
17
- onClick={onBack}
18
- className="text-as-primary/70 hover:text-as-primary flex items-center gap-2"
19
- >
20
- <ArrowDown className="h-4 w-4 rotate-90" />
21
- Back
22
- </Button>
23
- </div>
24
-
25
13
  <div className="flex flex-col items-center gap-4 text-center">
26
14
  <h3 className="text-as-primary text-xl font-bold">Earn Points with Every Swap</h3>
27
15
  <p className="text-as-primary/70 text-balance text-sm leading-relaxed">
@@ -32,6 +32,7 @@ export enum PanelView {
32
32
  ORDER_DETAILS,
33
33
  LOADING,
34
34
  POINTS_DETAIL,
35
+ FEE_DETAIL,
35
36
  }
36
37
 
37
38
  interface UseAnyspendFlowProps {