tiwiflix-wallet-connector 1.5.1 → 1.5.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.
package/dist/index.esm.js CHANGED
@@ -2344,6 +2344,11 @@ class WalletConnector {
2344
2344
  ...savedState,
2345
2345
  error: null, // Don't restore errors
2346
2346
  };
2347
+ // PATCH: Prevent invalid connected state
2348
+ if (this.state.status === ConnectionStatus.CONNECTED && !this.state.account) {
2349
+ this.state.status = ConnectionStatus.DISCONNECTED;
2350
+ this.state.wallet = null;
2351
+ }
2347
2352
  }
2348
2353
  this.eventEmitter = new EventEmitter$1();
2349
2354
  // Initialize advanced SDK management
@@ -6460,7 +6465,7 @@ const BaseModal = ({ isOpen, onClose, title, children, modalPosition = 'bottom',
6460
6465
  return modalContent;
6461
6466
  };
6462
6467
 
6463
- const AccountDetailsModal = ({ isOpen, onClose, account, onDisconnect, onCopyAddress, onOpenSwap, onOpenSend, onOpenActivity, balance, balanceLoading, twcBalance, usdValue, nftCount = 0, nftLoading = false, tonBalance = null, tonUsdValue = null, walletIcon, colors, isDarkMode, modalPosition = 'bottom', }) => {
6468
+ const AccountDetailsModal = ({ isOpen, onClose, account, onDisconnect, onCopyAddress, onOpenSwap, onOpenSend, onOpenActivity, balance, balanceLoading, twcBalance, usdValue, nftCount = 0, nftLoading = false, tonBalance = null, tonUsdValue = null, walletIcon, colors, isDarkMode, modalPosition = 'bottom', onSwitchWallet, }) => {
6464
6469
  // State for TWC price - hooks must be called before any conditional returns
6465
6470
  const [twcPrice, setTwcPrice] = useState(null);
6466
6471
  const [priceLoading, setPriceLoading] = useState(false);
@@ -7502,7 +7507,19 @@ const defaultStyles = {
7502
7507
  fontFamily: 'Lexend, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
7503
7508
  boxShadow: '0 4px 14px 0 rgba(51, 150, 255, 0.39)',
7504
7509
  }};
7505
- const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className, style, showBalance = false, modalPosition = 'center', theme = 'auto', buttonText = 'Connect Wallet', fetchTransactions, getExplorerUrl, fetchBalance, }) => {
7510
+ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className, style, showBalance = false, modalPosition = 'center', theme = 'auto', buttonText = 'Connect Wallet', fetchTransactions, getExplorerUrl, fetchBalance: fetchBalanceProp, }) => {
7511
+ // Provide a default fetchBalance if not passed
7512
+ const fetchBalance = fetchBalanceProp || (async (account) => {
7513
+ // Default: return zero balance with symbol based on chain
7514
+ let symbol = 'TWC';
7515
+ if (account?.chainType === 'ton')
7516
+ symbol = 'TON';
7517
+ if (account?.chainType === 'solana')
7518
+ symbol = 'SOL';
7519
+ if (account?.chainType === 'tron')
7520
+ symbol = 'TRX';
7521
+ return { amount: '0', symbol };
7522
+ });
7506
7523
  // Detect dark mode
7507
7524
  const [isDarkMode, setIsDarkMode] = useState(false);
7508
7525
  useEffect(() => {
@@ -8047,6 +8064,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8047
8064
  });
8048
8065
  }
8049
8066
  // Subscribe to events
8067
+ // Prevent repeated onConnect calls for the same account
8068
+ const lastOnConnectAddress = React.useRef(null);
8050
8069
  const unsubscribeAccount = connector.on(WalletEvent.ACCOUNT_CHANGED, async (acc) => {
8051
8070
  setAccount(acc);
8052
8071
  // Don't set isInitializing to false yet - wait for balance to load
@@ -8059,8 +8078,7 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8059
8078
  }
8060
8079
  }
