@b3dotfun/sdk 0.1.70-alpha.11 → 0.1.70-alpha.13
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 +7 -0
- package/dist/cjs/anyspend/react/components/AnySpend.js +30 -17
- package/dist/cjs/anyspend/react/components/AnySpendDeposit.d.ts +15 -1
- package/dist/cjs/anyspend/react/components/AnySpendDeposit.js +50 -13
- package/dist/cjs/anyspend/react/components/QRDeposit.d.ts +14 -3
- package/dist/cjs/anyspend/react/components/QRDeposit.js +24 -15
- package/dist/cjs/global-account/react/components/SignInWithB3/BetterAuthSignIn.js +3 -1
- package/dist/cjs/global-account/react/components/SignInWithB3/components/AuthButton.js +2 -1
- package/dist/cjs/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.js +1 -0
- package/dist/cjs/global-account/react/components/SignInWithB3/utils/signInUtils.js +2 -0
- package/dist/cjs/global-account/react/hooks/useBetterAuth.d.ts +1 -1
- package/dist/cjs/global-account/react/stores/useModalStore.d.ts +14 -0
- package/dist/esm/anyspend/react/components/AnySpend.d.ts +7 -0
- package/dist/esm/anyspend/react/components/AnySpend.js +30 -17
- package/dist/esm/anyspend/react/components/AnySpendDeposit.d.ts +15 -1
- package/dist/esm/anyspend/react/components/AnySpendDeposit.js +51 -14
- package/dist/esm/anyspend/react/components/QRDeposit.d.ts +14 -3
- package/dist/esm/anyspend/react/components/QRDeposit.js +25 -16
- package/dist/esm/global-account/react/components/SignInWithB3/BetterAuthSignIn.js +3 -1
- package/dist/esm/global-account/react/components/SignInWithB3/components/AuthButton.js +2 -1
- package/dist/esm/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.js +1 -0
- package/dist/esm/global-account/react/components/SignInWithB3/utils/signInUtils.js +2 -0
- package/dist/esm/global-account/react/hooks/useBetterAuth.d.ts +1 -1
- package/dist/esm/global-account/react/stores/useModalStore.d.ts +14 -0
- package/dist/styles/index.css +1 -1
- package/dist/types/anyspend/react/components/AnySpend.d.ts +7 -0
- package/dist/types/anyspend/react/components/AnySpendDeposit.d.ts +15 -1
- package/dist/types/anyspend/react/components/QRDeposit.d.ts +14 -3
- package/dist/types/global-account/react/hooks/useBetterAuth.d.ts +1 -1
- package/dist/types/global-account/react/stores/useModalStore.d.ts +14 -0
- package/package.json +1 -1
- package/src/anyspend/react/components/AnySpend.tsx +42 -16
- package/src/anyspend/react/components/AnySpendDeposit.tsx +87 -12
- package/src/anyspend/react/components/QRDeposit.tsx +46 -18
- package/src/anyspend/react/components/__tests__/QRDeposit.test.tsx +256 -0
- package/src/global-account/react/components/SignInWithB3/BetterAuthSignIn.tsx +12 -1
- package/src/global-account/react/components/SignInWithB3/components/AuthButton.tsx +9 -1
- package/src/global-account/react/components/SignInWithB3/steps/LoginStepBetterAuth.tsx +1 -0
- package/src/global-account/react/components/SignInWithB3/utils/signInUtils.ts +2 -0
- package/src/global-account/react/hooks/useBetterAuth.ts +1 -1
- package/src/global-account/react/stores/useModalStore.ts +14 -0
|
@@ -14,6 +14,14 @@ export interface QRDepositProps {
|
|
|
14
14
|
destinationToken: components["schemas"]["Token"];
|
|
15
15
|
/** The destination chain ID */
|
|
16
16
|
destinationChainId: number;
|
|
17
|
+
/**
|
|
18
|
+
* When true, the deposit is a PURE TRANSFER: the destination token/chain mirror
|
|
19
|
+
* the user's currently selected source token/chain, so the exact token the user
|
|
20
|
+
* sends lands in their wallet on the same chain — no swap/bridge order is created.
|
|
21
|
+
* The `destinationToken`/`destinationChainId` props are then used only as the
|
|
22
|
+
* initial source selection (they must be fully resolved before mount). Defaults to false.
|
|
23
|
+
*/
|
|
24
|
+
pureTransferOnly?: boolean;
|
|
17
25
|
/** Creator address (optional) */
|
|
18
26
|
creatorAddress?: string;
|
|
19
27
|
/** Contract config for custom execution after deposit */
|
|
@@ -24,8 +32,11 @@ export interface QRDepositProps {
|
|
|
24
32
|
onClose?: () => void;
|
|
25
33
|
/** Callback when order is created successfully */
|
|
26
34
|
onOrderCreated?: (orderId: string) => void;
|
|
27
|
-
/**
|
|
28
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Callback when deposit is completed. The argument carries the deposited amount string
|
|
37
|
+
* (e.g. "200.00") on the pure-transfer path, or the order tx hash otherwise.
|
|
38
|
+
*/
|
|
39
|
+
onSuccess?: (amountOrTxHash?: string) => void;
|
|
29
40
|
/** Custom classes for styling */
|
|
30
41
|
classes?: QRDepositClasses;
|
|
31
42
|
}
|
|
@@ -43,4 +54,4 @@ export interface QRDepositProps {
|
|
|
43
54
|
* onSuccess={(txHash) => console.log("Deposit complete:", txHash)}
|
|
44
55
|
* />
|
|
45
56
|
*/
|
|
46
|
-
export declare function QRDeposit({ mode, recipientAddress, sourceToken: sourceTokenProp, sourceChainId: sourceChainIdProp, destinationToken, destinationChainId, creatorAddress, depositContractConfig, onBack, onClose, onOrderCreated, onSuccess, classes, }: QRDepositProps): import("react/jsx-runtime").JSX.Element;
|
|
57
|
+
export declare function QRDeposit({ mode, recipientAddress, sourceToken: sourceTokenProp, sourceChainId: sourceChainIdProp, destinationToken, destinationChainId, pureTransferOnly, creatorAddress, depositContractConfig, onBack, onClose, onOrderCreated, onSuccess, classes, }: QRDepositProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type BetterAuthSocialProvider = "google" | "discord" | "apple" | "github" | "slack" | "microsoft";
|
|
1
|
+
export type BetterAuthSocialProvider = "google" | "discord" | "apple" | "github" | "slack" | "microsoft" | "twitter";
|
|
2
2
|
/** Thrown when email verification is required before the user can sign in. */
|
|
3
3
|
export declare class EmailVerificationRequiredError extends Error {
|
|
4
4
|
constructor(message?: string);
|
|
@@ -617,8 +617,16 @@ export interface AnySpendDepositModalProps extends BaseModalProps {
|
|
|
617
617
|
destinationTokenAddress: string;
|
|
618
618
|
/** The destination chain ID */
|
|
619
619
|
destinationTokenChainId: number;
|
|
620
|
+
/**
|
|
621
|
+
* When false, the destination token is user-selectable even though a default destination token is
|
|
622
|
+
* provided (the provided destination is used only as the initial/default value). Used by the
|
|
623
|
+
* wallet-funding deposit flow so the user can change the receive token away from the default
|
|
624
|
+
* (Base USDC). Defaults to true (locked destination). Does not affect the QR/pure-transfer path. */
|
|
625
|
+
lockDestinationToken?: boolean;
|
|
620
626
|
/** Callback when deposit succeeds */
|
|
621
627
|
onSuccess?: (amount: string) => void;
|
|
628
|
+
/** Callback when the modal's close control is pressed. */
|
|
629
|
+
onClose?: () => void;
|
|
622
630
|
/** Callback for opening a custom modal (e.g., for special token handling) */
|
|
623
631
|
onOpenCustomModal?: () => void;
|
|
624
632
|
/** Custom footer content */
|
|
@@ -661,6 +669,12 @@ export interface AnySpendDepositModalProps extends BaseModalProps {
|
|
|
661
669
|
classes?: AnySpendAllClasses;
|
|
662
670
|
/** Whether to allow direct transfer without swap */
|
|
663
671
|
allowDirectTransfer?: boolean;
|
|
672
|
+
/**
|
|
673
|
+
* When true, the QR-deposit path is a PURE TRANSFER: the destination token/chain
|
|
674
|
+
* mirror the user's selected source token, so the exact token they send lands in
|
|
675
|
+
* their wallet on the same chain (no swap/bridge).
|
|
676
|
+
*/
|
|
677
|
+
pureTransferOnly?: boolean;
|
|
664
678
|
/** Opaque metadata passed to the order for callbacks (e.g., workflow form data) */
|
|
665
679
|
callbackMetadata?: Record<string, unknown>;
|
|
666
680
|
}
|
package/package.json
CHANGED
|
@@ -110,6 +110,13 @@ export function AnySpend(props: {
|
|
|
110
110
|
sourceChainId?: number;
|
|
111
111
|
destinationTokenAddress?: string;
|
|
112
112
|
destinationTokenChainId?: number;
|
|
113
|
+
/**
|
|
114
|
+
* When false, the destination token is user-selectable even if a default destination token is
|
|
115
|
+
* provided (the provided destination is used only as the initial/default value). Used by the
|
|
116
|
+
* wallet-funding deposit flow so the user can change the receive token away from the default
|
|
117
|
+
* (Base USDC). Defaults to true (locked buy-mode display, current behavior).
|
|
118
|
+
*/
|
|
119
|
+
lockDestinationToken?: boolean;
|
|
113
120
|
recipientAddress?: string;
|
|
114
121
|
loadOrder?: string;
|
|
115
122
|
hideTransactionHistoryButton?: boolean;
|
|
@@ -166,6 +173,7 @@ function AnySpendInner({
|
|
|
166
173
|
sourceChainId,
|
|
167
174
|
destinationTokenAddress,
|
|
168
175
|
destinationTokenChainId,
|
|
176
|
+
lockDestinationToken = true,
|
|
169
177
|
mode = "modal",
|
|
170
178
|
defaultActiveTab = "crypto",
|
|
171
179
|
loadOrder,
|
|
@@ -191,6 +199,7 @@ function AnySpendInner({
|
|
|
191
199
|
sourceChainId?: number;
|
|
192
200
|
destinationTokenAddress?: string;
|
|
193
201
|
destinationTokenChainId?: number;
|
|
202
|
+
lockDestinationToken?: boolean;
|
|
194
203
|
mode?: "page" | "modal";
|
|
195
204
|
defaultActiveTab?: "crypto" | "fiat";
|
|
196
205
|
loadOrder?: string;
|
|
@@ -230,8 +239,10 @@ function AnySpendInner({
|
|
|
230
239
|
// in the same frame that onStatusResolved sets it (setState is async).
|
|
231
240
|
// When kycEnabled is false (default), pre-approve so the KYC gate is skipped.
|
|
232
241
|
const kycApprovedRef = useRef(!kycEnabled);
|
|
233
|
-
// Determine if we're in "buy mode" based on whether destination token props are provided
|
|
234
|
-
|
|
242
|
+
// Determine if we're in "buy mode" based on whether destination token props are provided.
|
|
243
|
+
// When lockDestinationToken is false, the provided destination is only a default and the user
|
|
244
|
+
// can change the receive token, so we stay out of buy mode (selectable swap mode).
|
|
245
|
+
const isBuyMode = !!(destinationTokenAddress && destinationTokenChainId) && lockDestinationToken;
|
|
235
246
|
|
|
236
247
|
// Add refs to track URL state
|
|
237
248
|
const initialUrlProcessed = useRef(false);
|
|
@@ -323,10 +334,16 @@ function AnySpendInner({
|
|
|
323
334
|
sessionStorage.setItem("anyspend_fiat_method", selectedFiatPaymentMethod);
|
|
324
335
|
}, [selectedFiatPaymentMethod]);
|
|
325
336
|
|
|
337
|
+
// Whether a default destination token was provided. When lockDestinationToken is false this is
|
|
338
|
+
// used only as the INITIAL/default value (the user can still change the receive token), so the
|
|
339
|
+
// wallet-funding deposit flow defaults to Base USDC but stays selectable.
|
|
340
|
+
const hasProvidedDestination = !!(destinationTokenAddress && destinationTokenChainId);
|
|
341
|
+
|
|
326
342
|
// Get initial chain IDs from URL or defaults
|
|
327
343
|
const initialSrcChainId = sourceChainId || parseInt(searchParams.get("fromChainId") || "0") || mainnet.id;
|
|
328
344
|
const initialDstChainId =
|
|
329
|
-
parseInt(searchParams.get("toChainId") || "0") ||
|
|
345
|
+
parseInt(searchParams.get("toChainId") || "0") ||
|
|
346
|
+
(isBuyMode ? destinationTokenChainId : hasProvidedDestination ? destinationTokenChainId : base.id);
|
|
330
347
|
|
|
331
348
|
// State for source chain/token selection
|
|
332
349
|
const [selectedSrcChainId, setSelectedSrcChainId] = useState<number>(initialSrcChainId);
|
|
@@ -368,19 +385,28 @@ function AnySpendInner({
|
|
|
368
385
|
const isHyperliquidUSDCAddress = (address?: string) =>
|
|
369
386
|
eqci(address, HYPERLIQUID_USDC_ADDRESS) || eqci(address, ZERO_ADDRESS);
|
|
370
387
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
388
|
+
// Build a token object from the provided destination props (handles the Hyperliquid USDC special case).
|
|
389
|
+
// The inline truthiness check lets TypeScript narrow both props to non-null, so it is `undefined`
|
|
390
|
+
// unless both props are provided — i.e. only meaningful when hasProvidedDestination/isBuyMode is true.
|
|
391
|
+
const providedDstToken: components["schemas"]["Token"] | undefined =
|
|
392
|
+
destinationTokenChainId && destinationTokenAddress
|
|
393
|
+
? destinationTokenChainId === HYPERLIQUID_CHAIN_ID && isHyperliquidUSDCAddress(destinationTokenAddress)
|
|
394
|
+
? getHyperliquidUSDCToken()
|
|
395
|
+
: {
|
|
396
|
+
symbol: "",
|
|
397
|
+
chainId: destinationTokenChainId,
|
|
398
|
+
address: destinationTokenAddress,
|
|
399
|
+
name: "",
|
|
400
|
+
decimals: 18,
|
|
401
|
+
metadata: {},
|
|
402
|
+
}
|
|
403
|
+
: undefined;
|
|
404
|
+
|
|
405
|
+
// In buy mode the provided destination is locked. Otherwise, if a default destination was provided
|
|
406
|
+
// (selectable deposit flow) seed it as the default; falling back to the chain's default token only
|
|
407
|
+
// when no destination prop is given (standard swap mode).
|
|
408
|
+
const defaultDstToken =
|
|
409
|
+
(isBuyMode || hasProvidedDestination) && providedDstToken ? providedDstToken : getDefaultToken(selectedDstChainId);
|
|
384
410
|
const dstTokenFromUrl = useTokenFromUrl({
|
|
385
411
|
defaultToken: defaultDstToken,
|
|
386
412
|
prefix: "to",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { USDC_BASE } from "@b3dotfun/sdk/anyspend";
|
|
1
2
|
import { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
2
3
|
import { GetQuoteResponse } from "@b3dotfun/sdk/anyspend/types/api_req_res";
|
|
3
4
|
import { Skeleton, useAccountWallet, useSimBalance, useTokenData } from "@b3dotfun/sdk/global-account/react";
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
NetworkPolygonPos,
|
|
12
13
|
} from "@web3icons/react";
|
|
13
14
|
import { ChevronRight } from "lucide-react";
|
|
14
|
-
import { useEffect, useMemo, useState } from "react";
|
|
15
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
15
16
|
import { AnySpend } from "./AnySpend";
|
|
16
17
|
import { AnySpendCustomExactIn } from "./AnySpendCustomExactIn";
|
|
17
18
|
import { ChainWarningText } from "./common/WarningText";
|
|
@@ -62,6 +63,13 @@ export interface AnySpendDepositProps {
|
|
|
62
63
|
destinationTokenAddress: string;
|
|
63
64
|
/** The destination chain ID */
|
|
64
65
|
destinationTokenChainId: number;
|
|
66
|
+
/**
|
|
67
|
+
* When false, the destination token is user-selectable even though a default destination token is
|
|
68
|
+
* provided (the provided destination is used only as the initial/default value). Used by the
|
|
69
|
+
* wallet-funding deposit flow so the user can change the receive token away from the default
|
|
70
|
+
* (Base USDC). Defaults to true (locked destination, current behavior). Does not affect the
|
|
71
|
+
* QR/pure-transfer path. */
|
|
72
|
+
lockDestinationToken?: boolean;
|
|
65
73
|
/** Callback when deposit succeeds */
|
|
66
74
|
onSuccess?: (amount: string) => void;
|
|
67
75
|
/** Callback for opening a custom modal (e.g., for special token handling) */
|
|
@@ -124,6 +132,13 @@ export interface AnySpendDepositProps {
|
|
|
124
132
|
classes?: AnySpendAllClasses;
|
|
125
133
|
/** When true, allows direct transfer without swap if source and destination token/chain are the same */
|
|
126
134
|
allowDirectTransfer?: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* When true, the QR-deposit path is a PURE TRANSFER: the destination token/chain
|
|
137
|
+
* mirror whatever source token the user selects, so the exact token they send lands
|
|
138
|
+
* in their wallet on the same chain (no swap/bridge). Forwarded to QRDeposit.
|
|
139
|
+
* Defaults to false.
|
|
140
|
+
*/
|
|
141
|
+
pureTransferOnly?: boolean;
|
|
127
142
|
/** Fixed destination token amount (in wei/smallest unit). When provided, user cannot change the amount. */
|
|
128
143
|
destinationTokenAmount?: string;
|
|
129
144
|
/** Opaque metadata passed to the order for callbacks (e.g., workflow form data) */
|
|
@@ -151,6 +166,21 @@ const DEFAULT_SUPPORTED_CHAINS: ChainConfig[] = [
|
|
|
151
166
|
// Minimum pool size to filter out low liquidity tokens
|
|
152
167
|
const DEFAULT_MIN_POOL_SIZE = 1_000_000;
|
|
153
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Self-described fallback metadata for the default funding token(s), keyed by
|
|
171
|
+
* `${chainId}:${lowercased address}`. Used when `useTokenData` misses (returns
|
|
172
|
+
* null) so the destination token's decimals/symbol are correct WITHOUT depending
|
|
173
|
+
* on the network — critical in pure-transfer mode where there's no server-side
|
|
174
|
+
* correction and the wrong decimals would make `useWatchTransfer` show a wrong amount.
|
|
175
|
+
*/
|
|
176
|
+
const KNOWN_FUNDING_TOKENS: Record<string, components["schemas"]["Token"]> = {
|
|
177
|
+
[`${USDC_BASE.chainId}:${USDC_BASE.address.toLowerCase()}`]: USDC_BASE,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
function getKnownFundingToken(chainId: number, address: string): components["schemas"]["Token"] | undefined {
|
|
181
|
+
return KNOWN_FUNDING_TOKENS[`${chainId}:${address.toLowerCase()}`];
|
|
182
|
+
}
|
|
183
|
+
|
|
154
184
|
type DepositStep = "select-chain" | "deposit" | "qr-deposit";
|
|
155
185
|
|
|
156
186
|
function formatUsd(value: number): string {
|
|
@@ -239,6 +269,7 @@ export function AnySpendDeposit({
|
|
|
239
269
|
sourceTokenChainId: initialSourceChainId,
|
|
240
270
|
destinationTokenAddress,
|
|
241
271
|
destinationTokenChainId,
|
|
272
|
+
lockDestinationToken = true,
|
|
242
273
|
onSuccess,
|
|
243
274
|
onOpenCustomModal,
|
|
244
275
|
mainFooter,
|
|
@@ -261,6 +292,7 @@ export function AnySpendDeposit({
|
|
|
261
292
|
isCustomDeposit = false,
|
|
262
293
|
classes,
|
|
263
294
|
allowDirectTransfer = false,
|
|
295
|
+
pureTransferOnly = false,
|
|
264
296
|
destinationTokenAmount,
|
|
265
297
|
callbackMetadata,
|
|
266
298
|
senderAddress,
|
|
@@ -289,20 +321,26 @@ export function AnySpendDeposit({
|
|
|
289
321
|
}, [showFiatOption, paymentType]);
|
|
290
322
|
|
|
291
323
|
// Fetch destination token data
|
|
292
|
-
const { data: destinationTokenData } = useTokenData(
|
|
324
|
+
const { data: destinationTokenData, isLoading: isDestinationTokenLoading } = useTokenData(
|
|
325
|
+
destinationTokenChainId,
|
|
326
|
+
destinationTokenAddress,
|
|
327
|
+
);
|
|
293
328
|
|
|
294
|
-
// Construct full destination token object
|
|
295
|
-
|
|
296
|
-
|
|
329
|
+
// Construct full destination token object. When `useTokenData` misses (it returns
|
|
330
|
+
// null on an API miss, not an error), fall back to a known-token entry so the
|
|
331
|
+
// decimals/symbol invariant holds regardless of the fetch — e.g. Base USDC is 6
|
|
332
|
+
// decimals, never the generic 18 fallback.
|
|
333
|
+
const destinationToken: components["schemas"]["Token"] = useMemo(() => {
|
|
334
|
+
const known = getKnownFundingToken(destinationTokenChainId, destinationTokenAddress);
|
|
335
|
+
return {
|
|
297
336
|
address: destinationTokenAddress,
|
|
298
337
|
chainId: destinationTokenChainId,
|
|
299
|
-
symbol: destinationTokenData?.symbol ?? "",
|
|
300
|
-
name: destinationTokenData?.name ?? "",
|
|
301
|
-
decimals: destinationTokenData?.decimals ?? 18,
|
|
302
|
-
metadata: { logoURI: destinationTokenData?.logoURI },
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
);
|
|
338
|
+
symbol: destinationTokenData?.symbol ?? known?.symbol ?? "",
|
|
339
|
+
name: destinationTokenData?.name ?? known?.name ?? "",
|
|
340
|
+
decimals: destinationTokenData?.decimals ?? known?.decimals ?? 18,
|
|
341
|
+
metadata: { logoURI: destinationTokenData?.logoURI ?? known?.metadata?.logoURI },
|
|
342
|
+
};
|
|
343
|
+
}, [destinationTokenAddress, destinationTokenChainId, destinationTokenData]);
|
|
306
344
|
|
|
307
345
|
// Fetch balances for EOA wallet (use senderAddress as fallback for pre-filled balance display)
|
|
308
346
|
const effectiveBalanceAddress = senderAddress || eoaAddress;
|
|
@@ -359,6 +397,13 @@ export function AnySpendDeposit({
|
|
|
359
397
|
return Object.values(chainBalances).reduce((sum, chain) => sum + chain.totalUsdValue, 0);
|
|
360
398
|
}, [chainBalances]);
|
|
361
399
|
|
|
400
|
+
const handleQRDepositSuccess = useCallback(
|
|
401
|
+
(txHash?: string) => {
|
|
402
|
+
onSuccess?.(txHash ?? "");
|
|
403
|
+
},
|
|
404
|
+
[onSuccess],
|
|
405
|
+
);
|
|
406
|
+
|
|
362
407
|
if (!recipientAddress) return null;
|
|
363
408
|
|
|
364
409
|
const tokenSymbol = destinationToken.symbol || "TOKEN";
|
|
@@ -644,15 +689,44 @@ export function AnySpendDeposit({
|
|
|
644
689
|
|
|
645
690
|
// QR Deposit view
|
|
646
691
|
if (step === "qr-deposit") {
|
|
692
|
+
// In pure-transfer mode QRDeposit captures `destinationToken` as its initial
|
|
693
|
+
// SOURCE token via useState, so it must be fully resolved (correct symbol +
|
|
694
|
+
// decimals) before QRDeposit mounts. Known funding tokens (e.g. Base USDC) are
|
|
695
|
+
// already fully resolved via the self-described fallback, so only show a spinner
|
|
696
|
+
// for unknown tokens whose metadata still has to load over the network.
|
|
697
|
+
const hasKnownDestination = !!getKnownFundingToken(destinationTokenChainId, destinationTokenAddress);
|
|
698
|
+
// NOTE: all current Fund Wallet callers derive `destinationTokenAddress` from
|
|
699
|
+
// `getDefaultDepositDestination`, which always returns Base USDC — exactly a
|
|
700
|
+
// `KNOWN_FUNDING_TOKENS` key — so `hasKnownDestination` is always true and this
|
|
701
|
+
// branch never fires for them. The guard is defensive for FUTURE callers that pass
|
|
702
|
+
// an unknown token together with `pureTransferOnly=true`.
|
|
703
|
+
if (pureTransferOnly && !hasKnownDestination && !destinationTokenData && isDestinationTokenLoading) {
|
|
704
|
+
return (
|
|
705
|
+
<div
|
|
706
|
+
className={cn(
|
|
707
|
+
"anyspend-deposit anyspend-deposit-qr-loading font-inter bg-as-surface-primary mx-auto w-full max-w-[460px] p-6",
|
|
708
|
+
mode === "page" && "border-as-border-secondary overflow-hidden rounded-2xl border shadow-xl",
|
|
709
|
+
)}
|
|
710
|
+
>
|
|
711
|
+
<div className="anyspend-deposit-qr-loading-content flex flex-col items-center justify-center gap-4 py-12">
|
|
712
|
+
<Skeleton className="h-8 w-8 rounded-full" />
|
|
713
|
+
<Skeleton className="h-4 w-40" />
|
|
714
|
+
</div>
|
|
715
|
+
</div>
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
647
719
|
return (
|
|
648
720
|
<QRDeposit
|
|
649
721
|
mode={mode}
|
|
650
722
|
recipientAddress={recipientAddress}
|
|
651
723
|
destinationToken={destinationToken}
|
|
652
724
|
destinationChainId={destinationTokenChainId}
|
|
725
|
+
pureTransferOnly={pureTransferOnly}
|
|
653
726
|
depositContractConfig={depositContractConfig}
|
|
654
727
|
onBack={handleBack}
|
|
655
728
|
onClose={onClose ?? handleBack}
|
|
729
|
+
onSuccess={handleQRDepositSuccess}
|
|
656
730
|
classes={classes?.qrDeposit}
|
|
657
731
|
/>
|
|
658
732
|
);
|
|
@@ -746,6 +820,7 @@ export function AnySpendDeposit({
|
|
|
746
820
|
sourceChainId={selectedChainId}
|
|
747
821
|
destinationTokenAddress={destinationTokenAddress}
|
|
748
822
|
destinationTokenChainId={destinationTokenChainId}
|
|
823
|
+
lockDestinationToken={lockDestinationToken}
|
|
749
824
|
onSuccess={txHash => onSuccess?.(txHash ?? "")}
|
|
750
825
|
onTokenSelect={onTokenSelect}
|
|
751
826
|
customUsdInputValues={customUsdInputValues}
|
|
@@ -11,7 +11,7 @@ import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
|
11
11
|
import { TokenSelector } from "@relayprotocol/relay-kit-ui";
|
|
12
12
|
import { Check, ChevronsUpDown, Copy, Loader2 } from "lucide-react";
|
|
13
13
|
import { QRCodeSVG } from "qrcode.react";
|
|
14
|
-
import { useEffect, useRef, useState } from "react";
|
|
14
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
15
15
|
import { useAnyspendOrderAndTransactions } from "../hooks/useAnyspendOrderAndTransactions";
|
|
16
16
|
import { useCreateDepositFirstOrder } from "../hooks/useCreateDepositFirstOrder";
|
|
17
17
|
import { useOnOrderSuccess } from "../hooks/useOnOrderSuccess";
|
|
@@ -36,6 +36,14 @@ export interface QRDepositProps {
|
|
|
36
36
|
destinationToken: components["schemas"]["Token"];
|
|
37
37
|
/** The destination chain ID */
|
|
38
38
|
destinationChainId: number;
|
|
39
|
+
/**
|
|
40
|
+
* When true, the deposit is a PURE TRANSFER: the destination token/chain mirror
|
|
41
|
+
* the user's currently selected source token/chain, so the exact token the user
|
|
42
|
+
* sends lands in their wallet on the same chain — no swap/bridge order is created.
|
|
43
|
+
* The `destinationToken`/`destinationChainId` props are then used only as the
|
|
44
|
+
* initial source selection (they must be fully resolved before mount). Defaults to false.
|
|
45
|
+
*/
|
|
46
|
+
pureTransferOnly?: boolean;
|
|
39
47
|
/** Creator address (optional) */
|
|
40
48
|
creatorAddress?: string;
|
|
41
49
|
/** Contract config for custom execution after deposit */
|
|
@@ -46,8 +54,11 @@ export interface QRDepositProps {
|
|
|
46
54
|
onClose?: () => void;
|
|
47
55
|
/** Callback when order is created successfully */
|
|
48
56
|
onOrderCreated?: (orderId: string) => void;
|
|
49
|
-
/**
|
|
50
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Callback when deposit is completed. The argument carries the deposited amount string
|
|
59
|
+
* (e.g. "200.00") on the pure-transfer path, or the order tx hash otherwise.
|
|
60
|
+
*/
|
|
61
|
+
onSuccess?: (amountOrTxHash?: string) => void;
|
|
51
62
|
/** Custom classes for styling */
|
|
52
63
|
classes?: QRDepositClasses;
|
|
53
64
|
}
|
|
@@ -85,6 +96,7 @@ export function QRDeposit({
|
|
|
85
96
|
sourceChainId: sourceChainIdProp,
|
|
86
97
|
destinationToken,
|
|
87
98
|
destinationChainId,
|
|
99
|
+
pureTransferOnly = false,
|
|
88
100
|
creatorAddress,
|
|
89
101
|
depositContractConfig,
|
|
90
102
|
onBack,
|
|
@@ -99,31 +111,45 @@ export function QRDeposit({
|
|
|
99
111
|
const orderCreatedRef = useRef(false);
|
|
100
112
|
const [transferResult, setTransferResult] = useState<TransferResult | null>(null);
|
|
101
113
|
|
|
102
|
-
// Source token/chain as state (can be changed by user)
|
|
103
|
-
|
|
114
|
+
// Source token/chain as state (can be changed by user).
|
|
115
|
+
// In pure-transfer mode the initial source is the passed destination (caller sets
|
|
116
|
+
// it to the desired default funding token), so the deposit mirrors the user's selection.
|
|
117
|
+
const [sourceChainId, setSourceChainId] = useState(
|
|
118
|
+
sourceChainIdProp ?? (pureTransferOnly ? destinationChainId : 8453),
|
|
119
|
+
);
|
|
104
120
|
const [sourceToken, setSourceToken] = useState<components["schemas"]["Token"]>(
|
|
105
|
-
sourceTokenProp ?? DEFAULT_ETH_ON_BASE,
|
|
121
|
+
sourceTokenProp ?? (pureTransferOnly ? destinationToken : DEFAULT_ETH_ON_BASE),
|
|
106
122
|
);
|
|
107
123
|
|
|
124
|
+
// In pure-transfer mode the effective destination mirrors the selected source,
|
|
125
|
+
// forcing isPureTransfer = true (no swap/bridge order is created).
|
|
126
|
+
const effectiveDestinationToken = pureTransferOnly ? sourceToken : destinationToken;
|
|
127
|
+
const effectiveDestinationChainId = pureTransferOnly ? sourceChainId : destinationChainId;
|
|
128
|
+
|
|
108
129
|
// Check if this is a pure transfer (same chain and token)
|
|
109
130
|
const isPureTransfer = isSameChainAndToken(
|
|
110
131
|
sourceChainId,
|
|
111
132
|
sourceToken.address,
|
|
112
|
-
|
|
113
|
-
|
|
133
|
+
effectiveDestinationChainId,
|
|
134
|
+
effectiveDestinationToken.address,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const handleTransferDetected = useCallback(
|
|
138
|
+
(result: TransferResult) => {
|
|
139
|
+
setTransferResult(result);
|
|
140
|
+
onSuccess?.(result.formattedAmount);
|
|
141
|
+
},
|
|
142
|
+
[onSuccess],
|
|
114
143
|
);
|
|
115
144
|
|
|
116
145
|
// Watch for pure transfers (same chain and token)
|
|
117
|
-
const { isWatching: isWatchingTransfer } = useWatchTransfer({
|
|
146
|
+
const { isWatching: isWatchingTransfer, reset: resetWatchTransfer } = useWatchTransfer({
|
|
118
147
|
address: recipientAddress,
|
|
119
148
|
chainId: sourceChainId,
|
|
120
149
|
tokenAddress: sourceToken.address,
|
|
121
150
|
tokenDecimals: sourceToken.decimals,
|
|
122
151
|
enabled: isPureTransfer && !transferResult,
|
|
123
|
-
onTransferDetected:
|
|
124
|
-
setTransferResult(result);
|
|
125
|
-
onSuccess?.();
|
|
126
|
-
},
|
|
152
|
+
onTransferDetected: handleTransferDetected,
|
|
127
153
|
});
|
|
128
154
|
|
|
129
155
|
// Handle token selection from TokenSelector
|
|
@@ -142,6 +168,8 @@ export function QRDeposit({
|
|
|
142
168
|
setGlobalAddress(undefined);
|
|
143
169
|
orderCreatedRef.current = false;
|
|
144
170
|
setTransferResult(null);
|
|
171
|
+
// Reset the balance watcher baseline so the new token isn't compared against the old token's balance
|
|
172
|
+
resetWatchTransfer();
|
|
145
173
|
|
|
146
174
|
// Update token and chain
|
|
147
175
|
setSourceChainId(newToken.chainId);
|
|
@@ -176,18 +204,18 @@ export function QRDeposit({
|
|
|
176
204
|
createOrder({
|
|
177
205
|
recipientAddress,
|
|
178
206
|
srcChain: sourceChainId,
|
|
179
|
-
dstChain:
|
|
207
|
+
dstChain: effectiveDestinationChainId,
|
|
180
208
|
srcToken: sourceToken,
|
|
181
|
-
dstToken:
|
|
209
|
+
dstToken: effectiveDestinationToken,
|
|
182
210
|
creatorAddress,
|
|
183
211
|
contractConfig: depositContractConfig,
|
|
184
212
|
});
|
|
185
213
|
}, [
|
|
186
214
|
recipientAddress,
|
|
187
215
|
sourceChainId,
|
|
188
|
-
|
|
216
|
+
effectiveDestinationChainId,
|
|
189
217
|
sourceToken,
|
|
190
|
-
|
|
218
|
+
effectiveDestinationToken,
|
|
191
219
|
creatorAddress,
|
|
192
220
|
depositContractConfig,
|
|
193
221
|
createOrder,
|
|
@@ -425,7 +453,7 @@ export function QRDeposit({
|
|
|
425
453
|
</div>
|
|
426
454
|
|
|
427
455
|
{/* Warnings */}
|
|
428
|
-
<ChainWarningText chainId={
|
|
456
|
+
<ChainWarningText chainId={effectiveDestinationChainId} />
|
|
429
457
|
<WarningText>
|
|
430
458
|
Only send {sourceToken.symbol} on {ALL_CHAINS[sourceChainId]?.name ?? "the specified chain"}. Other tokens
|
|
431
459
|
will not be converted.
|