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
@@ -1,5 +1,5 @@
1
1
  import type { TransactionReceipt } from "viem"
2
- import type { TransactionStateInfo } from "@0xtrails/api"
2
+ import type { IntentReceipt, TransactionStateInfo } from "@0xtrails/api"
3
3
  import type {
4
4
  TransactionState,
5
5
  TransactionStateStatus,
@@ -10,6 +10,7 @@ import {
10
10
  decodeGuestModuleEvents,
11
11
  } from "../../decoders.js"
12
12
  import { logger } from "../../logger.js"
13
+ import { isDisplayableTransactionHash } from "../../transactions.js"
13
14
 
14
15
  // Re-export for convenience
15
16
  export type { TransactionStateInfo } from "@0xtrails/api"
@@ -195,6 +196,112 @@ export function updateTransactionStateAtIndex(
195
196
  })
196
197
  }
197
198
 
199
+ export function mapIntentTransactionStatus(
200
+ status?: string | null,
201
+ ): TransactionStateStatus | null {
202
+ if (!status) return null
203
+ if (status === "SUCCEEDED") return "confirmed"
204
+ if (status === "ABORTED") return "aborted"
205
+ if (status === "FAILED" || status === "ERRORED" || status === "REVERTED") {
206
+ return "failed"
207
+ }
208
+ return "pending"
209
+ }
210
+
211
+ export function applyIntentReceiptToTransactionStates(params: {
212
+ currentStates: TransactionState[]
213
+ receipt: IntentReceipt
214
+ }): { transactionStates: TransactionState[]; changed: boolean } {
215
+ const { currentStates, receipt } = params
216
+
217
+ if (currentStates.length === 0) {
218
+ return { transactionStates: currentStates, changed: false }
219
+ }
220
+
221
+ const nextStates = [...currentStates]
222
+ let changed = false
223
+
224
+ const applyAtIndex = (
225
+ index: number,
226
+ status?: string | null,
227
+ txnHash?: string | null,
228
+ txnMinedAt?: string | null,
229
+ ) => {
230
+ const targetState = nextStates[index]
231
+ const mappedState = mapIntentTransactionStatus(status)
232
+ if (!targetState || !mappedState) {
233
+ return
234
+ }
235
+
236
+ const displayableHash = isDisplayableTransactionHash(txnHash) ? txnHash : ""
237
+ const nextExplorerUrl = displayableHash
238
+ ? getExplorerUrl({
239
+ txHash: displayableHash,
240
+ chainId: targetState.chainId,
241
+ })
242
+ : ""
243
+
244
+ const stateChanged =
245
+ targetState.state !== mappedState ||
246
+ targetState.transactionHash !== displayableHash ||
247
+ targetState.explorerUrl !== nextExplorerUrl ||
248
+ targetState.txnMinedAt !== (txnMinedAt || undefined)
249
+
250
+ if (!stateChanged) {
251
+ return
252
+ }
253
+
254
+ nextStates[index] = {
255
+ ...targetState,
256
+ state: mappedState,
257
+ transactionHash: displayableHash,
258
+ explorerUrl: nextExplorerUrl,
259
+ txnMinedAt: txnMinedAt || undefined,
260
+ }
261
+ changed = true
262
+ }
263
+
264
+ applyAtIndex(
265
+ 0,
266
+ receipt.depositTransaction?.status,
267
+ receipt.depositTransaction?.txnHash,
268
+ receipt.depositTransaction?.txnMinedAt,
269
+ )
270
+
271
+ const originStateIndex = nextStates.length > 1 ? 1 : 0
272
+ applyAtIndex(
273
+ originStateIndex,
274
+ receipt.originTransaction?.status,
275
+ receipt.originTransaction?.txnHash,
276
+ receipt.originTransaction?.txnMinedAt,
277
+ )
278
+
279
+ const destinationStateIndex = nextStates.length - 1
280
+ applyAtIndex(
281
+ destinationStateIndex,
282
+ receipt.destinationTransaction?.status,
283
+ receipt.destinationTransaction?.txnHash,
284
+ receipt.destinationTransaction?.txnMinedAt,
285
+ )
286
+
287
+ return {
288
+ transactionStates: changed ? nextStates : currentStates,
289
+ changed,
290
+ }
291
+ }
292
+
293
+ export function hasIntentReceiptObservedFundingProgress(
294
+ receipt: IntentReceipt,
295
+ ): boolean {
296
+ return (
297
+ receipt.depositTransaction?.status === "SUCCEEDED" ||
298
+ receipt.originTransaction?.status === "MINING" ||
299
+ receipt.originTransaction?.status === "SUCCEEDED" ||
300
+ receipt.destinationTransaction?.status === "MINING" ||
301
+ receipt.destinationTransaction?.status === "SUCCEEDED"
302
+ )
303
+ }
304
+
198
305
  /**
199
306
  * Apply refund information from an intent receipt to the transaction states.
200
307
  * Finds the appropriate transaction by chainId and updates refund fields.
@@ -52,6 +52,19 @@ export function toApiFundMethod(fundMethod: FundMethod): ApiFundMethod {
52
52
  }
53
53
  }
54
54
 
55
+ export function isExternalFundingMethod(
56
+ fundMethod: FundMethod | null | undefined,
57
+ ): fundMethod is Extract<
58
+ FundMethod,
59
+ "direct-transfer" | "onramp-mesh" | "onramp-meld"
60
+ > {
61
+ return (
62
+ fundMethod === "direct-transfer" ||
63
+ fundMethod === "onramp-mesh" ||
64
+ fundMethod === "onramp-meld"
65
+ )
66
+ }
67
+
55
68
  export type PrepareSendOptions = {
56
69
  account: Account
57
70
  originTokenAddress: string
@@ -100,10 +113,7 @@ export type PrepareSendOptions = {
100
113
  // Optional QueryClient to use instead of the singleton (for decoupled mode)
101
114
  queryClient?: QueryClient
102
115
  // Optional mutation callbacks for React Query integration
103
- commitIntentFn?: (
104
- intent: Intent,
105
- options?: { skipIntentMonitoring?: boolean },
106
- ) => Promise<CommitIntentResponse>
116
+ commitIntentFn?: (intent: Intent) => Promise<CommitIntentResponse>
107
117
  executeIntentFn?: (params: {
108
118
  intentId: string
109
119
  depositTransactionHash?: string
@@ -215,15 +225,11 @@ export type PrepareSendReturn = {
215
225
  selectedFeeOption,
216
226
  depositTransactionHash,
217
227
  skipCommit,
218
- commitOnly,
219
- skipIntentMonitoring,
220
228
  }: {
221
229
  onOriginSend?: () => void
222
230
  selectedFeeOption?: FeeOption | null
223
231
  depositTransactionHash?: string
224
232
  skipCommit?: boolean
225
- commitOnly?: boolean
226
- skipIntentMonitoring?: boolean
227
233
  }) => Promise<SendReturn>
228
234
  }
229
235
 
@@ -1,10 +1,6 @@
1
1
  import type { PublicClient, TransactionReceipt } from "viem"
2
2
  import { logger } from "../../logger.js"
3
3
  import { POLLING_INTERVALS } from "../constants.js"
4
- import { getAccountTransactionHistory } from "../../transactions.js"
5
- import type { Account } from "viem"
6
- import { checkAccountBalance } from "./balanceChecker.js"
7
- import { MINUTE_MS } from "../../utils/time.js"
8
4
 
9
5
  export interface ResilientDepositTrackerOptions {
10
6
  publicClient: PublicClient
@@ -24,213 +20,106 @@ export interface ResilientDepositTrackerOptions {
24
20
  }
25
21
 
26
22
  /**
27
- * Resilient deposit tracker that handles RPC failures gracefully
28
- * Falls back to polling when transaction submission fails
23
+ * Resilient deposit tracker that validates a known deposit transaction hash
24
+ * via receipt polling, including replacement tx handling.
29
25
  */