8061
8080
  if (acc && acc.chainType === 'ton') {
8062
- // For TON: fetch both balance and NFTs
8063
- setTwcBalance('0'); // TON wallets don't have TWC
8081
+ setTwcBalance('0');
8064
8082
  try {
8065
8083
  await Promise.all([
8066
8084
  fetchTONBalance(acc),
@@ -8084,18 +8102,15 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8084
8102
  }
8085
8103
  }
8086
8104
  else if (acc) {
8087
- // No fetchBalance, check for TWC balance
8088
8105
  const state = connector.getState();
8089
8106
  if (state.twcBalance && state.twcBalance !== '0' && state.twcBalance !== '0.00') {
8090
8107
  setTwcBalance(state.twcBalance);
8091
8108
  setUsdValueStable(state.usdValue ?? null);
8092
- // Save to cache
8093
8109
  if (acc.address) {
8094
8110
  saveTWCBalanceToCache(acc.address, state.twcBalance, state.usdValue ?? null);
8095
8111
  }
8096
8112
  }
8097
8113
  else if (acc.address) {
8098
- // Try to load from cache as fallback
8099
8114
  const cached = loadTWCBalanceFromCache(acc.address);
8100
8115
  if (cached?.balance) {
8101
8116
  setTwcBalance(cached.balance);
@@ -8108,7 +8123,10 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8108
8123
  setIsInitializing(false);
8109
8124
  }
8110
8125
  if (acc && onConnect) {
8111
- onConnect(acc);
8126
+ if (lastOnConnectAddress.current !== acc.address) {
8127
+ onConnect(acc);
8128
+ lastOnConnectAddress.current = acc.address;
8129
+ }
8112
8130
  }
8113
8131
  });
8114
8132
  const unsubscribeStatus = connector.on(WalletEvent.STATUS_CHANGED, (newStatus) => {
@@ -8503,6 +8521,12 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8503
8521
  setIsConnecting(false);
8504
8522
  }
8505
8523
  catch (err) {
8524
+ // Always clear connection state on error
8525
+ setAccount(null);
8526
+ setStatus(ConnectionStatus.DISCONNECTED);
8527
+ setIsInitializing(false);
8528
+ setIsConnecting(false);
8529
+ setShowModal(false);
8506
8530
  // Close modal if the user explicitly cancelled the connection
8507
8531
  const message = err instanceof Error ? err.message : String(err);
8508
8532
  const isUserCancellation = message.toLowerCase().includes('cancelled') ||
@@ -8513,22 +8537,11 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8513
8537
  message.toLowerCase().includes('rejected') ||
8514
8538
  message.toLowerCase().includes('modal closed by user') ||
8515
8539
  message.toLowerCase().includes('connection cancelled by user');
8516
- if (isUserCancellation) {
8517
- setShowModal(false);
8518
- // Reset connecting state and verify actual connection status
8519
- const actualAccount = await connector.getAccount();
8520
- if (!actualAccount) {
8521
- setAccount(null);
8522
- }
8523
- }
8524
- else if (isTonWallet) {
8540
+ if (!isUserCancellation && isTonWallet) {
8525
8541
  // For TON, only reopen the modal on non-cancellation errors
8526
8542
  setShowModal(true);
8527
8543
  }
8528
8544
  }
8529
- finally {
8530
- setIsConnecting(false);
8531
- }
8532
8545
  }, [connector, fetchBalance, loadBalance]);
8533
8546
  const handleDisconnect = useCallback(async () => {
8534
8547
  try {
@@ -8592,25 +8605,46 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8592
8605
  setError('Failed to switch network. Please switch manually to BSC in your wallet.');
8593
8606
  }
8594
8607
  }, [connector]);
8595
- const handleOpenModal = useCallback(() => {
8608
+ // Only open wallet selection modal if not connected, or if explicitly switching
8609
+ const handleOpenModal = useCallback((force = false) => {
8610
+ // If already connected and not explicitly switching, do nothing
8611
+ const state = connector.getState();
8612
+ if (!force && account && state.status === 'connected') {
8613
+ return;
8614
+ }
8596
8615
  // Show ALL wallets - use cached wallets to prevent re-renders
8597
8616
  if (!walletsRef.current) {
8598
8617
  walletsRef.current = connector.getAllWallets();
8599
8618
  }
8600
8619
  setAvailableWallets(walletsRef.current);
8601
8620
  setShowModal(true);
8602
- }, [connector]);
8621
+ }, [connector, account]);
8603
8622
  // Render button based on connection status
8604
8623
  const renderButton = () => {
8605
8624
  // Check if we're waiting for connection OR waiting for balance to load
8606
8625
  // Show loading if: initializing, connecting, OR (account exists but balance hasn't loaded/resolved yet)
8607
- // Treat "0", "0.00" or empty as "not loaded yet" - only show connected state when we have real (non-zero) balance
8608
- const hasRealBalance = (balance.amount && balance.amount !== '0' && balance.amount !== '0.00' && balance.amount !== '' && balance.amount !== 'Loading...') ||
8626
+ // For TON: treat as resolved after fetch completes (even if 0). For others: treat zero/empty as resolved.
8627
+ (balance.amount && balance.amount !== '0' && balance.amount !== '0.00' && balance.amount !== '' && balance.amount !== 'Loading...') ||
8609
8628
  (twcBalance && twcBalance !== '0' && twcBalance !== '0.00' && twcBalance !== '' && twcBalance !== 'Loading...');
8610
- // For TON wallets, balance is resolved after fetch completes (even if empty/0)
8611
- // For other wallets, balance is resolved only if we have a real (non-zero) balance
8612
- const isBalanceResolved = !balanceLoading && (hasRealBalance || (account?.chainType === 'ton' && !isInitializing));
8629
+ let isBalanceResolved;
8630
+ if (account?.chainType === 'ton') {
8631
+ // For TON, resolved if not loading or initializing
8632
+ isBalanceResolved = !balanceLoading && !isInitializing;
8633
+ }
8634
+ else {
8635
+ // For EVM, Solana, Tron: resolved if not loading (even if balance is zero)
8636
+ isBalanceResolved = !balanceLoading && !isInitializing;
8637
+ }
8613
8638
  const waitingForBalance = account && !isBalanceResolved;
8639
+ // Patch: If account is null and status is disconnected, always stop initializing/loading
8640
+ if (!account && status === ConnectionStatus.DISCONNECTED && (isInitializing || isConnecting || balanceLoading)) {
8641
+ if (isInitializing)
8642
+ setIsInitializing(false);
8643
+ if (isConnecting)
8644
+ setIsConnecting(false);
8645
+ if (balanceLoading)
8646
+ setBalanceLoading(false);
8647
+ }
8614
8648
  const isCheckingConnection = isInitializing || waitingForBalance || ((status === ConnectionStatus.CONNECTING || isConnecting) && !account);
8615
8649
  const isActuallyConnecting = (status === ConnectionStatus.CONNECTING || isConnecting) && account && isBalanceResolved;
8616
8650
  // Check connection state
@@ -8820,31 +8854,34 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8820
8854
  }
8821
8855
  // Wallet icon SVG component
8822
8856
  const WalletIcon = () => (jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { display: 'block' }, children: [jsx("path", { d: "M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1" }), jsx("path", { d: "M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4" })] }));
