0xtrails 0.2.1 → 0.2.4

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 (87) hide show
  1. package/dist/aave.d.ts.map +1 -1
  2. package/dist/{ccip-BbfANth7.js → ccip-BlV1Mry3.js} +1 -1
  3. package/dist/chains.d.ts +5 -1
  4. package/dist/chains.d.ts.map +1 -1
  5. package/dist/constants.d.ts +4 -4
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/{index-WpIVoh3X.js → index-BNWCIGfQ.js} +49015 -46131
  8. package/dist/index.d.ts +4 -3
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +2 -2
  11. package/dist/intentEntrypoint.d.ts +0 -8
  12. package/dist/intentEntrypoint.d.ts.map +1 -1
  13. package/dist/metaTxnMonitor.d.ts +5 -4
  14. package/dist/metaTxnMonitor.d.ts.map +1 -1
  15. package/dist/metaTxns.d.ts +3 -3
  16. package/dist/metaTxns.d.ts.map +1 -1
  17. package/dist/prepareSend.d.ts +3 -3
  18. package/dist/prepareSend.d.ts.map +1 -1
  19. package/dist/relayer.d.ts +10 -7
  20. package/dist/relayer.d.ts.map +1 -1
  21. package/dist/sequenceWallet.d.ts +3 -2
  22. package/dist/sequenceWallet.d.ts.map +1 -1
  23. package/dist/tokenBalances.d.ts +7 -0
  24. package/dist/tokenBalances.d.ts.map +1 -1
  25. package/dist/tokens.d.ts +2 -1
  26. package/dist/tokens.d.ts.map +1 -1
  27. package/dist/trails.d.ts +2 -2
  28. package/dist/trails.d.ts.map +1 -1
  29. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  30. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  31. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  32. package/dist/widget/components/EarnPools.d.ts.map +1 -1
  33. package/dist/widget/components/Fund.d.ts +1 -0
  34. package/dist/widget/components/Fund.d.ts.map +1 -1
  35. package/dist/widget/components/Pay.d.ts +1 -0
  36. package/dist/widget/components/Pay.d.ts.map +1 -1
  37. package/dist/widget/components/Recipients.d.ts.map +1 -1
  38. package/dist/widget/components/RefundWarning.d.ts +1 -0
  39. package/dist/widget/components/RefundWarning.d.ts.map +1 -1
  40. package/dist/widget/hooks/useBack.d.ts +5 -0
  41. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  42. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
  43. package/dist/widget/hooks/useInitialRedirect.d.ts +7 -0
  44. package/dist/widget/hooks/useInitialRedirect.d.ts.map +1 -0
  45. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -1
  46. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  47. package/dist/widget/index.js +1 -1
  48. package/dist/widget/widget.d.ts.map +1 -1
  49. package/package.json +18 -17
  50. package/src/aave.ts +90 -74
  51. package/src/chains.ts +23 -3
  52. package/src/constants.ts +10 -17
  53. package/src/error.ts +1 -1
  54. package/src/index.ts +8 -3
  55. package/src/intentEntrypoint.ts +0 -15
  56. package/src/metaTxnMonitor.ts +28 -22
  57. package/src/metaTxns.ts +5 -3
  58. package/src/prepareSend.ts +217 -286
  59. package/src/relayer.ts +15 -16
  60. package/src/sequenceWallet.ts +7 -3
  61. package/src/tokenBalances.ts +55 -1
  62. package/src/tokens.ts +10 -0
  63. package/src/trails.ts +2 -2
  64. package/src/widget/compiled.css +1 -1
  65. package/src/widget/components/AccountActionsDropdown.tsx +6 -2
  66. package/src/widget/components/AccountIntentTransactionHistory.tsx +1 -1
  67. package/src/widget/components/AccountSettings.tsx +5 -4
  68. package/src/widget/components/ChainFilterDropdown.tsx +1 -1
  69. package/src/widget/components/ChainList.tsx +1 -1
  70. package/src/widget/components/ConnectWallet.tsx +6 -2
  71. package/src/widget/components/EarnPools.tsx +2 -1
  72. package/src/widget/components/Fund.tsx +50 -27
  73. package/src/widget/components/Pay.tsx +24 -1
  74. package/src/widget/components/Receive.tsx +1 -1
  75. package/src/widget/components/Recipients.tsx +4 -2
  76. package/src/widget/components/RefundWarning.tsx +5 -1
  77. package/src/widget/components/SwapSettings.tsx +9 -9
  78. package/src/widget/components/TokenSelector.tsx +1 -1
  79. package/src/widget/components/WalletList.tsx +3 -3
  80. package/src/widget/hooks/useBack.tsx +111 -9
  81. package/src/widget/hooks/useDefaultTokenSelection.tsx +5 -1
  82. package/src/widget/hooks/useInitialRedirect.tsx +70 -0
  83. package/src/widget/hooks/useSelectedFeeToken.tsx +10 -16
  84. package/src/widget/hooks/useSendForm.ts +10 -10
  85. package/src/widget/hooks/useTokenList.ts +11 -2
  86. package/src/widget/widget.tsx +85 -106
  87. /package/dist/{style.css → 0xtrails.css} +0 -0
