@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.
- package/dist/cjs/anyspend/abis/escrow.d.ts +987 -0
- package/dist/cjs/anyspend/abis/escrow.js +1275 -0
- package/dist/cjs/anyspend/react/components/AnySpend.js +10 -168
- package/dist/cjs/anyspend/react/components/AnySpendCustom.js +2 -2
- package/dist/cjs/anyspend/react/components/AnyspendDepositHype.d.ts +10 -0
- package/dist/cjs/anyspend/react/components/AnyspendDepositHype.js +263 -0
- package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.d.ts +17 -0
- package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +53 -0
- package/dist/cjs/anyspend/react/components/common/ErrorSection.d.ts +6 -0
- package/dist/cjs/anyspend/react/components/common/ErrorSection.js +12 -0
- package/dist/cjs/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
- package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +2 -2
- package/dist/cjs/anyspend/react/components/common/PaySection.d.ts +20 -0
- package/dist/cjs/anyspend/react/components/common/PaySection.js +58 -0
- package/dist/cjs/anyspend/react/components/common/TabSection.d.ts +10 -0
- package/dist/cjs/anyspend/react/components/common/TabSection.js +18 -0
- package/dist/cjs/anyspend/react/components/index.d.ts +2 -0
- package/dist/cjs/anyspend/react/components/index.js +5 -1
- package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.d.ts +165 -0
- package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +184 -0
- package/dist/cjs/anyspend/react/hooks/useSigMint.d.ts +2 -2
- package/dist/cjs/global-account/react/components/B3DynamicModal.js +3 -0
- package/dist/cjs/global-account/react/components/custom/Button.d.ts +1 -1
- package/dist/cjs/global-account/react/components/ui/button.d.ts +1 -1
- package/dist/cjs/global-account/react/components/ui/command.d.ts +2 -2
- package/dist/cjs/global-account/react/hooks/useSiwe.native.d.ts +4 -0
- package/dist/cjs/global-account/react/hooks/useSiwe.native.js +40 -0
- package/dist/cjs/global-account/react/stores/useModalStore.d.ts +17 -1
- package/dist/cjs/shared/constants/chains/b3Chain.d.ts +1 -1
- package/dist/cjs/shared/constants/chains/supported.d.ts +3 -3
- package/dist/esm/anyspend/abis/escrow.d.ts +987 -0
- package/dist/esm/anyspend/abis/escrow.js +1272 -0
- package/dist/esm/anyspend/react/components/AnySpend.js +12 -170
- package/dist/esm/anyspend/react/components/AnySpendCustom.js +3 -3
- package/dist/esm/anyspend/react/components/AnyspendDepositHype.d.ts +10 -0
- package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +257 -0
- package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +17 -0
- package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +50 -0
- package/dist/esm/anyspend/react/components/common/ErrorSection.d.ts +6 -0
- package/dist/esm/anyspend/react/components/common/ErrorSection.js +9 -0
- package/dist/esm/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
- package/dist/esm/anyspend/react/components/common/PanelOnramp.js +2 -2
- package/dist/esm/anyspend/react/components/common/PaySection.d.ts +20 -0
- package/dist/esm/anyspend/react/components/common/PaySection.js +55 -0
- package/dist/esm/anyspend/react/components/common/TabSection.d.ts +10 -0
- package/dist/esm/anyspend/react/components/common/TabSection.js +15 -0
- package/dist/esm/anyspend/react/components/index.d.ts +2 -0
- package/dist/esm/anyspend/react/components/index.js +2 -0
- package/dist/esm/anyspend/react/hooks/useAnyspendFlow.d.ts +165 -0
- package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +180 -0
- package/dist/esm/anyspend/react/hooks/useSigMint.d.ts +2 -2
- package/dist/esm/global-account/react/components/B3DynamicModal.js +3 -0
- package/dist/esm/global-account/react/components/custom/Button.d.ts +1 -1
- package/dist/esm/global-account/react/components/ui/button.d.ts +1 -1
- package/dist/esm/global-account/react/components/ui/command.d.ts +2 -2
- package/dist/esm/global-account/react/hooks/useSiwe.native.d.ts +4 -0
- package/dist/esm/global-account/react/hooks/useSiwe.native.js +34 -0
- package/dist/esm/global-account/react/stores/useModalStore.d.ts +17 -1
- package/dist/esm/shared/constants/chains/b3Chain.d.ts +1 -1
- package/dist/esm/shared/constants/chains/supported.d.ts +3 -3
- package/dist/styles/index.css +1 -1
- package/dist/types/anyspend/abis/escrow.d.ts +987 -0
- package/dist/types/anyspend/react/components/AnyspendDepositHype.d.ts +10 -0
- package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +17 -0
- package/dist/types/anyspend/react/components/common/ErrorSection.d.ts +6 -0
- package/dist/types/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
- package/dist/types/anyspend/react/components/common/PaySection.d.ts +20 -0
- package/dist/types/anyspend/react/components/common/TabSection.d.ts +10 -0
- package/dist/types/anyspend/react/components/index.d.ts +2 -0
- package/dist/types/anyspend/react/hooks/useAnyspendFlow.d.ts +165 -0
- package/dist/types/anyspend/react/hooks/useSigMint.d.ts +2 -2
- package/dist/types/global-account/react/components/custom/Button.d.ts +1 -1
- package/dist/types/global-account/react/components/ui/button.d.ts +1 -1
- package/dist/types/global-account/react/components/ui/command.d.ts +2 -2
- package/dist/types/global-account/react/hooks/useSiwe.native.d.ts +4 -0
- package/dist/types/global-account/react/stores/useModalStore.d.ts +17 -1
- package/dist/types/shared/constants/chains/b3Chain.d.ts +1 -1
- package/dist/types/shared/constants/chains/supported.d.ts +3 -3
- package/package.json +13 -1
- package/src/anyspend/abis/escrow.ts +1272 -0
- package/src/anyspend/react/components/AnySpend.tsx +48 -389
- package/src/anyspend/react/components/AnySpendCustom.tsx +2 -10
- package/src/anyspend/react/components/AnyspendDepositHype.tsx +525 -0
- package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +152 -0
- package/src/anyspend/react/components/common/ErrorSection.tsx +21 -0
- package/src/anyspend/react/components/common/PanelOnramp.tsx +4 -2
- package/src/anyspend/react/components/common/PaySection.tsx +222 -0
- package/src/anyspend/react/components/common/TabSection.tsx +58 -0
- package/src/anyspend/react/components/index.ts +2 -0
- package/src/anyspend/react/hooks/useAnyspendFlow.ts +226 -0
- package/src/global-account/react/components/B3DynamicModal.tsx +3 -0
- package/src/global-account/react/hooks/useSiwe.native.tsx +40 -0
- 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
|
+
}
|