@b3dotfun/sdk 0.1.2-alpha.2 → 0.1.2-alpha.4
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/AnySpendCustomExactIn.d.ts +1 -0
- package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.js +110 -38
- package/dist/cjs/anyspend/react/components/AnySpendStakeUpsideExactIn.d.ts +2 -1
- package/dist/cjs/anyspend/react/components/AnySpendStakeUpsideExactIn.js +2 -2
- package/dist/cjs/anyspend/react/components/common/OrderDetails.js +4 -6
- package/dist/cjs/anyspend/react/components/common/OrderDetailsCollapsible.js +6 -7
- package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.d.ts +20 -1
- package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +118 -15
- package/dist/cjs/anyspend/react/hooks/useRecipientAddressState.js +1 -1
- package/dist/cjs/anyspend/utils/format.js +12 -2
- package/dist/cjs/global-account/react/stores/useModalStore.d.ts +2 -0
- package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.d.ts +1 -0
- package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.js +111 -39
- package/dist/esm/anyspend/react/components/AnySpendStakeUpsideExactIn.d.ts +2 -1
- package/dist/esm/anyspend/react/components/AnySpendStakeUpsideExactIn.js +2 -2
- package/dist/esm/anyspend/react/components/common/OrderDetails.js +4 -6
- package/dist/esm/anyspend/react/components/common/OrderDetailsCollapsible.js +6 -7
- package/dist/esm/anyspend/react/hooks/useAnyspendFlow.d.ts +20 -1
- package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +118 -16
- package/dist/esm/anyspend/react/hooks/useRecipientAddressState.js +1 -1
- package/dist/esm/anyspend/utils/format.js +12 -2
- package/dist/esm/global-account/react/stores/useModalStore.d.ts +2 -0
- package/dist/types/anyspend/react/components/AnySpendCustomExactIn.d.ts +1 -0
- package/dist/types/anyspend/react/components/AnySpendStakeUpsideExactIn.d.ts +2 -1
- package/dist/types/anyspend/react/hooks/useAnyspendFlow.d.ts +20 -1
- package/dist/types/global-account/react/stores/useModalStore.d.ts +2 -0
- package/package.json +1 -1
- package/src/anyspend/react/components/AnySpendCustomExactIn.tsx +125 -41
- package/src/anyspend/react/components/AnySpendStakeUpsideExactIn.tsx +3 -0
- package/src/anyspend/react/components/common/OrderDetails.tsx +4 -6
- package/src/anyspend/react/components/common/OrderDetailsCollapsible.tsx +7 -6
- package/src/anyspend/react/hooks/useAnyspendFlow.ts +140 -17
- package/src/anyspend/react/hooks/useRecipientAddressState.ts +1 -1
- package/src/anyspend/utils/format.ts +13 -2
- package/src/global-account/react/stores/useModalStore.ts +2 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { B3_TOKEN, getDefaultToken, USDC_BASE } from "../../../anyspend/index.js";
|
|
2
2
|
import { useAnyspendCreateOnrampOrder, useAnyspendCreateOrder, useAnyspendOrderAndTransactions, useAnyspendQuote, useGeoOnrampOptions, } from "../../../anyspend/react/index.js";
|
|
3
3
|
import { anyspendService } from "../../../anyspend/services/anyspend.js";
|
|
4
|
+
import { normalizeAddress } from "../../../anyspend/utils/index.js";
|
|
4
5
|
import { toast, useAccountWallet, useProfile, useRouter, useSearchParamsSSR, useTokenBalance, } from "../../../global-account/react/index.js";
|
|
5
6
|
import { formatTokenAmount, formatUnits } from "../../../shared/utils/number.js";
|
|
6
7
|
import { useEffect, useMemo, useState } from "react";
|
|
7
|
-
import { parseUnits } from "viem";
|
|
8
|
+
import { encodeFunctionData, parseUnits } from "viem";
|
|
8
9
|
import { base, mainnet } from "viem/chains";
|
|
9
10
|
import { CryptoPaymentMethodType } from "../components/common/CryptoPaymentMethod.js";
|
|
10
11
|
import { FiatPaymentMethod } from "../components/common/FiatPaymentMethod.js";
|
|
@@ -23,8 +24,50 @@ export var PanelView;
|
|
|
23
24
|
PanelView[PanelView["POINTS_DETAIL"] = 6] = "POINTS_DETAIL";
|
|
24
25
|
PanelView[PanelView["FEE_DETAIL"] = 7] = "FEE_DETAIL";
|
|
25
26
|
})(PanelView || (PanelView = {}));
|
|
27
|
+
/**
|
|
28
|
+
* Generates encoded function data for custom contract calls.
|
|
29
|
+
* Handles amount placeholder replacement and BigInt conversion.
|
|
30
|
+
*/
|
|
31
|
+
export function generateEncodedData(config, amountInWei) {
|
|
32
|
+
if (!config || !config.functionAbi || !config.functionName || !config.functionArgs) {
|
|
33
|
+
console.warn("customExactInConfig missing required fields for encoding:", {
|
|
34
|
+
hasConfig: !!config,
|
|
35
|
+
hasFunctionAbi: !!config?.functionAbi,
|
|
36
|
+
hasFunctionName: !!config?.functionName,
|
|
37
|
+
hasFunctionArgs: !!config?.functionArgs,
|
|
38
|
+
});
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const abi = JSON.parse(config.functionAbi);
|
|
43
|
+
const processedArgs = config.functionArgs.map(arg => {
|
|
44
|
+
// Replace amount placeholders ({{dstAmount}}, {{amount_out}}, etc.)
|
|
45
|
+
if (arg === "{{dstAmount}}" || arg === "{{amount_out}}") {
|
|
46
|
+
return BigInt(amountInWei);
|
|
47
|
+
}
|
|
48
|
+
// Convert numeric strings to BigInt for uint256 args
|
|
49
|
+
if (/^\d+$/.test(arg)) {
|
|
50
|
+
return BigInt(arg);
|
|
51
|
+
}
|
|
52
|
+
return arg;
|
|
53
|
+
});
|
|
54
|
+
return encodeFunctionData({
|
|
55
|
+
abi,
|
|
56
|
+
functionName: config.functionName,
|
|
57
|
+
args: processedArgs,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
console.error("Failed to encode function data:", e, {
|
|
62
|
+
functionAbi: config.functionAbi,
|
|
63
|
+
functionName: config.functionName,
|
|
64
|
+
functionArgs: config.functionArgs,
|
|
65
|
+
});
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
26
69
|
// This hook serves for order hype_duel and custom_exact_in
|
|
27
|
-
export function useAnyspendFlow({ paymentType = "crypto", recipientAddress, loadOrder, onOrderSuccess, onTransactionSuccess, sourceTokenAddress, sourceTokenChainId, destinationTokenAddress, destinationTokenChainId, slippage = 0, disableUrlParamManagement = false, orderType = "hype_duel", }) {
|
|
70
|
+
export function useAnyspendFlow({ paymentType = "crypto", recipientAddress, loadOrder, onOrderSuccess, onTransactionSuccess, sourceTokenAddress, sourceTokenChainId, destinationTokenAddress, destinationTokenChainId, slippage = 0, disableUrlParamManagement = false, orderType = "hype_duel", customExactInConfig, }) {
|
|
28
71
|
const searchParams = useSearchParamsSSR();
|
|
29
72
|
const router = useRouter();
|
|
30
73
|
// Panel and order state
|
|
@@ -39,6 +82,8 @@ export function useAnyspendFlow({ paymentType = "crypto", recipientAddress, load
|
|
|
39
82
|
const [selectedDstToken, setSelectedDstToken] = useState(defaultDstToken);
|
|
40
83
|
const [srcAmount, setSrcAmount] = useState(paymentType === "fiat" ? "5" : "0");
|
|
41
84
|
const [dstAmount, setDstAmount] = useState("");
|
|
85
|
+
const [dstAmountInput, setDstAmountInput] = useState(""); // User input for destination amount (EXACT_OUTPUT mode)
|
|
86
|
+
const [debouncedDstAmountInput, setDebouncedDstAmountInput] = useState(""); // Debounced version for quote requests
|
|
42
87
|
const [isSrcInputDirty, setIsSrcInputDirty] = useState(true);
|
|
43
88
|
// Derive destination chain ID from token or prop (cannot change)
|
|
44
89
|
const selectedDstChainId = destinationTokenChainId || selectedDstToken.chainId;
|
|
@@ -116,6 +161,16 @@ export function useAnyspendFlow({ paymentType = "crypto", recipientAddress, load
|
|
|
116
161
|
};
|
|
117
162
|
fetchDestinationToken();
|
|
118
163
|
}, [destinationTokenAddress, destinationTokenChainId]);
|
|
164
|
+
// Check if destination token is ready (matches the expected address from props)
|
|
165
|
+
// This is important for EXACT_OUTPUT mode where we need correct decimals
|
|
166
|
+
const isDestinationTokenReady = !destinationTokenAddress || selectedDstToken.address.toLowerCase() === destinationTokenAddress.toLowerCase();
|
|
167
|
+
// Debounce destination amount input for quote requests (500ms delay)
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
const timer = setTimeout(() => {
|
|
170
|
+
setDebouncedDstAmountInput(dstAmountInput);
|
|
171
|
+
}, 500);
|
|
172
|
+
return () => clearTimeout(timer);
|
|
173
|
+
}, [dstAmountInput]);
|
|
119
174
|
// Helper function for onramp vendor mapping
|
|
120
175
|
const getOnrampVendor = (paymentMethod) => {
|
|
121
176
|
switch (paymentMethod) {
|
|
@@ -129,8 +184,16 @@ export function useAnyspendFlow({ paymentType = "crypto", recipientAddress, load
|
|
|
129
184
|
};
|
|
130
185
|
// Get quote
|
|
131
186
|
// For fiat payments, always use USDC decimals (6) regardless of selectedSrcToken
|
|
132
|
-
const
|
|
133
|
-
const activeInputAmountInWei = parseUnits(srcAmount.replace(/,/g, ""),
|
|
187
|
+
const effectiveSrcDecimals = paymentType === "fiat" ? USDC_BASE.decimals : selectedSrcToken.decimals;
|
|
188
|
+
const activeInputAmountInWei = parseUnits(srcAmount.replace(/,/g, ""), effectiveSrcDecimals).toString();
|
|
189
|
+
// Calculate output amount in wei for EXACT_OUTPUT mode
|
|
190
|
+
// Only calculate when destination token is ready (has correct decimals)
|
|
191
|
+
// Use debounced value to reduce quote API calls
|
|
192
|
+
const activeOutputAmountInWei = isDestinationTokenReady
|
|
193
|
+
? parseUnits(debouncedDstAmountInput.replace(/,/g, "") || "0", selectedDstToken.decimals).toString()
|
|
194
|
+
: "0";
|
|
195
|
+
// Determine trade type based on which input was last edited
|
|
196
|
+
const tradeType = isSrcInputDirty ? "EXACT_INPUT" : "EXACT_OUTPUT";
|
|
134
197
|
// Build quote request based on order type
|
|
135
198
|
const quoteRequest = (() => {
|
|
136
199
|
const baseParams = {
|
|
@@ -145,8 +208,8 @@ export function useAnyspendFlow({ paymentType = "crypto", recipientAddress, load
|
|
|
145
208
|
return {
|
|
146
209
|
...baseParams,
|
|
147
210
|
type: "swap",
|
|
148
|
-
tradeType:
|
|
149
|
-
amount: activeInputAmountInWei,
|
|
211
|
+
tradeType: tradeType,
|
|
212
|
+
amount: tradeType === "EXACT_INPUT" ? activeInputAmountInWei : activeOutputAmountInWei,
|
|
150
213
|
};
|
|
151
214
|
}
|
|
152
215
|
else if (orderType === "hype_duel") {
|
|
@@ -157,6 +220,22 @@ export function useAnyspendFlow({ paymentType = "crypto", recipientAddress, load
|
|
|
157
220
|
};
|
|
158
221
|
}
|
|
159
222
|
else {
|
|
223
|
+
// custom_exact_in - for EXACT_OUTPUT, use custom type to get the quote
|
|
224
|
+
if (tradeType === "EXACT_OUTPUT") {
|
|
225
|
+
const encodedData = generateEncodedData(customExactInConfig, activeOutputAmountInWei);
|
|
226
|
+
return {
|
|
227
|
+
...baseParams,
|
|
228
|
+
type: "custom",
|
|
229
|
+
payload: {
|
|
230
|
+
amount: activeOutputAmountInWei,
|
|
231
|
+
data: encodedData || "",
|
|
232
|
+
to: customExactInConfig ? normalizeAddress(customExactInConfig.to) : "",
|
|
233
|
+
spenderAddress: customExactInConfig?.spenderAddress
|
|
234
|
+
? normalizeAddress(customExactInConfig.spenderAddress)
|
|
235
|
+
: undefined,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
160
239
|
return {
|
|
161
240
|
...baseParams,
|
|
162
241
|
type: "custom_exact_in",
|
|
@@ -165,22 +244,40 @@ export function useAnyspendFlow({ paymentType = "crypto", recipientAddress, load
|
|
|
165
244
|
}
|
|
166
245
|
})();
|
|
167
246
|
const { anyspendQuote, isLoadingAnyspendQuote, getAnyspendQuoteError } = useAnyspendQuote(quoteRequest);
|
|
247
|
+
// Combined loading state: includes debounce waiting period and actual quote fetching
|
|
248
|
+
// For EXACT_OUTPUT mode, also check if we're waiting for debounce
|
|
249
|
+
const isDebouncingDstAmount = tradeType === "EXACT_OUTPUT" && dstAmountInput !== debouncedDstAmountInput;
|
|
250
|
+
const isQuoteLoading = isLoadingAnyspendQuote || isDebouncingDstAmount;
|
|
168
251
|
// Get geo options for fiat
|
|
169
252
|
const { geoData, coinbaseAvailablePaymentMethods, stripeWeb2Support } = useGeoOnrampOptions(paymentType === "fiat" ? formatUnits(activeInputAmountInWei, USDC_BASE.decimals) : "0");
|
|
170
|
-
// Update
|
|
253
|
+
// Update amounts when quote changes based on trade type
|
|
171
254
|
useEffect(() => {
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
255
|
+
if (isSrcInputDirty) {
|
|
256
|
+
// EXACT_INPUT mode: update destination amount from quote
|
|
257
|
+
if (anyspendQuote?.data?.currencyOut?.amount && anyspendQuote.data.currencyOut.currency?.decimals) {
|
|
258
|
+
const amount = anyspendQuote.data.currencyOut.amount;
|
|
259
|
+
const decimals = anyspendQuote.data.currencyOut.currency.decimals;
|
|
260
|
+
// Apply slippage (0-100) - reduce amount by slippage percentage
|
|
261
|
+
const amountWithSlippage = (BigInt(amount) * BigInt(100 - slippage)) / BigInt(100);
|
|
262
|
+
const formattedAmount = formatTokenAmount(amountWithSlippage, decimals, 6, false);
|
|
263
|
+
setDstAmount(formattedAmount);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
setDstAmount("");
|
|
267
|
+
}
|
|
179
268
|
}
|
|
180
269
|
else {
|
|
181
|
-
|
|
270
|
+
// EXACT_OUTPUT mode: update source amount from quote
|
|
271
|
+
if (anyspendQuote?.data?.currencyIn?.amount && anyspendQuote.data.currencyIn.currency?.decimals) {
|
|
272
|
+
const amount = anyspendQuote.data.currencyIn.amount;
|
|
273
|
+
const decimals = anyspendQuote.data.currencyIn.currency.decimals;
|
|
274
|
+
const formattedAmount = formatTokenAmount(BigInt(amount), decimals, 6, false);
|
|
275
|
+
setSrcAmount(formattedAmount);
|
|
276
|
+
}
|
|
277
|
+
// Also set the display destination amount from the user input
|
|
278
|
+
setDstAmount(dstAmountInput);
|
|
182
279
|
}
|
|
183
|
-
}, [anyspendQuote, slippage]);
|
|
280
|
+
}, [anyspendQuote, slippage, isSrcInputDirty, dstAmountInput]);
|
|
184
281
|
// Update useEffect for URL parameter to not override loadOrder
|
|
185
282
|
useEffect(() => {
|
|
186
283
|
if (loadOrder || disableUrlParamManagement)
|
|
@@ -265,8 +362,11 @@ export function useAnyspendFlow({ paymentType = "crypto", recipientAddress, load
|
|
|
265
362
|
setSrcAmount,
|
|
266
363
|
dstAmount,
|
|
267
364
|
setDstAmount,
|
|
365
|
+
dstAmountInput,
|
|
366
|
+
setDstAmountInput,
|
|
268
367
|
isSrcInputDirty,
|
|
269
368
|
setIsSrcInputDirty,
|
|
369
|
+
tradeType,
|
|
270
370
|
// Payment methods
|
|
271
371
|
cryptoPaymentMethod,
|
|
272
372
|
setCryptoPaymentMethod,
|
|
@@ -287,8 +387,10 @@ export function useAnyspendFlow({ paymentType = "crypto", recipientAddress, load
|
|
|
287
387
|
// Quote data
|
|
288
388
|
anyspendQuote,
|
|
289
389
|
isLoadingAnyspendQuote,
|
|
390
|
+
isQuoteLoading, // Combined loading state (includes debounce + quote fetching)
|
|
290
391
|
getAnyspendQuoteError,
|
|
291
392
|
activeInputAmountInWei,
|
|
393
|
+
activeOutputAmountInWei, // User's destination amount input in wei (for EXACT_OUTPUT mode)
|
|
292
394
|
// Geo/onramp data
|
|
293
395
|
geoData,
|
|
294
396
|
coinbaseAvailablePaymentMethods,
|
|
@@ -35,7 +35,7 @@ export function useRecipientAddressState({ recipientAddressFromProps, walletAddr
|
|
|
35
35
|
// selectedRecipientAddress: explicitly selected by user (undefined means no explicit selection)
|
|
36
36
|
const [selectedRecipientAddress, setSelectedRecipientAddress] = useState(undefined);
|
|
37
37
|
// The effective recipient address, derived on each render, respecting priority.
|
|
38
|
-
const effectiveRecipientAddress =
|
|
38
|
+
const effectiveRecipientAddress = selectedRecipientAddress || recipientAddressFromProps || walletAddress || globalAddress;
|
|
39
39
|
// Helper function to reset user's manual selection.
|
|
40
40
|
const resetRecipientAddress = () => {
|
|
41
41
|
setSelectedRecipientAddress(undefined);
|
|
@@ -3,7 +3,11 @@ export const getStatusDisplay = (order) => {
|
|
|
3
3
|
const srcToken = order.metadata?.srcToken;
|
|
4
4
|
const dstToken = order.metadata?.dstToken;
|
|
5
5
|
const formattedSrcAmount = srcToken ? formatTokenAmount(BigInt(order.srcAmount), srcToken.decimals) : undefined;
|
|
6
|
-
|
|
6
|
+
// For custom orders, use payload.amount as fallback if actualDstAmount is not available
|
|
7
|
+
const actualDstAmount = order.settlement?.actualDstAmount ||
|
|
8
|
+
(order.type === "custom" || order.type === "custom_exact_in" || order.type === "deposit_first"
|
|
9
|
+
? order.payload.amount?.toString()
|
|
10
|
+
: undefined);
|
|
7
11
|
const formattedActualDstAmount = actualDstAmount && dstToken ? formatTokenAmount(BigInt(actualDstAmount), dstToken.decimals) : undefined;
|
|
8
12
|
switch (order.status) {
|
|
9
13
|
case "scanning_deposit_transaction": {
|
|
@@ -40,6 +44,7 @@ export const getStatusDisplay = (order) => {
|
|
|
40
44
|
};
|
|
41
45
|
case "executed": {
|
|
42
46
|
const receivedText = formattedActualDstAmount && dstToken ? `Received ${formattedActualDstAmount} ${dstToken.symbol}` : undefined;
|
|
47
|
+
const actionText = order.metadata?.action || "Order";
|
|
43
48
|
const { text, description } = order.type === "swap"
|
|
44
49
|
? { text: receivedText || "Swap Complete", description: "Your swap has been completed successfully." }
|
|
45
50
|
: order.type === "mint_nft"
|
|
@@ -48,7 +53,12 @@ export const getStatusDisplay = (order) => {
|
|
|
48
53
|
? { text: "Tournament Joined", description: "You have joined the tournament" }
|
|
49
54
|
: order.type === "fund_tournament"
|
|
50
55
|
? { text: "Tournament Funded", description: "You have funded the tournament" }
|
|
51
|
-
:
|
|
56
|
+
: order.type === "custom" || order.type === "custom_exact_in"
|
|
57
|
+
? {
|
|
58
|
+
text: receivedText || `${actionText} Complete`,
|
|
59
|
+
description: "Your order has been completed successfully.",
|
|
60
|
+
}
|
|
61
|
+
: { text: receivedText || "Order Complete", description: "Your order has been completed" };
|
|
52
62
|
return { text, status: "success", description };
|
|
53
63
|
}
|
|
54
64
|
case "refunding":
|
|
@@ -306,6 +306,8 @@ export interface AnySpendDepositUpsideProps extends BaseModalProps {
|
|
|
306
306
|
depositContractAddress: string;
|
|
307
307
|
/** Token to deposit */
|
|
308
308
|
token: components["schemas"]["Token"];
|
|
309
|
+
/** The exact amount of destination tokens to receive, in wei. This will pre-fill the output amount and switch to an exact output swap. */
|
|
310
|
+
destinationTokenAmount?: string;
|
|
309
311
|
/** Callback function called when the deposit is successful */
|
|
310
312
|
onSuccess?: () => void;
|
|
311
313
|
}
|
|
@@ -18,6 +18,7 @@ export interface AnySpendCustomExactInProps {
|
|
|
18
18
|
sourceTokenChainId?: number;
|
|
19
19
|
destinationToken: components["schemas"]["Token"];
|
|
20
20
|
destinationChainId: number;
|
|
21
|
+
destinationTokenAmount?: string;
|
|
21
22
|
onSuccess?: (amount: string) => void;
|
|
22
23
|
onOpenCustomModal?: () => void;
|
|
23
24
|
mainFooter?: React.ReactNode;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
2
|
-
export declare function AnySpendStakeUpsideExactIn({ loadOrder, mode, recipientAddress, sourceTokenAddress, sourceTokenChainId, stakingContractAddress, token, onSuccess, }: {
|
|
2
|
+
export declare function AnySpendStakeUpsideExactIn({ loadOrder, mode, recipientAddress, sourceTokenAddress, sourceTokenChainId, destinationTokenAmount, stakingContractAddress, token, onSuccess, }: {
|
|
3
3
|
loadOrder?: string;
|
|
4
4
|
mode?: "modal" | "page";
|
|
5
5
|
recipientAddress: string;
|
|
@@ -7,5 +7,6 @@ export declare function AnySpendStakeUpsideExactIn({ loadOrder, mode, recipientA
|
|
|
7
7
|
sourceTokenChainId?: number;
|
|
8
8
|
stakingContractAddress: string;
|
|
9
9
|
token: components["schemas"]["Token"];
|
|
10
|
+
destinationTokenAmount?: string;
|
|
10
11
|
onSuccess?: (amount: string) => void;
|
|
11
12
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -11,6 +11,19 @@ export declare enum PanelView {
|
|
|
11
11
|
POINTS_DETAIL = 6,
|
|
12
12
|
FEE_DETAIL = 7
|
|
13
13
|
}
|
|
14
|
+
export type CustomExactInConfig = {
|
|
15
|
+
functionAbi: string;
|
|
16
|
+
functionName: string;
|
|
17
|
+
functionArgs: string[];
|
|
18
|
+
to: string;
|
|
19
|
+
spenderAddress?: string;
|
|
20
|
+
action?: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Generates encoded function data for custom contract calls.
|
|
24
|
+
* Handles amount placeholder replacement and BigInt conversion.
|
|
25
|
+
*/
|
|
26
|
+
export declare function generateEncodedData(config: CustomExactInConfig | undefined, amountInWei: string): string | undefined;
|
|
14
27
|
interface UseAnyspendFlowProps {
|
|
15
28
|
paymentType?: "crypto" | "fiat";
|
|
16
29
|
recipientAddress?: string;
|
|
@@ -25,8 +38,9 @@ interface UseAnyspendFlowProps {
|
|
|
25
38
|
slippage?: number;
|
|
26
39
|
disableUrlParamManagement?: boolean;
|
|
27
40
|
orderType?: "hype_duel" | "custom_exact_in" | "swap";
|
|
41
|
+
customExactInConfig?: CustomExactInConfig;
|
|
28
42
|
}
|
|
29
|
-
export declare function useAnyspendFlow({ paymentType, recipientAddress, loadOrder, onOrderSuccess, onTransactionSuccess, sourceTokenAddress, sourceTokenChainId, destinationTokenAddress, destinationTokenChainId, slippage, disableUrlParamManagement, orderType, }: UseAnyspendFlowProps): {
|
|
43
|
+
export declare function useAnyspendFlow({ paymentType, recipientAddress, loadOrder, onOrderSuccess, onTransactionSuccess, sourceTokenAddress, sourceTokenChainId, destinationTokenAddress, destinationTokenChainId, slippage, disableUrlParamManagement, orderType, customExactInConfig, }: UseAnyspendFlowProps): {
|
|
30
44
|
activePanel: PanelView;
|
|
31
45
|
setActivePanel: import("react").Dispatch<import("react").SetStateAction<PanelView>>;
|
|
32
46
|
orderId: string | undefined;
|
|
@@ -91,8 +105,11 @@ export declare function useAnyspendFlow({ paymentType, recipientAddress, loadOrd
|
|
|
91
105
|
setSrcAmount: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
92
106
|
dstAmount: string;
|
|
93
107
|
setDstAmount: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
108
|
+
dstAmountInput: string;
|
|
109
|
+
setDstAmountInput: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
94
110
|
isSrcInputDirty: boolean;
|
|
95
111
|
setIsSrcInputDirty: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
112
|
+
tradeType: string;
|
|
96
113
|
cryptoPaymentMethod: CryptoPaymentMethodType;
|
|
97
114
|
setCryptoPaymentMethod: (method: CryptoPaymentMethodType) => void;
|
|
98
115
|
selectedCryptoPaymentMethod: CryptoPaymentMethodType;
|
|
@@ -176,8 +193,10 @@ export declare function useAnyspendFlow({ paymentType, recipientAddress, loadOrd
|
|
|
176
193
|
statusCode: number;
|
|
177
194
|
} | undefined;
|
|
178
195
|
isLoadingAnyspendQuote: boolean;
|
|
196
|
+
isQuoteLoading: boolean;
|
|
179
197
|
getAnyspendQuoteError: Error | null;
|
|
180
198
|
activeInputAmountInWei: string;
|
|
199
|
+
activeOutputAmountInWei: string;
|
|
181
200
|
geoData: import("@b3dotfun/sdk/anyspend/react").GeoData | undefined;
|
|
182
201
|
coinbaseAvailablePaymentMethods: {
|
|
183
202
|
id?: string;
|
|
@@ -306,6 +306,8 @@ export interface AnySpendDepositUpsideProps extends BaseModalProps {
|
|
|
306
306
|
depositContractAddress: string;
|
|
307
307
|
/** Token to deposit */
|
|
308
308
|
token: components["schemas"]["Token"];
|
|
309
|
+
/** The exact amount of destination tokens to receive, in wei. This will pre-fill the output amount and switch to an exact output swap. */
|
|
310
|
+
destinationTokenAmount?: string;
|
|
309
311
|
/** Callback function called when the deposit is successful */
|
|
310
312
|
onSuccess?: () => void;
|
|
311
313
|
}
|
package/package.json
CHANGED
|
@@ -12,15 +12,15 @@ import {
|
|
|
12
12
|
} from "@b3dotfun/sdk/global-account/react";
|
|
13
13
|
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
14
14
|
import { formatUnits } from "@b3dotfun/sdk/shared/utils/number";
|
|
15
|
-
import type { AnySpendCustomExactInClasses } from "./types/classes";
|
|
16
15
|
import invariant from "invariant";
|
|
17
16
|
import { ArrowDown, Loader2 } from "lucide-react";
|
|
18
17
|
import { motion } from "motion/react";
|
|
19
18
|
import { useEffect, useMemo, useRef } from "react";
|
|
19
|
+
import type { AnySpendCustomExactInClasses } from "./types/classes";
|
|
20
20
|
|
|
21
21
|
import { useSetActiveWallet } from "thirdweb/react";
|
|
22
22
|
import { B3_TOKEN } from "../../constants";
|
|
23
|
-
import { PanelView, useAnyspendFlow } from "../hooks/useAnyspendFlow";
|
|
23
|
+
import { generateEncodedData, PanelView, useAnyspendFlow } from "../hooks/useAnyspendFlow";
|
|
24
24
|
import { AnySpendFingerprintWrapper, getFingerprintConfig } from "./AnySpendFingerprintWrapper";
|
|
25
25
|
import { CryptoPaySection } from "./common/CryptoPaySection";
|
|
26
26
|
import { CryptoPaymentMethod, CryptoPaymentMethodType } from "./common/CryptoPaymentMethod";
|
|
@@ -53,6 +53,7 @@ export interface AnySpendCustomExactInProps {
|
|
|
53
53
|
sourceTokenChainId?: number;
|
|
54
54
|
destinationToken: components["schemas"]["Token"];
|
|
55
55
|
destinationChainId: number;
|
|
56
|
+
destinationTokenAmount?: string;
|
|
56
57
|
onSuccess?: (amount: string) => void;
|
|
57
58
|
onOpenCustomModal?: () => void;
|
|
58
59
|
mainFooter?: React.ReactNode;
|
|
@@ -105,6 +106,7 @@ function AnySpendCustomExactInInner({
|
|
|
105
106
|
customUsdInputValues,
|
|
106
107
|
preferEoa,
|
|
107
108
|
customExactInConfig,
|
|
109
|
+
destinationTokenAmount,
|
|
108
110
|
orderType = "custom_exact_in",
|
|
109
111
|
minDestinationAmount,
|
|
110
112
|
header,
|
|
@@ -135,8 +137,11 @@ function AnySpendCustomExactInInner({
|
|
|
135
137
|
srcAmount,
|
|
136
138
|
setSrcAmount,
|
|
137
139
|
dstAmount,
|
|
140
|
+
dstAmountInput,
|
|
141
|
+
setDstAmountInput,
|
|
138
142
|
isSrcInputDirty,
|
|
139
143
|
setIsSrcInputDirty,
|
|
144
|
+
tradeType,
|
|
140
145
|
selectedCryptoPaymentMethod,
|
|
141
146
|
effectiveCryptoPaymentMethod,
|
|
142
147
|
setSelectedCryptoPaymentMethod,
|
|
@@ -150,7 +155,9 @@ function AnySpendCustomExactInInner({
|
|
|
150
155
|
isBalanceLoading,
|
|
151
156
|
anyspendQuote,
|
|
152
157
|
isLoadingAnyspendQuote,
|
|
158
|
+
isQuoteLoading,
|
|
153
159
|
activeInputAmountInWei,
|
|
160
|
+
activeOutputAmountInWei,
|
|
154
161
|
geoData,
|
|
155
162
|
coinbaseAvailablePaymentMethods,
|
|
156
163
|
stripeWeb2Support,
|
|
@@ -170,6 +177,7 @@ function AnySpendCustomExactInInner({
|
|
|
170
177
|
slippage: SLIPPAGE_PERCENT,
|
|
171
178
|
disableUrlParamManagement: true,
|
|
172
179
|
orderType,
|
|
180
|
+
customExactInConfig,
|
|
173
181
|
});
|
|
174
182
|
|
|
175
183
|
const { connectedEOAWallet } = useAccountWallet();
|
|
@@ -188,6 +196,18 @@ function AnySpendCustomExactInInner({
|
|
|
188
196
|
}
|
|
189
197
|
}, [preferEoa, connectedEOAWallet, setActiveWallet]);
|
|
190
198
|
|
|
199
|
+
// Prefill destination amount if provided (for EXACT_OUTPUT mode)
|
|
200
|
+
const appliedDestinationAmount = useRef(false);
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (destinationTokenAmount && !appliedDestinationAmount.current) {
|
|
203
|
+
appliedDestinationAmount.current = true;
|
|
204
|
+
// Convert wei to human-readable format
|
|
205
|
+
const formattedAmount = formatUnits(destinationTokenAmount, destinationToken.decimals);
|
|
206
|
+
setDstAmountInput(formattedAmount);
|
|
207
|
+
setIsSrcInputDirty(false); // Switch to EXACT_OUTPUT mode
|
|
208
|
+
}
|
|
209
|
+
}, [destinationTokenAmount, destinationToken.decimals, setDstAmountInput, setIsSrcInputDirty]);
|
|
210
|
+
|
|
191
211
|
const selectedRecipientOrDefault = selectedRecipientAddress ?? recipientAddress;
|
|
192
212
|
|
|
193
213
|
const expectedDstAmountRaw = anyspendQuote?.data?.currencyOut?.amount ?? "0";
|
|
@@ -216,11 +236,15 @@ function AnySpendCustomExactInInner({
|
|
|
216
236
|
};
|
|
217
237
|
|
|
218
238
|
const btnInfo: { text: string; disable: boolean; error: boolean; loading: boolean } = useMemo(() => {
|
|
219
|
-
|
|
239
|
+
// Check for empty amount based on trade type
|
|
240
|
+
const isAmountEmpty =
|
|
241
|
+
tradeType === "EXACT_OUTPUT" ? !dstAmountInput || dstAmountInput === "0" : activeInputAmountInWei === "0";
|
|
242
|
+
|
|
243
|
+
if (isAmountEmpty) return { text: "Enter an amount", disable: true, error: false, loading: false };
|
|
220
244
|
if (orderType === "hype_duel" && selectedSrcToken?.address?.toLowerCase() === B3_TOKEN.address.toLowerCase()) {
|
|
221
245
|
return { text: "Convert to HYPE using B3", disable: false, error: false, loading: false };
|
|
222
246
|
}
|
|
223
|
-
if (
|
|
247
|
+
if (isQuoteLoading) return { text: "Loading quote...", disable: true, error: false, loading: true };
|
|
224
248
|
if (isCreatingOrder || isCreatingOnrampOrder)
|
|
225
249
|
return { text: "Creating order...", disable: true, error: false, loading: true };
|
|
226
250
|
if (!selectedRecipientOrDefault) return { text: "Select recipient", disable: false, error: false, loading: false };
|
|
@@ -275,7 +299,7 @@ function AnySpendCustomExactInInner({
|
|
|
275
299
|
return { text: "Continue", disable: false, error: false, loading: false };
|
|
276
300
|
}, [
|
|
277
301
|
activeInputAmountInWei,
|
|
278
|
-
|
|
302
|
+
isQuoteLoading,
|
|
279
303
|
isCreatingOrder,
|
|
280
304
|
isCreatingOnrampOrder,
|
|
281
305
|
selectedRecipientOrDefault,
|
|
@@ -290,6 +314,8 @@ function AnySpendCustomExactInInner({
|
|
|
290
314
|
DESTINATION_TOKEN_DETAILS.SYMBOL,
|
|
291
315
|
orderType,
|
|
292
316
|
selectedSrcToken,
|
|
317
|
+
tradeType,
|
|
318
|
+
dstAmountInput,
|
|
293
319
|
]);
|
|
294
320
|
|
|
295
321
|
const onMainButtonClick = async () => {
|
|
@@ -405,12 +431,12 @@ function AnySpendCustomExactInInner({
|
|
|
405
431
|
{paymentType === "crypto" && (
|
|
406
432
|
<CryptoReceiveSection
|
|
407
433
|
isDepositMode={false}
|
|
408
|
-
isBuyMode={
|
|
434
|
+
isBuyMode={false}
|
|
409
435
|
effectiveRecipientAddress={selectedRecipientOrDefault}
|
|
410
436
|
recipientName={recipientName || undefined}
|
|
411
437
|
customRecipientLabel={customRecipientLabel}
|
|
412
438
|
onSelectRecipient={() => setActivePanel(PanelView.RECIPIENT_SELECTION)}
|
|
413
|
-
dstAmount={dstAmount}
|
|
439
|
+
dstAmount={isSrcInputDirty ? dstAmount : dstAmountInput}
|
|
414
440
|
dstToken={selectedDstToken}
|
|
415
441
|
dstTokenSymbol={DESTINATION_TOKEN_DETAILS.SYMBOL}
|
|
416
442
|
dstTokenLogoURI={DESTINATION_TOKEN_DETAILS.LOGO_URI}
|
|
@@ -420,7 +446,7 @@ function AnySpendCustomExactInInner({
|
|
|
420
446
|
isSrcInputDirty={isSrcInputDirty}
|
|
421
447
|
onChangeDstAmount={value => {
|
|
422
448
|
setIsSrcInputDirty(false);
|
|
423
|
-
|
|
449
|
+
setDstAmountInput(value);
|
|
424
450
|
}}
|
|
425
451
|
anyspendQuote={anyspendQuote}
|
|
426
452
|
onShowPointsDetail={() => setActivePanel(PanelView.POINTS_DETAIL)}
|
|
@@ -472,21 +498,51 @@ function AnySpendCustomExactInInner({
|
|
|
472
498
|
invariant(anyspendQuote, "Relay price is not found");
|
|
473
499
|
invariant(selectedRecipientOrDefault, "Recipient address is not found");
|
|
474
500
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
501
|
+
if (tradeType === "EXACT_OUTPUT") {
|
|
502
|
+
// EXACT_OUTPUT mode: create a custom order (like AnySpendStakeUpside)
|
|
503
|
+
const srcAmountFromQuote = anyspendQuote.data?.currencyIn?.amount;
|
|
504
|
+
invariant(srcAmountFromQuote, "Source amount from quote is not found");
|
|
505
|
+
|
|
506
|
+
const expectedDstAmount = anyspendQuote.data?.currencyOut?.amount ?? "0";
|
|
507
|
+
const encodedData = generateEncodedData(customExactInConfig, activeOutputAmountInWei);
|
|
508
|
+
|
|
509
|
+
createOrder({
|
|
510
|
+
recipientAddress: selectedRecipientOrDefault,
|
|
511
|
+
orderType: "custom",
|
|
512
|
+
srcChain: selectedSrcChainId,
|
|
513
|
+
dstChain: selectedDstChainId,
|
|
514
|
+
srcToken: selectedSrcToken,
|
|
515
|
+
dstToken: selectedDstToken,
|
|
516
|
+
srcAmount: srcAmountFromQuote,
|
|
517
|
+
expectedDstAmount,
|
|
518
|
+
creatorAddress: globalAddress,
|
|
519
|
+
payload: {
|
|
520
|
+
amount: activeOutputAmountInWei,
|
|
521
|
+
data: encodedData,
|
|
522
|
+
to: customExactInConfig ? normalizeAddress(customExactInConfig.to) : undefined,
|
|
523
|
+
spenderAddress: customExactInConfig?.spenderAddress
|
|
524
|
+
? normalizeAddress(customExactInConfig.spenderAddress)
|
|
525
|
+
: undefined,
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
} else {
|
|
529
|
+
// EXACT_INPUT mode: create custom_exact_in order (original behavior)
|
|
530
|
+
const srcAmountBigInt = BigInt(activeInputAmountInWei);
|
|
531
|
+
const payload = buildCustomPayload(selectedRecipientOrDefault);
|
|
532
|
+
|
|
533
|
+
createOrder({
|
|
534
|
+
recipientAddress: selectedRecipientOrDefault,
|
|
535
|
+
orderType,
|
|
536
|
+
srcChain: selectedSrcChainId,
|
|
537
|
+
dstChain: selectedDstChainId,
|
|
538
|
+
srcToken: selectedSrcToken,
|
|
539
|
+
dstToken: selectedDstToken,
|
|
540
|
+
srcAmount: srcAmountBigInt.toString(),
|
|
541
|
+
expectedDstAmount: expectedDstAmountRaw,
|
|
542
|
+
creatorAddress: globalAddress,
|
|
543
|
+
payload,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
490
546
|
} catch (err: any) {
|
|
491
547
|
console.error(err);
|
|
492
548
|
toast.error("Failed to create order: " + err.message);
|
|
@@ -524,24 +580,52 @@ function AnySpendCustomExactInInner({
|
|
|
524
580
|
return;
|
|
525
581
|
}
|
|
526
582
|
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
583
|
+
const onrampOptions = {
|
|
584
|
+
vendor,
|
|
585
|
+
paymentMethod: paymentMethodString,
|
|
586
|
+
country: geoData?.country || "US",
|
|
587
|
+
redirectUrl: window.location.origin,
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
if (tradeType === "EXACT_OUTPUT") {
|
|
591
|
+
// EXACT_OUTPUT mode: create a custom order (like AnySpendStakeUpside)
|
|
592
|
+
const expectedDstAmount = anyspendQuote.data?.currencyOut?.amount ?? "0";
|
|
593
|
+
const encodedData = generateEncodedData(customExactInConfig, activeOutputAmountInWei);
|
|
594
|
+
|
|
595
|
+
createOnrampOrder({
|
|
596
|
+
recipientAddress: selectedRecipientOrDefault,
|
|
597
|
+
orderType: "custom",
|
|
598
|
+
dstChain: selectedDstChainId,
|
|
599
|
+
dstToken: selectedDstToken,
|
|
600
|
+
srcFiatAmount: srcAmount,
|
|
601
|
+
onramp: onrampOptions,
|
|
602
|
+
expectedDstAmount,
|
|
603
|
+
creatorAddress: globalAddress,
|
|
604
|
+
payload: {
|
|
605
|
+
amount: activeOutputAmountInWei,
|
|
606
|
+
data: encodedData,
|
|
607
|
+
to: customExactInConfig ? normalizeAddress(customExactInConfig.to) : undefined,
|
|
608
|
+
spenderAddress: customExactInConfig?.spenderAddress
|
|
609
|
+
? normalizeAddress(customExactInConfig.spenderAddress)
|
|
610
|
+
: undefined,
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
} else {
|
|
614
|
+
// EXACT_INPUT mode: create custom_exact_in order (original behavior)
|
|
615
|
+
const payload = buildCustomPayload(selectedRecipientOrDefault);
|
|
616
|
+
|
|
617
|
+
createOnrampOrder({
|
|
618
|
+
recipientAddress: selectedRecipientOrDefault,
|
|
619
|
+
orderType,
|
|
620
|
+
dstChain: selectedDstChainId,
|
|
621
|
+
dstToken: selectedDstToken,
|
|
622
|
+
srcFiatAmount: srcAmount,
|
|
623
|
+
onramp: onrampOptions,
|
|
624
|
+
expectedDstAmount: expectedDstAmountRaw,
|
|
625
|
+
creatorAddress: globalAddress,
|
|
626
|
+
payload,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
545
629
|
} catch (err: any) {
|
|
546
630
|
console.error(err);
|
|
547
631
|
toast.error("Failed to create order: " + err.message);
|