@b3dotfun/sdk 0.0.62-alpha.2 → 0.0.62-alpha.3

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 (79) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.js +61 -23
  2. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +3 -0
  3. package/dist/cjs/anyspend/react/components/AnyspendDepositHype.js +4 -4
  4. package/dist/cjs/anyspend/react/components/common/CryptoPaySection.js +4 -6
  5. package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +9 -17
  6. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.d.ts +6 -1
  7. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +11 -1
  8. package/dist/cjs/anyspend/react/components/common/OrderDetails.js +56 -145
  9. package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.d.ts +2 -1
  10. package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.js +39 -15
  11. package/dist/cjs/anyspend/react/components/common/PaySection.js +1 -1
  12. package/dist/cjs/anyspend/react/components/common/TokenBalance.js +1 -1
  13. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +12 -11
  14. package/dist/cjs/anyspend/react/hooks/useAutoSelectCryptoPaymentMethod.d.ts +26 -0
  15. package/dist/cjs/anyspend/react/hooks/useAutoSelectCryptoPaymentMethod.js +56 -0
  16. package/dist/cjs/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.d.ts +10 -0
  17. package/dist/cjs/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.js +73 -0
  18. package/dist/cjs/anyspend/react/hooks/useConnectedWalletDisplay.d.ts +14 -0
  19. package/dist/cjs/anyspend/react/hooks/useConnectedWalletDisplay.js +57 -0
  20. package/dist/cjs/anyspend/react/hooks/usePhantomTransfer.d.ts +36 -0
  21. package/dist/cjs/anyspend/react/hooks/usePhantomTransfer.js +211 -0
  22. package/dist/cjs/global-account/react/hooks/index.d.ts +2 -1
  23. package/dist/cjs/global-account/react/hooks/index.js +5 -3
  24. package/dist/cjs/global-account/react/hooks/useTokenBalanceDirect.d.ts +12 -0
  25. package/dist/cjs/global-account/react/hooks/useTokenBalanceDirect.js +62 -0
  26. package/dist/cjs/global-account/react/hooks/useTokenFromUrl.js +4 -3
  27. package/dist/esm/anyspend/react/components/AnySpend.js +62 -24
  28. package/dist/esm/anyspend/react/components/AnySpendCustom.js +3 -0
  29. package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +4 -4
  30. package/dist/esm/anyspend/react/components/common/CryptoPaySection.js +5 -7
  31. package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +9 -17
  32. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +6 -1
  33. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +11 -1
  34. package/dist/esm/anyspend/react/components/common/OrderDetails.js +57 -146
  35. package/dist/esm/anyspend/react/components/common/OrderTokenAmount.d.ts +2 -1
  36. package/dist/esm/anyspend/react/components/common/OrderTokenAmount.js +40 -16
  37. package/dist/esm/anyspend/react/components/common/PaySection.js +1 -1
  38. package/dist/esm/anyspend/react/components/common/TokenBalance.js +2 -2
  39. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +12 -11
  40. package/dist/esm/anyspend/react/hooks/useAutoSelectCryptoPaymentMethod.d.ts +26 -0
  41. package/dist/esm/anyspend/react/hooks/useAutoSelectCryptoPaymentMethod.js +53 -0
  42. package/dist/esm/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.d.ts +10 -0
  43. package/dist/esm/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.js +70 -0
  44. package/dist/esm/anyspend/react/hooks/useConnectedWalletDisplay.d.ts +14 -0
  45. package/dist/esm/anyspend/react/hooks/useConnectedWalletDisplay.js +54 -0
  46. package/dist/esm/anyspend/react/hooks/usePhantomTransfer.d.ts +36 -0
  47. package/dist/esm/anyspend/react/hooks/usePhantomTransfer.js +208 -0
  48. package/dist/esm/global-account/react/hooks/index.d.ts +2 -1
  49. package/dist/esm/global-account/react/hooks/index.js +2 -1
  50. package/dist/esm/global-account/react/hooks/useTokenBalanceDirect.d.ts +12 -0
  51. package/dist/esm/global-account/react/hooks/useTokenBalanceDirect.js +59 -0
  52. package/dist/esm/global-account/react/hooks/useTokenFromUrl.js +4 -3
  53. package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +6 -1
  54. package/dist/types/anyspend/react/components/common/OrderTokenAmount.d.ts +2 -1
  55. package/dist/types/anyspend/react/hooks/useAutoSelectCryptoPaymentMethod.d.ts +26 -0
  56. package/dist/types/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.d.ts +10 -0
  57. package/dist/types/anyspend/react/hooks/useConnectedWalletDisplay.d.ts +14 -0
  58. package/dist/types/anyspend/react/hooks/usePhantomTransfer.d.ts +36 -0
  59. package/dist/types/global-account/react/hooks/index.d.ts +2 -1
  60. package/dist/types/global-account/react/hooks/useTokenBalanceDirect.d.ts +12 -0
  61. package/package.json +1 -1
  62. package/src/anyspend/react/components/AnySpend.tsx +73 -22
  63. package/src/anyspend/react/components/AnySpendCustom.tsx +4 -0
  64. package/src/anyspend/react/components/AnyspendDepositHype.tsx +7 -3
  65. package/src/anyspend/react/components/common/CryptoPaySection.tsx +5 -7
  66. package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +9 -18
  67. package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +22 -0
  68. package/src/anyspend/react/components/common/OrderDetails.tsx +66 -188
  69. package/src/anyspend/react/components/common/OrderTokenAmount.tsx +48 -17
  70. package/src/anyspend/react/components/common/PaySection.tsx +1 -0
  71. package/src/anyspend/react/components/common/TokenBalance.tsx +2 -2
  72. package/src/anyspend/react/hooks/useAnyspendFlow.ts +13 -10
  73. package/src/anyspend/react/hooks/useAutoSelectCryptoPaymentMethod.ts +72 -0
  74. package/src/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.ts +80 -0
  75. package/src/anyspend/react/hooks/useConnectedWalletDisplay.ts +69 -0
  76. package/src/anyspend/react/hooks/usePhantomTransfer.ts +301 -0
  77. package/src/global-account/react/hooks/index.ts +2 -1
  78. package/src/global-account/react/hooks/useTokenBalanceDirect.tsx +84 -0
  79. package/src/global-account/react/hooks/useTokenFromUrl.tsx +6 -5