@@ -109,11 +109,12 @@ export const EarnPools: React.FC<EarnPoolsProps> = ({
109
109
 
110
110
  // Search filter - split by spaces to allow "usdc base" type searches
111
111
  const searchTerms = searchFilter
112
+ .trim()
112
113
  .toLowerCase()
113
114
  .split(/\s+/)
114
115
  .filter((term) => term.length > 0)
115
116
  const searchMatch =
116
- !searchFilter ||
117
+ !searchFilter.trim() ||
117
118
  searchTerms.every(
118
119
  (term) =>
119
120
  pool.token?.symbol?.toLowerCase().includes(term) ||
@@ -48,6 +48,7 @@ interface FundProps {
48
48
  onSend: (amount: string, recipient: string) => void
49
49
  paymasterUrls?: Array<{ chainId: number; url: string }>
50
50
  gasless?: boolean
51
+ isSequenceWallet?: boolean
51
52
  setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
52
53
  quoteProvider?: string
53
54
  fundMethod?: string
@@ -83,6 +84,7 @@ export const Fund: React.FC<FundProps> = ({
83
84
  onSend,
84
85
  paymasterUrls,
85
86
  gasless,
87
+ isSequenceWallet = false,
86
88
  setWalletConfirmRetryHandler,
87
89
  quoteProvider,
88
90
  fundMethod,
@@ -819,9 +821,11 @@ export const Fund: React.FC<FundProps> = ({
819
821
  <div className="flex items-center space-x-2">
820
822
  {/* Amount Input */}
821
823
  <div className="flex-1">
822
- <div
823
- className="flex items-center justify-start cursor-text"
824
+ <button
825
+ type="button"
826
+ className="flex items-center justify-start cursor-text bg-transparent border-none p-0 w-full"
824
827
  onClick={() => inputRef.current?.focus()}
828
+ aria-label="Focus amount input"
825
829
  >
826
830
  <div className="flex items-center">
827
831
  <input
@@ -861,7 +865,7 @@ export const Fund: React.FC<FundProps> = ({
861
865
  <div className="ml-2 animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
862
866
  )}
863
867
  </div>
864
- </div>
868
+ </button>
865
869
  </div>
866
870
 
867
871
  {/* Token Selection Button */}
@@ -1050,7 +1054,7 @@ export const Fund: React.FC<FundProps> = ({
1050
1054
  <div className="relative">
1051
1055
  {toToken ? (
1052
1056
  /* Display only - destination token is fixed */
1053
- <div className="w-full flex items-center justify-between space-x-3 trails-bg-secondary trails-border-radius-list-button px-4 py-2">
1057
+ <div className="w-full flex items-center justify-between space-x-3 trails-bg-secondary trails-border-radius-list-button px-4 py-2 pb-6">
1054
1058
  <div className="text-left">
1055
1059
  <div className="font-medium trails-text-primary text-sm whitespace-nowrap">
1056
1060
  Receiving amount
@@ -1059,17 +1063,26 @@ export const Fund: React.FC<FundProps> = ({
1059
1063
  <div className="flex items-center space-x-2">
1060
1064
  {selectedDestToken ? (
1061
1065
  <>
1062
- <div className="font-medium trails-text-primary text-sm">
1063
- {isLoadingQuote ? (
1064
- <div className="h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
1065
- ) : prepareSendQuote?.destinationAmountFormatted ? (
1066
- <span className="whitespace-nowrap">
1067
- {prepareSendQuote.destinationAmountFormatted}{" "}
1068
- {selectedDestToken.symbol}
1069
- </span>
1070
- ) : (
1071
- ""
1072
- )}
1066
+ <div className="relative">
1067
+ <div className="font-medium trails-text-primary text-sm">
1068
+ {isLoadingQuote ? (
1069
+ <div className="h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
1070
+ ) : prepareSendQuote?.destinationAmountFormatted ? (
1071
+ <span className="whitespace-nowrap">
1072
+ {prepareSendQuote.destinationAmountFormatted}{" "}
1073
+ {selectedDestToken.symbol}
1074
+ </span>
1075
+ ) : (
1076
+ ""
1077
+ )}
1078
+ </div>
1079
+ {/* USD Display - Hidden during loading, absolutely positioned */}
1080
+ {!isLoadingQuote &&
1081
+ prepareSendQuote?.destinationAmountUsdDisplay && (
1082
+ <div className="absolute top-full right-0 text-xs trails-text-muted mt-0.5">
1083
+ ≈ {prepareSendQuote.destinationAmountUsdDisplay}
1084
+ </div>
1085
+ )}
1073
1086
  </div>
1074
1087
  <TokenImage
1075
1088
  symbol={selectedDestToken.symbol}
@@ -1095,7 +1108,7 @@ export const Fund: React.FC<FundProps> = ({
1095
1108
  <button
1096
1109
  type="button"
1097
1110
  onClick={() => setShowDestinationTokenSelector(true)}
1098
- className="w-full flex items-center justify-between space-x-3 hover:trails-hover-bg hover:bg-gray-50 dark:hover:bg-gray-700 trails-border-radius-list-button px-4 py-2 transition-all duration-200 cursor-pointer"
1111
+ className="w-full flex items-center justify-between space-x-3 hover:trails-hover-bg hover:bg-gray-50 dark:hover:bg-gray-700 trails-border-radius-list-button px-4 py-2 pb-6 transition-all duration-200 cursor-pointer"
1099
1112
  >
1100
1113
  <div className="text-left">
1101
1114
  <div className="font-medium trails-text-primary text-sm whitespace-nowrap">
@@ -1106,17 +1119,26 @@ export const Fund: React.FC<FundProps> = ({
1106
1119
  <div className="flex items-center space-x-2">
1107
1120
  {selectedDestToken ? (
1108
1121
  <>
1109
- <div className="font-medium trails-text-primary text-sm">
1110
- {isLoadingQuote ? (
1111
- <div className="h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
1112
- ) : prepareSendQuote?.destinationAmountFormatted ? (
1113
- <span className="whitespace-nowrap">
1114
- {prepareSendQuote.destinationAmountFormatted}{" "}
1115
- {selectedDestToken.symbol}
1116
- </span>
1117
- ) : (
1118
- ""
1119
- )}
1122
+ <div className="relative">
1123
+ <div className="font-medium trails-text-primary text-sm">
1124
+ {isLoadingQuote ? (
1125
+ <div className="h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
1126
+ ) : prepareSendQuote?.destinationAmountFormatted ? (
1127
+ <span className="whitespace-nowrap">
1128
+ {prepareSendQuote.destinationAmountFormatted}{" "}
1129
+ {selectedDestToken.symbol}
1130
+ </span>
1131
+ ) : (
1132
+ ""
1133
+ )}
1134
+ </div>
1135
+ {/* USD Display - Hidden during loading, absolutely positioned */}
1136
+ {!isLoadingQuote &&
1137
+ prepareSendQuote?.destinationAmountUsdDisplay && (
1138
+ <div className="absolute top-full right-0 text-xs trails-text-muted mt-0.5">
1139
+ ≈ {prepareSendQuote.destinationAmountUsdDisplay}
1140
+ </div>
1141
+ )}
1120
1142
  </div>
1121
1143
  <TokenImage
1122
1144
  symbol={selectedDestToken.symbol}
@@ -1149,6 +1171,7 @@ export const Fund: React.FC<FundProps> = ({
1149
1171
  fundMethod={fundMethod}
1150
1172
  isSenderContractOnOrigin={isSenderContractOnOrigin}
1151
1173
  isSenderContractOnDestination={isSenderContractOnDestination}
1174
+ isSequenceWallet={isSequenceWallet}
1152
1175
  />
1153
1176
 
1154
1177
  {/* Error Display */}
@@ -45,6 +45,7 @@ interface PayProps {
45
45
  onSend: (amount: string, recipient: string) => void
46
46
  paymasterUrls?: Array<{ chainId: number; url: string }>
47
47
  gasless?: boolean
48
+ isSequenceWallet?: boolean
48
49
  setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
49
50
  quoteProvider?: string
50
51
  fundMethod?: string
@@ -82,6 +83,7 @@ export const Pay: React.FC<PayProps> = ({
82
83
  onSend,
83
84
  paymasterUrls,
84
85
  gasless,
86
+ isSequenceWallet = false,
85
87
  setWalletConfirmRetryHandler,
86
88
  quoteProvider,
87
89
  fundMethod,
@@ -143,6 +145,18 @@ export const Pay: React.FC<PayProps> = ({
143
145
  allSupportedTokens: true, // Show all tokens for destination selection
144
146
  })
145
147
 
148
+ // Get origin token balance for display
149
+ const originTokenBalance = useMemo(() => {
150
+ if (!originToken || !filteredTokensFormatted) return null
151
+
152
+ return filteredTokensFormatted.find(
153
+ (token) =>
154
+ token.contractAddress?.toLowerCase() ===
155
+ originToken.contractAddress?.toLowerCase() &&
156
+ token.chainId === originToken.chainId,
157
+ )
158
+ }, [originToken, filteredTokensFormatted])
159
+
146
160
  // Use useSendForm for quote functionality with EXACT_OUTPUT trade type
147
161
  const {
148
162
  amountUsdDisplay,
@@ -729,8 +743,16 @@ export const Pay: React.FC<PayProps> = ({
729
743
  <span>&nbsp;</span>
730
744
  )}
731
745
  </div>
746
+ {/* Token Balance */}
732
747
  <div className="text-xs trails-text-muted">
733
- <span>&nbsp;</span>
748
+ {originTokenBalance?.balanceFormatted ? (
749
+ <>
750
+ Balance: {originTokenBalance.balanceFormatted}{" "}
751
+ {originToken?.symbol}
752
+ </>
753
+ ) : (
754
+ <span>&nbsp;</span>
755
+ )}
734
756
  </div>
735
757
  </div>
736
758
  </div>
@@ -999,6 +1021,7 @@ export const Pay: React.FC<PayProps> = ({
999
1021
  fundMethod={fundMethod}
1000
1022
  isSenderContractOnOrigin={isSenderContractOnOrigin}
1001
1023
  isSenderContractOnDestination={isSenderContractOnDestination}
1024
+ isSequenceWallet={isSequenceWallet}
1002
1025
  />
1003
1026
 
1004
1027
  {/* Error Display */}
@@ -104,7 +104,7 @@ export const Receive: React.FC<ReceiveProps> = ({
104
104
  {/* QR Code Section */}
105
105
  <div className="space-y-3 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
106
106
  <div className="flex justify-center">
107
- <div className="flex flex-col items-center">
107
+ <div className="flex flex-col items-center" title={qrCodeUrl}>
108
108
  <QrCode url={qrCodeUrl} size={300} />
109
109
  </div>
110
110
  </div>
@@ -236,7 +236,8 @@ export const Recipients: React.FC<RecipientsProps> = ({
236
236
  const isHighlighted =
237
237
  searchQuery &&
238
238
  isAddress(searchQuery) &&
239
- recipient.address.toLowerCase() === searchQuery.toLowerCase()
239
+ recipient.address.toLowerCase() ===
240
+ searchQuery.trim().toLowerCase()
240
241
 
241
242
  return (
242
243
  <button
@@ -339,7 +340,8 @@ export const Recipients: React.FC<RecipientsProps> = ({
339
340
  const isHighlighted =
340
341
  searchQuery &&
341
342
  isAddress(searchQuery) &&
342
- wallet.address.toLowerCase() === searchQuery.toLowerCase()
343
+ wallet.address.toLowerCase() ===
344
+ searchQuery.trim().toLowerCase()
343
345
 
344
346
  return (
345
347
  <button
@@ -4,17 +4,21 @@ interface RefundWarningProps {
4
4
  fundMethod?: string
5
5
  isSenderContractOnOrigin?: boolean
6
6
  isSenderContractOnDestination?: boolean
7
+ isSequenceWallet?: boolean
7
8
  }
8
9
 
9
10
  export const RefundWarning: React.FC<RefundWarningProps> = ({
10
11
  fundMethod,
11
12
  isSenderContractOnOrigin,
12
13
  isSenderContractOnDestination,
14
+ isSequenceWallet = false,
13
15
  }) => {
14
16
  // Determine if we should show the warning
15
17
  const shouldShowWarning =
16
18
  fundMethod === "exchange" ||
17
- (isSenderContractOnOrigin && !isSenderContractOnDestination)
19
+ (isSenderContractOnOrigin &&
20
+ !isSenderContractOnDestination &&
21
+ !isSequenceWallet)
18
22
 
19
23
  if (!shouldShowWarning) {
20
24
  return null
@@ -1,12 +1,12 @@
1
1
  import { Settings } from "lucide-react"
2
2
  import type React from "react"
3
3
  import { useEffect, useRef, useState } from "react"
4
- import { SwapDisplayMode } from "./SwapDisplayMode.js"
4
+ // import { SwapDisplayMode } from "./SwapDisplayMode.js"
5
5
  import { SlippageToleranceSettings } from "./SlippageToleranceSettings.js"
6
- import { useSwapSettings } from "../hooks/useSwapSettings.js"
6
+ // import { useSwapSettings } from "../hooks/useSwapSettings.js"
7
7
 
8
8
  export const SwapSettings: React.FC = () => {
9
- const { isSimpleSwapMode, setIsSimpleSwapModeWithStorage } = useSwapSettings()
9
+ //const { isSimpleSwapMode, setIsSimpleSwapModeWithStorage } = useSwapSettings()
10
10
  const [isSettingsDropdownOpen, setIsSettingsDropdownOpen] = useState(false)
11
11
  const settingsDropdownRef = useRef<HTMLDivElement>(null)
12
12
 
@@ -27,10 +27,10 @@ export const SwapSettings: React.FC = () => {
27
27
  }
28
28
  }, [isSettingsDropdownOpen])
29
29
 
30
- const handleModeSelect = (isSimple: boolean) => {
31
- setIsSimpleSwapModeWithStorage(isSimple)
32
- setIsSettingsDropdownOpen(false)
33
- }
30
+ // const handleModeSelect = (isSimple: boolean) => {
31
+ // setIsSimpleSwapModeWithStorage(isSimple)
32
+ // setIsSettingsDropdownOpen(false)
33
+ // }
34
34
 
35
35
  return (
36
36
  <div className="relative" ref={settingsDropdownRef}>
@@ -47,10 +47,10 @@ export const SwapSettings: React.FC = () => {
47
47
  {isSettingsDropdownOpen && (
48
48
  <div className="absolute right-0 top-full mt-2 w-80 trails-bg-card rounded-lg shadow-lg border trails-border-primary z-20">
49
49
  <div className="p-4 space-y-4">
50
- <SwapDisplayMode
50
+ {/* <SwapDisplayMode
51
51
  isSimpleMode={isSimpleSwapMode}
52
52
  onModeChange={handleModeSelect}
53
- />
53
+ /> */}
54
54
 
55
55
  <div className="border-t border-gray-200 dark:border-gray-700 pt-4">
56
56
  <SlippageToleranceSettings />
@@ -120,7 +120,7 @@ export const TokenSelector: React.FC<TokenSelectorProps> = ({
120
120
  // Then apply search filtering
121
121
  if (!searchQuery.trim()) return true
122
122
 
123
- const query = searchQuery.toLowerCase()
123
+ const query = searchQuery.trim().toLowerCase()
124
124
  return (
125
125
  recentToken.symbol.toLowerCase().includes(query) ||
126
126
  recentToken.name.toLowerCase().includes(query) ||
@@ -45,10 +45,11 @@ export const WalletList: React.FC<WalletListProps> = ({
45
45
  const filteredWalletOptions = useMemo(() => {
46
46
  if (!searchTerm.trim()) return prefilteredWalletOptions
47
47
 
48
+ const query = searchTerm.trim().toLowerCase()
48
49
  return prefilteredWalletOptions.filter((wallet) => {
49
50
  return (
50
- wallet.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
51
- wallet.id.toLowerCase().includes(searchTerm.toLowerCase())
51
+ wallet.name.toLowerCase().includes(query) ||
52
+ wallet.id.toLowerCase().includes(query)
52
53
  )
53
54
  })
54
55
  }, [prefilteredWalletOptions, searchTerm])
@@ -63,7 +64,6 @@ export const WalletList: React.FC<WalletListProps> = ({
63
64
  headerContent="More Wallets"
64
65
  headerContentAlign="left"
65
66
  onBack={onBack}
66
- showAccountActions={true}
67
67
  />
68
68
 
69
69
  {/* Search Input */}
@@ -1,4 +1,10 @@
1
- import { createContext, useContext, useState, useEffect } from "react"
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useState,
5
+ useEffect,
6
+ useCallback,
7
+ } from "react"
2
8
  import type { ReactNode } from "react"
3
9
  import { useCurrentScreen, type Screen } from "./useCurrentScreen.js"
4
10
  import { logger } from "../../logger.js"
@@ -9,6 +15,11 @@ interface BackContextType {
9
15
  clearHistory: () => void
10
16
  isNavigatingBack: boolean
11
17
  getHistory: () => Screen[]
18
+ getBreadcrumbHistory: () => Screen[]
19
+ addBreadcrumb: (screen: Screen) => void
20
+ setAccountSettingsSource: (screen: Screen) => void
21
+ getAccountSettingsSource: () => Screen | null
22
+ setCurrentScreenWithBack: (screen: Screen, backTo?: Screen) => void
12
23
  }
13
24
 
14
25
  const BackContext = createContext<BackContextType | null>(null)
@@ -20,6 +31,9 @@ interface BackProviderProps {
20
31
  export function BackProvider({ children }: BackProviderProps) {
21
32
  const { currentScreen, setCurrentScreen } = useCurrentScreen()
22
33
  const [history, setHistory] = useState<Screen[]>([])
34
+ const [navigationStack, setNavigationStack] = useState<
35
+ Array<[Screen, Screen?]>
36
+ >([])
23
37
  const [isNavigatingBack, setIsNavigatingBack] = useState(false)
24
38
 
25
39
  // Track screen changes and update history
@@ -45,20 +59,83 @@ export function BackProvider({ children }: BackProviderProps) {
45
59
  })
46
60
  }, [currentScreen])
47
61
 
48
- const canGoBack = history.length > 1
62
+ // Get the previous screen from navigation stack
63
+ const getPreviousScreen = (): Screen | null => {
64
+ if (navigationStack.length === 0) {
65
+ return null
66
+ }
67
+
68
+ // Find the current screen in the navigation stack
69
+ const currentIndex = navigationStack.findIndex(
70
+ ([screen]) => screen === currentScreen,
71
+ )
72
+ if (currentIndex === -1) {
73
+ return null
74
+ }
75
+
76
+ const currentEntry = navigationStack[currentIndex]
77
+ if (!currentEntry) {
78
+ return null
79
+ }
80
+ const [, backToScreen] = currentEntry
81
+
82
+ // If this screen has a specific back target, use it
83
+ if (backToScreen) {
84
+ return backToScreen
85
+ }
86
+
87
+ // Otherwise, go to the previous screen in the stack
88
+ if (currentIndex > 0) {
89
+ const previousEntry = navigationStack[currentIndex - 1]
90
+ if (previousEntry) {
91
+ const [previousScreen] = previousEntry
92
+ return previousScreen
93
+ }
94
+ }
95
+
96
+ return null
97
+ }
98
+
99
+ // Function to set current screen with optional back target
100
+ const setCurrentScreenWithBack = useCallback(
101
+ (screen: Screen, backTo?: Screen) => {
102
+ setNavigationStack((prevStack) => {
103
+ // Remove any existing entry for this screen
104
+ const filteredStack = prevStack.filter(
105
+ ([existingScreen]) => existingScreen !== screen,
106
+ )
107
+
108
+ // Add new entry
109
+ const newEntry: [Screen, Screen?] = backTo ? [screen, backTo] : [screen]
110
+ const newStack = [...filteredStack, newEntry]
111
+
112
+ // Keep stack limited to last 10 entries
113
+ if (newStack.length > 10) {
114
+ return newStack.slice(-10)
115
+ }
116
+
117
+ return newStack
118
+ })
119
+
120
+ setCurrentScreen(screen)
121
+ },
122
+ [setCurrentScreen],
123
+ )
124
+
125
+ const canGoBack = navigationStack.length > 0 || history.length > 1
49
126
 
50
127
  const goBack = canGoBack
51
128
  ? () => {
52
- // Remove current screen and go to previous one
53
- const newHistory = history.slice(0, -1)
54
- const previousScreen = newHistory[newHistory.length - 1]
129
+ const previousScreen = getPreviousScreen()
55
130
 
56
131
  logger.console.log(
57
- "[trails-sdk] goBack",
132
+ "[trails-sdk] goBack (navigation stack)",
58
133
  "previousScreen:",
59
134
  previousScreen,
60
135
  "currentScreen:",
61
136
  currentScreen,
137
+ "navigationStack:",
138
+ navigationStack,
62
139
  )
63
140
 
64
141
  // Set flag to prevent auto-selection when navigating back
@@ -67,24 +144,44 @@ export function BackProvider({ children }: BackProviderProps) {
67
144
  }
68
145
 
69
146
  if (previousScreen) {
70
- setHistory(newHistory)
147
+ // Remove current screen from navigation stack
148
+ setNavigationStack((prevStack) =>
149
+ prevStack.filter(([screen]) => screen !== currentScreen),
150
+ )
71
151
  setCurrentScreen(previousScreen)
152
+ } else {
153
+ // Fallback: if no navigation stack, use regular history
154
+ const regularHistory = history
155
+ if (regularHistory.length > 1) {
156
+ const fallbackScreen = regularHistory[regularHistory.length - 2]
157
+ if (fallbackScreen) {
158
+ logger.console.log(
159
+ "[trails-sdk] goBack (fallback to regular history)",
160
+ "previousScreen:",
161
+ fallbackScreen,
162
+ )
163
+ setCurrentScreen(fallbackScreen)
164
+ }
165
+ }
72
166
  }
73
167
 
74
- // Reset the flag after a short delay to allow the screen change to complete
168
+ // Reset the flag after a longer delay to allow the screen change to complete
169
+ // and prevent post-connection navigation from interfering with manual back navigation
75
170
  if (setIsNavigatingBack) {
76
171
  setTimeout(() => {
77
172
  setIsNavigatingBack(false)
78
- }, 100)
173
+ }, 500)
79
174
  }
80
175
  }
81
176
  : undefined
82
177
 
83
178
  const clearHistory = () => {
84
179
  setHistory([currentScreen])
180
+ setNavigationStack([[currentScreen]])
85
181
  }
86
182
 
87
183
  const getHistory = () => history
184
+ const getBreadcrumbHistory = () => navigationStack.map(([screen]) => screen)
88
185
 
89
186
  const value: BackContextType = {
90
187
  goBack,
@@ -92,6 +189,11 @@ export function BackProvider({ children }: BackProviderProps) {
92
189
  clearHistory,
93
190
  isNavigatingBack,
94
191
  getHistory,
192
+ getBreadcrumbHistory,
193
+ addBreadcrumb: () => {}, // Deprecated, kept for compatibility
194
+ setAccountSettingsSource: () => {}, // Deprecated, kept for compatibility
195
+ getAccountSettingsSource: () => null, // Deprecated, kept for compatibility
196
+ setCurrentScreenWithBack,
95
197
  }
96
198
 
97
199
  return <BackContext.Provider value={value}>{children}</BackContext.Provider>
@@ -187,7 +187,11 @@ function useDefaultTokenSelectionInternal(): UseDefaultTokenSelectionReturn {
187
187
  tokenWithInfo.contractInfo?.logoURI || tokenWithInfo.imageUrl || ""
188
188
  const decimals = token.contractInfo?.decimals
189
189
  if (!decimals) {
190
- throw new Error("Decimals not found")
190
+ logger.console.warn("[trails-sdk] Missing decimals for token:", {
191
+ token: token.contractInfo,
192
+ chainId: token.chainId,
193
+ })
194
+ return null // Return null instead of throwing
191
195
  }
192
196
 
193
197
  return {
@@ -0,0 +1,70 @@
1
+ import { useEffect, useState } from "react"
2
+ import { useCurrentScreen, type Screen } from "./useCurrentScreen.js"
3
+ import { useBack } from "./useBack.js"
4
+ import { logger } from "../../logger.js"
5
+ import type { Mode } from "../../mode.js"
6
+
7
+ export function useInitialRedirect(
8
+ isConnected: boolean,
9
+ currentMode: Mode,
10
+ getInitialScreenForMode: (mode: Mode) => Screen,
11
+ ) {
12
+ const { setCurrentScreen } = useCurrentScreen()
13
+ const { getBreadcrumbHistory } = useBack()
14
+ const [hasConnectedBefore, setHasConnectedBefore] = useState(false)
15
+ const [isInitialScreenSet, setIsInitialScreenSet] = useState(false)
16
+
17
+ useEffect(() => {
18
+ if (!isConnected || isInitialScreenSet) {
19
+ return
20
+ }
21
+
22
+ // Check if this is the first time connecting (no previous wallet connected)
23
+ const isFirstTimeConnection = !hasConnectedBefore
24
+
25
+ if (isFirstTimeConnection) {
26
+ // First time connecting - go to appropriate mode screen
27
+ const initialScreen = getInitialScreenForMode(currentMode)
28
+ logger.console.log(
29
+ "[trails-sdk] First time connection, redirecting to:",
30
+ initialScreen,
31
+ )
32
+ setCurrentScreen(initialScreen)
33
+ setHasConnectedBefore(true)
34
+ } else {
35
+ // Subsequent connections - check if user came from account-settings
36
+ const breadcrumbHistory = getBreadcrumbHistory()
37
+ const lastBreadcrumbScreen =
38
+ breadcrumbHistory.length > 0
39
+ ? breadcrumbHistory[breadcrumbHistory.length - 1]
40
+ : null
41
+
42
+ if (lastBreadcrumbScreen === "account-settings") {
43
+ // User came from account-settings, return there
44
+ logger.console.log(
45
+ "[trails-sdk] Returning to account-settings after wallet connection",
46
+ )
47
+ setCurrentScreen("account-settings")
48
+ } else {
49
+ // User came from elsewhere, go to appropriate mode screen
50
+ const initialScreen = getInitialScreenForMode(currentMode)
51
+ logger.console.log(
52
+ "[trails-sdk] Subsequent connection, redirecting to:",
53
+ initialScreen,
54
+ )
55
+ setCurrentScreen(initialScreen)
56
+ }
57
+ setIsInitialScreenSet(true)
58
+ }
59
+ }, [
60
+ isConnected,
61
+ currentMode,
62
+ hasConnectedBefore,
63
+ getInitialScreenForMode,
64
+ setCurrentScreen,
65
+ getBreadcrumbHistory,
66
+ isInitialScreenSet,
67
+ ])
68
+
69
+ return { hasConnectedBefore, isInitialScreenSet }
70
+ }
@@ -65,22 +65,16 @@ export const SelectedFeeTokenProvider: React.FC<
65
65
  const [availableTokens, setAvailableTokens] = useState<TokenWithBalance[]>([])
66
66
 
67
67
  // Wrapper to log all state changes to selectedFeeToken
68
- const setSelectedFeeTokenInternal = useCallback(
69
- (token: FeeOption | null) => {
70
- logger.console.log(
71
- "[trails-sdk] [FEE-SELECT] selectedFeeToken state changing:",
72
- {
73
- from: selectedFeeToken,
74
- to: token,
75
- fromNull: selectedFeeToken === null,
76
- toNull: token === null,
77
- stackTrace: new Error().stack?.split("\n").slice(2, 5).join("\n"), // Get call stack
78
- },
79
- )
80
- setSelectedFeeTokenInternalRaw(token)
81
- },
82
- [selectedFeeToken],
83
- )
68
+ const setSelectedFeeTokenInternal = useCallback((token: FeeOption | null) => {
69
+ logger.console.log(
70
+ "[trails-sdk] [FEE-SELECT] selectedFeeToken state changing:",
71
+ {
72
+ to: token,
73
+ toNull: token === null,
74
+ },
75
+ )
76
+ setSelectedFeeTokenInternalRaw(token)
77
+ }, [])
84
78
 
85
79
  // Wrapper to track when user makes an explicit selection
86
80
  const setSelectedFeeToken = useCallback(