0xtrails 0.12.2 → 0.13.0

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 (243) hide show
  1. package/dist/abis/trailsHydrate.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +41 -0
  3. package/dist/analytics.d.ts.map +1 -1
  4. package/dist/{ccip-62W6LwH2.js → ccip-Cg9-lJ6K.js} +16 -16
  5. package/dist/chainSwitch.d.ts.map +1 -1
  6. package/dist/chains.d.ts +9 -3
  7. package/dist/chains.d.ts.map +1 -1
  8. package/dist/error.d.ts +1 -0
  9. package/dist/error.d.ts.map +1 -1
  10. package/dist/{index-C0QTNYIA.js → index-DEojZg7b.js} +50431 -50424
  11. package/dist/index.d.ts +1 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +377 -421
  14. package/dist/intentReceiptPoller.d.ts.map +1 -1
  15. package/dist/intents.d.ts +1 -3
  16. package/dist/intents.d.ts.map +1 -1
  17. package/dist/mutations.d.ts +1 -4
  18. package/dist/mutations.d.ts.map +1 -1
  19. package/dist/prepareSend.d.ts.map +1 -1
  20. package/dist/query/balance.hooks.d.ts.map +1 -1
  21. package/dist/query/chains.hooks.d.ts.map +1 -1
  22. package/dist/query/chains.queries.d.ts +4 -1
  23. package/dist/query/chains.queries.d.ts.map +1 -1
  24. package/dist/queryParams.d.ts.map +1 -1
  25. package/dist/recover.d.ts.map +1 -1
  26. package/dist/tokens.d.ts.map +1 -1
  27. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
  28. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts.map +1 -1
  29. package/dist/transactionIntent/deposits/standardDeposit.d.ts +1 -7
  30. package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
  31. package/dist/transactionIntent/handlers/intentHandler.d.ts.map +1 -1
  32. package/dist/transactionIntent/helpers/transactionStateHelpers.d.ts +10 -1
  33. package/dist/transactionIntent/helpers/transactionStateHelpers.d.ts.map +1 -1
  34. package/dist/transactionIntent/types.d.ts +3 -6
  35. package/dist/transactionIntent/types.d.ts.map +1 -1
  36. package/dist/transactionIntent/utils/resilientDepositTracker.d.ts +3 -3
  37. package/dist/transactionIntent/utils/resilientDepositTracker.d.ts.map +1 -1
  38. package/dist/transactions.d.ts +2 -0
  39. package/dist/transactions.d.ts.map +1 -1
  40. package/dist/umd/trails.min.js +200 -200
  41. package/dist/walletUtils.d.ts +4 -0
  42. package/dist/walletUtils.d.ts.map +1 -1
  43. package/dist/wallets.d.ts +2 -1
  44. package/dist/wallets.d.ts.map +1 -1
  45. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  46. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -1
  47. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  48. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  49. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  50. package/dist/widget/components/ConnectedWallets.d.ts +5 -1
  51. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
  52. package/dist/widget/components/DepositTracker.d.ts +1 -1
  53. package/dist/widget/components/DepositTracker.d.ts.map +1 -1
  54. package/dist/widget/components/DirectTransfer.d.ts +0 -8
  55. package/dist/widget/components/DirectTransfer.d.ts.map +1 -1
  56. package/dist/widget/components/Fund.d.ts +1 -1
  57. package/dist/widget/components/Fund.d.ts.map +1 -1
  58. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  59. package/dist/widget/components/FundWalletSelection.d.ts +0 -8
  60. package/dist/widget/components/FundWalletSelection.d.ts.map +1 -1
  61. package/dist/widget/components/FundingMethodSelectorButton.d.ts +1 -1
  62. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -1
  63. package/dist/widget/components/MeldStepsFlow.d.ts.map +1 -1
  64. package/dist/widget/components/OnrampErrorScreen.d.ts.map +1 -1
  65. package/dist/widget/components/OnrampPaymentMethods.d.ts.map +1 -1
  66. package/dist/widget/components/OnrampProviderConfirmation.d.ts +0 -6
  67. package/dist/widget/components/OnrampProviderConfirmation.d.ts.map +1 -1
  68. package/dist/widget/components/Pay.d.ts.map +1 -1
  69. package/dist/widget/components/QrCode.d.ts.map +1 -1
  70. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  71. package/dist/widget/components/Receipt.d.ts.map +1 -1
  72. package/dist/widget/components/ReceiptRecoverableFunds.d.ts +25 -0
  73. package/dist/widget/components/ReceiptRecoverableFunds.d.ts.map +1 -0
  74. package/dist/widget/components/RecipientSelectorButton.d.ts +3 -1
  75. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -1
  76. package/dist/widget/components/Recipients.d.ts.map +1 -1
  77. package/dist/widget/components/Swap.d.ts +2 -16
  78. package/dist/widget/components/Swap.d.ts.map +1 -1
  79. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  80. package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
  81. package/dist/widget/components/TransactionHistoryItem.d.ts.map +1 -1
  82. package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -1
  83. package/dist/widget/components/TruncatedTransactionHash.d.ts.map +1 -1
  84. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  85. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  86. package/dist/widget/components/WidgetProviders.d.ts.map +1 -1
  87. package/dist/widget/components/Withdraw.d.ts.map +1 -1
  88. package/dist/widget/css/compiled.css +1 -1
  89. package/dist/widget/hooks/useClickTracking.d.ts.map +1 -1
  90. package/dist/widget/hooks/useDebugScreens.d.ts +1 -10
  91. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  92. package/dist/widget/hooks/useDepositMonitor.d.ts +2 -4
  93. package/dist/widget/hooks/useDepositMonitor.d.ts.map +1 -1
  94. package/dist/widget/hooks/useExternalFundingReceiptSync.d.ts +11 -0
  95. package/dist/widget/hooks/useExternalFundingReceiptSync.d.ts.map +1 -0
  96. package/dist/widget/hooks/useIntentReceiptBalances.d.ts +16 -0
  97. package/dist/widget/hooks/useIntentReceiptBalances.d.ts.map +1 -0
  98. package/dist/widget/hooks/useQuote.d.ts +0 -4
  99. package/dist/widget/hooks/useQuote.d.ts.map +1 -1
  100. package/dist/widget/hooks/useScreenTracking.d.ts +2 -0
  101. package/dist/widget/hooks/useScreenTracking.d.ts.map +1 -0
  102. package/dist/widget/hooks/useSelectedFundMethod.d.ts +0 -2
  103. package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -1
  104. package/dist/widget/hooks/useSendForm.d.ts +0 -4
  105. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  106. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  107. package/dist/widget/hooks/useTrailsSendTransaction.d.ts.map +1 -1
  108. package/dist/widget/hooks/useViewManager.d.ts +89 -0
  109. package/dist/widget/hooks/useViewManager.d.ts.map +1 -0
  110. package/dist/widget/hooks/useWalletConnectUri.d.ts.map +1 -1
  111. package/dist/widget/hooks/useWalletConnectionContext.d.ts +1 -1
  112. package/dist/widget/hooks/useWalletConnectionContext.d.ts.map +1 -1
  113. package/dist/widget/index.d.ts +1 -1
  114. package/dist/widget/index.d.ts.map +1 -1
  115. package/dist/widget/index.js +7 -6
  116. package/dist/widget/providers/TrailsProvider.d.ts.map +1 -1
  117. package/dist/widget/types/commonProps.d.ts +1 -6
  118. package/dist/widget/types/commonProps.d.ts.map +1 -1
  119. package/dist/widget/utils/forexRateStore.d.ts.map +1 -1
  120. package/dist/widget/utils/fundMethodSwitchState.d.ts +10 -0
  121. package/dist/widget/utils/fundMethodSwitchState.d.ts.map +1 -0
  122. package/dist/widget/utils/localeStore.d.ts.map +1 -1
  123. package/dist/widget/utils/viewManagerGuards.d.ts +5 -0
  124. package/dist/widget/utils/viewManagerGuards.d.ts.map +1 -0
  125. package/dist/widget/widget.d.ts +23 -2
  126. package/dist/widget/widget.d.ts.map +1 -1
  127. package/package.json +2 -2
  128. package/src/abis/trailsHydrate.ts +2 -1
  129. package/src/analytics.ts +60 -0
  130. package/src/chainSwitch.ts +11 -8
  131. package/src/chains.ts +82 -37
  132. package/src/constants.ts +2 -2
  133. package/src/error.ts +8 -0
  134. package/src/index.ts +1 -12
  135. package/src/intentReceiptPoller.ts +27 -0
  136. package/src/intents.ts +36 -87
  137. package/src/mutations.ts +11 -102
  138. package/src/onramp-client/index.ts +3 -3
  139. package/src/prepareSend.ts +6 -2
  140. package/src/query/balance.hooks.ts +31 -10
  141. package/src/query/chains.hooks.ts +7 -1
  142. package/src/query/chains.queries.ts +8 -5
  143. package/src/queryParams.ts +8 -6
  144. package/src/recover.ts +9 -9
  145. package/src/tokens.ts +4 -2
  146. package/src/transactionIntent/deposits/depositOrchestrator.ts +0 -2
  147. package/src/transactionIntent/deposits/gaslessDeposit.ts +8 -0
  148. package/src/transactionIntent/deposits/standardDeposit.ts +25 -35
  149. package/src/transactionIntent/handlers/intentHandler.ts +234 -138
  150. package/src/transactionIntent/helpers/transactionStateHelpers.ts +108 -1
  151. package/src/transactionIntent/types.ts +14 -8
  152. package/src/transactionIntent/utils/resilientDepositTracker.ts +72 -183
  153. package/src/transactions.ts +16 -0
  154. package/src/walletUtils.ts +188 -1
  155. package/src/wallets.ts +50 -15
  156. package/src/widget/compiled.css +1 -1
  157. package/src/widget/components/AccountActionsDropdown.tsx +4 -6
  158. package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +4 -6
  159. package/src/widget/components/AccountSettings.tsx +36 -22
  160. package/src/widget/components/ClassicSwap.tsx +67 -9
  161. package/src/widget/components/ConnectWallet.tsx +5 -7
  162. package/src/widget/components/ConnectedWallets.tsx +143 -82
  163. package/src/widget/components/DepositTracker.tsx +4 -5
  164. package/src/widget/components/DirectTransfer.tsx +85 -84
  165. package/src/widget/components/Earn.tsx +3 -3
  166. package/src/widget/components/Fund.tsx +90 -17
  167. package/src/widget/components/FundMethods.tsx +77 -43
  168. package/src/widget/components/FundWalletSelection.tsx +13 -397
  169. package/src/widget/components/FundingMethodSelectorButton.tsx +11 -10
  170. package/src/widget/components/MeldStepsFlow.tsx +64 -30
  171. package/src/widget/components/OnrampErrorScreen.tsx +2 -18
  172. package/src/widget/components/OnrampPaymentMethods.tsx +4 -6
  173. package/src/widget/components/OnrampProviderConfirmation.tsx +91 -110
  174. package/src/widget/components/OriginTransferInformation.tsx +2 -2
  175. package/src/widget/components/Pay.tsx +27 -7
  176. package/src/widget/components/PaymentMethods.tsx +10 -10
  177. package/src/widget/components/PoolDeposit.tsx +2 -2
  178. package/src/widget/components/QrCode.tsx +13 -11
  179. package/src/widget/components/QuoteDetails.tsx +16 -6
  180. package/src/widget/components/Receipt.tsx +66 -6
  181. package/src/widget/components/ReceiptRecoverableFunds.tsx +135 -0
  182. package/src/widget/components/RecipientSelectorButton.tsx +6 -17
  183. package/src/widget/components/Recipients.tsx +38 -29
  184. package/src/widget/components/Swap.tsx +2 -25
  185. package/src/widget/components/TokenList.tsx +2 -2
  186. package/src/widget/components/TokenSelector.tsx +11 -11
  187. package/src/widget/components/TokenSelectorButton.tsx +3 -3
  188. package/src/widget/components/TrailsHookModal.tsx +1 -1
  189. package/src/widget/components/TransactionDetails.tsx +43 -37
  190. package/src/widget/components/TransactionHistoryItem.tsx +1 -42
  191. package/src/widget/components/TransferPendingVertical.tsx +11 -4
  192. package/src/widget/components/TruncatedTransactionHash.tsx +5 -0
  193. package/src/widget/components/WalletConfirmation.tsx +5 -8
  194. package/src/widget/components/WalletConnect.tsx +6 -2
  195. package/src/widget/components/WidgetProviders.tsx +34 -43
  196. package/src/widget/components/Withdraw.tsx +25 -11
  197. package/src/widget/hooks/useClickTracking.ts +5 -0
  198. package/src/widget/hooks/useDebugScreens.ts +40 -86
  199. package/src/widget/hooks/useDepositMonitor.ts +14 -149
  200. package/src/widget/hooks/useExternalFundingReceiptSync.ts +79 -0
  201. package/src/widget/hooks/useIntentReceiptBalances.ts +141 -0
  202. package/src/widget/hooks/useQuote.ts +7 -16
  203. package/src/widget/hooks/useScreenTracking.ts +14 -0
  204. package/src/widget/hooks/useSelectedFundMethod.tsx +0 -5
  205. package/src/widget/hooks/useSendForm.ts +5 -14
  206. package/src/widget/hooks/useTokenList.ts +3 -16
  207. package/src/widget/hooks/useTrailsSendTransaction.ts +1 -5
  208. package/src/widget/hooks/useViewManager.tsx +505 -0
  209. package/src/widget/hooks/useWalletConnectUri.tsx +77 -18
  210. package/src/widget/hooks/useWalletConnectionContext.tsx +1 -1
  211. package/src/widget/index.tsx +1 -0
  212. package/src/widget/providers/TrailsProvider.tsx +0 -41
  213. package/src/widget/styles.ts +1 -1
  214. package/src/widget/types/commonProps.ts +0 -8
  215. package/src/widget/utils/forexRateStore.ts +0 -2
  216. package/src/widget/utils/fundMethodSwitchState.ts +25 -0
  217. package/src/widget/utils/localeStore.ts +0 -1
  218. package/src/widget/utils/viewManagerGuards.ts +49 -0
  219. package/src/widget/widget.tsx +405 -316
  220. package/dist/intentStorage.d.ts +0 -24
  221. package/dist/intentStorage.d.ts.map +0 -1
  222. package/dist/mode.d.ts +0 -2
  223. package/dist/mode.d.ts.map +0 -1
  224. package/dist/widget/hooks/useBack.d.ts +0 -22
  225. package/dist/widget/hooks/useBack.d.ts.map +0 -1
  226. package/dist/widget/hooks/useCurrentScreen.d.ts +0 -13
  227. package/dist/widget/hooks/useCurrentScreen.d.ts.map +0 -1
  228. package/dist/widget/hooks/useInitialRedirect.d.ts +0 -7
  229. package/dist/widget/hooks/useInitialRedirect.d.ts.map +0 -1
  230. package/dist/widget/hooks/useMode.d.ts +0 -20
  231. package/dist/widget/hooks/useMode.d.ts.map +0 -1
  232. package/dist/widget/hooks/usePreviousScreen.d.ts +0 -12
  233. package/dist/widget/hooks/usePreviousScreen.d.ts.map +0 -1
  234. package/dist/widget/workers/intentExecutionWorker.d.ts +0 -73
  235. package/dist/widget/workers/intentExecutionWorker.d.ts.map +0 -1
  236. package/src/intentStorage.ts +0 -106
  237. package/src/mode.ts +0 -1
  238. package/src/widget/hooks/useBack.tsx +0 -210
  239. package/src/widget/hooks/useCurrentScreen.tsx +0 -73
  240. package/src/widget/hooks/useInitialRedirect.tsx +0 -70
  241. package/src/widget/hooks/useMode.tsx +0 -51
  242. package/src/widget/hooks/usePreviousScreen.ts +0 -36
  243. package/src/widget/workers/intentExecutionWorker.ts +0 -502