@@ -31,22 +31,17 @@ import { cn } from "@b3dotfun/sdk/shared/utils";
31
31
  import centerTruncate from "@b3dotfun/sdk/shared/utils/centerTruncate";
32
32
  import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
33
33
 
34
- import {
35
- createAssociatedTokenAccountInstruction,
36
- createTransferCheckedInstruction,
37
- getAssociatedTokenAddressSync,
38
- } from "@solana/spl-token";
39
- import { ComputeBudgetProgram, Connection, PublicKey, SystemProgram, Transaction } from "@solana/web3.js";
40
34
  import { WalletCoinbase, WalletMetamask, WalletPhantom, WalletTrust, WalletWalletConnect } from "@web3icons/react";
41
35
  import { CheckIcon, ChevronRight, Copy, ExternalLink, Home, Loader2, RefreshCcw } from "lucide-react";
42
36
  import { motion } from "motion/react";
43
37
  import { QRCodeSVG } from "qrcode.react";
44
- import { memo, useCallback, useEffect, useMemo, useState } from "react";
38
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
45
39
  import TimeAgo from "react-timeago";
46
40
  import { toast } from "sonner";
47
41
  import { encodeFunctionData, erc20Abi } from "viem";
48
42
  import { b3 } from "viem/chains";
49
43
  import { useWaitForTransactionReceipt, useWalletClient } from "wagmi";
44
+ import { usePhantomTransfer } from "../../hooks/usePhantomTransfer";
50
45
  import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./Accordion";
51
46
  import ConnectWalletPayment from "./ConnectWalletPayment";
52
47
  import { CryptoPaymentMethodType } from "./CryptoPaymentMethod";
