@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.
Files changed (35) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.d.ts +2 -0
  2. package/dist/cjs/anyspend/react/components/AnySpend.js +25 -8
  3. package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.js +4 -5
  4. package/dist/cjs/anyspend/react/components/AnySpendDeposit.js +1 -1
  5. package/dist/cjs/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  6. package/dist/cjs/anyspend/react/components/common/CryptoPaySection.js +2 -2
  7. package/dist/cjs/anyspend/react/components/common/OrderDetails.js +20 -1
  8. package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.d.ts +3 -1
  9. package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.js +16 -2
  10. package/dist/cjs/anyspend/react/components/common/PaymentStripeWeb2.js +2 -2
  11. package/dist/cjs/anyspend/types/api.d.ts +10 -287
  12. package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -0
  13. package/dist/esm/anyspend/react/components/AnySpend.js +26 -9
  14. package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.js +4 -5
  15. package/dist/esm/anyspend/react/components/AnySpendDeposit.js +1 -1
  16. package/dist/esm/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  17. package/dist/esm/anyspend/react/components/common/CryptoPaySection.js +2 -2
  18. package/dist/esm/anyspend/react/components/common/OrderDetails.js +20 -1
  19. package/dist/esm/anyspend/react/components/common/OrderTokenAmount.d.ts +3 -1
  20. package/dist/esm/anyspend/react/components/common/OrderTokenAmount.js +16 -2
  21. package/dist/esm/anyspend/react/components/common/PaymentStripeWeb2.js +2 -2
  22. package/dist/esm/anyspend/types/api.d.ts +10 -287
  23. package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -0
  24. package/dist/types/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
  25. package/dist/types/anyspend/react/components/common/OrderTokenAmount.d.ts +3 -1
  26. package/dist/types/anyspend/types/api.d.ts +10 -287
  27. package/package.json +1 -1
  28. package/src/anyspend/react/components/AnySpend.tsx +30 -6
  29. package/src/anyspend/react/components/AnySpendCustomExactIn.tsx +4 -4
  30. package/src/anyspend/react/components/AnySpendDeposit.tsx +1 -0
  31. package/src/anyspend/react/components/common/CryptoPaySection.tsx +4 -0
  32. package/src/anyspend/react/components/common/OrderDetails.tsx +20 -1
  33. package/src/anyspend/react/components/common/OrderTokenAmount.tsx +19 -1
  34. package/src/anyspend/react/components/common/PaymentStripeWeb2.tsx +2 -2
  35. package/src/anyspend/types/api.ts +10 -287
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3dotfun/sdk",
3
- "version": "0.1.63",
3
+ "version": "0.1.64-alpha.1",
4
4
  "source": "src/index.ts",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "react-native": "./dist/cjs/index.native.js",
@@ -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
- const amount = anyspendQuote.data.currencyOut.amount;
669
- const decimals = anyspendQuote.data.currencyOut.currency.decimals;
670
- setDstAmount(formatTokenAmount(BigInt(amount), decimals, 6, false));
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
- setDstAmount("");
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.decimals, setDstAmountInput, setIsSrcInputDirty]);
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}
@@ -719,6 +719,7 @@ export function AnySpendDeposit({
719
719
  returnHomeLabel={returnHomeLabel}
720
720
  classes={classes?.anySpend}
721
721
  allowDirectTransfer={allowDirectTransfer}
722
+ destinationTokenAmount={destinationTokenAmount}
722
723
  />
723
724
  )}
724
725
  </div>
@@ -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
- }, [token.address, token.chainId, token.decimals, chainId, context, onChangeInput, rawBalance]);
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 without redirect - handle success in the modal
162
+ // Payment succeeded
163
163
  setMessage(null);
164
164
 
165
- // Add waitingForDeposit=true to query params
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());