@@ -51,6 +51,7 @@ interface OnrampTransferResult {
51
51
  txHash?: string
52
52
  transferId?: string
53
53
  }
54
+
54
55
  import {
55
56
  createStorage,
56
57
  useAccount,
@@ -61,7 +62,7 @@ import {
61
62
  useDisconnect,
62
63
  useSwitchAccount,
63
64
  } from "wagmi"
64
- import { getSessionId, trackWidgetScreen } from "../analytics.js"
65
+ import { getSessionId } from "../analytics.js"
65
66
  import { getChainInfo } from "../chains.js"
66
67
  // Config is now managed exclusively by TrailsProvider
67
68
  import { cssObjectToString } from "../cssUtils.js"
@@ -73,14 +74,11 @@ import {
73
74
  getIsUserRejectionError,
74
75
  getPrettifiedErrorMessage,
75
76
  } from "../error.js"
76
- import { removeStoredIntent } from "../intentStorage.js"
77
77
  import { logger } from "../logger.js"
78
- import type { Mode } from "../mode.js"
78
+ import { abortControllerRegistry } from "../abortController.js"
79
79
  import type { Pool } from "../pools.js"
80
80
  import { usePools } from "../pools.js"
81
- // TODO: temporary ignore this Biome warning for now; do not refactor code in this pass.
82
- // biome-ignore lint/style/useImportType: Keep current import shape for now per requested temporary ignores.
83
- import { type PrepareSendQuote } from "../prepareSend.js"
81
+ import type { PrepareSendQuote } from "../prepareSend.js"
84
82
  import { isValidInteger, isValidNumeric } from "../utils/validation.js"
85
83
  import type { Theme } from "../theme.js"
86
84
  import type { Token } from "../tokens.js"
@@ -135,14 +133,13 @@ import WalletConnectScreen from "./components/WalletConnect.js"
135
133
  import WalletConnectionPending from "./components/WalletConnectionPending.js"
136
134
  import { FundWalletSelection } from "./components/FundWalletSelection.js"
137
135
  import { WalletList } from "./components/WalletList.js"
138
- import { useBack } from "./hooks/useBack.js"
139
136
  import { useCheckout } from "./hooks/useCheckout.js"
140
- import { useCurrentScreen, type Screen } from "./hooks/useCurrentScreen.js"
141
137
  import { useDebugScreens } from "./hooks/useDebugScreens.js"
138
+ import { useExternalFundingReceiptSync } from "./hooks/useExternalFundingReceiptSync.js"
139
+ import { useScreenTracking } from "./hooks/useScreenTracking.js"
142
140
  import { useEarnPool } from "./hooks/useEarnPool.js"
143
- import { useInitialRedirect } from "./hooks/useInitialRedirect.js"
141
+
144
142
  import { useIsSequenceWallet } from "./hooks/useIsSequenceWallet.js"
145
- import { useMode } from "./hooks/useMode.js"
146
143
  import { useOriginSelectedToken as useSelectedToken } from "./hooks/useOriginSelectedToken.js"
147
144
  import { PriceImpactWarningProvider } from "./hooks/usePriceImpactWarning.js"
148
145
  import { useRecentTokens } from "./hooks/useRecentTokens.js"
@@ -151,9 +148,11 @@ import { useSelectedFundMethod } from "./hooks/useSelectedFundMethod.js"
151
148
  import { useSelectedRecipient } from "./hooks/useSelectedRecipient.js"
152
149
  import type { OnCompleteProps } from "./hooks/useSendForm.js"
153
150
  import { useTargetAmount } from "./hooks/useTargetAmount.js"
151
+ import { useSwapState } from "./hooks/useSwapState.js"
154
152
  import { useWidgetAnalytics } from "./analytics/useWidgetAnalytics.js"
155
153
  import { useWalletConnectionContext } from "./hooks/useWalletConnectionContext.js"
156
154
  import { useWidgetProps } from "./hooks/useWidgetProps.js"
155
+ import { clearFundStateForMethodSwitch } from "./utils/fundMethodSwitchState.js"
157
156
  import { useOnRampProviderWidget } from "./hooks/useOnRampProviderWidget.js"
158
157
  import { WidgetProviders } from "./components/WidgetProviders.js"
159
158
  import {
@@ -161,7 +160,14 @@ import {
161
160
  type TrailsProviderProps,
162
161
  } from "./providers/TrailsProvider.js"
163
162
  import { useTrailsModal } from "./providers/TrailsModalProvider.js"
164
- import { usePreviousScreen } from "./hooks/usePreviousScreen.js"
163
+ import {
164
+ useViewManager,
165
+ useNavigationEffect,
166
+ useScreenGuard,
167
+ getInitialScreen,
168
+ type Screen,
169
+ type Mode,
170
+ } from "./hooks/useViewManager.js"
165
171
  import PaymentMethods from "./components/PaymentMethods.js"
166
172
  import type { OnrampQuote } from "./hooks/useOnRampQuote.js"
167
173
  import {
@@ -169,14 +175,15 @@ import {
169
175
  isNativeToken,
170
176
  normalizeAddress,
171
177
  } from "../utils/address.js"
178
+ import { isWalletConnectConnector } from "../walletUtils.js"
172
179
  import type { SendOptions, SwapReturn } from "./hooks/useQuote.js"
173
180
  import { hasFailedOrAbortedTransaction } from "./utils/transactionFailure.js"
174
-
175
- export type FundMethodListOption =
176
- | "connected-wallet"
177
- | "crypto-transfer"
178
- | "cc-onramp"
179
- | "exchange-onramp"
181
+ import {
182
+ shouldClearTransferFlowOnNavigate,
183
+ shouldRedirectToConnect,
184
+ } from "./utils/viewManagerGuards.js"
185
+ import type { FundMethod } from "../transactionIntent/types.js"
186
+ import { isExternalFundingMethod } from "../transactionIntent/types.js"
180
187
 
181
188
  // Validate toToken - must be "ETH", "USDC", or a valid hex address
182
189
  const isValidToToToken = (toToken: string | null | undefined) => {
@@ -191,6 +198,21 @@ const isValidToToToken = (toToken: string | null | undefined) => {
191
198
  return isAddress(token)
192
199
  }
193
200
 
201
+ export type FundMethodListOption =
202
+ | "connected-wallet"
203
+ | "crypto-transfer"
204
+ | "cc-onramp"
205
+ | "exchange-onramp"
206
+
207
+ export const WidgetFundMethod = {
208
+ Wallet: "wallet",
209
+ CryptoTransfer: "crypto-transfer",
210
+ Onramp: "onramp",
211
+ OnrampExchange: "onramp-exchange",
212
+ } as const
213
+ export type WidgetFundMethod =
214
+ (typeof WidgetFundMethod)[keyof typeof WidgetFundMethod]
215
+
194
216
  export type TrailsWidgetProps = {
195
217
  apiKey: string
196
218
  sequenceIndexerUrl?: string
@@ -218,6 +240,17 @@ export type TrailsWidgetProps = {
218
240
  renderInline?: boolean
219
241
  theme?: Theme
220
242
  mode?: Mode
243
+ /**
244
+ * Pre-select funding method used by the widget.
245
+ *
246
+ * This applies across modes (fund/pay/swap/earn) wherever funding method is used.
247
+ * In fund mode it also controls the initial destination flow:
248
+ * - "wallet" -> Funding with wallet flow
249
+ * - "crypto-transfer" --> Funding with QR Code or deposit to address flow
250
+ * - "onramp" --> Funding with onramp flow
251
+ * - "onramp-exchange" --> Funding with exchange flow
252
+ */
253
+ fundMethod?: WidgetFundMethod
221
254
  walletOptions?: string[]
222
255
  onOriginConfirmation?: (data: {
223
256
  txHash: string
@@ -302,6 +335,10 @@ export type TrailsWidgetProps = {
302
335
  * Default: false.
303
336
  */
304
337
  hideUnlistedFundMethods?: boolean
338
+ /** ISO 4217 currency code to pre-select in onramp mode (e.g. "EUR", "GBP") */
339
+ fiatCurrency?: string
340
+ /** Fiat amount to pre-populate in onramp mode */
341
+ fiatAmount?: string
305
342
  }
306
343
  toast?: boolean
307
344
  appName?: string
@@ -619,6 +656,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
619
656
  decoupleWagmi,
620
657
  hideDisconnect,
621
658
  defaultInputMode,
659
+ fundMethod: configuredFundMethod,
622
660
  } = useWidgetProps()
623
661
  const { address, chainId, connector } = useAccount()
624
662
  const connectors = useConnectors()
@@ -688,11 +726,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
688
726
  },
689
727
  })
690
728
 
691
- // Handle fromAccount preselection (one-time only)
692
- // We use a ref to ensure this only runs once on mount.
693
- // Without this guard, the effect re-fires on every render cycle
694
- // and overrides the user's wallet selection (e.g. switching back
695
- // from MetaMask to Privy embedded wallet).
729
+ // Handle fromAccount preselection
696
730
  const hasInitializedFromAccount = useRef(false)
697
731
  useEffect(() => {
698
732
  if (hasInitializedFromAccount.current) return
@@ -737,11 +771,31 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
737
771
  isModalOpen,
738
772
  )
739
773
  }, [isModalOpen])
740
- const { mode: currentMode, configuredMode, resetMode } = useMode()
741
- const { currentScreen, setCurrentScreen } = useCurrentScreen()
742
- const { previousScreen } = usePreviousScreen()
743
- const { goBack, clearHistory, setCurrentScreenWithBack, getPreviousScreen } =
744
- useBack()
774
+ const {
775
+ mode: currentMode,
776
+ configuredMode,
777
+ resetMode,
778
+ screen,
779
+ navigate,
780
+ goBack,
781
+ goHome,
782
+ canGoBack,
783
+ backScreen,
784
+ state,
785
+ } = useViewManager()
786
+ const { setSellAmount, setBuyAmount } = useSwapState()
787
+
788
+ // Abort in-flight requests when navigating away from transfer screens.
789
+ useNavigationEffect(({ from, to }) => {
790
+ if (shouldClearTransferFlowOnNavigate(from, to)) {
791
+ abortControllerRegistry.abortAll()
792
+ setPrepareSendQuote(null)
793
+ }
794
+
795
+ if (to === "fund-methods") {
796
+ fundMethodSelectionStartRef.current = selectedFundMethod
797
+ }
798
+ })
745
799
  const {
746
800
  connectionContext,
747
801
  clearConnectionContext,
@@ -753,12 +807,34 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
753
807
  // Wrapper function that clears errors when going back
754
808
  const handleBack = () => {
755
809
  setError(null)
756
- goBack?.()
810
+ if (canGoBack) goBack()
811
+ else goHome()
757
812
  }
758
813
  const [previousAddress, setPreviousAddress] = useState<string | undefined>(
759
814
  address,
760
815
  )
761
816
  const { selectedFundMethod, setSelectedFundMethod } = useSelectedFundMethod()
817
+ const fundMethodSelectionStartRef = useRef(selectedFundMethod)
818
+ const clearStateForFundMethodSwitch = useCallback(
819
+ (nextMethod: typeof selectedFundMethod) => {
820
+ const previousMethod = fundMethodSelectionStartRef.current
821
+ const didSwitchMethods = clearFundStateForMethodSwitch(
822
+ previousMethod,
823
+ nextMethod,
824
+ {
825
+ clearSelectedToken,
826
+ setSellAmount,
827
+ setBuyAmount,
828
+ setPrepareSendQuote,
829
+ },
830
+ )
831
+
832
+ if (didSwitchMethods) {
833
+ fundMethodSelectionStartRef.current = nextMethod
834
+ }
835
+ },
836
+ [clearSelectedToken, setSellAmount, setBuyAmount],
837
+ )
762
838
  const { selectedPool, setSelectedPool } = useEarnPool()
763
839
  const [selectedWalletId, setSelectedWalletId] = useState<string | null>(
764
840
  () => {
@@ -788,9 +864,6 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
788
864
  useState<PrepareSendQuote | null>(null)
789
865
  const [onrampProviderQuote, setOnrampProviderQuote] =
790
866
  useState<OnrampQuote | null>(null)
791
- const [prepareSendFn, setPrepareSendFn] = useState<
792
- ((options?: SendOptions) => Promise<SwapReturn | null>) | null
793
- >(null)
794
867
 
795
868
  // Store widget creation parameters for retry functionality
796
869
  const [widgetCreationParams, setWidgetCreationParams] = useState<{
@@ -831,73 +904,94 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
831
904
  >(null)
832
905
  const { connectAsync } = useConnect()
833
906
 
834
- const getInitialScreenForMode = useCallback((mode: Mode) => {
835
- if (mode === "swap") {
836
- return "swap"
837
- } else if (mode === "earn") {
838
- return "earn"
839
- } else if (mode === "fund") {
840
- return "fund-methods"
841
- } else if (mode === "withdraw") {
842
- return "withdraw"
843
- } else if (mode === "pay") {
844
- return "send-form"
845
- } else {
846
- return "send-form"
847
- }
848
- }, [])
849
-
850
- const prevConfiguredModeRef = useRef(configuredMode)
907
+ const hasAppliedFundMethodNavigation = useRef<string | null>(null)
851
908
  useEffect(() => {
852
- if (!isConnected) return
853
- if (configuredMode === prevConfiguredModeRef.current) return
854
- prevConfiguredModeRef.current = configuredMode
855
- setCurrentScreen("home")
856
- }, [configuredMode, isConnected, setCurrentScreen])
857
-
858
- const goHome = useCallback(() => {
859
- const initialScreen = getInitialScreenForMode(currentMode)
860
- setCurrentScreen(initialScreen)
861
- }, [currentMode, setCurrentScreen, getInitialScreenForMode])
909
+ const isWidgetActive = renderInline || isModalOpen
910
+ if (!isWidgetActive) {
911
+ hasAppliedFundMethodNavigation.current = null
912
+ }
913
+ }, [renderInline, isModalOpen])
862
914
 
863
915
  useEffect(() => {
864
- if (currentScreen === "home") {
865
- goHome()
916
+ const isWidgetActive = renderInline || isModalOpen
917
+ if (!isWidgetActive) return
918
+
919
+ let mappedFundMethod: FundMethod | null = null
920
+ if (configuredFundMethod === WidgetFundMethod.Wallet) {
921
+ mappedFundMethod = "wallet"
922
+ } else if (configuredFundMethod === WidgetFundMethod.CryptoTransfer) {
923
+ mappedFundMethod = "direct-transfer"
924
+ } else if (
925
+ configuredFundMethod === WidgetFundMethod.Onramp &&
926
+ currentMode !== "pay" &&
927
+ currentMode !== "earn"
928
+ ) {
929
+ mappedFundMethod = "onramp-meld"
930
+ } else if (
931
+ configuredFundMethod === WidgetFundMethod.OnrampExchange &&
932
+ currentMode !== "pay"
933
+ ) {
934
+ mappedFundMethod = "onramp-mesh"
866
935
  }
867
- }, [currentScreen, goHome])
868
936
 
869
- // Use the simple initial redirect hook
870
- const { hasConnectedBefore } = useInitialRedirect(
871
- isConnected,
937
+ if (!mappedFundMethod) return
938
+ if (selectedFundMethod === mappedFundMethod) return
939
+ setSelectedFundMethod(mappedFundMethod)
940
+ }, [
941
+ configuredFundMethod,
942
+ selectedFundMethod,
943
+ setSelectedFundMethod,
944
+ renderInline,
945
+ isModalOpen,
872
946
  currentMode,
873
- getInitialScreenForMode,
874
- )
947
+ ])
875
948
 
876
- // Keep screen and wallet-connection state in sync without bouncing between screens
877
949
  useEffect(() => {
878
- const connectionExemptScreens = [
879
- "connect",
880
- "wallet-connect",
881
- "wallet-connection-pending",
882
- "wallet-list",
883
- ]
884
-
885
- if (!isConnected && !connectionExemptScreens.includes(currentScreen)) {
886
- setCurrentScreen("connect")
950
+ const isWidgetActive = renderInline || isModalOpen
951
+ if (!isWidgetActive) return
952
+ if (!isConnected) return
953
+ if (currentMode !== "fund") return
954
+
955
+ if (!configuredFundMethod) {
887
956
  return
888
957
  }
889
958
 
890
- if (isConnected && connectionExemptScreens.includes(currentScreen)) {
891
- setCurrentScreen(getInitialScreenForMode(currentMode))
959
+ const navKey = `${configuredFundMethod}:${currentMode}:${isWidgetActive}`
960
+ if (hasAppliedFundMethodNavigation.current === navKey) return
961
+ hasAppliedFundMethodNavigation.current = navKey
962
+
963
+ if (configuredFundMethod === WidgetFundMethod.Onramp) {
964
+ setSelectedFundMethod("onramp-meld")
965
+ navigate("fund-form")
966
+ } else if (configuredFundMethod === WidgetFundMethod.Wallet) {
967
+ setSelectedFundMethod("wallet")
968
+ navigate("tokens")
969
+ } else if (configuredFundMethod === WidgetFundMethod.CryptoTransfer) {
970
+ setSelectedFundMethod("direct-transfer")
971
+ navigate("fund-form")
972
+ } else if (configuredFundMethod === WidgetFundMethod.OnrampExchange) {
973
+ setSelectedFundMethod("onramp-mesh")
974
+ navigate("fund-form")
892
975
  }
893
976
  }, [
894
- isConnected,
895
- currentScreen,
896
977
  currentMode,
897
- setCurrentScreen,
898
- getInitialScreenForMode,
978
+ isConnected,
979
+ configuredFundMethod,
980
+ renderInline,
981
+ isModalOpen,
982
+ navigate,
983
+ setSelectedFundMethod,
899
984
  ])
900
985
 
986
+ useEffect(() => {
987
+ if (!isConnected || screen !== "connect") return
988
+ if (currentMode === "fund" && configuredFundMethod) return
989
+ goHome()
990
+ }, [isConnected, screen, currentMode, configuredFundMethod, goHome])
991
+
992
+ // Global wallet connection check — redirect to connect screen when disconnected
993
+ useScreenGuard(shouldRedirectToConnect(isConnected, screen), "connect")
994
+
901
995
  const modeToButtonText: Record<string, string> = {
902
996
  fund: "Fund",
903
997
  swap: "Swap",
@@ -1149,18 +1243,11 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1149
1243
  )
1150
1244
 
1151
1245
  // Auto-navigate from pending → receipt when first transaction is aborted or failed
1152
- useEffect(() => {
1153
- if (currentScreen !== "pending") return
1154
- if (!transactionStates || transactionStates.length === 0) return
1155
-
1156
- const firstTx = transactionStates[0]
1157
- if (firstTx && hasFailedOrAbortedTransaction(firstTx)) {
1158
- logger.console.log(
1159
- `[trails-sdk] First transaction state is ${firstTx.state}, navigating to receipt`,
1160
- )
1161
- setCurrentScreen("receipt")
1162
- }
1163
- }, [transactionStates, currentScreen, setCurrentScreen])
1246
+ const firstTx = transactionStates?.[0]
1247
+ useScreenGuard(
1248
+ screen === "pending" && !!firstTx && hasFailedOrAbortedTransaction(firstTx),
1249
+ "receipt",
1250
+ )
1164
1251
 
1165
1252
  const { checkoutOnHandlers } = useCheckout({
1166
1253
  onCheckoutStart,
@@ -1177,10 +1264,29 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1177
1264
  })
1178
1265
 
1179
1266
  const { clearSelectedFeeOption } = useSelectedFeeOption()
1267
+ useExternalFundingReceiptSync({
1268
+ enabled:
1269
+ isExternalFundingMethod(selectedFundMethod) &&
1270
+ !!prepareSendQuote?.intentId &&
1271
+ (screen === "direct-transfer-screen" ||
1272
+ screen === "onramp-confirmation" ||
1273
+ screen === "pending"),
1274
+ intentId: prepareSendQuote?.intentId ?? undefined,
1275
+ fallbackTransactionStates: prepareSendQuote?.transactionStates,
1276
+ setTransactionStates,
1277
+ onStatusUpdate: checkoutOnHandlers?.triggerCheckoutStatusUpdate,
1278
+ onFundingObserved: () => {
1279
+ if (
1280
+ screen === "direct-transfer-screen" ||
1281
+ screen === "onramp-confirmation"
1282
+ ) {
1283
+ navigate("pending")
1284
+ }
1285
+ },
1286
+ })
1180
1287
 
1181
1288
  // Use the debug screens hook
1182
1289
  const { handleDebugScreenSelect } = useDebugScreens({
1183
- setCurrentScreen,
1184
1290
  setSelectedToken,
1185
1291
  setTransactionStates,
1186
1292
  setPrepareSendQuote,
@@ -1188,30 +1294,20 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1188
1294
  setSelectedWalletId,
1189
1295
  setShowWalletConnectionRetry,
1190
1296
  setError,
1191
- setIsConnecting,
1192
- setOnrampProps,
1193
1297
  isConnected,
1194
1298
  })
1195
1299
 
1196
1300
  // Auto-detect mode changes and switch screens accordingly
1197
- useEffect(() => {
1198
- if (
1199
- selectedPool &&
1200
- (currentScreen === "send-form" || currentScreen === "fund-form")
1201
- ) {
1202
- const targetScreen = currentMode === "fund" ? "fund-form" : "send-form"
1203
- if (currentScreen !== targetScreen) {
1204
- setCurrentScreen(targetScreen)
1205
- }
1206
- }
1207
- }, [currentMode, currentScreen, selectedPool, setCurrentScreen])
1301
+ const poolTargetScreen: Screen =
1302
+ currentMode === "fund" ? "fund-form" : "send-form"
1303
+ useScreenGuard(
1304
+ !!selectedPool &&
1305
+ (screen === "send-form" || screen === "fund-form") &&
1306
+ screen !== poolTargetScreen,
1307
+ poolTargetScreen,
1308
+ )
1208
1309
 
1209
- useEffect(() => {
1210
- trackWidgetScreen({
1211
- screen: currentScreen,
1212
- userAddress: address || undefined,
1213
- })
1214
- }, [currentScreen, address])
1310
+ useScreenTracking(address || undefined)
1215
1311
 
1216
1312
  useEffect(() => {
1217
1313
  const status = hostTransactionState.status
@@ -1225,27 +1321,27 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1225
1321
 
1226
1322
  switch (status) {
1227
1323
  case "awaiting-origin":
1228
- setCurrentScreen("select-origin-token")
1324
+ navigate("select-origin-token")
1229
1325
  break
1230
1326
  case "awaiting-amount":
1231
- setCurrentScreen("select-origin-amount")
1327
+ navigate("select-origin-amount")
1232
1328
  break
1233
1329
  case "confirmation":
1234
- setCurrentScreen("wallet-confirmation")
1330
+ navigate("wallet-confirmation")
1235
1331
  break
1236
1332
  case "pending":
1237
- setCurrentScreen("pending")
1333
+ navigate("pending")
1238
1334
  break
1239
1335
  case "success":
1240
1336
  case "error":
1241
1337
  if (hostTransactionStates.length > 0) {
1242
- setCurrentScreen("receipt")
1338
+ navigate("receipt")
1243
1339
  } else {
1244
- setCurrentScreen("pending")
1340
+ navigate("pending")
1245
1341
  }
1246
1342
  break
1247
1343
  default:
1248
- setCurrentScreen("pending")
1344
+ navigate("pending")
1249
1345
  break
1250
1346
  }
1251
1347
  }, [
@@ -1253,7 +1349,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1253
1349
  hostTransactionStates.length,
1254
1350
  isModalOpen,
1255
1351
  openTrailsModal,
1256
- setCurrentScreen,
1352
+ navigate,
1257
1353
  ])
1258
1354
 
1259
1355
  useEffect(() => {
@@ -1305,14 +1401,14 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1305
1401
  "earn",
1306
1402
  ]
1307
1403
 
1308
- if (screensToReset.includes(currentScreen)) {
1404
+ if (screensToReset.includes(screen)) {
1309
1405
  logger.console.log(
1310
1406
  "[trails-sdk] Resetting to tokens screen due to account change",
1311
- currentScreen,
1407
+ screen,
1312
1408
  )
1313
- if (previousScreen !== "select-funding-wallet") {
1314
- const initialScreen = getInitialScreenForMode(currentMode)
1315
- setCurrentScreen(initialScreen)
1409
+ if (!state.stack.some((e) => e.screen === "select-funding-wallet")) {
1410
+ const initialScreen = getInitialScreen(currentMode)
1411
+ navigate(initialScreen)
1316
1412
  clearSelectedToken()
1317
1413
  }
1318
1414
  setError(null)
@@ -1326,12 +1422,11 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1326
1422
  }, [
1327
1423
  address,
1328
1424
  previousAddress,
1329
- currentScreen,
1425
+ screen,
1330
1426
  currentMode,
1331
1427
  clearSelectedToken,
1332
- setCurrentScreen,
1333
- getInitialScreenForMode,
1334
- previousScreen,
1428
+ navigate,
1429
+ state.stack,
1335
1430
  ])
1336
1431
 
1337
1432
  // Update generated calldata when amount changes in earn mode
@@ -1466,9 +1561,9 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1466
1561
  // Navigate back to the origin screen (recipients)
1467
1562
  const originScreen = connectionContext.originScreen
1468
1563
  if (originScreen) {
1469
- setCurrentScreen(originScreen)
1564
+ navigate(originScreen)
1470
1565
  } else {
1471
- setCurrentScreen("recipients")
1566
+ navigate("recipients")
1472
1567
  }
1473
1568
  } else if (isFundMethodSelection) {
1474
1569
  logger.console.log(
@@ -1478,9 +1573,9 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1478
1573
  clearConnectionContext()
1479
1574
 
1480
1575
  if (currentMode === "fund") {
1481
- setCurrentScreen("tokens")
1576
+ navigate("tokens", { backTarget: "fund-methods" })
1482
1577
  } else {
1483
- setCurrentScreen("home")
1578
+ goHome()
1484
1579
  }
1485
1580
  } else {
1486
1581
  // Normal connection - switch active wallet and navigate home
@@ -1491,20 +1586,8 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1491
1586
  // Clear the connection context
1492
1587
  clearConnectionContext()
1493
1588
 
1494
- // Check if this is the first wallet connection
1495
- if (!hasConnectedBefore) {
1496
- // First connection - go to initial screen for the mode
1497
- logger.console.log(
1498
- "[trails-sdk] First wallet connection, going home",
1499
- )
1500
- goHome()
1501
- } else {
1502
- // Subsequent connection - go to home which will route to the appropriate screen
1503
- logger.console.log(
1504
- "[trails-sdk] Subsequent wallet connection, going to home",
1505
- )
1506
- goHome()
1507
- }
1589
+ logger.console.log("[trails-sdk] Wallet connected, going home")
1590
+ goHome()
1508
1591
  }
1509
1592
  } else if (walletConnector === getWalletConnectConnector()) {
1510
1593
  // Store the current connector as previous before switching to WalletConnect
@@ -1512,7 +1595,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1512
1595
  setPreviousConnector(connector)
1513
1596
  }
1514
1597
  // Route to dedicated WalletConnect screen where we show our own QR
1515
- setCurrentScreen("wallet-connect")
1598
+ navigate("wallet-connect")
1516
1599
  setIsConnecting(false)
1517
1600
  return
1518
1601
  }
@@ -1560,7 +1643,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1560
1643
  logger.console.error("[trails-sdk] Failed to disconnect:", error)
1561
1644
  }
1562
1645
 
1563
- setCurrentScreen("connect")
1646
+ navigate("connect")
1564
1647
  }
1565
1648
 
1566
1649
  const handleContinue = async () => {
@@ -1600,9 +1683,9 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1600
1683
  // Navigate back to the origin screen (recipients)
1601
1684
  const originScreen = connectionContext.originScreen
1602
1685
  if (originScreen) {
1603
- setCurrentScreen(originScreen)
1686
+ navigate(originScreen)
1604
1687
  } else {
1605
- setCurrentScreen("recipients")
1688
+ navigate("recipients")
1606
1689
  }
1607
1690
  } else if (isFundMethodSelection) {
1608
1691
  logger.console.log(
@@ -1612,22 +1695,22 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1612
1695
  clearConnectionContext()
1613
1696
 
1614
1697
  if (currentMode === "fund") {
1615
- setCurrentScreen("tokens")
1698
+ navigate("tokens", { backTarget: "fund-methods" })
1616
1699
  } else {
1617
- setCurrentScreen("home")
1700
+ goHome()
1618
1701
  }
1619
1702
  } else {
1620
1703
  // Normal flow - navigate based on mode
1621
1704
  if (currentMode === "swap") {
1622
- setCurrentScreen("swap")
1705
+ navigate("swap")
1623
1706
  } else if (currentMode === "earn") {
1624
- setCurrentScreen("earn")
1707
+ navigate("earn")
1625
1708
  } else if (currentMode === "fund") {
1626
- setCurrentScreen("fund-methods")
1709
+ navigate("fund-methods")
1627
1710
  } else if (currentMode === "pay") {
1628
- setCurrentScreen("send-form")
1711
+ navigate("send-form")
1629
1712
  } else {
1630
- setCurrentScreen("account-settings")
1713
+ navigate("account-settings")
1631
1714
  }
1632
1715
  }
1633
1716
  }
@@ -1671,18 +1754,18 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1671
1754
  if (currentMode === "earn") {
1672
1755
  if (toAddress && toChainId) {
1673
1756
  // Skip earn-pools and go directly to send-form when toAddress and toChainId are specified
1674
- setCurrentScreen("send-form")
1757
+ navigate("send-form")
1675
1758
  } else if (selectedPool) {
1676
1759
  // If a pool is already selected (auto-selected or manually), go to send-form
1677
- setCurrentScreen("send-form")
1760
+ navigate("send-form")
1678
1761
  } else {
1679
1762
  // Go to earn-pools for pool selection when no specific destination is set
1680
- setCurrentScreen("earn-pools")
1763
+ navigate("earn-pools")
1681
1764
  }
1682
1765
  } else if (currentMode === "swap") {
1683
- setCurrentScreen("swap")
1766
+ navigate("swap")
1684
1767
  } else {
1685
- setCurrentScreen(currentMode === "fund" ? "fund-form" : "send-form")
1768
+ navigate(currentMode === "fund" ? "fund-form" : "send-form")
1686
1769
  }
1687
1770
 
1688
1771
  handleTrackToken(token)
@@ -1772,16 +1855,11 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1772
1855
 
1773
1856
  const handleSendAnother = () => {
1774
1857
  invalidateTokenBalancesCache()
1775
- const initialScreen = getInitialScreenForMode(currentMode)
1776
- setCurrentScreen(initialScreen)
1777
1858
  resetState()
1778
1859
  }
1779
1860
 
1780
1861
  const resetState = useCallback(() => {
1781
- resetMode()
1782
1862
  setSelectedFundMethod("wallet")
1783
- // Reset to appropriate screen based on mode
1784
- setCurrentScreen("home")
1785
1863
  setSelectedPool(null)
1786
1864
  setSelectedWalletId(null)
1787
1865
  setIsConnecting(false)
@@ -1794,13 +1872,12 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1794
1872
  setTransactionStates([])
1795
1873
  setPrepareSendQuote(null)
1796
1874
  setTotalCompletionSeconds(null)
1797
- clearHistory()
1798
1875
  // Clear selected fee option, but localStorage preference is preserved
1799
1876
  // Auto-selection will use the preference when fee options render next time
1800
1877
  clearSelectedFeeOption()
1801
1878
  resetHostTransactionState()
1879
+ goHome()
1802
1880
  }, [
1803
- resetMode,
1804
1881
  setSelectedFundMethod,
1805
1882
  setDestinationTxHash,
1806
1883
  setDestinationChainId,
@@ -1808,19 +1885,11 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1808
1885
  setOriginTxHash,
1809
1886
  setOriginChainId,
1810
1887
  setSelectedPool,
1811
- setCurrentScreen,
1812
- clearHistory,
1888
+ goHome,
1813
1889
  clearSelectedFeeOption,
1814
1890
  resetHostTransactionState,
1815
1891
  ])
1816
1892
 
1817
- // Update the currentScreen state when the widget receives an updated 'mode' prop passed in by the consuming app.
1818
- // This allows the widget to change modes without remounting the component.
1819
- useEffect(() => {
1820
- const initialScreen = getInitialScreenForMode(configuredMode)
1821
- setCurrentScreen(initialScreen)
1822
- }, [configuredMode, getInitialScreenForMode, setCurrentScreen])
1823
-
1824
1893
  // When pendingSelection exists (from useTrailsSendTransaction),
1825
1894
  // open the modal and navigate to select-origin-token screen
1826
1895
  useEffect(() => {
@@ -1830,60 +1899,81 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1830
1899
  openTrailsModal()
1831
1900
  }
1832
1901
  // Navigate to select-origin-token screen
1833
- if (currentScreen !== "select-origin-token") {
1834
- setCurrentScreen("select-origin-token")
1902
+ if (screen !== "select-origin-token") {
1903
+ navigate("select-origin-token")
1835
1904
  }
1836
1905
  }
1837
- }, [
1838
- pendingSelection,
1839
- isModalOpen,
1840
- currentScreen,
1841
- setCurrentScreen,
1842
- openTrailsModal,
1843
- ])
1906
+ }, [pendingSelection, isModalOpen, screen, navigate, openTrailsModal])
1907
+
1908
+ const onOpenRef = useRef(onOpen)
1909
+ onOpenRef.current = onOpen
1910
+ const onCloseRef = useRef(onClose)
1911
+ onCloseRef.current = onClose
1912
+ const pendingSelectionRef = useRef(pendingSelection)
1913
+ pendingSelectionRef.current = pendingSelection
1914
+ const prepareSendQuoteRef = useRef(prepareSendQuote)
1915
+ prepareSendQuoteRef.current = prepareSendQuote
1916
+ const screenRef = useRef(screen)
1917
+ screenRef.current = screen
1918
+
1919
+ const initialActiveConnectorRef = useRef<any>(null)
1920
+ const initialActiveAddressRef = useRef<string | null>(null)
1844
1921
 
1845
1922
  const handleOpenModal = useCallback(() => {
1923
+ if (!decoupleWagmi) {
1924
+ initialActiveConnectorRef.current = connector ?? null
1925
+ initialActiveAddressRef.current = address ?? null
1926
+ }
1846
1927
  openTrailsModal()
1847
- onOpen?.()
1848
- }, [openTrailsModal, onOpen])
1849
-
1850
- const screensWithCommittedIntent: Screen[] = [
1851
- "wallet-confirmation",
1852
- "onramp-confirmation",
1853
- ]
1928
+ onOpenRef.current?.()
1929
+ }, [openTrailsModal, decoupleWagmi, connector, address])
1854
1930
 
