@b3dotfun/sdk 0.1.63 → 0.1.64-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/anyspend/react/components/AnySpend.d.ts +2 -0
- package/dist/cjs/anyspend/react/components/AnySpend.js +25 -8
- package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.js +4 -5
- package/dist/cjs/anyspend/react/components/AnySpendDeposit.js +1 -1
- package/dist/cjs/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/common/CryptoPaySection.js +2 -2
- package/dist/cjs/anyspend/react/components/common/OrderDetails.js +20 -1
- package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.js +16 -2
- package/dist/cjs/anyspend/react/components/common/PaymentStripeWeb2.js +2 -2
- package/dist/cjs/anyspend/types/api.d.ts +10 -287
- package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -0
- package/dist/esm/anyspend/react/components/AnySpend.js +26 -9
- package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.js +4 -5
- package/dist/esm/anyspend/react/components/AnySpendDeposit.js +1 -1
- package/dist/esm/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
- package/dist/esm/anyspend/react/components/common/CryptoPaySection.js +2 -2
- package/dist/esm/anyspend/react/components/common/OrderDetails.js +20 -1
- package/dist/esm/anyspend/react/components/common/OrderTokenAmount.d.ts +3 -1
- package/dist/esm/anyspend/react/components/common/OrderTokenAmount.js +16 -2
- package/dist/esm/anyspend/react/components/common/PaymentStripeWeb2.js +2 -2
- package/dist/esm/anyspend/types/api.d.ts +10 -287
- package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -0
- package/dist/types/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
- package/dist/types/anyspend/react/components/common/OrderTokenAmount.d.ts +3 -1
- package/dist/types/anyspend/types/api.d.ts +10 -287
- package/package.json +1 -1
- package/src/anyspend/react/components/AnySpend.tsx +30 -6
- package/src/anyspend/react/components/AnySpendCustomExactIn.tsx +4 -4
- package/src/anyspend/react/components/AnySpendDeposit.tsx +1 -0
- package/src/anyspend/react/components/common/CryptoPaySection.tsx +4 -0
- package/src/anyspend/react/components/common/OrderDetails.tsx +20 -1
- package/src/anyspend/react/components/common/OrderTokenAmount.tsx +19 -1
- package/src/anyspend/react/components/common/PaymentStripeWeb2.tsx +2 -2
- package/src/anyspend/types/api.ts +10 -287
package/package.json
CHANGED
|
@@ -45,7 +45,7 @@ import invariant from "invariant";
|
|
|
45
45
|
import { ArrowDown, CheckCircle, HistoryIcon, Loader2 } from "lucide-react";
|
|
46
46
|
import { motion } from "motion/react";
|
|
47
47
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
48
|
-
import { parseUnits } from "viem";
|
|
48
|
+
import { formatUnits, parseUnits } from "viem";
|
|
49
49
|
import { base, mainnet } from "viem/chains";
|
|
50
50
|
import { components } from "../../types/api";
|
|
51
51
|
import { useAutoSelectCryptoPaymentMethod } from "../hooks/useAutoSelectCryptoPaymentMethod";
|
|
@@ -124,6 +124,8 @@ export function AnySpend(props: {
|
|
|
124
124
|
classes?: AnySpendClasses;
|
|
125
125
|
/** When true, allows direct transfer without swap if source and destination token/chain are the same */
|
|
126
126
|
allowDirectTransfer?: boolean;
|
|
127
|
+
/** Fixed destination token amount (in wei/smallest unit). When provided, user cannot change the amount. */
|
|
128
|
+
destinationTokenAmount?: string;
|
|
127
129
|
}) {
|
|
128
130
|
const fingerprintConfig = getFingerprintConfig();
|
|
129
131
|
|
|
@@ -155,6 +157,7 @@ function AnySpendInner({
|
|
|
155
157
|
returnHomeLabel,
|
|
156
158
|
classes,
|
|
157
159
|
allowDirectTransfer = false,
|
|
160
|
+
destinationTokenAmount,
|
|
158
161
|
}: {
|
|
159
162
|
sourceChainId?: number;
|
|
160
163
|
destinationTokenAddress?: string;
|
|
@@ -175,6 +178,7 @@ function AnySpendInner({
|
|
|
175
178
|
returnHomeLabel?: string;
|
|
176
179
|
classes?: AnySpendClasses;
|
|
177
180
|
allowDirectTransfer?: boolean;
|
|
181
|
+
destinationTokenAmount?: string;
|
|
178
182
|
}) {
|
|
179
183
|
const searchParams = useSearchParamsSSR();
|
|
180
184
|
const router = useRouter();
|
|
@@ -368,6 +372,18 @@ function AnySpendInner({
|
|
|
368
372
|
appliedDstMetadataRef.current = false;
|
|
369
373
|
}, [selectedDstToken.address, selectedDstToken.chainId]);
|
|
370
374
|
|
|
375
|
+
// Prefill destination amount if provided (for fixed amount mode)
|
|
376
|
+
const appliedDestinationAmount = useRef(false);
|
|
377
|
+
useEffect(() => {
|
|
378
|
+
// Only apply when we have real metadata (not default decimals)
|
|
379
|
+
if (destinationTokenAmount && dstTokenMetadata?.decimals && !appliedDestinationAmount.current) {
|
|
380
|
+
appliedDestinationAmount.current = true;
|
|
381
|
+
const formattedAmount = formatUnits(BigInt(destinationTokenAmount), dstTokenMetadata.decimals);
|
|
382
|
+
setDstAmount(formattedAmount);
|
|
383
|
+
setIsSrcInputDirty(false); // Switch to EXACT_OUTPUT mode
|
|
384
|
+
}
|
|
385
|
+
}, [destinationTokenAmount, dstTokenMetadata]);
|
|
386
|
+
|
|
371
387
|
// Load swap configuration from URL on initial render
|
|
372
388
|
useEffect(() => {
|
|
373
389
|
// Skip if we've already processed the URL, if we have an order to load, or if URL param management is disabled
|
|
@@ -665,9 +681,12 @@ function AnySpendInner({
|
|
|
665
681
|
anyspendQuote.data.currencyOut?.currency?.decimals
|
|
666
682
|
) {
|
|
667
683
|
if (isSrcInputDirty) {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
684
|
+
// Don't override dstAmount if we have a fixed destinationTokenAmount
|
|
685
|
+
if (!destinationTokenAmount) {
|
|
686
|
+
const amount = anyspendQuote.data.currencyOut.amount;
|
|
687
|
+
const decimals = anyspendQuote.data.currencyOut.currency.decimals;
|
|
688
|
+
setDstAmount(formatTokenAmount(BigInt(amount), decimals, 6, false));
|
|
689
|
+
}
|
|
671
690
|
} else {
|
|
672
691
|
const amount = anyspendQuote.data.currencyIn.amount;
|
|
673
692
|
const decimals = anyspendQuote.data.currencyIn.currency.decimals;
|
|
@@ -675,12 +694,15 @@ function AnySpendInner({
|
|
|
675
694
|
}
|
|
676
695
|
} else {
|
|
677
696
|
if (isSrcInputDirty) {
|
|
678
|
-
|
|
697
|
+
// Don't reset dstAmount if we have a fixed destinationTokenAmount
|
|
698
|
+
if (!destinationTokenAmount) {
|
|
699
|
+
setDstAmount("");
|
|
700
|
+
}
|
|
679
701
|
} else {
|
|
680
702
|
setSrcAmount("");
|
|
681
703
|
}
|
|
682
704
|
}
|
|
683
|
-
}, [anyspendQuote, isSrcInputDirty]);
|
|
705
|
+
}, [anyspendQuote, isSrcInputDirty, destinationTokenAmount]);
|
|
684
706
|
|
|
685
707
|
useEffect(() => {
|
|
686
708
|
if (oat?.data?.order.status === "executed" && !onSuccessCalled.current) {
|
|
@@ -1205,6 +1227,7 @@ function AnySpendInner({
|
|
|
1205
1227
|
anyspendQuote={anyspendQuote}
|
|
1206
1228
|
onTokenSelect={onTokenSelect}
|
|
1207
1229
|
onShowFeeDetail={() => navigateToPanel(PanelView.FEE_DETAIL, "forward")}
|
|
1230
|
+
skipAutoMaxOnTokenChange={!!destinationTokenAmount}
|
|
1208
1231
|
/>
|
|
1209
1232
|
) : (
|
|
1210
1233
|
<motion.div
|
|
@@ -1306,6 +1329,7 @@ function AnySpendInner({
|
|
|
1306
1329
|
setIsSrcInputDirty(false);
|
|
1307
1330
|
setDstAmount(value);
|
|
1308
1331
|
}}
|
|
1332
|
+
disableAmountInput={!!destinationTokenAmount}
|
|
1309
1333
|
anyspendQuote={isDirectTransfer ? undefined : anyspendQuote}
|
|
1310
1334
|
onShowPointsDetail={
|
|
1311
1335
|
isDirectTransfer ? undefined : () => navigateToPanel(PanelView.POINTS_DETAIL, "forward")
|
|
@@ -222,14 +222,13 @@ function AnySpendCustomExactInInner({
|
|
|
222
222
|
// Prefill destination amount if provided (for EXACT_OUTPUT mode)
|
|
223
223
|
const appliedDestinationAmount = useRef(false);
|
|
224
224
|
useEffect(() => {
|
|
225
|
-
if (destinationTokenAmount && !appliedDestinationAmount.current) {
|
|
225
|
+
if (destinationTokenAmount && destinationToken?.decimals && !appliedDestinationAmount.current) {
|
|
226
226
|
appliedDestinationAmount.current = true;
|
|
227
|
-
// Convert wei to human-readable format
|
|
228
227
|
const formattedAmount = formatUnits(destinationTokenAmount, destinationToken.decimals);
|
|
229
228
|
setDstAmountInput(formattedAmount);
|
|
230
229
|
setIsSrcInputDirty(false); // Switch to EXACT_OUTPUT mode
|
|
231
230
|
}
|
|
232
|
-
}, [destinationTokenAmount, destinationToken
|
|
231
|
+
}, [destinationTokenAmount, destinationToken, setDstAmountInput, setIsSrcInputDirty]);
|
|
233
232
|
|
|
234
233
|
const selectedRecipientOrDefault = selectedRecipientAddress ?? recipientAddress;
|
|
235
234
|
|
|
@@ -419,6 +418,7 @@ function AnySpendCustomExactInInner({
|
|
|
419
418
|
onSelectCryptoPaymentMethod={() => setActivePanel(PanelView.CRYPTO_PAYMENT_METHOD)}
|
|
420
419
|
anyspendQuote={anyspendQuote}
|
|
421
420
|
onTokenSelect={onTokenSelect}
|
|
421
|
+
skipAutoMaxOnTokenChange={!!destinationTokenAmount}
|
|
422
422
|
/>
|
|
423
423
|
) : (
|
|
424
424
|
<motion.div
|
|
@@ -474,7 +474,7 @@ function AnySpendCustomExactInInner({
|
|
|
474
474
|
recipientName={recipientName || undefined}
|
|
475
475
|
customRecipientLabel={customRecipientLabel}
|
|
476
476
|
onSelectRecipient={() => setActivePanel(PanelView.RECIPIENT_SELECTION)}
|
|
477
|
-
dstAmount={isSrcInputDirty ? dstAmount : dstAmountInput}
|
|
477
|
+
dstAmount={isSrcInputDirty && !destinationTokenAmount ? dstAmount : dstAmountInput}
|
|
478
478
|
dstToken={selectedDstToken}
|
|
479
479
|
dstTokenSymbol={DESTINATION_TOKEN_DETAILS.SYMBOL}
|
|
480
480
|
dstTokenLogoURI={DESTINATION_TOKEN_DETAILS.LOGO_URI}
|
|
@@ -32,6 +32,8 @@ interface CryptoPaySectionProps {
|
|
|
32
32
|
onShowFeeDetail?: () => void;
|
|
33
33
|
// Custom classes for styling
|
|
34
34
|
classes?: CryptoPaySectionClasses;
|
|
35
|
+
/** When true, skip auto-setting max balance when token changes (used for fixed destination amount mode) */
|
|
36
|
+
skipAutoMaxOnTokenChange?: boolean;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
export function CryptoPaySection({
|
|
@@ -49,6 +51,7 @@ export function CryptoPaySection({
|
|
|
49
51
|
onTokenSelect,
|
|
50
52
|
onShowFeeDetail,
|
|
51
53
|
classes,
|
|
54
|
+
skipAutoMaxOnTokenChange = false,
|
|
52
55
|
}: CryptoPaySectionProps) {
|
|
53
56
|
const { data: srcTokenMetadata } = useTokenData(selectedSrcToken?.chainId, selectedSrcToken?.address);
|
|
54
57
|
|
|
@@ -134,6 +137,7 @@ export function CryptoPaySection({
|
|
|
134
137
|
token={selectedSrcToken}
|
|
135
138
|
setToken={setSelectedSrcToken}
|
|
136
139
|
onTokenSelect={onTokenSelect}
|
|
140
|
+
skipAutoMaxOnTokenChange={skipAutoMaxOnTokenChange}
|
|
137
141
|
/>
|
|
138
142
|
</div>
|
|
139
143
|
<div className={classes?.balanceRow || "flex items-center justify-between"}>
|
|
@@ -493,6 +493,25 @@ export const OrderDetails = memo(function OrderDetails({
|
|
|
493
493
|
}
|
|
494
494
|
}, [isPayableState, isComponentReady, handlePayment]);
|
|
495
495
|
|
|
496
|
+
// Auto-redirect to redirectUrl when order is executed (for onramp orders)
|
|
497
|
+
useEffect(() => {
|
|
498
|
+
if (order.status === "executed" && order.onrampMetadata?.redirectUrl) {
|
|
499
|
+
const baseUrl = order.onrampMetadata.redirectUrl;
|
|
500
|
+
try {
|
|
501
|
+
const url = new URL(baseUrl);
|
|
502
|
+
// Prevent Open Redirect vulnerabilities by ensuring the protocol is http or https
|
|
503
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
504
|
+
console.error(`Attempted redirect to a URL with an invalid protocol: ${url.protocol}`);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const redirectUrl = `${baseUrl.replace(/\/$/, "")}/${order.id}`;
|
|
508
|
+
window.location.href = redirectUrl;
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.error("Invalid redirect URL provided:", baseUrl, error);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}, [order.status, order.onrampMetadata?.redirectUrl, order.id]);
|
|
514
|
+
|
|
496
515
|
if (!srcToken || !dstToken) {
|
|
497
516
|
return <div>Loading...</div>;
|
|
498
517
|
}
|
|
@@ -940,7 +959,7 @@ export const OrderDetails = memo(function OrderDetails({
|
|
|
940
959
|
<OrderStatus order={order} selectedCryptoPaymentMethod={effectiveCryptoPaymentMethod} />
|
|
941
960
|
{statusDisplay === "processing" && (
|
|
942
961
|
<>
|
|
943
|
-
{order.onrampMetadata ? (
|
|
962
|
+
{order.onrampMetadata && order.onrampMetadata.vendor !== "none" ? (
|
|
944
963
|
<PaymentVendorUI order={order} dstTokenSymbol={dstToken.symbol} />
|
|
945
964
|
) : effectiveCryptoPaymentMethod === CryptoPaymentMethodType.CONNECT_WALLET ||
|
|
946
965
|
effectiveCryptoPaymentMethod === CryptoPaymentMethodType.GLOBAL_WALLET ? (
|
|
@@ -32,6 +32,7 @@ export function OrderTokenAmount({
|
|
|
32
32
|
tokenSelectClassName,
|
|
33
33
|
onTokenSelect,
|
|
34
34
|
walletAddress,
|
|
35
|
+
skipAutoMaxOnTokenChange = false,
|
|
35
36
|
}: {
|
|
36
37
|
disabled?: boolean;
|
|
37
38
|
inputValue: string;
|
|
@@ -50,6 +51,8 @@ export function OrderTokenAmount({
|
|
|
50
51
|
tokenSelectClassName?: string;
|
|
51
52
|
onTokenSelect?: (token: components["schemas"]["Token"], event: { preventDefault: () => void }) => void;
|
|
52
53
|
walletAddress?: string | undefined;
|
|
54
|
+
/** When true, skip auto-setting max balance when token changes (used for fixed destination amount mode) */
|
|
55
|
+
skipAutoMaxOnTokenChange?: boolean;
|
|
53
56
|
}) {
|
|
54
57
|
// Track previous token to detect changes
|
|
55
58
|
const prevTokenRef = useRef<string>(token.address);
|
|
@@ -64,6 +67,12 @@ export function OrderTokenAmount({
|
|
|
64
67
|
// Only handle "from" context
|
|
65
68
|
if (context !== "from") return;
|
|
66
69
|
|
|
70
|
+
// Skip auto-max when in fixed destination amount mode
|
|
71
|
+
if (skipAutoMaxOnTokenChange) {
|
|
72
|
+
prevTokenRef.current = token.address;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
// Check if token changed or if this is the initial load with balance
|
|
68
77
|
const isTokenChanged = prevTokenRef.current !== token.address;
|
|
69
78
|
|
|
@@ -88,7 +97,16 @@ export function OrderTokenAmount({
|
|
|
88
97
|
// Update refs
|
|
89
98
|
prevTokenRef.current = token.address;
|
|
90
99
|
}
|
|
91
|
-
}, [
|
|
100
|
+
}, [
|
|
101
|
+
token.address,
|
|
102
|
+
token.chainId,
|
|
103
|
+
token.decimals,
|
|
104
|
+
chainId,
|
|
105
|
+
context,
|
|
106
|
+
onChangeInput,
|
|
107
|
+
rawBalance,
|
|
108
|
+
skipAutoMaxOnTokenChange,
|
|
109
|
+
]);
|
|
92
110
|
|
|
93
111
|
const handleTokenSelect = (newToken: any) => {
|
|
94
112
|
const token: components["schemas"]["Token"] = {
|
|
@@ -159,10 +159,10 @@ function StripePaymentForm({
|
|
|
159
159
|
JSON.stringify({ orderId: order.id, paymentIntentId: result.paymentIntent.id }, null, 2),
|
|
160
160
|
);
|
|
161
161
|
|
|
162
|
-
// Payment succeeded
|
|
162
|
+
// Payment succeeded
|
|
163
163
|
setMessage(null);
|
|
164
164
|
|
|
165
|
-
//
|
|
165
|
+
// Stay on page and show waiting state (redirect will happen in OrderDetails when order is executed)
|
|
166
166
|
const currentUrl = new URL(window.location.href);
|
|
167
167
|
currentUrl.searchParams.set("waitingForDeposit", "true");
|
|
168
168
|
window.history.replaceState(null, "", currentUrl.toString());
|