@b3dotfun/sdk 0.0.28-alpha.0 → 0.0.28-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.
Files changed (93) hide show
  1. package/dist/cjs/anyspend/abis/escrow.d.ts +987 -0
  2. package/dist/cjs/anyspend/abis/escrow.js +1275 -0
  3. package/dist/cjs/anyspend/react/components/AnySpend.js +10 -168
  4. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +2 -2
  5. package/dist/cjs/anyspend/react/components/AnyspendDepositHype.d.ts +10 -0
  6. package/dist/cjs/anyspend/react/components/AnyspendDepositHype.js +263 -0
  7. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.d.ts +17 -0
  8. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +53 -0
  9. package/dist/cjs/anyspend/react/components/common/ErrorSection.d.ts +6 -0
  10. package/dist/cjs/anyspend/react/components/common/ErrorSection.js +12 -0
  11. package/dist/cjs/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  12. package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +2 -2
  13. package/dist/cjs/anyspend/react/components/common/PaySection.d.ts +20 -0
  14. package/dist/cjs/anyspend/react/components/common/PaySection.js +58 -0
  15. package/dist/cjs/anyspend/react/components/common/TabSection.d.ts +10 -0
  16. package/dist/cjs/anyspend/react/components/common/TabSection.js +18 -0
  17. package/dist/cjs/anyspend/react/components/index.d.ts +2 -0
  18. package/dist/cjs/anyspend/react/components/index.js +5 -1
  19. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.d.ts +165 -0
  20. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +184 -0
  21. package/dist/cjs/anyspend/react/hooks/useSigMint.d.ts +2 -2
  22. package/dist/cjs/global-account/react/components/B3DynamicModal.js +3 -0
  23. package/dist/cjs/global-account/react/components/custom/Button.d.ts +1 -1
  24. package/dist/cjs/global-account/react/components/ui/button.d.ts +1 -1
  25. package/dist/cjs/global-account/react/components/ui/command.d.ts +2 -2
  26. package/dist/cjs/global-account/react/hooks/useSiwe.native.d.ts +4 -0
  27. package/dist/cjs/global-account/react/hooks/useSiwe.native.js +40 -0
  28. package/dist/cjs/global-account/react/stores/useModalStore.d.ts +17 -1
  29. package/dist/cjs/shared/constants/chains/b3Chain.d.ts +1 -1
  30. package/dist/cjs/shared/constants/chains/supported.d.ts +3 -3
  31. package/dist/esm/anyspend/abis/escrow.d.ts +987 -0
  32. package/dist/esm/anyspend/abis/escrow.js +1272 -0
  33. package/dist/esm/anyspend/react/components/AnySpend.js +12 -170
  34. package/dist/esm/anyspend/react/components/AnySpendCustom.js +3 -3
  35. package/dist/esm/anyspend/react/components/AnyspendDepositHype.d.ts +10 -0
  36. package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +257 -0
  37. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +17 -0
  38. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +50 -0
  39. package/dist/esm/anyspend/react/components/common/ErrorSection.d.ts +6 -0
  40. package/dist/esm/anyspend/react/components/common/ErrorSection.js +9 -0
  41. package/dist/esm/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  42. package/dist/esm/anyspend/react/components/common/PanelOnramp.js +2 -2
  43. package/dist/esm/anyspend/react/components/common/PaySection.d.ts +20 -0
  44. package/dist/esm/anyspend/react/components/common/PaySection.js +55 -0
  45. package/dist/esm/anyspend/react/components/common/TabSection.d.ts +10 -0
  46. package/dist/esm/anyspend/react/components/common/TabSection.js +15 -0
  47. package/dist/esm/anyspend/react/components/index.d.ts +2 -0
  48. package/dist/esm/anyspend/react/components/index.js +2 -0
  49. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.d.ts +165 -0
  50. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +180 -0
  51. package/dist/esm/anyspend/react/hooks/useSigMint.d.ts +2 -2
  52. package/dist/esm/global-account/react/components/B3DynamicModal.js +3 -0
  53. package/dist/esm/global-account/react/components/custom/Button.d.ts +1 -1
  54. package/dist/esm/global-account/react/components/ui/button.d.ts +1 -1
  55. package/dist/esm/global-account/react/components/ui/command.d.ts +2 -2
  56. package/dist/esm/global-account/react/hooks/useSiwe.native.d.ts +4 -0
  57. package/dist/esm/global-account/react/hooks/useSiwe.native.js +34 -0
  58. package/dist/esm/global-account/react/stores/useModalStore.d.ts +17 -1
  59. package/dist/esm/shared/constants/chains/b3Chain.d.ts +1 -1
  60. package/dist/esm/shared/constants/chains/supported.d.ts +3 -3
  61. package/dist/styles/index.css +1 -1
  62. package/dist/types/anyspend/abis/escrow.d.ts +987 -0
  63. package/dist/types/anyspend/react/components/AnyspendDepositHype.d.ts +10 -0
  64. package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +17 -0
  65. package/dist/types/anyspend/react/components/common/ErrorSection.d.ts +6 -0
  66. package/dist/types/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
  67. package/dist/types/anyspend/react/components/common/PaySection.d.ts +20 -0
  68. package/dist/types/anyspend/react/components/common/TabSection.d.ts +10 -0
  69. package/dist/types/anyspend/react/components/index.d.ts +2 -0
  70. package/dist/types/anyspend/react/hooks/useAnyspendFlow.d.ts +165 -0
  71. package/dist/types/anyspend/react/hooks/useSigMint.d.ts +2 -2
  72. package/dist/types/global-account/react/components/custom/Button.d.ts +1 -1
  73. package/dist/types/global-account/react/components/ui/button.d.ts +1 -1
  74. package/dist/types/global-account/react/components/ui/command.d.ts +2 -2
  75. package/dist/types/global-account/react/hooks/useSiwe.native.d.ts +4 -0
  76. package/dist/types/global-account/react/stores/useModalStore.d.ts +17 -1
  77. package/dist/types/shared/constants/chains/b3Chain.d.ts +1 -1
  78. package/dist/types/shared/constants/chains/supported.d.ts +3 -3
  79. package/package.json +13 -1
  80. package/src/anyspend/abis/escrow.ts +1272 -0
  81. package/src/anyspend/react/components/AnySpend.tsx +48 -389
  82. package/src/anyspend/react/components/AnySpendCustom.tsx +2 -10
  83. package/src/anyspend/react/components/AnyspendDepositHype.tsx +525 -0
  84. package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +152 -0
  85. package/src/anyspend/react/components/common/ErrorSection.tsx +21 -0
  86. package/src/anyspend/react/components/common/PanelOnramp.tsx +4 -2
  87. package/src/anyspend/react/components/common/PaySection.tsx +222 -0
  88. package/src/anyspend/react/components/common/TabSection.tsx +58 -0
  89. package/src/anyspend/react/components/index.ts +2 -0
  90. package/src/anyspend/react/hooks/useAnyspendFlow.ts +226 -0
  91. package/src/global-account/react/components/B3DynamicModal.tsx +3 -0
  92. package/src/global-account/react/hooks/useSiwe.native.tsx +40 -0
  93. package/src/global-account/react/stores/useModalStore.ts +19 -1