1855
1931
  const handleCloseModal = useCallback(() => {
1856
- if (
1857
- prepareSendQuote?.intentId &&
1858
- screensWithCommittedIntent.includes(currentScreen)
1859
- ) {
1860
- removeStoredIntent(prepareSendQuote.intentId)
1861
- }
1862
- if (pendingSelection) {
1932
+ const pending = pendingSelectionRef.current
1933
+ if (pending) {
1863
1934
  logger.console.log(
1864
1935
  "[trails-sdk] handleCloseModal called, rejecting pending selection",
1865
1936
  )
1866
- pendingSelection.reject(new Error("Modal closed by user"))
1937
+ pending.reject(new Error("Modal closed by user"))
1867
1938
  setPendingSelection(undefined)
1868
1939
  } else {
1869
1940
  logger.console.log(
1870
1941
  "[trails-sdk] handleCloseModal called, no pending selection",
1871
1942
  )
1872
1943
  }
1944
+
1945
+ if (!decoupleWagmi && initialActiveConnectorRef.current) {
1946
+ const originalStillConnected = connections.some(
1947
+ (c) => c.connector.id === initialActiveConnectorRef.current?.id,
1948
+ )
1949
+ if (
1950
+ originalStillConnected &&
1951
+ initialActiveConnectorRef.current.id !== connector?.id
1952
+ ) {
1953
+ logger.console.log(
1954
+ "[trails-sdk] Restoring host app active wallet:",
1955
+ initialActiveAddressRef.current,
1956
+ )
1957
+ switchAccount({ connector: initialActiveConnectorRef.current })
1958
+ }
1959
+ }
1960
+ initialActiveConnectorRef.current = null
1961
+ initialActiveAddressRef.current = null
1962
+
1873
1963
  logger.console.log("[trails-sdk] handleCloseModal called, closing modal")
1874
1964
  closeTrailsModal()
1875
1965
  resetState()
1876
1966
  resetHostTransactionState()
1877
- onClose?.()
1967
+ onCloseRef.current?.()
1878
1968
  }, [
1879
1969
  closeTrailsModal,
1880
1970
  resetState,
1881
1971
  resetHostTransactionState,
1882
- onClose,
1883
- pendingSelection,
1884
1972
  setPendingSelection,
1885
- prepareSendQuote,
1886
- currentScreen,
1973
+ decoupleWagmi,
1974
+ connections,
1975
+ connector,
1976
+ switchAccount,
1887
1977
  ])
1888
1978
 
1889
1979
  // Register handleCloseModal with TrailsModalProvider so ScreenHeader can use it
@@ -1906,7 +1996,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1906
1996
  )