8823
- return (jsxs("button", { onClick: handleOpenModal, style: {
8824
- display: 'flex',
8825
- alignItems: 'center',
8826
- gap: '10px',
8827
- padding: '12px 12px',
8828
- borderRadius: '12px',
8829
- border: 'none',
8830
- backgroundColor: '#FF9814', // Warm golden-orange
8831
- color: '#000000', // Black text
8832
- fontWeight: '700',
8833
- fontSize: '15px',
8834
- cursor: 'pointer',
8835
- transition: 'all 0.15s ease',
8836
- fontFamily: 'Lexend, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
8837
- boxShadow: '0 2px 8px rgba(255, 152, 20, 0.3)',
8838
- ...style,
8839
- }, className: className, onMouseEnter: (e) => {
8840
- e.currentTarget.style.backgroundColor = '#E8870F';
8841
- e.currentTarget.style.transform = 'translateY(-1px)';
8842
- e.currentTarget.style.boxShadow = '0 4px 12px rgba(255, 152, 20, 0.4)';
8843
- }, onMouseLeave: (e) => {
8844
- e.currentTarget.style.backgroundColor = '#FF9814';
8845
- e.currentTarget.style.transform = 'translateY(0)';
8846
- e.currentTarget.style.boxShadow = '0 2px 8px rgba(255, 152, 20, 0.3)';
8847
- }, children: [jsx("span", { style: { color: '#000000', display: 'flex', alignItems: 'center' }, children: jsx(WalletIcon, {}) }), jsx("span", { style: { color: '#000000', fontWeight: '500', fontSize: '14px' }, children: buttonText })] }));
8857
+ // Only show connect button if not connected
8858
+ if (!account || status !== ConnectionStatus.CONNECTED) {
8859
+ return (jsxs("button", { onClick: () => handleOpenModal(), style: {
8860
+ display: 'flex',
8861
+ alignItems: 'center',
8862
+ gap: '10px',
8863
+ padding: '12px 12px',
8864
+ borderRadius: '12px',
8865
+ border: 'none',
8866
+ backgroundColor: '#FF9814', // Warm golden-orange
8867
+ color: '#000000', // Black text
8868
+ fontWeight: '700',
8869
+ fontSize: '15px',
8870
+ cursor: 'pointer',
8871
+ transition: 'all 0.15s ease',
8872
+ fontFamily: 'Lexend, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
8873
+ boxShadow: '0 2px 8px rgba(255, 152, 20, 0.3)',
8874
+ ...style,
8875
+ }, className: className, onMouseEnter: (e) => {
8876
+ e.currentTarget.style.backgroundColor = '#E8870F';
8877
+ e.currentTarget.style.transform = 'translateY(-1px)';
8878
+ e.currentTarget.style.boxShadow = '0 4px 12px rgba(255, 152, 20, 0.4)';
8879
+ }, onMouseLeave: (e) => {
8880
+ e.currentTarget.style.backgroundColor = '#FF9814';
8881
+ e.currentTarget.style.transform = 'translateY(0)';
8882
+ e.currentTarget.style.boxShadow = '0 2px 8px rgba(255, 152, 20, 0.3)';
8883
+ }, children: [jsx("span", { style: { color: '#000000', display: 'flex', alignItems: 'center' }, children: jsx(WalletIcon, {}) }), jsx("span", { style: { color: '#000000', fontWeight: '500', fontSize: '14px' }, children: buttonText })] }));
8884
+ }
8848
8885
  };