@@ -0,0 +1,525 @@
1
+ import { B3_TOKEN } from "@b3dotfun/sdk/anyspend";
2
+ import { Button, ShinyButton, StyleRoot, TransitionPanel } from "@b3dotfun/sdk/global-account/react";
3
+ import { cn } from "@b3dotfun/sdk/shared/utils/cn";
4
+ import invariant from "invariant";
5
+ import { motion } from "motion/react";
6
+ import { useMemo } from "react";
7
+ import { toast } from "sonner";
8
+ import { encodeFunctionData } from "viem";
9
+ import { base } from "viem/chains";
10
+ import { PanelView, useAnyspendFlow } from "../hooks/useAnyspendFlow";
11
+ import { AnySpendFingerprintWrapper, getFingerprintConfig } from "./AnySpendFingerprintWrapper";
12
+ import { CryptoPaymentMethod, CryptoPaymentMethodType } from "./common/CryptoPaymentMethod";
13
+ import { CryptoReceiveSection } from "./common/CryptoReceiveSection";
14
+ import { ErrorSection } from "./common/ErrorSection";
15
+ import { FiatPaymentMethod, FiatPaymentMethodComponent } from "./common/FiatPaymentMethod";
16
+ import { OrderDetails } from "./common/OrderDetails";
17
+ import { OrderStatus } from "./common/OrderStatus";
18
+ import { PaySection } from "./common/PaySection";
19
+ import { RecipientSelection } from "./common/RecipientSelection";
20
+
21
+ import { ESCROW_ABI } from "@b3dotfun/sdk/anyspend/abis/escrow";
22
+ import { ArrowDown } from "lucide-react";
23
+ import { PanelOnramp } from "./common/PanelOnramp";
24
+
25
+ function generateEncodedDataForDepositHype(amount: string, beneficiary: string): string {
26
+ invariant(BigInt(amount) > 0, "Amount must be greater than zero");
27
+ const encodedData = encodeFunctionData({
28
+ abi: ESCROW_ABI,
29
+ functionName: "depositFor",
30
+ args: [beneficiary as `0x${string}`, B3_TOKEN.address as `0x${string}`, BigInt(amount)],
31
+ });
32
+ return encodedData;
33
+ }
34
+
35
+ export function AnySpendDepositHype(props: {
36
+ loadOrder?: string;
37
+ mode?: "modal" | "page";
38
+ recipientAddress: string;
39
+ paymentType?: "crypto" | "fiat";
40
+ sourceTokenAddress?: string;
41
+ sourceTokenChainId?: number;
42
+ onSuccess?: () => void;
43
+ depositContractAddress?: string;
44
+ }) {
45
+ const fingerprintConfig = getFingerprintConfig();
46
+
47
+ return (
48
+ <AnySpendFingerprintWrapper fingerprint={fingerprintConfig}>
49
+ <AnySpendDepositHypeInner {...props} />
50
+ </AnySpendFingerprintWrapper>
51
+ );
52
+ }
53
+
54
+ function AnySpendDepositHypeInner({
55
+ loadOrder,
56
+ mode = "modal",
57
+ recipientAddress,
58
+ paymentType = "crypto",
59
+ sourceTokenAddress,
60
+ sourceTokenChainId,
61
+ onSuccess,
62
+ depositContractAddress,
63
+ }: {
64
+ loadOrder?: string;
65
+ mode?: "modal" | "page";
66
+ recipientAddress: string;
67
+ paymentType?: "crypto" | "fiat";
68
+ sourceTokenAddress?: string;
69
+ sourceTokenChainId?: number;
70
+ onSuccess?: () => void;
71
+ depositContractAddress?: string;
72
+ }) {
73
+ // Use shared flow hook
74
+ const {
75
+ activePanel,
76
+ setActivePanel,
77
+ orderId,
78
+ oat,
79
+ selectedSrcChainId,
80
+ setSelectedSrcChainId,
81
+ selectedSrcToken,
82
+ setSelectedSrcToken,
83
+ srcAmount,
84
+ setSrcAmount,
85
+ dstAmount,
86
+ setIsSrcInputDirty,
87
+ selectedCryptoPaymentMethod,
88
+ setSelectedCryptoPaymentMethod,
89
+ selectedFiatPaymentMethod,
90
+ setSelectedFiatPaymentMethod,
91
+ selectedRecipientAddress,
92
+ setSelectedRecipientAddress,
93
+ recipientName,
94
+ globalAddress,
95
+ anyspendQuote,
96
+ isLoadingAnyspendQuote,
97
+ getAnyspendQuoteError,
98
+ activeInputAmountInWei,
99
+ geoData,
100
+ coinbaseAvailablePaymentMethods,
101
+ stripeWeb2Support,
102
+ createOrder,
103
+ isCreatingOrder,
104
+ createOnrampOrder,
105
+ isCreatingOnrampOrder,
106
+ } = useAnyspendFlow({
107
+ paymentType,
108
+ recipientAddress,
109
+ loadOrder,
110
+ isDepositMode: true,
111
+ onTransactionSuccess: onSuccess,
112
+ sourceTokenAddress,
113
+ sourceTokenChainId,
114
+ });
115
+
116
+ // Button state logic
117
+ const btnInfo: { text: string; disable: boolean; error: boolean } = useMemo(() => {
118
+ if (activeInputAmountInWei === "0") return { text: "Enter an amount", disable: true, error: false };
119
+ if (isLoadingAnyspendQuote) return { text: "Loading quote...", disable: true, error: false };
120
+ if (isCreatingOrder || isCreatingOnrampOrder) return { text: "Creating order...", disable: true, error: false };
121
+ if (!selectedRecipientAddress) return { text: "Select recipient", disable: false, error: false };
122
+ if (!anyspendQuote || !anyspendQuote.success) return { text: "Get quote error", disable: true, error: true };
123
+ if (!dstAmount) return { text: "No quote available", disable: true, error: true };
124
+
125
+ // Check minimum deposit amount (10 HYPE)
126
+ // Use the raw amount from the quote instead of the formatted display string
127
+ if (anyspendQuote.data?.currencyOut?.amount && anyspendQuote.data.currencyOut.currency?.decimals) {
128
+ const rawAmountInWei = anyspendQuote.data.currencyOut.amount;
129
+ const decimals = anyspendQuote.data.currencyOut.currency.decimals;
130
+ const actualAmount = parseFloat(rawAmountInWei) / Math.pow(10, decimals);
131
+
132
+ if (actualAmount < 10) {
133
+ return { text: "Minimum 10 HYPE deposit", disable: true, error: true };
134
+ }
135
+ }
136
+
137
+ if (paymentType === "crypto") {
138
+ if (selectedCryptoPaymentMethod === CryptoPaymentMethodType.NONE) {
139
+ return { text: "Choose payment method", disable: false, error: false };
140
+ }
141
+ return { text: "Continue to deposit", disable: false, error: false };
142
+ }
143
+
144
+ if (paymentType === "fiat") {
145
+ if (selectedFiatPaymentMethod === FiatPaymentMethod.NONE) {
146
+ return { text: "Select payment method", disable: false, error: false };
147
+ }
148
+ return { text: "Buy", disable: false, error: false };
149
+ }
150
+
151
+ return { text: "Continue to deposit", disable: false, error: false };
152
+ }, [
153
+ activeInputAmountInWei,
154
+ isLoadingAnyspendQuote,
155
+ isCreatingOrder,
156
+ isCreatingOnrampOrder,
157
+ selectedRecipientAddress,
158
+ anyspendQuote,
159
+ dstAmount,
160
+ paymentType,
161
+ selectedCryptoPaymentMethod,
162
+ selectedFiatPaymentMethod,
163
+ ]);
164
+
165
+ const onMainButtonClick = async () => {
166
+ if (btnInfo.disable) return;
167
+
168
+ if (!selectedRecipientAddress) {
169
+ setActivePanel(PanelView.RECIPIENT_SELECTION);
170
+ return;
171
+ }
172
+
173
+ if (paymentType === "crypto") {
174
+ if (selectedCryptoPaymentMethod === CryptoPaymentMethodType.NONE) {
175
+ setActivePanel(PanelView.CRYPTO_PAYMENT_METHOD);
176
+ return;
177
+ }
178
+ await handleCryptoOrder();
179
+ } else if (paymentType === "fiat") {
180
+ if (selectedFiatPaymentMethod === FiatPaymentMethod.NONE) {
181
+ setActivePanel(PanelView.FIAT_PAYMENT_METHOD);
182
+ return;
183
+ }
184
+ await handleFiatOrder();
185
+ }
186
+ };
187
+
188
+ // Handle crypto order creation
189
+ const handleCryptoOrder = async () => {
190
+ try {
191
+ invariant(anyspendQuote, "Relay price is not found");
192
+ invariant(selectedRecipientAddress, "Recipient address is not found");
193
+ invariant(depositContractAddress, "Deposit contract address is not found");
194
+
195
+ const srcAmountBigInt = BigInt(activeInputAmountInWei);
196
+ const depositAmountWei = anyspendQuote.data?.currencyOut?.amount || "0";
197
+ const encodedData = generateEncodedDataForDepositHype(depositAmountWei, selectedRecipientAddress);
198
+
199
+ createOrder({
200
+ recipientAddress: selectedRecipientAddress,
201
+ orderType: "custom",
202
+ srcChain: selectedSrcChainId,
203
+ dstChain: base.id,
204
+ srcToken: selectedSrcToken,
205
+ dstToken: B3_TOKEN,
206
+ srcAmount: srcAmountBigInt.toString(),
207
+ creatorAddress: globalAddress,
208
+ payload: {
209
+ amount: depositAmountWei,
210
+ data: encodedData,
211
+ to: depositContractAddress,
212
+ action: "deposit HYPE",
213
+ },
214
+ });
215
+ } catch (err: any) {
216
+ console.error(err);
217
+ toast.error("Failed to create order: " + err.message);
218
+ }
219
+ };
220
+
221
+ // Handle fiat order creation
222
+ const handleFiatOrder = async () => {
223
+ try {
224
+ invariant(anyspendQuote, "Relay price is not found");
225
+ invariant(selectedRecipientAddress, "Recipient address is not found");
226
+ invariant(depositContractAddress, "Deposit contract address is not found");
227
+
228
+ if (!srcAmount || parseFloat(srcAmount) <= 0) {
229
+ toast.error("Please enter a valid amount");
230
+ return;
231
+ }
232
+
233
+ // Determine vendor and payment method string
234
+ let vendor: "coinbase" | "stripe" | "stripe-web2";
235
+ let paymentMethodString = "";
236
+
237
+ if (selectedFiatPaymentMethod === FiatPaymentMethod.COINBASE_PAY) {
238
+ if (coinbaseAvailablePaymentMethods.length === 0) {
239
+ toast.error("Coinbase Pay not available");
240
+ return;
241
+ }
242
+ vendor = "coinbase";
243
+ paymentMethodString = coinbaseAvailablePaymentMethods[0]?.id || "";
244
+ } else if (selectedFiatPaymentMethod === FiatPaymentMethod.STRIPE) {
245
+ if (!stripeWeb2Support || !stripeWeb2Support.isSupport) {
246
+ toast.error("Stripe not available");
247
+ return;
248
+ }
249
+ vendor = "stripe-web2";
250
+ paymentMethodString = "";
251
+ } else {
252
+ toast.error("Please select a payment method");
253
+ return;
254
+ }
255
+
256
+ const depositAmountWei = anyspendQuote.data?.currencyOut?.amount || "0";
257
+ const encodedData = generateEncodedDataForDepositHype(depositAmountWei, selectedRecipientAddress);
258
+
259
+ createOnrampOrder({
260
+ recipientAddress: selectedRecipientAddress,
261
+ orderType: "custom",
262
+ dstChain: base.id,
263
+ dstToken: B3_TOKEN,
264
+ srcFiatAmount: srcAmount,
265
+ onramp: {
266
+ vendor: vendor,
267
+ paymentMethod: paymentMethodString,
268
+ country: geoData?.country || "US",
269
+ redirectUrl: window.location.origin,
270
+ },
271
+ expectedDstAmount: anyspendQuote?.data?.currencyOut?.amount?.toString() || "0",
272
+ creatorAddress: globalAddress,
273
+ payload: {
274
+ amount: depositAmountWei,
275
+ data: encodedData,
276
+ to: depositContractAddress,
277
+ action: "deposit HYPE",
278
+ },
279
+ });
280
+ } catch (err: any) {
281
+ console.error(err);
282
+ toast.error("Failed to create order: " + err.message);
283
+ }
284
+ };
285
+
286
+ // Order details view
287
+ const orderDetailsView = (
288
+ <div className={"mx-auto w-[460px] max-w-full"}>
289
+ <div className="relative flex flex-col gap-4">
290
+ {oat && (
291
+ <>
292
+ <OrderStatus order={oat.data.order} />
293
+ <OrderDetails
294
+ mode={mode}
295
+ order={oat.data.order}
296
+ depositTxs={oat.data.depositTxs}
297
+ relayTx={oat.data.relayTx}
298
+ executeTx={oat.data.executeTx}
299
+ refundTxs={oat.data.refundTxs}
300
+ cryptoPaymentMethod={paymentType === "fiat" ? CryptoPaymentMethodType.NONE : selectedCryptoPaymentMethod}
301
+ onBack={() => {
302
+ setActivePanel(PanelView.MAIN);
303
+ onSuccess?.();
304
+ }}
305
+ />
306
+ </>
307
+ )}
308
+ </div>
309
+ </div>
310
+ );
311
+
312
+ // Loading view
313
+ const loadingView = (
314
+ <div className="mx-auto flex w-full flex-col items-center gap-4 p-5">
315
+ <div className="text-as-primary">Loading order details...</div>
316
+ </div>
317
+ );
318
+
319
+ // Panel views
320
+ const recipientSelectionView = (
321
+ <RecipientSelection
322
+ initialValue={selectedRecipientAddress || ""}
323
+ onBack={() => setActivePanel(PanelView.MAIN)}
324
+ onConfirm={address => {
325
+ setSelectedRecipientAddress(address);
326
+ setActivePanel(PanelView.MAIN);
327
+ }}
328
+ />
329
+ );
330
+
331
+ const cryptoPaymentMethodView = (
332
+ <CryptoPaymentMethod
333
+ globalAddress={globalAddress}
334
+ globalWallet={undefined}
335
+ selectedPaymentMethod={selectedCryptoPaymentMethod}
336
+ setSelectedPaymentMethod={setSelectedCryptoPaymentMethod}
337
+ isCreatingOrder={isCreatingOrder}
338
+ onBack={() => setActivePanel(PanelView.MAIN)}
339
+ onSelectPaymentMethod={(method: CryptoPaymentMethodType) => {
340
+ setSelectedCryptoPaymentMethod(method);
341
+ setActivePanel(PanelView.MAIN);
342
+ }}
343
+ />
344
+ );
345
+
346
+ const fiatPaymentMethodView = (
347
+ <FiatPaymentMethodComponent
348
+ selectedPaymentMethod={selectedFiatPaymentMethod}
349
+ setSelectedPaymentMethod={setSelectedFiatPaymentMethod}
350
+ onBack={() => setActivePanel(PanelView.MAIN)}
351
+ onSelectPaymentMethod={(method: FiatPaymentMethod) => {
352
+ setSelectedFiatPaymentMethod(method);
353
+ setActivePanel(PanelView.MAIN);
354
+ }}
355
+ srcAmountOnRamp={srcAmount}
356
+ />
357
+ );
358
+
359
+ // If showing token selection, render with panel transitions
360
+ return (
361
+ <StyleRoot>
362
+ <div
363
+ className={cn(
364
+ "anyspend-container font-inter mx-auto w-full max-w-[460px]",
365
+ mode === "page" &&
366
+ "bg-as-surface-primary border-as-border-secondary overflow-hidden rounded-2xl border shadow-xl",
367
+ )}
368
+ >
369
+ <TransitionPanel
370
+ activeIndex={
371
+ orderId
372
+ ? oat
373
+ ? PanelView.ORDER_DETAILS
374
+ : PanelView.LOADING
375
+ : activePanel === PanelView.ORDER_DETAILS
376
+ ? PanelView.MAIN
377
+ : activePanel
378
+ }
379
+ className={cn("rounded-2xl", {
380
+ "mt-0": mode === "modal",
381
+ })}
382
+ variants={{
383
+ enter: { x: 300, opacity: 0 },
384
+ center: { x: 0, opacity: 1 },
385
+ exit: { x: -300, opacity: 0 },
386
+ }}
387
+ transition={{ type: "spring", stiffness: 300, damping: 30 }}
388
+ >
389
+ {[
390
+ <div key="main-view" className={cn(mode === "page" && "p-6")}>
391
+ <div className="mx-auto flex w-[460px] max-w-full flex-col items-center gap-2">
392
+ {/* Header */}
393
+ <div className="mb-4 flex flex-col items-center gap-3 text-center">
394
+ <div>
395
+ <h1 className="text-as-primary text-xl font-bold">
396
+ {paymentType === "crypto" ? "Deposit Crypto" : "Fund with Fiat"}
397
+ </h1>
398
+ </div>
399
+ </div>
400
+
401
+ <div className="relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2">
402
+ <div className="relative flex w-full max-w-[calc(100vw-32px)] flex-col gap-2">
403
+ {/* Send section */}
404
+ {paymentType === "crypto" ? (
405
+ <PaySection
406
+ paymentType="crypto"
407
+ selectedSrcChainId={selectedSrcChainId}
408
+ setSelectedSrcChainId={setSelectedSrcChainId}
409
+ selectedSrcToken={selectedSrcToken}
410
+ setSelectedSrcToken={setSelectedSrcToken}
411
+ srcAmount={srcAmount}
412
+ setSrcAmount={setSrcAmount}
413
+ setIsSrcInputDirty={setIsSrcInputDirty}
414
+ selectedCryptoPaymentMethod={selectedCryptoPaymentMethod}
415
+ selectedFiatPaymentMethod={selectedFiatPaymentMethod}
416
+ onSelectCryptoPaymentMethod={() => setActivePanel(PanelView.CRYPTO_PAYMENT_METHOD)}
417
+ onSelectFiatPaymentMethod={() => setActivePanel(PanelView.FIAT_PAYMENT_METHOD)}
418
+ anyspendQuote={anyspendQuote}
419
+ />
420
+ ) : (
421
+ <motion.div
422
+ initial={{ opacity: 0, y: 20, filter: "blur(10px)" }}
423
+ animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
424
+ transition={{ duration: 0.3, delay: 0, ease: "easeInOut" }}
425
+ >
426
+ <PanelOnramp
427
+ srcAmountOnRamp={srcAmount}
428
+ setSrcAmountOnRamp={setSrcAmount}
429
+ selectedPaymentMethod={selectedFiatPaymentMethod}
430
+ setActivePanel={setActivePanel}
431
+ _recipientAddress={recipientAddress}
432
+ destinationToken={B3_TOKEN}
433
+ destinationChainId={base.id}
434
+ destinationAmount={dstAmount}
435
+ onDestinationTokenChange={() => {}}
436
+ onDestinationChainChange={() => {}}
437
+ fiatPaymentMethodIndex={PanelView.FIAT_PAYMENT_METHOD}
438
+ />
439
+ </motion.div>
440
+ )}
441
+
442
+ {/* Reverse swap direction section */}
443
+ <Button
444
+ variant="ghost"
445
+ className={cn(
446
+ "swap-direction-button border-as-stroke bg-as-surface-primary absolute left-1/2 top-[calc(50%+56px)] z-10 h-10 w-10 -translate-x-1/2 -translate-y-1/2 cursor-default rounded-xl border-2 sm:h-8 sm:w-8 sm:rounded-xl",
447
+ paymentType === "fiat" && "hidden",
448
+ )}
449
+ >
450
+ <div className="relative flex items-center justify-center transition-opacity">
451
+ <ArrowDown className="text-as-primary/50 h-5 w-5" />
452
+ </div>
453
+ </Button>
454
+
455
+ {/* Receive section - Hidden when fiat tab is active */}
456
+ {paymentType === "crypto" && (
457
+ <CryptoReceiveSection
458
+ isDepositMode={false}
459
+ isBuyMode={true}
460
+ selectedRecipientAddress={recipientAddress}
461
+ recipientName={recipientName || undefined}
462
+ onSelectRecipient={() => setActivePanel(PanelView.RECIPIENT_SELECTION)}
463
+ dstAmount={dstAmount}
464
+ dstToken={B3_TOKEN}
465
+ selectedDstChainId={base.id}
466
+ setSelectedDstChainId={() => {}}
467
+ setSelectedDstToken={() => {}}
468
+ onChangeDstAmount={value => {
469
+ setIsSrcInputDirty(false);
470
+ setSrcAmount(value);
471
+ }}
472
+ anyspendQuote={anyspendQuote}
473
+ />
474
+ )}
475
+ </div>
476
+ </div>
477
+
478
+ {/* Error message section */}
479
+ <ErrorSection error={getAnyspendQuoteError} />
480
+
481
+ {/* Main button section */}
482
+ <motion.div
483
+ initial={{ opacity: 0, y: 20, filter: "blur(10px)" }}
484
+ animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
485
+ transition={{ duration: 0.3, delay: 0.2, ease: "easeInOut" }}
486
+ className={cn("mt-4 flex w-full max-w-[460px] flex-col gap-2", getAnyspendQuoteError && "mt-0")}
487
+ >
488
+ <ShinyButton
489
+ accentColor={"hsl(var(--as-brand))"}
490
+ disabled={btnInfo.disable}
491
+ onClick={onMainButtonClick}
492
+ className={cn(
493
+ "as-main-button relative w-full",
494
+ btnInfo.error ? "!bg-as-red" : btnInfo.disable ? "!bg-as-on-surface-2" : "!bg-as-brand",
495
+ )}
496
+ textClassName={cn(
497
+ btnInfo.error ? "text-white" : btnInfo.disable ? "text-as-secondary" : "text-white",
498
+ )}
499
+ >
500
+ {btnInfo.text}
501
+ </ShinyButton>
502
+ </motion.div>
503
+ </div>
504
+ </div>,
505
+ <div key="crypto-payment-method-view" className={cn(mode === "page" && "p-6")}>
506
+ {cryptoPaymentMethodView}
507
+ </div>,
508
+ <div key="fiat-payment-method-view" className={cn(mode === "page" && "p-6")}>
509
+ {fiatPaymentMethodView}
510
+ </div>,
511
+ <div key="recipient-selection-view" className={cn(mode === "page" && "p-6")}>
512
+ {recipientSelectionView}
513
+ </div>,
514
+ <div key="order-details-view" className={cn(mode === "page" && "p-6")}>
515
+ {orderDetailsView}
516
+ </div>,
517
+ <div key="loading-view" className={cn(mode === "page" && "p-6")}>
518
+ {loadingView}
519
+ </div>,
520
+ ]}
521
+ </TransitionPanel>
522
+ </div>
523
+ </StyleRoot>
524
+ );
525
+ }
@@ -0,0 +1,152 @@
1
+ import { formatUsername } from "@b3dotfun/sdk/shared/utils";
2
+ import { cn } from "@b3dotfun/sdk/shared/utils/cn";
3
+ import { shortenAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
4
+ import { formatDisplayNumber } from "@b3dotfun/sdk/shared/utils/number";
5
+ import { ChevronRight } from "lucide-react";
6
+ import { motion } from "motion/react";
7
+ import { components } from "../../../types/api";
8
+ import { OrderTokenAmount } from "./OrderTokenAmount";
9
+
10
+ interface CryptoReceiveSectionProps {
11
+ isDepositMode?: boolean;
12
+ isBuyMode?: boolean;
13
+ // Recipient data
14
+ selectedRecipientAddress?: string;
15
+ recipientName?: string;
16
+ onSelectRecipient: () => void;
17
+ // Token data
18
+ dstAmount: string;
19
+ dstToken: components["schemas"]["Token"];
20
+ // Token selection for non-buy mode
21
+ selectedDstChainId?: number;
22
+ setSelectedDstChainId?: (chainId: number) => void;
23
+ setSelectedDstToken?: (token: components["schemas"]["Token"]) => void;
24
+ onChangeDstAmount?: (value: string) => void;
25
+ // Quote data
26
+ anyspendQuote?: any;
27
+ }
28
+
29
+ export function CryptoReceiveSection({
30
+ isDepositMode = false,
31
+ isBuyMode = false,
32
+ selectedRecipientAddress,
33
+ recipientName,
34
+ onSelectRecipient,
35
+ dstAmount,
36
+ dstToken,
37
+ selectedDstChainId,
38
+ setSelectedDstChainId,
39
+ setSelectedDstToken,
40
+ onChangeDstAmount,
41
+ anyspendQuote,
42
+ }: CryptoReceiveSectionProps) {
43
+ return (
44
+ <motion.div
45
+ initial={{ opacity: 0, y: 20, filter: "blur(10px)" }}
46
+ animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
47
+ transition={{ duration: 0.3, delay: 0.1, ease: "easeInOut" }}
48
+ 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"
49
+ >
50
+ <div className="flex w-full items-center justify-between">
51
+ <div className="text-as-primary/50 flex h-7 items-center text-sm">{isDepositMode ? "Deposit" : "Receive"}</div>
52
+ {selectedRecipientAddress ? (
53
+ <button
54
+ className={cn("text-as-tertiarry flex h-7 items-center gap-2 rounded-lg")}
55
+ onClick={onSelectRecipient}
56
+ >
57
+ <>
58
+ <span className="text-as-tertiarry flex items-center gap-1 text-sm">
59
+ {recipientName ? formatUsername(recipientName) : shortenAddress(selectedRecipientAddress || "")}
60
+ </span>
61
+ <ChevronRight className="h-4 w-4" />
62
+ </>
63
+ </button>
64
+ ) : (
65
+ <button className="text-as-primary/70 flex items-center gap-1 rounded-lg" onClick={onSelectRecipient}>
66
+ <div className="text-sm font-medium">Select recipient</div>
67
+ </button>
68
+ )}
69
+ </div>
70
+ {isBuyMode || isDepositMode ? (
71
+ // Fixed destination token display for buy mode and deposit mode
72
+ <div className="flex items-center justify-between">
73
+ <div className="text-as-primary text-2xl font-bold">{dstAmount || "0"}</div>
74
+ <div className="bg-as-brand/10 border-as-brand/30 flex items-center gap-3 rounded-xl border px-4 py-3">
75
+ {dstToken.metadata?.logoURI && (
76
+ <img src={dstToken.metadata.logoURI} alt={dstToken.symbol} className="h-8 w-8 rounded-full" />
77
+ )}
78
+ <span className="text-as-brand text-lg font-bold">{dstToken.symbol}</span>
79
+ </div>
80
+ </div>
81
+ ) : (
82
+ // Token selection for regular swap mode
83
+ <OrderTokenAmount
84
+ address={selectedRecipientAddress}
85
+ context="to"
86
+ inputValue={dstAmount}
87
+ onChangeInput={onChangeDstAmount || (() => {})}
88
+ chainId={selectedDstChainId || dstToken.chainId}
89
+ setChainId={setSelectedDstChainId || (() => {})}
90
+ token={dstToken}
91
+ setToken={setSelectedDstToken || (() => {})}
92
+ />
93
+ )}
94
+ <div className="text-as-primary/50 flex h-5 items-center text-sm">
95
+ {formatDisplayNumber(anyspendQuote?.data?.currencyOut?.amountUsd, {
96
+ style: "currency",
97
+ fallback: "",
98
+ })}
99
+ {anyspendQuote?.data?.currencyIn?.amountUsd &&
100
+ anyspendQuote?.data?.currencyOut?.amountUsd &&
101
+ (() => {
102
+ const calculatePriceImpact = (inputUsd?: string | number, outputUsd?: string | number) => {
103
+ if (!inputUsd || !outputUsd) {
104
+ return { percentage: "0.00", isNegative: false };
105
+ }
106
+
107
+ const input = Number(inputUsd);
108
+ const output = Number(outputUsd);
109
+
110
+ // Handle edge cases
111
+ if (input === 0 || isNaN(input) || isNaN(output) || input <= output) {
112
+ return { percentage: "0.00", isNegative: false };
113
+ }
114
+
115
+ const percentageValue = ((output - input) / input) * 100;
116
+
117
+ // Handle the -0.00% case
118
+ if (percentageValue > -0.005 && percentageValue < 0) {
119
+ return { percentage: "0.00", isNegative: false };
120
+ }
121
+
122
+ return {
123
+ percentage: Math.abs(percentageValue).toFixed(2),
124
+ isNegative: percentageValue < 0,
125
+ };
126
+ };
127
+
128
+ const { percentage, isNegative } = calculatePriceImpact(
129
+ anyspendQuote.data.currencyIn.amountUsd,
130
+ anyspendQuote.data.currencyOut.amountUsd,
131
+ );
132
+
133
+ // Parse the percentage as a number for comparison
134
+ const percentageNum = parseFloat(percentage);
135
+
136
+ // Don't show if less than 1%
137
+ if (percentageNum < 1) {
138
+ return null;
139
+ }
140
+
141
+ // Using inline style to ensure color displays
142
+ return (
143
+ <span className="ml-2" style={{ color: percentageNum >= 10 ? "red" : "#FFD700" }}>
144
+ ({isNegative ? "-" : ""}
145
+ {percentage}%)
146
+ </span>
147
+ );
148
+ })()}
149
+ </div>
150
+ </motion.div>
151
+ );
152
+ }
@@ -0,0 +1,21 @@
1
+ import { CircleAlert } from "lucide-react";
2
+
3
+ interface ErrorSectionProps {
4
+ error?: Error | null;
5
+ message?: string;
6
+ }
7
+
8
+ export function ErrorSection({ error, message }: ErrorSectionProps) {
9
+ if (!error && !message) {
10
+ return null;
11
+ }
12
+
13
+ const errorMessage = message || error?.message || "An error occurred";
14
+
15
+ return (
16
+ <div className="error-section bg-as-on-surface-1 flex w-full max-w-[460px] items-center gap-2 rounded-2xl px-4 py-2">
17
+ <CircleAlert className="bg-as-red h-4 min-h-4 w-4 min-w-4 rounded-full p-0 text-sm font-medium text-white" />
18
+ <div className="text-as-red text-sm">{errorMessage}</div>
19
+ </div>
20
+ );
21
+ }