1907
1997
 
1908
1998
  function handleWalletConfirmComplete() {
1909
- setCurrentScreen("pending")
1999
+ navigate("pending")
1910
2000
  }
1911
2001
 
1912
2002
  function handleElapsedTime(totalCompletionSeconds: number = 0) {
@@ -1943,11 +2033,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1943
2033
  setDestinationChainId(lastTransactionState?.chainId)
1944
2034
  }
1945
2035
 
1946
- // clear the stored intent from local storage
1947
- if (prepareSendQuote?.intentId) {
1948
- removeStoredIntent(prepareSendQuote.intentId)
1949
- }
1950
- setCurrentScreen("receipt")
2036
+ navigate("receipt")
1951
2037
  }
1952
2038
 
1953
2039
  async function handleOnrampComplete(transferData: OnrampTransferResult) {
@@ -1958,7 +2044,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1958
2044
  logger.console.log("[trails-sdk] handleOnrampComplete state:", {
1959
2045
  hasOnrampContinueSend: !!onrampContinueSend,
1960
2046
  hasPrepareSendQuote: !!prepareSendQuote,
1961
- currentScreen,
2047
+ screen,
1962
2048
  onrampContinueSendInProgress,
1963
2049
  })
1964
2050
 
@@ -1978,7 +2064,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
1978
2064
 
1979
2065
  // Navigate away from onramp screen immediately
1980
2066
  logger.console.log("[trails-sdk] Navigating away from onramp screen")
1981
- setCurrentScreen("pending")
2067
+ navigate("pending")
1982
2068
 
1983
2069
  // Continue with the send flow after onramp completes
1984
2070
  if (onrampContinueSend) {
@@ -2036,7 +2122,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2036
2122
 
2037
2123
  // Navigate to pending screen immediately
2038
2124
  logger.console.log("[trails-sdk] Navigating to pending screen")
2039
- setCurrentScreen("pending")
2125
+ navigate("pending")
2040
2126
 
2041
2127
  // Continue with the send flow after deposit is confirmed
2042
2128
  if (onrampContinueSend) {
@@ -2082,7 +2168,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2082
2168
  setPreviousConnector(connector)
2083
2169
  }
2084
2170
  setSelectedWalletId(null)
2085
- setCurrentScreen("wallet-connect")
2171
+ navigate("wallet-connect")
2086
2172
  }
2087
2173
 
2088
2174
  const handleReconnectPreviousWallet = async () => {
@@ -2108,11 +2194,11 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2108
2194
  error,
2109
2195
  )
2110
2196
  // If reconnection fails, go back to fund methods
2111
- setCurrentScreen("fund-methods")
2197
+ navigate("fund-methods")
2112
2198
  }