8849
8886
  // Group wallets by chain
8850
8887
  availableWallets.reduce((acc, wallet) => {
@@ -8863,7 +8900,9 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8863
8900
  .tiwiflix-scrollbar-hide::-webkit-scrollbar {
8864
8901
  display: none;
8865
8902
  }
8866
- ` }), renderButton(), jsx(AccountDetailsModal, { isOpen: showDetailsModal, onClose: () => setShowDetailsModal(false), account: account, onDisconnect: handleDisconnect, onCopyAddress: handleCopyAddress, onOpenSwap: () => setShowSwapModal(true), onOpenSend: () => setShowSendModal(true), onOpenActivity: () => setShowActivityModal(true), balance: balance, balanceLoading: balanceLoading, twcBalance: twcBalance, usdValue: usdValue, nftCount: nftCount, nftLoading: nftLoading, tonBalance: balance.amount || tonBalance, tonUsdValue: balance.usdValue ?? tonUsdValue, walletIcon: walletIcon, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition }), jsx(ActivityModal, { isOpen: showActivityModal, onClose: () => setShowActivityModal(false), transactions: txs, loading: txsLoading, error: txsError, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition }), jsx(SwapModal, { isOpen: showSwapModal && account?.chainType !== 'ton', onClose: () => setShowSwapModal(false), colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition }), jsx(SendModal, { isOpen: showSendModal, onClose: () => setShowSendModal(false), balance: balance, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition, onSend: async () => {
8903
+ ` }), renderButton(), jsx(AccountDetailsModal, { isOpen: showDetailsModal, onClose: () => setShowDetailsModal(false), account: account, onDisconnect: handleDisconnect, onCopyAddress: handleCopyAddress, onOpenSwap: () => setShowSwapModal(true), onOpenSend: () => setShowSendModal(true), onOpenActivity: () => setShowActivityModal(true), balance: balance, balanceLoading: balanceLoading, twcBalance: twcBalance, usdValue: usdValue, nftCount: nftCount, nftLoading: nftLoading, tonBalance: balance.amount || tonBalance, tonUsdValue: balance.usdValue ?? tonUsdValue, walletIcon: walletIcon, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition,
8904
+ // Add a Switch Wallet button to allow explicit wallet switching
8905
+ onSwitchWallet: () => handleOpenModal(true) }), jsx(ActivityModal, { isOpen: showActivityModal, onClose: () => setShowActivityModal(false), transactions: txs, loading: txsLoading, error: txsError, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition }), jsx(SwapModal, { isOpen: showSwapModal && account?.chainType !== 'ton', onClose: () => setShowSwapModal(false), colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition }), jsx(SendModal, { isOpen: showSendModal, onClose: () => setShowSendModal(false), balance: balance, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition, onSend: async () => {
8867
8906
  alert('Send functionality coming soon');
8868
8907
  setShowSendModal(false);
8869
8908
  } }), jsx(TWCBalanceModal, { isOpen: showTWCBalanceModal, onClose: () => setShowTWCBalanceModal(false), twcBalance: twcBalance, usdValue: usdValue, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition, onBuyTWC: () => {
package/dist/index.js CHANGED
@@ -2346,6 +2346,11 @@ class WalletConnector {
2346
2346
  ...savedState,
2347
2347
  error: null, // Don't restore errors
2348
2348
  };
2349
+ // PATCH: Prevent invalid connected state
2350
+ if (this.state.status === exports.ConnectionStatus.CONNECTED && !this.state.account) {
2351
+ this.state.status = exports.ConnectionStatus.DISCONNECTED;
2352
+ this.state.wallet = null;
2353
+ }
2349
2354
  }
2350
2355
  this.eventEmitter = new EventEmitter$1();
2351
2356
  // Initialize advanced SDK management
@@ -6462,7 +6467,7 @@ const BaseModal = ({ isOpen, onClose, title, children, modalPosition = 'bottom',
6462
6467
  return modalContent;
6463
6468
  };
6464
6469
 
6465
- const AccountDetailsModal = ({ isOpen, onClose, account, onDisconnect, onCopyAddress, onOpenSwap, onOpenSend, onOpenActivity, balance, balanceLoading, twcBalance, usdValue, nftCount = 0, nftLoading = false, tonBalance = null, tonUsdValue = null, walletIcon, colors, isDarkMode, modalPosition = 'bottom', }) => {
6470
+ const AccountDetailsModal = ({ isOpen, onClose, account, onDisconnect, onCopyAddress, onOpenSwap, onOpenSend, onOpenActivity, balance, balanceLoading, twcBalance, usdValue, nftCount = 0, nftLoading = false, tonBalance = null, tonUsdValue = null, walletIcon, colors, isDarkMode, modalPosition = 'bottom', onSwitchWallet, }) => {
6466
6471
  // State for TWC price - hooks must be called before any conditional returns
6467
6472
  const [twcPrice, setTwcPrice] = React.useState(null);
6468
6473
  const [priceLoading, setPriceLoading] = React.useState(false);
@@ -7504,7 +7509,19 @@ const defaultStyles = {
7504
7509
  fontFamily: 'Lexend, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
7505
7510
  boxShadow: '0 4px 14px 0 rgba(51, 150, 255, 0.39)',
7506
7511
  }};
7507
- const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className, style, showBalance = false, modalPosition = 'center', theme = 'auto', buttonText = 'Connect Wallet', fetchTransactions, getExplorerUrl, fetchBalance, }) => {
7512
+ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className, style, showBalance = false, modalPosition = 'center', theme = 'auto', buttonText = 'Connect Wallet', fetchTransactions, getExplorerUrl, fetchBalance: fetchBalanceProp, }) => {
7513
+ // Provide a default fetchBalance if not passed
7514
+ const fetchBalance = fetchBalanceProp || (async (account) => {
7515
+ // Default: return zero balance with symbol based on chain
7516
+ let symbol = 'TWC';
7517
+ if (account?.chainType === 'ton')
7518
+ symbol = 'TON';
7519
+ if (account?.chainType === 'solana')
7520
+ symbol = 'SOL';
7521
+ if (account?.chainType === 'tron')
7522
+ symbol = 'TRX';
7523
+ return { amount: '0', symbol };
7524
+ });
7508
7525
  // Detect dark mode
7509
7526
  const [isDarkMode, setIsDarkMode] = React.useState(false);
7510
7527
  React.useEffect(() => {
@@ -8049,6 +8066,8 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8049
8066
  });
8050
8067
  }
8051
8068
  // Subscribe to events
8069
+ // Prevent repeated onConnect calls for the same account
8070
+ const lastOnConnectAddress = React.useRef(null);
8052
8071
  const unsubscribeAccount = connector.on(exports.WalletEvent.ACCOUNT_CHANGED, async (acc) => {
8053
8072
  setAccount(acc);
8054
8073
  // Don't set isInitializing to false yet - wait for balance to load
@@ -8061,8 +8080,7 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8061
8080
  }
8062
8081
  }
8063
8082
  if (acc && acc.chainType === 'ton') {
8064
- // For TON: fetch both balance and NFTs
8065
- setTwcBalance('0'); // TON wallets don't have TWC
8083
+ setTwcBalance('0');
8066
8084
  try {
8067
8085
  await Promise.all([
8068
8086
  fetchTONBalance(acc),
@@ -8086,18 +8104,15 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8086
8104
  }
8087
8105
  }
8088
8106
  else if (acc) {
8089
- // No fetchBalance, check for TWC balance
8090
8107
  const state = connector.getState();
8091
8108
  if (state.twcBalance && state.twcBalance !== '0' && state.twcBalance !== '0.00') {
8092
8109
  setTwcBalance(state.twcBalance);
8093
8110
  setUsdValueStable(state.usdValue ?? null);
8094
- // Save to cache
8095
8111
  if (acc.address) {
8096
8112
  saveTWCBalanceToCache(acc.address, state.twcBalance, state.usdValue ?? null);
8097
8113
  }
8098
8114
  }
8099
8115
  else if (acc.address) {
8100
- // Try to load from cache as fallback
8101
8116
  const cached = loadTWCBalanceFromCache(acc.address);
8102
8117
  if (cached?.balance) {
8103
8118
  setTwcBalance(cached.balance);
@@ -8110,7 +8125,10 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8110
8125
  setIsInitializing(false);
8111
8126
  }
8112
8127
  if (acc && onConnect) {
8113
- onConnect(acc);
8128
+ if (lastOnConnectAddress.current !== acc.address) {
8129
+ onConnect(acc);
8130
+ lastOnConnectAddress.current = acc.address;
8131
+ }
8114
8132
  }
8115
8133
  });
8116
8134
  const unsubscribeStatus = connector.on(exports.WalletEvent.STATUS_CHANGED, (newStatus) => {
@@ -8505,6 +8523,12 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8505
8523
  setIsConnecting(false);
8506
8524
  }
8507
8525
  catch (err) {
8526
+ // Always clear connection state on error
8527
+ setAccount(null);
8528
+ setStatus(exports.ConnectionStatus.DISCONNECTED);
8529
+ setIsInitializing(false);
8530
+ setIsConnecting(false);
8531
+ setShowModal(false);
8508
8532
  // Close modal if the user explicitly cancelled the connection
8509
8533
  const message = err instanceof Error ? err.message : String(err);
8510
8534
  const isUserCancellation = message.toLowerCase().includes('cancelled') ||
@@ -8515,22 +8539,11 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8515
8539
  message.toLowerCase().includes('rejected') ||
8516
8540
  message.toLowerCase().includes('modal closed by user') ||
8517
8541
  message.toLowerCase().includes('connection cancelled by user');
8518
- if (isUserCancellation) {
8519
- setShowModal(false);
8520
- // Reset connecting state and verify actual connection status
8521
- const actualAccount = await connector.getAccount();
8522
- if (!actualAccount) {
8523
- setAccount(null);
8524
- }
8525
- }
8526
- else if (isTonWallet) {
8542
+ if (!isUserCancellation && isTonWallet) {
8527
8543
  // For TON, only reopen the modal on non-cancellation errors
8528
8544
  setShowModal(true);
8529
8545
  }
8530
8546
  }
8531
- finally {
8532
- setIsConnecting(false);
8533
- }
8534
8547
  }, [connector, fetchBalance, loadBalance]);
8535
8548
  const handleDisconnect = React.useCallback(async () => {
8536
8549
  try {
@@ -8594,25 +8607,46 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8594
8607
  setError('Failed to switch network. Please switch manually to BSC in your wallet.');
8595
8608
  }
8596
8609
  }, [connector]);
8597
- const handleOpenModal = React.useCallback(() => {
8610
+ // Only open wallet selection modal if not connected, or if explicitly switching
8611
+ const handleOpenModal = React.useCallback((force = false) => {
8612
+ // If already connected and not explicitly switching, do nothing
8613
+ const state = connector.getState();
8614
+ if (!force && account && state.status === 'connected') {
8615
+ return;
8616
+ }
8598
8617
  // Show ALL wallets - use cached wallets to prevent re-renders
8599
8618
  if (!walletsRef.current) {
8600
8619
  walletsRef.current = connector.getAllWallets();
8601
8620
  }
8602
8621
  setAvailableWallets(walletsRef.current);
8603
8622
  setShowModal(true);
8604
- }, [connector]);
8623
+ }, [connector, account]);
8605
8624
  // Render button based on connection status
8606
8625
  const renderButton = () => {
8607
8626
  // Check if we're waiting for connection OR waiting for balance to load
8608
8627
  // Show loading if: initializing, connecting, OR (account exists but balance hasn't loaded/resolved yet)
8609
- // Treat "0", "0.00" or empty as "not loaded yet" - only show connected state when we have real (non-zero) balance
8610
- const hasRealBalance = (balance.amount && balance.amount !== '0' && balance.amount !== '0.00' && balance.amount !== '' && balance.amount !== 'Loading...') ||
8628
+ // For TON: treat as resolved after fetch completes (even if 0). For others: treat zero/empty as resolved.
8629
+ (balance.amount && balance.amount !== '0' && balance.amount !== '0.00' && balance.amount !== '' && balance.amount !== 'Loading...') ||
8611
8630
  (twcBalance && twcBalance !== '0' && twcBalance !== '0.00' && twcBalance !== '' && twcBalance !== 'Loading...');
8612
- // For TON wallets, balance is resolved after fetch completes (even if empty/0)
8613
- // For other wallets, balance is resolved only if we have a real (non-zero) balance
8614
- const isBalanceResolved = !balanceLoading && (hasRealBalance || (account?.chainType === 'ton' && !isInitializing));
8631
+ let isBalanceResolved;
8632
+ if (account?.chainType === 'ton') {
8633
+ // For TON, resolved if not loading or initializing
8634
+ isBalanceResolved = !balanceLoading && !isInitializing;
8635
+ }
8636
+ else {
8637
+ // For EVM, Solana, Tron: resolved if not loading (even if balance is zero)
8638
+ isBalanceResolved = !balanceLoading && !isInitializing;
8639
+ }
8615
8640
  const waitingForBalance = account && !isBalanceResolved;
8641
+ // Patch: If account is null and status is disconnected, always stop initializing/loading
8642
+ if (!account && status === exports.ConnectionStatus.DISCONNECTED && (isInitializing || isConnecting || balanceLoading)) {
8643
+ if (isInitializing)
8644
+ setIsInitializing(false);
8645
+ if (isConnecting)
8646
+ setIsConnecting(false);
8647
+ if (balanceLoading)
8648
+ setBalanceLoading(false);
8649
+ }
8616
8650
  const isCheckingConnection = isInitializing || waitingForBalance || ((status === exports.ConnectionStatus.CONNECTING || isConnecting) && !account);
8617
8651
  const isActuallyConnecting = (status === exports.ConnectionStatus.CONNECTING || isConnecting) && account && isBalanceResolved;
8618
8652
  // Check connection state
@@ -8822,31 +8856,34 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8822
8856
  }
8823
8857
  // Wallet icon SVG component
8824
8858
  const WalletIcon = () => (jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { display: 'block' }, children: [jsxRuntime.jsx("path", { d: "M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1" }), jsxRuntime.jsx("path", { d: "M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4" })] }));
8825
- return (jsxRuntime.jsxs("button", { onClick: handleOpenModal, style: {
8826
- display: 'flex',
8827
- alignItems: 'center',
8828
- gap: '10px',
8829
- padding: '12px 12px',
8830
- borderRadius: '12px',
8831
- border: 'none',
8832
- backgroundColor: '#FF9814', // Warm golden-orange
8833
- color: '#000000', // Black text
8834
- fontWeight: '700',
8835
- fontSize: '15px',
8836
- cursor: 'pointer',
8837
- transition: 'all 0.15s ease',
8838
- fontFamily: 'Lexend, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
8839
- boxShadow: '0 2px 8px rgba(255, 152, 20, 0.3)',
8840
- ...style,
8841
- }, className: className, onMouseEnter: (e) => {
8842
- e.currentTarget.style.backgroundColor = '#E8870F';
8843
- e.currentTarget.style.transform = 'translateY(-1px)';
8844
- e.currentTarget.style.boxShadow = '0 4px 12px rgba(255, 152, 20, 0.4)';
8845
- }, onMouseLeave: (e) => {
8846
- e.currentTarget.style.backgroundColor = '#FF9814';
8847
- e.currentTarget.style.transform = 'translateY(0)';
8848
- e.currentTarget.style.boxShadow = '0 2px 8px rgba(255, 152, 20, 0.3)';
8849
- }, children: [jsxRuntime.jsx("span", { style: { color: '#000000', display: 'flex', alignItems: 'center' }, children: jsxRuntime.jsx(WalletIcon, {}) }), jsxRuntime.jsx("span", { style: { color: '#000000', fontWeight: '500', fontSize: '14px' }, children: buttonText })] }));
8859
+ // Only show connect button if not connected
8860
+ if (!account || status !== exports.ConnectionStatus.CONNECTED) {
8861
+ return (jsxRuntime.jsxs("button", { onClick: () => handleOpenModal(), style: {
8862
+ display: 'flex',
8863
+ alignItems: 'center',
8864
+ gap: '10px',
8865
+ padding: '12px 12px',
8866
+ borderRadius: '12px',
8867
+ border: 'none',
8868
+ backgroundColor: '#FF9814', // Warm golden-orange
8869
+ color: '#000000', // Black text
8870
+ fontWeight: '700',
8871
+ fontSize: '15px',
8872
+ cursor: 'pointer',
8873
+ transition: 'all 0.15s ease',
8874
+ fontFamily: 'Lexend, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
8875
+ boxShadow: '0 2px 8px rgba(255, 152, 20, 0.3)',
8876
+ ...style,
8877
+ }, className: className, onMouseEnter: (e) => {
8878
+ e.currentTarget.style.backgroundColor = '#E8870F';
8879
+ e.currentTarget.style.transform = 'translateY(-1px)';
8880
+ e.currentTarget.style.boxShadow = '0 4px 12px rgba(255, 152, 20, 0.4)';
8881
+ }, onMouseLeave: (e) => {
8882
+ e.currentTarget.style.backgroundColor = '#FF9814';
8883
+ e.currentTarget.style.transform = 'translateY(0)';
8884
+ e.currentTarget.style.boxShadow = '0 2px 8px rgba(255, 152, 20, 0.3)';
8885
+ }, children: [jsxRuntime.jsx("span", { style: { color: '#000000', display: 'flex', alignItems: 'center' }, children: jsxRuntime.jsx(WalletIcon, {}) }), jsxRuntime.jsx("span", { style: { color: '#000000', fontWeight: '500', fontSize: '14px' }, children: buttonText })] }));
8886
+ }
8850
8887
  };