@@ -250,6 +245,11 @@ export const OrderDetails = memo(function OrderDetails({
250
245
  const { switchChainAndExecuteWithEOA, switchChainAndExecute, isSwitchingOrExecuting } =
251
246
  useUnifiedChainSwitchAndExecute();
252
247
 
248
+ // Track if auto-payment was attempted to avoid re-triggering
249
+ const autoPaymentAttempted = useRef(false);
250
+ // Track if component is ready for auto-payment (all data loaded)
251
+ const [isComponentReady, setIsComponentReady] = useState(false);
252
+
253
253
  const roundedUpSrcAmount = useMemo(() => {
254
254
  // Display the full transfer amount without rounding since users need to see the exact value they're transferring.
255
255
  // Use 21 significant digits (max allowed by Intl.NumberFormat)
@@ -314,18 +314,25 @@ export const OrderDetails = memo(function OrderDetails({
314
314
  }
315
315
  }, [order, switchChainAndExecuteWithEOA, switchChainAndExecute, depositDeficit, effectiveCryptoPaymentMethod]);
316
316
 
317
+ // Use Phantom transfer hook for Solana payments
318
+ const { initiateTransfer: initiatePhantomTransfer, getConnectedAddress: getPhantomAddress } = usePhantomTransfer();
319
+
317
320
  // Main payment handler that triggers chain switch and payment
318
- const handlePayment = async () => {
321
+ const handlePayment = useCallback(async () => {
319
322
  console.log("Initiating payment process. Target chain:", order.srcChain, "Current chain:", walletClient?.chain?.id);
320
323
  if (order.srcChain === RELAY_SOLANA_MAINNET_CHAIN_ID) {
321
324
  // Use the existing depositDeficit calculation to determine amount to send
322
325
  const amountToSend = depositDeficit > BigInt(0) ? depositDeficit.toString() : order.srcAmount;
323
- await initiatePhantomTransfer(amountToSend, order.srcTokenAddress, order.globalAddress);
326
+ await initiatePhantomTransfer({
327
+ amountLamports: amountToSend,
328
+ tokenAddress: order.srcTokenAddress,
329
+ recipientAddress: order.globalAddress,
330
+ });
324
331
  } else {
325
332
  // Use unified payment process for both EOA and AA wallets
326
333
  await handleUnifiedPaymentProcess();
327
334
  }
328
- };
335
+ }, [order, walletClient?.chain?.id, depositDeficit, handleUnifiedPaymentProcess, initiatePhantomTransfer]);
329
336
 
330
337
  // When waitingForDeposit is true, we show a message to the user to wait for the deposit to be processed.
331
338
  const setWaitingForDeposit = useCallback(() => {
@@ -368,17 +375,57 @@ export const OrderDetails = memo(function OrderDetails({
368
375
  }
369
376
  }, [setWaitingForDeposit, txSuccess]);
370
377
 
371
- const isPhantomMobile = useMemo(() => navigator.userAgent.includes("Phantom"), []);
372
- const isPhantomBrowser = useMemo(() => (window as any).phantom?.solana?.isPhantom, []);
373
-
374
378
  // Get connected Phantom wallet address if available
375
- const phantomWalletAddress = useMemo(() => {
376
- const phantom = (window as any).phantom?.solana;
377
- if (phantom?.isConnected && phantom?.publicKey) {
378
- return phantom.publicKey.toString();
379
+ const phantomWalletAddress = useMemo(() => getPhantomAddress(), [getPhantomAddress]);
380
+
381
+ // Calculate status display before using it
382
+ const { text: statusText, status: statusDisplay } = getStatusDisplay(order);
383
+
384
+ // Memoize the payable state calculation to avoid recalculating on every render
385
+ const isPayableState = useMemo(() => {
386
+ const waitingForDeposit = new URLSearchParams(window.location.search).get("waitingForDeposit") === "true";
387
+
388
+ return (
389
+ refundTxs.length === 0 &&
390
+ !executeTx &&
391
+ !(relayTxs.length > 0 && relayTxs.every(tx => tx.status === "success")) &&
392
+ !depositTxs?.length &&
393
+ !waitingForDeposit &&
394
+ statusDisplay === "processing" &&
395
+ !order.onrampMetadata &&
396
+ (effectiveCryptoPaymentMethod === CryptoPaymentMethodType.CONNECT_WALLET ||
397
+ effectiveCryptoPaymentMethod === CryptoPaymentMethodType.GLOBAL_WALLET)
398
+ );
399
+ }, [
400
+ refundTxs.length,
401
+ executeTx,
402
+ relayTxs,
403
+ depositTxs?.length,
404
+ statusDisplay,
405
+ order.onrampMetadata,
406
+ effectiveCryptoPaymentMethod,
407
+ ]);
408
+
409
+ // Mark component as ready once all critical data is available
410
+ // This ensures we don't trigger payment before the component has fully initialized
411
+ useEffect(() => {
412
+ if (!isComponentReady && srcToken && dstToken && statusDisplay) {
413
+ setIsComponentReady(true);
379
414
  }
380
- return null;
381
- }, []);
415
+ }, [isComponentReady, srcToken, dstToken, statusDisplay]);
416
+
417
+ // Auto-trigger payment when component is ready and order is in payable state
418
+ // This effect only runs when isPayableState or isComponentReady changes
419
+ useEffect(() => {
420
+ // Only trigger payment if:
421
+ // 1. We haven't attempted payment yet
422
+ // 2. Component is fully ready (all data loaded)
423
+ // 3. Order is in a payable state
424
+ if (!autoPaymentAttempted.current && isComponentReady && isPayableState) {
425
+ autoPaymentAttempted.current = true;
426
+ handlePayment();
427
+ }
428
+ }, [isPayableState, isComponentReady, handlePayment]);
382
429
 
383
430
  if (!srcToken || !dstToken) {
384
431
  return <div>Loading...</div>;
@@ -398,175 +445,6 @@ export const OrderDetails = memo(function OrderDetails({
398
445
  ? formatTokenAmount(BigInt(actualDstAmount), dstToken.decimals)
399
446
  : undefined;
400
447
 
401
- const { text: statusText, status: statusDisplay } = getStatusDisplay(order);
402
-
403
- const initiatePhantomTransfer = async (amountLamports: string, tokenAddress: string, recipientAddress: string) => {
404
- try {
405
- if (!isPhantomBrowser && !isPhantomMobile) {
406
- toast.error("Phantom wallet not installed. Please install Phantom wallet to continue.");
407
- return;
408
- }
409
-
410
- // Step 2: Ensure Phantom is connected/unlocked
411
- const phantom = (window as any).phantom?.solana;
412
- if (!phantom) {
413
- toast.error("Phantom wallet not accessible");
414
- return;
415
- }
416
-
417
- // Connect and unlock wallet if needed
418
- let publicKey;
419
- try {
420
- const connection = await phantom.connect();
421
- publicKey = connection.publicKey;
422
- } catch (connectError) {
423
- toast.error("Failed to connect to Phantom wallet");
424
- return;
425
- }
426
-
427
- // Step 3: Create transaction with priority fees
428
- const connection = new Connection("https://mainnet.helius-rpc.com/?api-key=efafd9b3-1807-4cf8-8aa4-3d984f56d8fb");
429
-
430
- const fromPubkey = new PublicKey(publicKey.toString());
431
- const toPubkey = new PublicKey(recipientAddress);
432
- const amount = BigInt(amountLamports);
433
-
434
- // Step 4: Get recent priority fees to determine optimal pricing
435
- let priorityFee = 10000; // Default fallback (10,000 micro-lamports)
436
- try {
437
- const recentFees = await connection.getRecentPrioritizationFees({
438
- lockedWritableAccounts: [fromPubkey],
439
- });
440
-
441
- if (recentFees && recentFees.length > 0) {
442
- // Use 75th percentile of recent fees for good priority
443
- const sortedFees = recentFees.map(fee => fee.prioritizationFee).sort((a, b) => a - b);
444
- const percentile75Index = Math.floor(sortedFees.length * 0.75);
445
- priorityFee = Math.max(sortedFees[percentile75Index] || 10000, 10000);
446
- }
447
- } catch (feeError) {
448
- console.warn("Failed to fetch recent priority fees, using default:", feeError);
449
- }
450
-
451
- let transaction: any;
452
-
453
- // Check if this is native SOL transfer
454
- if (tokenAddress === "11111111111111111111111111111111") {
455
- // Native SOL transfer with priority fees
456
- const computeUnitLimit = 1000; // SOL transfer + compute budget instructions need ~600-800 CU
457
- const computeUnitPrice = Math.min(priorityFee, 100000); // Cap at 100k micro-lamports for safety
458
-
459
- transaction = new Transaction()
460
- .add(
461
- // Set compute unit limit first (must come before other instructions)
462
- ComputeBudgetProgram.setComputeUnitLimit({
463
- units: computeUnitLimit,
464
- }),
465
- )
466
- .add(
467
- // Set priority fee
468
- ComputeBudgetProgram.setComputeUnitPrice({
469
- microLamports: computeUnitPrice,
470
- }),
471
- )
472
- .add(
473
- // Actual transfer instruction
474
- SystemProgram.transfer({
475
- fromPubkey,
476
- toPubkey,
477
- lamports: Number(amount),
478
- }),
479
- );
480
-
481
- console.log(`Using priority fee: ${computeUnitPrice} micro-lamports per CU, limit: ${computeUnitLimit} CU`);
482
- } else {
483
- // SPL Token transfer with priority fees
484
- const mintPubkey = new PublicKey(tokenAddress);
485
-
486
- // Get associated token accounts
487
- const fromTokenAccount = getAssociatedTokenAddressSync(mintPubkey, fromPubkey);
488
- const toTokenAccount = getAssociatedTokenAddressSync(mintPubkey, toPubkey);
489
-
490
- // Check if destination token account exists
491
- const toTokenAccountInfo = await connection.getAccountInfo(toTokenAccount);
492
- const needsDestinationAccount = !toTokenAccountInfo;
493
-
494
- // Get mint info to determine decimals
495
- const mintInfo = await connection.getParsedAccountInfo(mintPubkey);
496
- const decimals = (mintInfo.value?.data as any)?.parsed?.info?.decimals || 9;
497
-
498
- // SPL transfers need more compute units than SOL transfers
499
- // Add extra CU if we need to create destination account
500
- const computeUnitLimit = needsDestinationAccount ? 40000 : 20000;
501
- const computeUnitPrice = Math.min(priorityFee, 100000);
502
-
503
- // Create transfer instruction
504
- const transferInstruction = createTransferCheckedInstruction(
505
- fromTokenAccount,
506
- mintPubkey,
507
- toTokenAccount,
508
- fromPubkey,
509
- Number(amount),
510
- decimals,
511
- );
512
-
513
- transaction = new Transaction()
514
- .add(
515
- ComputeBudgetProgram.setComputeUnitLimit({
516
- units: computeUnitLimit,
517
- }),
518
- )
519
- .add(
520
- ComputeBudgetProgram.setComputeUnitPrice({
521
- microLamports: computeUnitPrice,
522
- }),
523
- );
524
-
525
- // Add create destination account instruction if needed
526
- if (needsDestinationAccount) {
527
- transaction.add(
528
- createAssociatedTokenAccountInstruction(
529
- fromPubkey, // payer
530
- toTokenAccount, // ata
531
- toPubkey, // owner
532
- mintPubkey, // mint
533
- ),
534
- );
535
- }
536
-
537
- // Add the transfer instruction
538
- transaction.add(transferInstruction);
539
-
540
- console.log(
541
- `SPL Token transfer: ${computeUnitPrice} micro-lamports per CU, limit: ${computeUnitLimit} CU, creating destination: ${needsDestinationAccount}`,
542
- );
543
- }
544
-
545
- // Step 5: Get latest blockhash and simulate transaction to verify
546
- const { blockhash } = await connection.getLatestBlockhash("confirmed");
547
- transaction.recentBlockhash = blockhash;
548
- transaction.feePayer = fromPubkey;
549
-
550
- // Step 6: Sign and send transaction with priority fees
551
- const signedTransaction = await phantom.signAndSendTransaction(transaction);
552
-
553
- toast.success(`Transaction successful! Signature: ${signedTransaction.signature}`);
554
- console.log("Transaction sent with priority fees. Signature:", signedTransaction.signature);
555
- } catch (error: unknown) {
556
- console.error("Transfer error:", error);
557
- const errorMessage = error instanceof Error ? error.message : String(error);
558
- if (errorMessage.includes("User rejected")) {
559
- toast.error("Transaction was cancelled by user");
560
- } else if (errorMessage.includes("insufficient")) {
561
- toast.error("Insufficient balance for this transaction");
562
- } else if (errorMessage.includes("blockhash not found")) {
563
- toast.error("Network congestion detected. Please try again in a moment.");
564
- } else {
565
- toast.error(`Transfer failed: ${errorMessage}`);
566
- }
567
- }
568
- };
569
-
570
448
  if (refundTxs.length > 0) {
571
449
  return (
572
450
  <>
@@ -3,10 +3,13 @@
3
3
  import { ChevronsUpDown } from "lucide-react";
4
4
  import { useEffect, useRef } from "react";
5
5
  import { NumericFormat } from "react-number-format";
6
+ import { formatUnits } from "viem";
6
7
 
7
8
  import { ALL_CHAINS, RELAY_SOLANA_MAINNET_CHAIN_ID } from "@b3dotfun/sdk/anyspend";
8
9
  import { components } from "@b3dotfun/sdk/anyspend/types/api";
9
- import { Button } from "@b3dotfun/sdk/global-account/react";
10
+ import { getNativeRequired } from "@b3dotfun/sdk/anyspend/utils/chain";
11
+ import { isNativeToken } from "@b3dotfun/sdk/anyspend/utils/token";
12
+ import { Button, useTokenBalance } from "@b3dotfun/sdk/global-account/react";
10
13
  import { cn } from "@b3dotfun/sdk/shared/utils";
11
14
  import { TokenSelector } from "@relayprotocol/relay-kit-ui";
12
15
  import { ChainTokenIcon } from "./ChainTokenIcon";
@@ -28,6 +31,7 @@ export function OrderTokenAmount({
28
31
  amountClassName,
29
32
  tokenSelectClassName,
30
33
  onTokenSelect,
34
+ walletAddress,
31
35
  }: {
32
36
  disabled?: boolean;
33
37
  inputValue: string;
@@ -45,25 +49,57 @@ export function OrderTokenAmount({
45
49
  amountClassName?: string;
46
50
  tokenSelectClassName?: string;
47
51
  onTokenSelect?: (token: components["schemas"]["Token"], event: { preventDefault: () => void }) => void;
52
+ walletAddress?: string | undefined;
48
53
  }) {
49
54
  // Track previous token to detect changes
50
55
  const prevTokenRef = useRef<string>(token.address);
56
+ // Track if initial balance has been set
57
+ const initialBalanceSetRef = useRef(false);
58
+
59
+ // Only get token balance when context is "from" (for setting max amount)
60
+ const { rawBalance } = useTokenBalance({
61
+ token,
62
+ address: context === "from" && walletAddress ? walletAddress : undefined,
63
+ });
64
+
65
+ // Reset balance ref when token address or chain changes
66
+ useEffect(() => {
67
+ initialBalanceSetRef.current = false;
68
+ }, [token.address, token.chainId]);
51
69
 
52
70
  useEffect(() => {
53
- // Only trigger when token actually changes
54
- if (prevTokenRef.current !== token.address) {
55
- console.log(`Token changed from ${prevTokenRef.current} to ${token.address}`);
56
-
57
- // For "from" context, reset to default value when token changes
58
- if (context === "from") {
59
- // Reset input to default for new token
60
- onChangeInput("0.01");
71
+ // Only handle "from" context
72
+ if (context !== "from") return;
73
+
74
+ // Check if token changed or if this is the initial load with balance
75
+ const isTokenChanged = prevTokenRef.current !== token.address;
76
+ const isInitialLoad = !initialBalanceSetRef.current && rawBalance;
77
+
78
+ if ((isTokenChanged || isInitialLoad) && rawBalance) {
79
+ console.log(
80
+ `Setting max balance - Token: ${token.address}, Changed: ${isTokenChanged}, Initial: ${isInitialLoad}`,
81
+ );
82
+
83
+ // Calculate max amount with gas reserve for native tokens
84
+ let maxAmount: bigint;
85
+
86
+ if (isNativeToken(token.address)) {
87
+ const gasReserve = getNativeRequired(token.chainId);
88
+ // Ensure we don't go negative
89
+ maxAmount = rawBalance > gasReserve ? rawBalance - gasReserve : BigInt(0);
90
+ } else {
91
+ // For ERC20 tokens, use full balance
92
+ maxAmount = rawBalance;
61
93
  }
62
94
 
63
- // Update ref to current token
95
+ // Set the max amount as input value
96
+ onChangeInput(formatUnits(maxAmount, token.decimals));
97
+
98
+ // Update refs
64
99
  prevTokenRef.current = token.address;
100
+ initialBalanceSetRef.current = true;
65
101
  }
66
- }, [token.address, chainId, context, onChangeInput]);
102
+ }, [token.address, token.chainId, token.decimals, chainId, context, onChangeInput, rawBalance]);
67
103
 
68
104
  const handleTokenSelect = (newToken: any) => {
69
105
  const token: components["schemas"]["Token"] = {
@@ -97,13 +133,8 @@ export function OrderTokenAmount({
97
133
  // Set the chain ID first
98
134
  setChainId(newToken.chainId);
99
135
 
100
- // Then set the new token
136
+ // Then set the new token - the useEffect will handle setting the max balance
101
137
  setToken(token);
102
-
103
- // If this is the source token, reset the amount immediately
104
- if (context === "from") {
105
- onChangeInput("0.01");
106
- }
107
138
  };
108
139
 
109
140
  return (
@@ -116,6 +116,7 @@ export function CryptoPaySection({
116
116
  </div>
117
117
  <OrderTokenAmount
118
118
  address={connectedAddress}
119
+ walletAddress={connectedAddress}
119
120
  context="from"
120
121
  inputValue={srcAmount}
121
122
  onChangeInput={value => {
@@ -1,7 +1,7 @@
1
1
  import { components } from "@b3dotfun/sdk/anyspend/types/api";
2
2
  import { getNativeRequired } from "@b3dotfun/sdk/anyspend/utils/chain";
3
3
  import { isNativeToken } from "@b3dotfun/sdk/anyspend/utils/token";
4
- import { useTokenBalance } from "@b3dotfun/sdk/global-account/react";
4
+ import { useTokenBalanceDirect } from "@b3dotfun/sdk/global-account/react";
5
5
  import { formatUnits } from "viem";
6
6
 
7
7
  export function TokenBalance({
@@ -13,7 +13,7 @@ export function TokenBalance({
13
13
  walletAddress: string | undefined;
14
14
  onChangeInput: (value: string) => void;
15
15
  }) {
16
- const { rawBalance, formattedBalance, isLoading } = useTokenBalance({
16
+ const { rawBalance, formattedBalance, isLoading } = useTokenBalanceDirect({
17
17
  token,
18
18
  address: walletAddress,
19
19
  });
@@ -23,6 +23,8 @@ import { useAccount } from "wagmi";
23
23
  import { components } from "../../types/api";
24
24
  import { CryptoPaymentMethodType } from "../components/common/CryptoPaymentMethod";
25
25
  import { FiatPaymentMethod } from "../components/common/FiatPaymentMethod";
26
+ import { useAutoSelectCryptoPaymentMethod } from "./useAutoSelectCryptoPaymentMethod";
27
+ import { useAutoSetActiveWalletFromWagmi } from "./useAutoSetActiveWalletFromWagmi";
26
28
 
27
29
  export enum PanelView {
28
30
  MAIN,
@@ -92,6 +94,9 @@ export function useAnyspendFlow({
92
94
  const recipientProfile = useProfile({ address: selectedRecipientAddress, fresh: true });
93
95
  const recipientName = recipientProfile.data?.name;
94
96
 
97
+ // Auto-set active wallet from wagmi
98
+ useAutoSetActiveWalletFromWagmi();
99
+
95
100
  // Set default recipient address when wallet changes
96
101
  useEffect(() => {
97
102
  if (!selectedRecipientAddress && globalAddress) {
@@ -116,16 +121,14 @@ export function useAnyspendFlow({
116
121
  }
117
122
  }, [rawBalance, srcAmount, selectedSrcToken.decimals, isBalanceLoading, paymentType]);
118
123
 
119
- // Auto-set crypto payment method based on balance
120
- useEffect(() => {
121
- if (paymentType === "crypto" && !isBalanceLoading) {
122
- if (hasEnoughBalance) {
123
- setSelectedCryptoPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
124
- } else {
125
- setSelectedCryptoPaymentMethod(CryptoPaymentMethodType.TRANSFER_CRYPTO);
126
- }
127
- }
128
- }, [paymentType, hasEnoughBalance, isBalanceLoading]);
124
+ // Auto-select crypto payment method based on available wallets and balance
125
+ useAutoSelectCryptoPaymentMethod({
126
+ paymentType,
127
+ selectedCryptoPaymentMethod,
128
+ setSelectedCryptoPaymentMethod,
129
+ hasEnoughBalance,
130
+ isBalanceLoading,
131
+ });
129
132
 
130
133
  // Fetch specific token when sourceTokenAddress and sourceTokenChainId are provided
131
134
  useEffect(() => {
@@ -0,0 +1,72 @@
1
+ import { useEffect } from "react";
2
+ import { CryptoPaymentMethodType } from "../components/common/CryptoPaymentMethod";
3
+ import { useConnectedWalletDisplay } from "./useConnectedWalletDisplay";
4
+
5
+ interface UseAutoSelectCryptoPaymentMethodParams {
6
+ /** Current payment type (crypto or fiat) */
7
+ paymentType?: "crypto" | "fiat";
8
+ /** Currently selected payment method */
9
+ selectedCryptoPaymentMethod: CryptoPaymentMethodType;
10
+ /** Function to update the selected payment method */
11
+ setSelectedCryptoPaymentMethod: (method: CryptoPaymentMethodType) => void;
12
+ /** Whether user has enough balance to pay */
13
+ hasEnoughBalance: boolean;
14
+ /** Whether balance is still loading */
15
+ isBalanceLoading: boolean;
16
+ }
17
+
18
+ /**
19
+ * Custom hook to automatically select appropriate crypto payment method
20
+ * based on available wallets and balance.
21
+ *
22
+ * Auto-selection logic:
23
+ * - Only auto-selects when payment method is NONE (doesn't override user choices)
24
+ * - If EOA/Wagmi wallet connected + has balance → CONNECT_WALLET
25
+ * - If EOA/Wagmi wallet connected + insufficient balance → TRANSFER_CRYPTO
26
+ * - If only Global wallet available → GLOBAL_WALLET
27
+ * - If no wallets → remains NONE
28
+ */
29
+ export function useAutoSelectCryptoPaymentMethod({
30
+ paymentType = "crypto",
31
+ selectedCryptoPaymentMethod,
32
+ setSelectedCryptoPaymentMethod,
33
+ hasEnoughBalance,
34
+ isBalanceLoading,
35
+ }: UseAutoSelectCryptoPaymentMethodParams) {
36
+ // Get suggested payment method based on available wallets
37
+ const { suggestedPaymentMethod } = useConnectedWalletDisplay(selectedCryptoPaymentMethod);
38
+
39
+ useEffect(() => {
40
+ // Only auto-select when on crypto payment type and payment method is NONE
41
+ if (paymentType !== "crypto" || selectedCryptoPaymentMethod !== CryptoPaymentMethodType.NONE) {
42
+ return;
43
+ }
44
+
45
+ // If we have a suggested payment method (wallet is connected), use it
46
+ if (suggestedPaymentMethod !== CryptoPaymentMethodType.NONE) {
47
+ // If we have balance info and enough balance, use CONNECT_WALLET
48
+ // Otherwise, default to TRANSFER_CRYPTO if balance is insufficient
49
+ if (!isBalanceLoading) {
50
+ if (hasEnoughBalance && suggestedPaymentMethod === CryptoPaymentMethodType.CONNECT_WALLET) {
51
+ setSelectedCryptoPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
52
+ } else if (!hasEnoughBalance && suggestedPaymentMethod === CryptoPaymentMethodType.CONNECT_WALLET) {
53
+ // Wallet connected but insufficient balance - suggest transfer
54
+ setSelectedCryptoPaymentMethod(CryptoPaymentMethodType.TRANSFER_CRYPTO);
55
+ } else {
56
+ // Use suggested method (e.g., GLOBAL_WALLET)
57
+ setSelectedCryptoPaymentMethod(suggestedPaymentMethod);
58
+ }
59
+ } else {
60
+ // Balance still loading, use suggested method
61
+ setSelectedCryptoPaymentMethod(suggestedPaymentMethod);
62
+ }
63
+ }
64
+ }, [
65
+ paymentType,
66
+ selectedCryptoPaymentMethod,
67
+ suggestedPaymentMethod,
68
+ hasEnoughBalance,
69
+ isBalanceLoading,
70
+ setSelectedCryptoPaymentMethod,
71
+ ]);
72
+ }
@@ -0,0 +1,80 @@
1
+ import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
2
+ import { useCallback, useEffect, useRef } from "react";
3
+ import { useSetActiveWallet } from "thirdweb/react";
4
+ import { WalletId, createWallet } from "thirdweb/wallets";
5
+ import { useAccount } from "wagmi";
6
+
7
+ /**
8
+ * Hook that automatically sets the active thirdweb wallet when a wagmi wallet connects.
9
+ *
10
+ * This is useful for syncing wagmi wallet connections with thirdweb's wallet system,
11
+ * ensuring that when users connect via wagmi, the active wallet is properly set.
12
+ *
13
+ * Place this hook in components that stay mounted throughout the user flow
14
+ * (not in components that unmount during navigation).
15
+ */
16
+ export function useAutoSetActiveWalletFromWagmi() {
17
+ const { address: wagmiAddress, connector: wagmiConnector } = useAccount();
18
+ const setActiveWallet = useSetActiveWallet();
19
+ const prevWagmiAddress = useRef<string | undefined>(undefined);
20
+
21
+ // Map wagmi connector names to thirdweb wallet IDs
22
+ const getThirdwebWalletId = useCallback((connectorName: string): WalletId | null => {
23
+ const walletMap: Record<string, WalletId> = {
24
+ MetaMask: "io.metamask",
25
+ "Coinbase Wallet": "com.coinbase.wallet",
26
+ Rainbow: "me.rainbow",
27
+ WalletConnect: "walletConnect",
28
+ Phantom: "app.phantom",
29
+ };
30
+ return walletMap[connectorName] || null;
31
+ }, []);
32
+
33
+ // Create thirdweb wallet from wagmi connector
34
+ const createThirdwebWalletFromConnector = useCallback(
35
+ async (connectorName: string) => {
36
+ const walletId = getThirdwebWalletId(connectorName);
37
+ if (!walletId) {
38
+ console.warn(`No thirdweb wallet ID found for connector: ${connectorName}`);
39
+ return null;
40
+ }
41
+
42
+ try {
43
+ const thirdwebWallet = createWallet(walletId);
44
+ await thirdwebWallet.connect({ client });
45
+ return thirdwebWallet;
46
+ } catch (error) {
47
+ console.error(`Failed to create thirdweb wallet for ${connectorName}:`, error);
48
+ return null;
49
+ }
50
+ },
51
+ [getThirdwebWalletId],
52
+ );
53
+
54
+ // Listen for wagmi wallet connections and automatically set active wallet
55
+ useEffect(() => {
56
+ const isNewConnection = wagmiAddress && wagmiAddress !== prevWagmiAddress.current;
57
+
58
+ if (isNewConnection && wagmiConnector?.name) {
59
+ prevWagmiAddress.current = wagmiAddress;
60
+
61
+ const setupThirdwebWallet = async () => {
62
+ try {
63
+ const thirdwebWallet = await createThirdwebWalletFromConnector(wagmiConnector.name);
64
+ if (thirdwebWallet) {
65
+ setActiveWallet(thirdwebWallet);
66
+ console.log(`Auto-set active wallet for ${wagmiConnector.name}`);
67
+ }
68
+ } catch (error) {
69
+ console.error("Failed to auto-set active wallet:", error);
70
+ }
71
+ };
72
+
73
+ setupThirdwebWallet();
74
+ }
75
+
76
+ if (!wagmiAddress) {
77
+ prevWagmiAddress.current = undefined;
78
+ }
79
+ }, [wagmiAddress, wagmiConnector?.name, setActiveWallet, createThirdwebWalletFromConnector]);
80
+ }