@b3dotfun/sdk 0.0.49-alpha.0 → 0.0.49-alpha.10

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 (131) 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/PanelOnramp.d.ts +2 -1
  20. package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +36 -21
  21. package/dist/cjs/anyspend/react/components/common/PanelOnrampPayment.js +2 -1
  22. package/dist/cjs/anyspend/react/components/common/PointsDetailPanel.js +1 -2
  23. package/dist/cjs/anyspend/react/contexts/FeatureFlagsContext.js +1 -1
  24. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
  25. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +1 -0
  26. package/dist/cjs/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +48 -12
  27. package/dist/cjs/anyspend/types/api.d.ts +133 -178
  28. package/dist/cjs/anyspend/utils/chain.js +4 -4
  29. package/dist/cjs/bondkit/bondkitToken.d.ts +3 -1
  30. package/dist/cjs/bondkit/bondkitToken.js +19 -0
  31. package/dist/cjs/bondkit/components/TradingView.d.ts +1 -1
  32. package/dist/cjs/bondkit/components/TradingView.js +14 -3
  33. package/dist/cjs/bondkit/components/index.d.ts +1 -1
  34. package/dist/cjs/bondkit/components/index.js +1 -1
  35. package/dist/cjs/bondkit/components/types.d.ts +1 -0
  36. package/dist/cjs/bondkit/config.d.ts +1 -0
  37. package/dist/cjs/bondkit/config.js +1 -0
  38. package/dist/cjs/bondkit/index.d.ts +1 -1
  39. package/dist/cjs/bondkit/index.js +2 -6
  40. package/dist/cjs/bondkit/types.d.ts +15 -0
  41. package/dist/cjs/global-account/react/components/ui/tooltip.js +1 -1
  42. package/dist/cjs/shared/generated/chain-networks.json +40 -7
  43. package/dist/esm/anyspend/constants/index.d.ts +1 -0
  44. package/dist/esm/anyspend/constants/index.js +11 -1
  45. package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -1
  46. package/dist/esm/anyspend/react/components/AnySpend.js +18 -5
  47. package/dist/esm/anyspend/react/components/AnySpendCustom.js +14 -5
  48. package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +11 -4
  49. package/dist/esm/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  50. package/dist/esm/anyspend/react/components/common/CryptoPaySection.js +4 -4
  51. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  52. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +19 -13
  53. package/dist/esm/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
  54. package/dist/esm/anyspend/react/components/common/FeeBreakDown.js +16 -0
  55. package/dist/esm/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
  56. package/dist/esm/anyspend/react/components/common/FeeDetailPanel.js +113 -0
  57. package/dist/esm/anyspend/react/components/common/OrderDetails.d.ts +1 -0
  58. package/dist/esm/anyspend/react/components/common/OrderDetails.js +6 -6
  59. package/dist/esm/anyspend/react/components/common/OrderDetailsCollapsible.d.ts +1 -0
  60. package/dist/esm/anyspend/react/components/common/OrderDetailsCollapsible.js +4 -3
  61. package/dist/esm/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  62. package/dist/esm/anyspend/react/components/common/PanelOnramp.js +38 -23
  63. package/dist/esm/anyspend/react/components/common/PanelOnrampPayment.js +2 -1
  64. package/dist/esm/anyspend/react/components/common/PointsDetailPanel.js +2 -3
  65. package/dist/esm/anyspend/react/contexts/FeatureFlagsContext.js +1 -1
  66. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
  67. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +1 -0
  68. package/dist/esm/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +48 -12
  69. package/dist/esm/anyspend/types/api.d.ts +133 -178
  70. package/dist/esm/anyspend/utils/chain.js +4 -4
  71. package/dist/esm/bondkit/bondkitToken.d.ts +3 -1
  72. package/dist/esm/bondkit/bondkitToken.js +19 -0
  73. package/dist/esm/bondkit/components/TradingView.d.ts +1 -1
  74. package/dist/esm/bondkit/components/TradingView.js +14 -3
  75. package/dist/esm/bondkit/components/index.d.ts +1 -1
  76. package/dist/esm/bondkit/components/index.js +1 -1
  77. package/dist/esm/bondkit/components/types.d.ts +1 -0
  78. package/dist/esm/bondkit/config.d.ts +1 -0
  79. package/dist/esm/bondkit/config.js +1 -0
  80. package/dist/esm/bondkit/index.d.ts +1 -1
  81. package/dist/esm/bondkit/index.js +1 -1
  82. package/dist/esm/bondkit/types.d.ts +15 -0
  83. package/dist/esm/global-account/react/components/ui/tooltip.js +1 -1
  84. package/dist/esm/shared/generated/chain-networks.json +40 -7
  85. package/dist/styles/index.css +1 -1
  86. package/dist/types/anyspend/constants/index.d.ts +1 -0
  87. package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -1
  88. package/dist/types/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  89. package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  90. package/dist/types/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
  91. package/dist/types/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
  92. package/dist/types/anyspend/react/components/common/OrderDetails.d.ts +1 -0
  93. package/dist/types/anyspend/react/components/common/OrderDetailsCollapsible.d.ts +1 -0
  94. package/dist/types/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  95. package/dist/types/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
  96. package/dist/types/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +48 -12
  97. package/dist/types/anyspend/types/api.d.ts +133 -178
  98. package/dist/types/bondkit/bondkitToken.d.ts +3 -1
  99. package/dist/types/bondkit/components/TradingView.d.ts +1 -1
  100. package/dist/types/bondkit/components/index.d.ts +1 -1
  101. package/dist/types/bondkit/components/types.d.ts +1 -0
  102. package/dist/types/bondkit/config.d.ts +1 -0
  103. package/dist/types/bondkit/index.d.ts +1 -1
  104. package/dist/types/bondkit/types.d.ts +15 -0
  105. package/package.json +1 -1
  106. package/src/anyspend/constants/index.ts +12 -1
  107. package/src/anyspend/react/components/AnySpend.tsx +33 -0
  108. package/src/anyspend/react/components/AnySpendCustom.tsx +69 -7
  109. package/src/anyspend/react/components/AnyspendDepositHype.tsx +24 -0
  110. package/src/anyspend/react/components/common/CryptoPaySection.tsx +14 -2
  111. package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +31 -11
  112. package/src/anyspend/react/components/common/FeeBreakDown.tsx +105 -0
  113. package/src/anyspend/react/components/common/FeeDetailPanel.tsx +334 -0
  114. package/src/anyspend/react/components/common/OrderDetails.tsx +7 -0
  115. package/src/anyspend/react/components/common/OrderDetailsCollapsible.tsx +16 -0
  116. package/src/anyspend/react/components/common/PanelOnramp.tsx +58 -27
  117. package/src/anyspend/react/components/common/PanelOnrampPayment.tsx +18 -6
  118. package/src/anyspend/react/components/common/PointsDetailPanel.tsx +1 -13
  119. package/src/anyspend/react/contexts/FeatureFlagsContext.tsx +2 -2
  120. package/src/anyspend/react/hooks/useAnyspendFlow.ts +1 -0
  121. package/src/anyspend/types/api.ts +135 -178
  122. package/src/anyspend/utils/chain.ts +4 -4
  123. package/src/bondkit/bondkitToken.ts +24 -0
  124. package/src/bondkit/components/TradingView.tsx +15 -3
  125. package/src/bondkit/components/index.ts +1 -1
  126. package/src/bondkit/components/types.ts +1 -0
  127. package/src/bondkit/config.ts +2 -0
  128. package/src/bondkit/index.ts +1 -1
  129. package/src/bondkit/types.ts +19 -0
  130. package/src/global-account/react/components/ui/tooltip.tsx +11 -9
  131. package/src/shared/generated/chain-networks.json +40 -7
@@ -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 */}
@@ -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">
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { createContext, useContext, ReactNode } from "react";
3
+ import { ReactNode, createContext, useContext } from "react";
4
4
 
5
5
  export interface FeatureFlags {
6
6
  showPoints?: boolean;
@@ -18,7 +18,7 @@ interface FeatureFlagsProviderProps {
18
18
  }
19
19
 
20
20
  const defaultFeatureFlags: FeatureFlags = {
21
- showPoints: false,
21
+ showPoints: true,
22
22
  };
23
23
 
24
24
  export function FeatureFlagsProvider({ children, featureFlags = defaultFeatureFlags }: FeatureFlagsProviderProps) {
@@ -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 {