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