2113
2199
  } else {
2114
2200
  // If no previous connector, go back to fund methods
2115
- setCurrentScreen("fund-methods")
2201
+ navigate("fund-methods")
2116
2202
  }
2117
2203
  }
2118
2204
 
@@ -2164,7 +2250,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2164
2250
  onRampMethod && onRampMethod === "onramp-meld"
2165
2251
  ? "onramp-meld"
2166
2252
  : "onramp-mesh"
2167
- setCurrentScreen(targetScreen)
2253
+ navigate(targetScreen)
2168
2254
 
2169
2255
  if (
2170
2256
  targetScreen === "onramp-meld" &&
@@ -2240,10 +2326,6 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2240
2326
  setPrepareSendQuote(quote ?? null)
2241
2327
  // Navigate to QR wallet selection when using QR code funding method
2242
2328
  if (selectedFundMethod === "direct-transfer") {
2243
- if (sendFn) {
2244
- setPrepareSendFn(() => sendFn)
2245
- }
2246
-
2247
2329
  if (!sendFn) {
2248
2330
  handleSendError(
2249
2331
  "Unable to continue: direct-transfer send function is missing.",
@@ -2252,17 +2334,18 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2252
2334
  }
2253
2335
 
2254
2336
  setError(null)
2337
+
2255
2338
  void (async () => {
2256
2339
  try {
2257
- await sendFn({ commitOnly: true, skipIntentMonitoring: true })
2258
- setCurrentScreenWithBack("direct-transfer-screen", currentScreen)
2340
+ await sendFn()
2341
+ navigate("direct-transfer-screen", { backTarget: screen })
2259
2342
  } catch (error) {
2260
2343
  handleSendError(error as Error | string | null)
2261
2344
  }
2262
2345
  })()
2263
2346
  return
2264
2347
  }
2265
- setCurrentScreen("wallet-confirmation")
2348
+ navigate("wallet-confirmation")
2266
2349
  }
2267
2350
 
2268
2351
  const handleWaitingOnrampConfirm = (
@@ -2279,7 +2362,6 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2279
2362
  paymentMethodType?: string
2280
2363
  },
2281
2364
  sessionId?: string,
2282
- sendFn?: ((options?: SendOptions) => Promise<SwapReturn | null>) | null,
2283
2365
  popupWindowRef?: Window | null,
2284
2366
  ) => {
2285
2367
  logger.console.log(
@@ -2288,9 +2370,6 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2288
2370
  )
2289
2371
  setPrepareSendQuote(quote ?? null)
2290
2372
  setOnrampProviderQuote(onrampProviderQuote ?? null)
2291
- if (sendFn) {
2292
- setPrepareSendFn(() => sendFn)
2293
- }
2294
2373
  setOnrampPopupWindowRef(popupWindowRef ?? null)
2295
2374
  if (params) {
2296
2375
  setWidgetCreationParams(params)
@@ -2304,7 +2383,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2304
2383
  )
2305
2384
  setExternalSessionId(null)
2306
2385
  }