30
26
  export async function trackDepositResilient({
31
27
  publicClient,
32
- originChainId,
33
- originIntentAddress,
34
- originTokenAddress,
35
28
  depositAmount,
36
29
  txHash,
37
30
  abortSignal,
38
- sequenceProjectAccessKey,
39
- sequenceIndexerUrl,
40
31
  maxRetries = 60, // 60 attempts = ~3 minutes with 3s intervals
41
32
  onDepositDetected,
42
33
  }: ResilientDepositTrackerOptions): Promise<TransactionReceipt | null> {
43
34
  logger.console.log("[trails-sdk] Starting resilient deposit tracking", {
44
35
  hasHash: !!txHash,
45
36
  txHash,
46
- originIntentAddress,
47
37
  depositAmount,
48
38
  })
49
39
 
50
- let attempts = 0
51
- let lastError: Error | null = null
52
-
53
- // If we have a transaction hash, try to get its receipt first
54
- if (txHash) {
55
- while (attempts < maxRetries) {
56
- if (abortSignal?.aborted) {
57
- logger.console.log("[trails-sdk] Deposit tracking aborted by signal")
58
- return null
59
- }
60
-
61
- try {
62
- // Try to get the transaction receipt
63
- const receipt = await publicClient.getTransactionReceipt({
64
- hash: txHash,
65
- })
66
-
67
- if (receipt) {
68
- logger.console.log("[trails-sdk] Deposit transaction receipt found", {
69
- hash: receipt.transactionHash,
70
- status: receipt.status,
71
- blockNumber: receipt.blockNumber,
72
- })
73
-
74
- // Notify callback if provided
75
- if (onDepositDetected) {
76
- onDepositDetected(txHash, receipt)
77
- }
78
-
79
- return receipt
80
- }
81
- } catch (error) {
82
- // Receipt not found yet, this is expected for pending transactions
83
- if (attempts === 0) {
84
- logger.console.log(
85
- "[trails-sdk] Transaction receipt not available yet, will continue polling",
86
- { txHash, error: (error as Error)?.message },
87
- )
88
- }
89
- lastError = error as Error
90
- }
91
-
92
- attempts++
93
- await new Promise((resolve) =>
94
- setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_RECEIPT),
95
- )
96
- }
97
-
40
+ if (!txHash) {
98
41
  logger.console.warn(
99
- "[trails-sdk] Could not get receipt for known transaction hash after retries",
100
- {
101
- txHash,
102
- attempts,
103
- lastError: lastError?.message,
104
- },
42
+ "[trails-sdk] Resilient deposit tracker requires a tx hash; skipping receipt wait",
105
43
  )
44
+ return null
106
45
  }
107
46
 
108
- // Fall back to polling the intent address for deposits
109
- logger.console.log(
110
- "[trails-sdk] Falling back to intent address polling for deposit detection",
111
- )
112
-
113
- attempts = 0
114
- const pollingStartTime = Date.now()
115
- const maxPollingDuration = 10 * MINUTE_MS // 10 minutes max
116
-
117
- while (attempts < maxRetries) {
118
- if (abortSignal?.aborted) {
119
- logger.console.log("[trails-sdk] Deposit tracking aborted by signal")
120
- return null
121
- }
122
-
123
- // Check if we've exceeded maximum polling duration
124
- if (Date.now() - pollingStartTime > maxPollingDuration) {
125
- logger.console.warn(
126
- "[trails-sdk] Maximum polling duration exceeded for deposit detection",
127
- )
128
- break
129
- }
130
-
131
- try {
132
- // Primary method: Check transaction history via indexer to find ANY transaction to intent address
133
- if (sequenceIndexerUrl && sequenceProjectAccessKey) {
134
- try {
135
- const response = await getAccountTransactionHistory({
136
- chainId: originChainId,
137
- accountAddress: originIntentAddress,
138
- abortSignal,
139
- apiKey: sequenceProjectAccessKey,
140
- indexerUrl: sequenceIndexerUrl,
141
- })
142
-
143
- if (response.transactions.length > 0) {
144
- const recentTx = response.transactions[0]
145
- if (recentTx?.txnHash) {
146
- logger.console.log(
147
- "[trails-sdk] Found deposit transaction to intent address via indexer",
148
- { txHash: recentTx.txnHash },
149
- )
150
-
151
- // Get the full receipt
152
- const receipt = await publicClient.getTransactionReceipt({
153
- hash: recentTx.txnHash as `0x${string}`,
154
- })
155
-
156
- logger.console.log(
157
- "[trails-sdk] Deposit transaction detected - proceeding",
158
- {
159
- txHash: receipt.transactionHash,
160
- to: receipt.to,
161
- status: receipt.status,
162
- },
163
- )
164
-
165
- // Notify callback if provided
166
- if (onDepositDetected) {
167
- onDepositDetected(
168
- receipt.transactionHash as `0x${string}`,
169
- receipt,
170
- )
171
- }
172
-
173
- return receipt
174
- }
175
- }
176
- } catch (indexerError) {
177
- logger.console.debug(
178
- "[trails-sdk] Indexer check failed, will continue polling",
179
- { error: (indexerError as Error)?.message },
180
- )
181
- }
182
- }
183
-
184
- // Fallback method: Check if ANY balance exists at the intent address
185
- // This indicates a transaction has occurred even if we can't find the specific tx
186
- const balanceCheck = await checkAccountBalance({
187
- account: {
188
- address: originIntentAddress as `0x${string}`,
189
- } as Account,
190
- tokenAddress: originTokenAddress,
191
- depositAmount: "1", // Check for any non-zero balance
192
- publicClient: publicClient,
193
- })
47
+ if (abortSignal?.aborted) {
48
+ logger.console.log("[trails-sdk] Deposit tracking aborted by signal")
49
+ return null
50
+ }
194
51
 
195
- if (balanceCheck.hasEnoughBalance) {
52
+ let resolvedHash: `0x${string}` = txHash
53
+ const timeoutMs = maxRetries * POLLING_INTERVALS.TRANSACTION_RECEIPT
54
+ let timeoutId: ReturnType<typeof setTimeout> | null = null
55
+
56
+ try {
57
+ const receiptPromise = publicClient.waitForTransactionReceipt({
58
+ hash: txHash,
59
+ retryCount: maxRetries,
60
+ retryDelay: POLLING_INTERVALS.TRANSACTION_RECEIPT,
61
+ onReplaced: (replacement) => {
62
+ resolvedHash = replacement.transaction.hash
196
63
  logger.console.log(
197
- "[trails-sdk] Detected funds at intent address (transaction occurred)",
64
+ "[trails-sdk] Deposit transaction replaced while waiting for receipt",
198
65
  {
199
- balance: balanceCheck.balanceFormatted,
200
- intentAddress: originIntentAddress,
66
+ reason: replacement.reason,
67
+ replacedHash: replacement.replacedTransaction.hash,
68
+ replacementHash: replacement.transaction.hash,
201
69
  },
202
70
  )
203
-
204
- // We detected a deposit via balance, but don't have the tx hash
205
- // This means a transaction definitely occurred
206
- // Return null to indicate we detected funds but have no transaction details
207
- logger.console.log(
208
- "[trails-sdk] Funds detected but no transaction hash available - proceeding anyway",
209
- )
210
- return null
211
- }
212
- } catch (error) {
213
- logger.console.debug("[trails-sdk] Error during deposit polling", {
214
- attempt: attempts,
215
- error: (error as Error)?.message,
216
- })
71
+ },
72
+ })
73
+
74
+ const timeoutPromise = new Promise<never>((_, reject) => {
75
+ timeoutId = setTimeout(() => {
76
+ reject(new Error("Timed out waiting for transaction receipt"))
77
+ }, timeoutMs)
78
+ })
79
+
80
+ const abortPromise = new Promise<never>((_, reject) => {
81
+ if (!abortSignal) return
82
+ abortSignal.addEventListener(
83
+ "abort",
84
+ () =>
85
+ reject(new Error("Aborted while waiting for transaction receipt")),
86
+ { once: true },
87
+ )
88
+ })
89
+
90
+ const receipt = await Promise.race([
91
+ receiptPromise,
92
+ timeoutPromise,
93
+ abortPromise,
94
+ ])
95
+
96
+ logger.console.log("[trails-sdk] Deposit transaction receipt found", {
97
+ hash: receipt.transactionHash,
98
+ status: receipt.status,
99
+ blockNumber: receipt.blockNumber,
100
+ })
101
+
102
+ if (onDepositDetected) {
103
+ onDepositDetected(resolvedHash, receipt)
217
104
  }
218
105
 
219
- attempts++
220
- await new Promise((resolve) =>
221
- setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY),
106
+ return receipt
107
+ } catch (error) {
108
+ logger.console.warn(
109
+ "[trails-sdk] Could not get receipt for known transaction hash",
110
+ {
111
+ txHash,
112
+ resolvedHash,
113
+ timeoutMs,
114
+ error: (error as Error)?.message,
115
+ },
222
116
  )
117
+ return null
118
+ } finally {
119
+ if (timeoutId) {
120
+ clearTimeout(timeoutId)
121
+ }
223
122
  }
224
-
225
- logger.console.warn(
226
- "[trails-sdk] Deposit tracking ended without detecting deposit",
227
- {
228
- attempts,
229
- duration: Date.now() - pollingStartTime,
230
- },
231
- )
232
-
233
- return null
234
123
  }
235
124
 
236
125
  /**
@@ -101,6 +101,22 @@ export type GetAccountTransactionHistoryParams = {
101
101
  abortSignal?: AbortSignal
102
102
  }
103
103
 
104
+ export function isSyntheticTransactionHash(
105
+ transactionHash?: string | null,
106
+ ): boolean {
107
+ const normalizedHash = transactionHash?.toLowerCase()
108
+
109
+ return Boolean(normalizedHash && /0{10,}$/.test(normalizedHash))
110
+ }
111
+
112
+ export function isDisplayableTransactionHash(
113
+ transactionHash?: string | null,
114
+ ): transactionHash is string {
115
+ return Boolean(
116
+ transactionHash && !isSyntheticTransactionHash(transactionHash),
117
+ )
118
+ }
119
+
104
120
  function isTransactionHistoryItemFromApi(
105
121
  value: unknown,
106
122
  ): value is TransactionHistoryItemFromAPI {
@@ -1,9 +1,184 @@
1
1
  import type { Connector } from "wagmi"
2
- import type { WalletClient } from "viem"
2
+ import {
3
+ BaseError,
4
+ formatTransactionRequest,
5
+ InvalidInputRpcError,
6
+ InvalidParamsRpcError,
7
+ MethodNotFoundRpcError,
8
+ MethodNotSupportedRpcError,
9
+ UnknownRpcError,
10
+ type RpcTransactionRequest,
11
+ type WalletClient,
12
+ } from "viem"
13
+ import { parseAccount } from "viem/accounts"
14
+ import { attemptSwitchChain } from "./chainSwitch.js"
3
15
  import { getIsContract } from "./contractUtils.js"
4
16
  import { SECOND_MS } from "./utils/time.js"
5
17
  import { logger } from "./logger.js"
6
18
 
19
+ type WalletSendTransactionParams = Parameters<
20
+ WalletClient["sendTransaction"]
21
+ >[0]
22
+
23
+ // Matches viem fallback trigger errors while probing wallet_sendTransaction:
24
+ // https://github.com/wevm/viem/blob/main/src/actions/wallet/sendTransaction.ts
25
+ function isUnsupportedWalletNamespaceError(error: unknown): boolean {
26
+ return (
27
+ error instanceof InvalidInputRpcError ||
28
+ error instanceof InvalidParamsRpcError ||
29
+ error instanceof MethodNotFoundRpcError ||
30
+ error instanceof MethodNotSupportedRpcError
31
+ )
32
+ }
33
+
34
+ // EIP-1474 defines "method not supported" as -32004:
35
+ // https://eips.ethereum.org/EIPS/eip-1474#error-codes
36
+ // Some Privy RPC endpoints currently return non-standard -32604 for
37
+ // wallet_sendTransaction, which viem surfaces as UnknownRpcError instead of
38
+ // MethodNotSupportedRpcError. We normalize that shape here so fallback can run.
39
+ function isUnsupportedPrivyWalletSendTransactionError(error: unknown): boolean {
40
+ if (!(error instanceof UnknownRpcError)) {
41
+ return false
42
+ }
43
+
44
+ const details = error.details.toLowerCase()
45
+ return (
46
+ details.includes("method is not supported") || details.includes("-32604")
47
+ )
48
+ }
49
+
50
+ function isRecoverableWalletSendError(error: unknown): boolean {
51
+ if (
52
+ isUnsupportedWalletNamespaceError(error) ||
53
+ isUnsupportedPrivyWalletSendTransactionError(error)
54
+ ) {
55
+ return true
56
+ }
57
+
58
+ if (error instanceof BaseError) {
59
+ return (
60
+ error.walk(
61
+ (innerError) =>
62
+ isUnsupportedWalletNamespaceError(innerError) ||
63
+ isUnsupportedPrivyWalletSendTransactionError(innerError),
64
+ ) !== null
65
+ )
66
+ }
67
+
68
+ return false
69
+ }
70
+
71
+ function buildEthSendTransactionRequest(
72
+ txParams: WalletSendTransactionParams,
73
+ ): RpcTransactionRequest {
74
+ if (!txParams.account) {
75
+ throw new Error(
76
+ "Wallet account address is required for eth_sendTransaction fallback",
77
+ )
78
+ }
79
+
80
+ const { chain: _chain, account, ...request } = txParams
81
+
82
+ return formatTransactionRequest({
83
+ ...request,
84
+ account: parseAccount(account),
85
+ })
86
+ }
87
+
88
+ async function getTransportChainId(
89
+ transport: WalletClient["transport"],
90
+ ): Promise<number> {
91
+ if (!transport || typeof transport.request !== "function") {
92
+ throw new Error("Invalid wallet transport")
93
+ }
94
+
95
+ const currentChainIdHex = await transport.request({ method: "eth_chainId" })
96
+ if (typeof currentChainIdHex !== "string") {
97
+ throw new Error("Wallet transport returned an invalid chain ID")
98
+ }
99
+
100
+ const currentChainId = Number.parseInt(currentChainIdHex, 16)
101
+ if (!Number.isSafeInteger(currentChainId)) {
102
+ throw new Error("Wallet transport returned an invalid chain ID")
103
+ }
104
+
105
+ return currentChainId
106
+ }
107
+
108
+ async function assertFallbackTransportMatchesChain(
109
+ walletClient: WalletClient,
110
+ txParams: WalletSendTransactionParams,
111
+ ): Promise<void> {
112
+ const expectedChainId = txParams.chain?.id
113
+ if (!expectedChainId) {
114
+ return
115
+ }
116
+
117
+ const transport = walletClient.transport
118
+ let currentChainId = await getTransportChainId(transport)
119
+
120
+ if (currentChainId !== expectedChainId) {
121
+ logger.console.warn(
122
+ "[wallet-utils] Switching wallet transport chain before eth_sendTransaction fallback",
123
+ {
124
+ currentChainId,
125
+ expectedChainId,
126
+ },
127
+ )
128
+
129
+ await attemptSwitchChain({
130
+ walletClient,
131
+ desiredChainId: expectedChainId,
132
+ })
133
+
134
+ currentChainId = await getTransportChainId(transport)
135
+ if (currentChainId !== expectedChainId) {
136
+ throw new Error(
137
+ `Wallet transport chain ID ${currentChainId} does not match requested chain ID ${expectedChainId}`,
138
+ )
139
+ }
140
+ }
141
+ }
142
+
143
+ export async function sendWalletTransaction(
144
+ walletClient: WalletClient,
145
+ txParams: WalletSendTransactionParams,
146
+ ): Promise<`0x${string}`> {
147
+ try {
148
+ return await walletClient.sendTransaction(txParams)
149
+ } catch (error) {
150
+ if (!isRecoverableWalletSendError(error)) {
151
+ throw error
152
+ }
153
+
154
+ logger.console.warn(
155
+ "[wallet-utils] Falling back to eth_sendTransaction for wallet send",
156
+ {
157
+ error: error instanceof Error ? error.message : String(error),
158
+ },
159
+ )
160
+
161
+ const fallbackRequest = buildEthSendTransactionRequest(txParams)
162
+ const transport = walletClient.transport
163
+ if (!transport || typeof transport.request !== "function") {
164
+ throw new Error("Invalid wallet transport")
165
+ }
166
+
167
+ await assertFallbackTransportMatchesChain(walletClient, txParams)
168
+
169
+ // Send directly through transport with eth_sendTransaction to bypass
170
+ // wallet namespace probing for providers that reject wallet_sendTransaction.
171
+ // Related viem logic:
172
+ // https://github.com/wevm/viem/blob/main/src/actions/wallet/sendTransaction.ts
173
+ const txHash = await transport.request({
174
+ method: "eth_sendTransaction",
175
+ params: [fallbackRequest],
176
+ })
177
+
178
+ return txHash as `0x${string}`
179
+ }
180
+ }
181
+
7
182
  /**
8
183
  * Checks if a wallet connector is a Sequence wallet.
9
184
  * @param connector - The wagmi connector to check
@@ -26,6 +201,18 @@ export function isSequenceWalletById(walletId: string | undefined): boolean {
26
201
  return walletId.toLowerCase().includes("sequence")
27
202
  }
28
203
 
204
+ export function isWalletConnectConnector(
205
+ connector: Connector | undefined,
206
+ ): boolean {
207
+ if (!connector) return false
208
+ const connectorName = connector.name?.toLowerCase() || ""
209
+ const connectorId = connector.id?.toLowerCase() || ""
210
+ return (
211
+ connectorName.includes("walletconnect") ||
212
+ connectorId.includes("walletconnect")
213
+ )
214
+ }
215
+
29
216
  /**
30
217
  * Checks if an address is a smart contract wallet.
31
218
  * @param address - The wallet address to check