8851
8888
  // Group wallets by chain
8852
8889
  availableWallets.reduce((acc, wallet) => {
@@ -8865,7 +8902,9 @@ const ConnectButton = ({ connector, onConnect, onDisconnect, onError, className,
8865
8902
  .tiwiflix-scrollbar-hide::-webkit-scrollbar {
8866
8903
  display: none;
8867
8904
  }
8868
- ` }), renderButton(), jsxRuntime.jsx(AccountDetailsModal, { isOpen: showDetailsModal, onClose: () => setShowDetailsModal(false), account: account, onDisconnect: handleDisconnect, onCopyAddress: handleCopyAddress, onOpenSwap: () => setShowSwapModal(true), onOpenSend: () => setShowSendModal(true), onOpenActivity: () => setShowActivityModal(true), balance: balance, balanceLoading: balanceLoading, twcBalance: twcBalance, usdValue: usdValue, nftCount: nftCount, nftLoading: nftLoading, tonBalance: balance.amount || tonBalance, tonUsdValue: balance.usdValue ?? tonUsdValue, walletIcon: walletIcon, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition }), jsxRuntime.jsx(ActivityModal, { isOpen: showActivityModal, onClose: () => setShowActivityModal(false), transactions: txs, loading: txsLoading, error: txsError, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition }), jsxRuntime.jsx(SwapModal, { isOpen: showSwapModal && account?.chainType !== 'ton', onClose: () => setShowSwapModal(false), colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition }), jsxRuntime.jsx(SendModal, { isOpen: showSendModal, onClose: () => setShowSendModal(false), balance: balance, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition, onSend: async () => {
8905
+ ` }), renderButton(), jsxRuntime.jsx(AccountDetailsModal, { isOpen: showDetailsModal, onClose: () => setShowDetailsModal(false), account: account, onDisconnect: handleDisconnect, onCopyAddress: handleCopyAddress, onOpenSwap: () => setShowSwapModal(true), onOpenSend: () => setShowSendModal(true), onOpenActivity: () => setShowActivityModal(true), balance: balance, balanceLoading: balanceLoading, twcBalance: twcBalance, usdValue: usdValue, nftCount: nftCount, nftLoading: nftLoading, tonBalance: balance.amount || tonBalance, tonUsdValue: balance.usdValue ?? tonUsdValue, walletIcon: walletIcon, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition,
8906
+ // Add a Switch Wallet button to allow explicit wallet switching
8907
+ onSwitchWallet: () => handleOpenModal(true) }), jsxRuntime.jsx(ActivityModal, { isOpen: showActivityModal, onClose: () => setShowActivityModal(false), transactions: txs, loading: txsLoading, error: txsError, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition }), jsxRuntime.jsx(SwapModal, { isOpen: showSwapModal && account?.chainType !== 'ton', onClose: () => setShowSwapModal(false), colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition }), jsxRuntime.jsx(SendModal, { isOpen: showSendModal, onClose: () => setShowSendModal(false), balance: balance, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition, onSend: async () => {
8869
8908
  alert('Send functionality coming soon');
8870
8909
  setShowSendModal(false);
8871
8910
  } }), jsxRuntime.jsx(TWCBalanceModal, { isOpen: showTWCBalanceModal, onClose: () => setShowTWCBalanceModal(false), twcBalance: twcBalance, usdValue: usdValue, colors: colors, isDarkMode: isDarkMode, modalPosition: modalPosition, onBuyTWC: () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiwiflix-wallet-connector",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "Multi-chain wallet connector for Tiwiflix supporting EVM, TON, Solana, and Tron wallets",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",