2307
- setCurrentScreen("onramp-confirmation")
2386
+ navigate("onramp-confirmation")
2308
2387
  }
2309
2388
 
2310
2389
  async function handleWalletConfirmRetry() {
@@ -2327,7 +2406,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2327
2406
  const { targetAmountUsd, targetAmountUsdFormatted } = useTargetAmount()
2328
2407
 
2329
2408
  const renderScreenContent = () => {
2330
- switch (currentScreen) {
2409
+ switch (screen) {
2331
2410
  case "connect": {
2332
2411
  const showDisconnect = !decoupleWagmi && !hideDisconnect
2333
2412
  return (
@@ -2354,20 +2433,20 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2354
2433
  // Check if we need to show WalletConnect QR
2355
2434
  // Don't show QR if we have an EIP-6963 connector or injected connector
2356
2435
  const shouldShowQR =
2357
- !isEIP6963Connector &&
2358
- walletConnector === getWalletConnectConnector()
2436
+ !isEIP6963Connector && isWalletConnectConnector(walletConnector)
2359
2437
 
2360
2438
  if (shouldShowQR) {
2361
2439
  saveLastClickedWallet(walletId)
2362
- setCurrentScreen("wallet-connect")
2440
+ navigate("wallet-connect")
2363
2441
  } else {
2364
2442
  saveLastClickedWallet(walletId)
2365
2443
  // Preserve the existing back route from the navigation stack
2366
- const backRoute = getPreviousScreen()
2367
- setCurrentScreenWithBack(
2368
- "wallet-connection-pending",
2369
- backRoute || undefined,
2370
- )
2444
+ const backRoute = backScreen
2445
+ const safeBackRoute =
2446
+ backRoute === "wallet-confirmation" ? undefined : backRoute
2447
+ navigate("wallet-connection-pending", {
2448
+ backTarget: safeBackRoute || undefined,
2449
+ })
2371
2450
  // Auto-trigger connection - pass the actual connector
2372
2451
  setTimeout(() => {
2373
2452
  handleConnectWallet(walletId, walletConnector)
@@ -2391,44 +2470,52 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2391
2470
  : "Choose a funding method"
2392
2471
  }
2393
2472
  onBack={() => {
2394
- const previous = getPreviousScreen()
2395
- if (previous) {
2473
+ if (currentMode !== configuredMode) {
2396
2474
  resetMode()
2397
- goBack?.()
2398
- } else {
2399
- goHome()
2475
+ return
2476
+ }
2477
+
2478
+ if (canGoBack) {
2479
+ goBack()
2480
+ return
2400
2481
  }
2482
+
2483
+ goHome()
2401
2484
  }}
2402
2485
  onramp={onrampFactory}
2403
2486
  onSelectWalletConnect={handleSelectWalletConnect}
2404
2487
  onSelectConnectedAccount={() => {
2488
+ clearStateForFundMethodSwitch("wallet")
2405
2489
  setSelectedFundMethod("wallet")
2406
2490
  if (currentMode === "fund") {
2407
- setCurrentScreen("select-origin-token")
2491
+ navigate("select-origin-token", { backTarget: "fund-methods" })
2408
2492
  } else {
2409
- setCurrentScreen("home")
2493
+ goHome()
2410
2494
  }
2411
2495
  }}
2412
2496
  onSelectTransferCrypto={() => {
2497
+ clearStateForFundMethodSwitch("direct-transfer")
2413
2498
  setSelectedFundMethod("direct-transfer")
2414
2499
  if (currentMode === "fund") {
2415
- setCurrentScreenWithBack("fund-form", "fund-methods")
2500
+ navigate("fund-form", { backTarget: "fund-methods" })
2416
2501
  } else {
2417
- setCurrentScreen("home")
2502
+ goHome()
2418
2503
  }
2419
2504
  }}
2420
2505
  onSelectOnrampMeld={() => {
2506
+ clearStateForFundMethodSwitch("onramp-meld")
2421
2507
  if (currentMode === "fund") {
2422
- setCurrentScreen("fund-form")
2508
+ navigate("fund-form")
2423
2509
  } else {
2424
- setCurrentScreen("home")
2510
+ goHome()
2425
2511
  }
2426
2512
  }}
2427
2513
  onSelectOnrampExchange={() => {
2514
+ clearStateForFundMethodSwitch("onramp-mesh")
2428
2515
  if (currentMode === "fund") {
2429
- setCurrentScreen("fund-form")
2516
+ navigate("fund-form")
2430
2517
  } else {
2431
- setCurrentScreen("home")
2518
+ goHome()
2432
2519
  }
2433
2520
  }}
2434
2521
  />
@@ -2439,33 +2526,31 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2439
2526
  onBack={() => goHome()}
2440
2527
  onSelectWalletConnect={handleSelectWalletConnect}
2441
2528
  onSelectConnectedAccount={() => {
2529
+ clearStateForFundMethodSwitch("wallet")
2442
2530
  setSelectedFundMethod("wallet")
2443
- setCurrentScreen("home")
2531
+ goHome()
2444
2532
  }}
2445
2533
  onSelectQrCode={() => {
2446
- setCurrentScreenWithBack("qrcode-options", "payment-methods")
2534
+ navigate("qrcode-options", { backTarget: "payment-methods" })
2447
2535
  }}
2448
2536
  />
2449
2537
  )
2450
2538
  case "select-funding-wallet":
2451
2539
  return <FundWalletSelection />
2452
2540
  case "onramp-payment-methods":
2453
- return (
2454
- <OnrampPaymentMethods
2455
- onBack={() => setCurrentScreen("fund-methods")}
2456
- />
2457
- )
2541
+ return <OnrampPaymentMethods onBack={() => navigate("fund-methods")} />
2458
2542
  case "qrcode-options":
2459
2543
  return (
2460
2544
  <QRCodeOptions
2461
2545
  onBack={handleBack}
2462
2546
  onSelectWalletConnect={handleSelectWalletConnect}
2463
2547
  onSelectQRCode={() => {
2548
+ clearStateForFundMethodSwitch("direct-transfer")
2464
2549
  setSelectedFundMethod("direct-transfer")
2465
2550
  if (currentMode === "fund") {
2466
- setCurrentScreen("fund-form")
2551
+ navigate("fund-form")
2467
2552
  } else {
2468
- setCurrentScreen("home")
2553
+ goHome()
2469
2554
  }
2470
2555
  }}
2471
2556
  />
@@ -2482,14 +2567,14 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2482
2567
  onRecentTokenSelect={handleRecentTokenSelect}
2483
2568
  fundMethod={selectedFundMethod}
2484
2569
  renderInline={renderInline}
2485
- onNavigateToFundMethods={() => setCurrentScreen("fund-methods")}
2570
+ onNavigateToFundMethods={() => navigate("fund-methods")}
2486
2571
  />
2487
2572
  )
2488
2573
  case "select-origin-token":
2489
2574
  return (
2490
2575
  <TokenList
2491
2576
  onContinue={handleTokenSelect}
2492
- onBack={handleCloseModal}
2577
+ onBack={handleBack}
2493
2578
  targetAmountUsd={null}
2494
2579
  targetAmountUsdFormatted={null}
2495
2580
  onError={handleTokenListError}
@@ -2503,7 +2588,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2503
2588
  return (
2504
2589
  <OriginSelectionAmount
2505
2590
  token={selectedToken}
2506
- onBack={() => setCurrentScreen("select-origin-token")}
2591
+ onBack={() => navigate("select-origin-token")}
2507
2592
  onCancel={handleCloseModal}
2508
2593
  onSubmit={handleOriginAmountSubmit}
2509
2594
  />
@@ -2515,7 +2600,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2515
2600
  selectedToken={selectedToken}
2516
2601
  onSend={handleOnSend}
2517
2602
  onWaitingForWalletConfirm={handleWaitingForWalletConfirm}
2518
- onConfirm={() => setCurrentScreen("pending")}
2603
+ onConfirm={() => navigate("pending")}
2519
2604
  onComplete={handleTransferComplete}
2520
2605
  account={walletClient?.account}
2521
2606
  toRecipient={
@@ -2587,9 +2672,9 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2587
2672
  onSend={handleOnSend}
2588
2673
  onWaitingForWalletConfirm={handleWaitingForWalletConfirm}
2589
2674
  onrampPropsCallback={setOnrampProps}
2590
- screenNavigationCallback={setCurrentScreen}
2675
+ screenNavigationCallback={navigate}
2591
2676
  onWaitingForOnrampConfirm={handleWaitingOnrampConfirm}
2592
- onConfirm={() => setCurrentScreen("pending")}
2677
+ onConfirm={() => navigate("pending")}
2593
2678
  onComplete={handleTransferComplete}
2594
2679
  account={walletClient?.account}
2595
2680
  toAmount={toAmount || undefined}
@@ -2622,18 +2707,17 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2622
2707
  onBack={handleBack}
2623
2708
  onRetry={() => {
2624
2709
  // Go back to fund screen to regenerate widget session
2625
- setCurrentScreen("fund-form")
2710
+ navigate("fund-form")
2626
2711
  }}
2627
2712
  onIntentExecuted={() => {
2628
2713
  // Navigate to pending screen when intent is executed
2629
- setCurrentScreen("pending")
2714
+ navigate("pending")
2630
2715
  }}
2631
2716
  quote={prepareSendQuote}
2632
2717
  onRampQuote={onrampProviderQuote}
2633
2718
  widgetCreationParams={widgetCreationParams}
2634
2719
  onCreateWidgetSession={createWidgetSession}
2635
2720
  externalSessionId={externalSessionId}
2636
- sendFn={prepareSendFn}
2637
2721
  popupWindowRef={onrampPopupWindowRef}
2638
2722
  />
2639
2723
  )
@@ -2675,7 +2759,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2675
2759
  onBack={handleBack}
2676
2760
  onSelect={(walletId) => {
2677
2761
  setQrCodeWalletId(walletId)
2678
- setCurrentScreen("direct-transfer-screen")
2762
+ navigate("direct-transfer-screen")
2679
2763
  }}
2680
2764
  selectedWalletId={qrCodeWalletId}
2681
2765
  />
@@ -2685,27 +2769,18 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2685
2769
  <DirectTransfer
2686
2770
  onBack={() => {
2687
2771
  if (currentMode === "fund") {
2688
- setCurrentScreen("fund-form")
2772
+ navigate("fund-form")
2689
2773
  } else {
2690
- setCurrentScreen("home")
2774
+ goHome()
2691
2775
  }
2692
2776
  }}
2693
- onChangeWallet={() => setCurrentScreen("qr-code-wallet-select")}
2777
+ onChangeWallet={() => navigate("qr-code-wallet-select")}
2694
2778
  quote={prepareSendQuote}
2695
2779
  selectedWalletId={qrCodeWalletId}
2696
2780
  walletOptions={qrCodeWallets}
2697
- sendFn={prepareSendFn}
2698
- onIntentExecuted={() => setCurrentScreen("pending")}
2781
+ onIntentExecuted={() => navigate("pending")}
2699
2782
  />
2700
2783
  )
2701
- // case "onramp-meld":
2702
- // return (
2703
- // <OnrampDeposit
2704
- // onBack={handleBack}
2705
- // quote={prepareSendQuote}
2706
- // onDepositComplete={handleOnrampDepositComplete}
2707
- // />
2708
- // )
2709
2784
  case "pending":
2710
2785
  return (
2711
2786
  <TransferPending
@@ -2716,7 +2791,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2716
2791
  timestamp={hostTransactionTimestamp ?? undefined}
2717
2792
  onContinue={() => {
2718
2793
  logger.console.log("[trails-sdk] onContinue called")
2719
- setCurrentScreen("receipt")
2794
+ navigate("receipt")
2720
2795
  }}
2721
2796
  />
2722
2797
  )
@@ -2789,7 +2864,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2789
2864
  <div className="flex flex-col h-full">
2790
2865
  {onrampFactory({
2791
2866
  setCurrentScreen: (screen) => {
2792
- setCurrentScreen(screen)
2867
+ navigate(screen)
2793
2868
  },
2794
2869
  onComplete: handleOnrampComplete,
2795
2870
  onError: (error) => {
@@ -2835,7 +2910,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2835
2910
  </div>
2836
2911
  )
2837
2912
  }
2838
- case "meld-onramp":
2913
+ case "onramp-meld":
2839
2914
  return <MeldStepsFlow onBack={handleBack} />
2840
2915
  case "wallet-connect":
2841
2916
  return (
@@ -2857,14 +2932,17 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2857
2932
  )
2858
2933
 
2859
2934
  if (
2860
- allWallets.find((w) => w.id === walletId)?.connector ===
2861
- getWalletConnectConnector()
2935
+ isWalletConnectConnector(
2936
+ allWallets.find((w) => w.id === walletId)?.connector as
2937
+ | Connector
2938
+ | undefined,
2939
+ )
2862
2940
  ) {
2863
2941
  saveLastClickedWallet(walletId)
2864
2942
  logger.console.log(
2865
2943
  "[trails-sdk] WalletConnect selected, going to wallet-connect screen",
2866
2944
  )
2867
- setCurrentScreen("wallet-connect")
2945
+ navigate("wallet-connect")
2868
2946
  } else {
2869
2947
  saveLastClickedWallet(walletId)
2870
2948
  logger.console.log(
@@ -2873,11 +2951,12 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2873
2951
  // Don't reset the redirect flag - user has already been through initial flow
2874
2952
  setTimeout(() => {
2875
2953
  // Preserve the existing back route from the navigation stack
2876
- const backRoute = getPreviousScreen()
2877
- setCurrentScreenWithBack(
2878
- "wallet-connection-pending",
2879
- backRoute || undefined,
2880
- )
2954
+ const backRoute = backScreen
2955
+ const safeBackRoute =
2956
+ backRoute === "wallet-confirmation" ? undefined : backRoute
2957
+ navigate("wallet-connection-pending", {
2958
+ backTarget: safeBackRoute || undefined,
2959
+ })
2881
2960
  }, 100)
2882
2961
  // Auto-trigger connection
2883
2962
  setTimeout(() => {
@@ -2896,13 +2975,13 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2896
2975
  case "earn":
2897
2976
  return (
2898
2977
  <Earn
2899
- onContinue={() => setCurrentScreen("send-form")}
2978
+ onContinue={() => navigate("send-form")}
2900
2979
  account={walletClient?.account}
2901
2980
  walletClient={walletClient ?? undefined}
2902
2981
  onTransactionStateChange={handleTransactionStateChange}
2903
2982
  onError={handleSendError}
2904
2983
  onWaitingForWalletConfirm={handleWaitingForWalletConfirm}
2905
- onConfirm={() => setCurrentScreen("pending")}
2984
+ onConfirm={() => navigate("pending")}
2906
2985
  onComplete={handleTransferComplete}
2907
2986
  onSend={handleOnSend}
2908
2987
  paymasterUrls={paymasterUrls}
@@ -2925,7 +3004,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2925
3004
  onPoolSelect={(pool) => {
2926
3005
  logger.console.log("Selected pool:", pool)
2927
3006
  setSelectedPool(pool)
2928
- setCurrentScreen("send-form")
3007
+ navigate("send-form")
2929
3008
  }}
2930
3009
  />
2931
3010
  )
@@ -2935,7 +3014,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2935
3014
  onSend={handleOnSend}
2936
3015
  onWaitingForWalletConfirm={handleWaitingForWalletConfirm}
2937
3016
  onWaitingForOnrampConfirm={handleWaitingOnrampConfirm}
2938
- onConfirm={() => setCurrentScreen("pending")}
3017
+ onConfirm={() => navigate("pending")}
2939
3018
  onComplete={handleTransferComplete}
2940
3019
  selectedToken={selectedToken}
2941
3020
  account={walletClient?.account}
@@ -2967,7 +3046,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
2967
3046
  <Withdraw
2968
3047
  onSend={handleOnSend}
2969
3048
  onWaitingForWalletConfirm={handleWaitingForWalletConfirm}
2970
- onConfirm={() => setCurrentScreen("pending")}
3049
+ onConfirm={() => navigate("pending")}
2971
3050
  onComplete={handleTransferComplete}
2972
3051
  selectedToken={selectedToken}
2973
3052
  account={walletClient?.account}
@@ -3027,7 +3106,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
3027
3106
  case "user-preferences":
3028
3107
  return <UserPreferences onBack={handleBack} />
3029
3108
  case "chain-list":
3030
- return <ChainList onBack={() => setCurrentScreen("tokens")} />
3109
+ return <ChainList onBack={() => navigate("tokens")} />
3031
3110
  case "recipients":
3032
3111
  return <Recipients onBack={handleBack} />
3033
3112
  case "disconnect":
@@ -3037,13 +3116,10 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
3037
3116
  disconnectHandler={handleWalletDisconnect}
3038
3117
  />
3039
3118
  )
3040
- case "home": {
3041
- // Navigation handled by useEffect above, otherwise it would cause a loop
3042
- return null
3043
- }
3044
- default:
3045
- return null
3046
3119
  }
3120
+
3121
+ const unhandledScreen: never = screen
3122
+ throw new Error(`[trails-sdk] Unhandled screen state: ${unhandledScreen}`)
3047
3123
  }
3048
3124
 
3049
3125
  const renderScreen = () => {
@@ -3072,7 +3148,7 @@ const WidgetContent = forwardRef<TrailsWidgetRef>((_, ref) => {
3072
3148
  >
3073
3149
  <AnimatePresence mode="wait">
3074
3150
  <motion.div
3075
- key={currentScreen}
3151
+ key={screen}
3076
3152
  initial={{ opacity: 0, x: 20 }}
3077
3153
  animate={{ opacity: 1, x: 0 }}
3078
3154
  exit={{ opacity: 0, x: -20 }}
@@ -3164,11 +3240,24 @@ export const TrailsWidget = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
3164
3240
 
3165
3241
  // Memoize connectors separately to ensure stable references
3166
3242
  const connectors = React.useMemo(() => {
3167
- if (Array.isArray(props.wagmiConnectors)) {
3243
+ if (!Array.isArray(props.wagmiConnectors)) {
3244
+ return getConnectorsInternal(props.apiKey, props.trailsApiUrl)
3245
+ }
3246
+
3247
+ if (
3248
+ props.wagmiConnectors.some((connector) =>
3249
+ isWalletConnectConnector(connector),
3250
+ )
3251
+ ) {
3168
3252
  return props.wagmiConnectors
3169
3253
  }
3170
- return getConnectorsInternal(props.apiKey)
3171
- }, [props.apiKey, props.wagmiConnectors])
3254
+
3255
+ logger.console.warn(
3256
+ "[trails-sdk] wagmiConnectors is missing WalletConnect connector; appending internal WalletConnect connector to avoid broken QR flow",
3257
+ )
3258
+ // TODO: Remove this append fallback in a future major release. The correct long-term behavior may be to hide WalletConnect options when consumers intentionally omit the connector.
3259
+ return [...props.wagmiConnectors, getWalletConnectConnector()]
3260
+ }, [props.apiKey, props.trailsApiUrl, props.wagmiConnectors])
3172
3261
 
3173
3262
  // Create stable wagmi config using useMemo with minimal dependencies
3174
3263
  const wagmiConfig = React.useMemo(() => {