0xtrails 0.5.0 → 0.6.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 (189) hide show
  1. package/dist/analytics.d.ts +8 -3
  2. package/dist/analytics.d.ts.map +1 -1
  3. package/dist/{ccip-DhEkQ6QC.js → ccip-Dw5AN7oU.js} +1 -1
  4. package/dist/cctp.d.ts +0 -149
  5. package/dist/cctp.d.ts.map +1 -1
  6. package/dist/chains.d.ts +28 -3
  7. package/dist/chains.d.ts.map +1 -1
  8. package/dist/config.d.ts +11 -0
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/constants.d.ts +1 -1
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/contractUtils.d.ts.map +1 -1
  13. package/dist/estimate.d.ts.map +1 -1
  14. package/dist/fees.d.ts.map +1 -1
  15. package/dist/gasless.d.ts +12 -0
  16. package/dist/gasless.d.ts.map +1 -1
  17. package/dist/{index-MhD2DA7_.js → index-BtVUTbEZ.js} +30984 -38945
  18. package/dist/index.d.ts +7 -5
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +108 -107
  21. package/dist/indexerClient.d.ts +2 -2
  22. package/dist/intents.d.ts +0 -17
  23. package/dist/intents.d.ts.map +1 -1
  24. package/dist/mutations.d.ts.map +1 -1
  25. package/dist/paymasterSend.d.ts.map +1 -1
  26. package/dist/prepareSend.d.ts +1 -1
  27. package/dist/prepareSend.d.ts.map +1 -1
  28. package/dist/sendUserOp.d.ts +0 -18
  29. package/dist/sendUserOp.d.ts.map +1 -1
  30. package/dist/tokenBalances.d.ts.map +1 -1
  31. package/dist/tokens.d.ts +10 -8
  32. package/dist/tokens.d.ts.map +1 -1
  33. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +4 -5
  34. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
  35. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts +4 -5
  36. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts.map +1 -1
  37. package/dist/transactionIntent/deposits/standardDeposit.d.ts +2 -2
  38. package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
  39. package/dist/transactionIntent/execution/transactionState.d.ts +2 -2
  40. package/dist/transactionIntent/execution/transactionState.d.ts.map +1 -1
  41. package/dist/transactionIntent/handlers/crossChain.d.ts +4 -4
  42. package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
  43. package/dist/transactionIntent/handlers/index.d.ts +0 -1
  44. package/dist/transactionIntent/handlers/index.d.ts.map +1 -1
  45. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +4 -34
  46. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
  47. package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
  48. package/dist/transactionIntent/quote/quoteHelpers.d.ts +2 -1
  49. package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
  50. package/dist/transactionIntent/types.d.ts +6 -19
  51. package/dist/transactionIntent/types.d.ts.map +1 -1
  52. package/dist/transactionIntent/utils/index.d.ts +0 -1
  53. package/dist/transactionIntent/utils/index.d.ts.map +1 -1
  54. package/dist/transactions.d.ts +2 -20
  55. package/dist/transactions.d.ts.map +1 -1
  56. package/dist/utils.d.ts +8 -2
  57. package/dist/utils.d.ts.map +1 -1
  58. package/dist/walletUtils.d.ts +21 -0
  59. package/dist/walletUtils.d.ts.map +1 -0
  60. package/dist/wallets.d.ts +33 -240
  61. package/dist/wallets.d.ts.map +1 -1
  62. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  63. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  64. package/dist/widget/components/FeeOption.d.ts +8 -13
  65. package/dist/widget/components/FeeOption.d.ts.map +1 -1
  66. package/dist/widget/components/FeeOptions.d.ts +11 -5
  67. package/dist/widget/components/FeeOptions.d.ts.map +1 -1
  68. package/dist/widget/components/NativeGasOption.d.ts.map +1 -1
  69. package/dist/widget/components/Pay.d.ts.map +1 -1
  70. package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
  71. package/dist/widget/components/QRCodeDeposit.d.ts +5 -0
  72. package/dist/widget/components/QRCodeDeposit.d.ts.map +1 -1
  73. package/dist/widget/components/QRCodeWalletSelect.d.ts +13 -0
  74. package/dist/widget/components/QRCodeWalletSelect.d.ts.map +1 -0
  75. package/dist/widget/components/QrCode.d.ts.map +1 -1
  76. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  77. package/dist/widget/components/Receipt.d.ts.map +1 -1
  78. package/dist/widget/components/ScreenHeader.d.ts +1 -1
  79. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  80. package/dist/widget/components/Toast.d.ts.map +1 -1
  81. package/dist/widget/components/TokenImage.d.ts.map +1 -1
  82. package/dist/widget/css/compiled.css +1 -1
  83. package/dist/widget/hooks/useCheckout.d.ts +15 -1
  84. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  85. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  86. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  87. package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
  88. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  89. package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
  90. package/dist/widget/hooks/useIsConnectedWalletSmartContract.d.ts +7 -0
  91. package/dist/widget/hooks/useIsConnectedWalletSmartContract.d.ts.map +1 -0
  92. package/dist/widget/hooks/useIsSequenceWallet.d.ts +6 -0
  93. package/dist/widget/hooks/useIsSequenceWallet.d.ts.map +1 -0
  94. package/dist/widget/hooks/useQuote.d.ts +5 -8
  95. package/dist/widget/hooks/useQuote.d.ts.map +1 -1
  96. package/dist/widget/hooks/useRecentTokens.d.ts.map +1 -1
  97. package/dist/widget/hooks/useSelectedFeeOption.d.ts +30 -0
  98. package/dist/widget/hooks/useSelectedFeeOption.d.ts.map +1 -0
  99. package/dist/widget/hooks/useSendForm.d.ts +6 -15
  100. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  101. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  102. package/dist/widget/index.js +1 -1
  103. package/dist/widget/providers/TrailsProvider.d.ts +23 -12
  104. package/dist/widget/providers/TrailsProvider.d.ts.map +1 -1
  105. package/dist/widget/widget.d.ts +11 -0
  106. package/dist/widget/widget.d.ts.map +1 -1
  107. package/package.json +8 -8
  108. package/src/analytics.ts +53 -21
  109. package/src/cctp.ts +0 -1016
  110. package/src/chains.ts +93 -39
  111. package/src/config.ts +24 -6
  112. package/src/constants.ts +1 -4
  113. package/src/contractUtils.ts +6 -6
  114. package/src/estimate.ts +3 -6
  115. package/src/fees.ts +5 -10
  116. package/src/gasless.ts +45 -0
  117. package/src/index.ts +7 -6
  118. package/src/indexerClient.ts +2 -2
  119. package/src/intents.ts +52 -206
  120. package/src/mutations.ts +3 -2
  121. package/src/paymasterSend.ts +2 -5
  122. package/src/prepareSend.ts +9 -12
  123. package/src/sendUserOp.ts +3 -64
  124. package/src/tokenBalances.ts +2 -1
  125. package/src/tokens.ts +62 -133
  126. package/src/trailsClient.ts +1 -1
  127. package/src/transactionIntent/deposits/depositOrchestrator.ts +14 -15
  128. package/src/transactionIntent/deposits/gaslessDeposit.ts +70 -100
  129. package/src/transactionIntent/deposits/standardDeposit.ts +22 -28
  130. package/src/transactionIntent/execution/transactionState.ts +2 -2
  131. package/src/transactionIntent/handlers/crossChain.ts +165 -385
  132. package/src/transactionIntent/handlers/index.ts +0 -1
  133. package/src/transactionIntent/handlers/sameChainSameToken.ts +228 -94
  134. package/src/transactionIntent/quote/normalizeQuote.ts +4 -6
  135. package/src/transactionIntent/quote/quoteHelpers.ts +35 -3
  136. package/src/transactionIntent/types.ts +6 -27
  137. package/src/transactionIntent/utils/index.ts +0 -1
  138. package/src/transactions.ts +6 -203
  139. package/src/umd.tsx +1 -3
  140. package/src/utils.ts +28 -8
  141. package/src/walletUtils.ts +42 -0
  142. package/src/wallets.ts +361 -203
  143. package/src/widget/compiled.css +1 -1
  144. package/src/widget/components/AccountIntentTransactionHistory.tsx +73 -4
  145. package/src/widget/components/AccountSettings.tsx +17 -17
  146. package/src/widget/components/ChainList.tsx +3 -3
  147. package/src/widget/components/ClassicSwap.tsx +19 -10
  148. package/src/widget/components/ConfigDisplay.tsx +1 -1
  149. package/src/widget/components/FeeOption.tsx +63 -20
  150. package/src/widget/components/FeeOptions.tsx +54 -123
  151. package/src/widget/components/NativeGasOption.tsx +3 -1
  152. package/src/widget/components/Pay.tsx +18 -11
  153. package/src/widget/components/PoolDeposit.tsx +23 -10
  154. package/src/widget/components/QRCodeDeposit.tsx +50 -30
  155. package/src/widget/components/QRCodeWalletSelect.tsx +77 -0
  156. package/src/widget/components/QrCode.tsx +188 -233
  157. package/src/widget/components/QuoteDetails.tsx +48 -2
  158. package/src/widget/components/Receipt.tsx +5 -2
  159. package/src/widget/components/ScreenHeader.tsx +10 -8
  160. package/src/widget/components/Toast.tsx +10 -0
  161. package/src/widget/components/TokenImage.tsx +56 -13
  162. package/src/widget/hooks/useCheckout.ts +71 -0
  163. package/src/widget/hooks/useCurrentScreen.tsx +1 -0
  164. package/src/widget/hooks/useDebugScreens.ts +5 -0
  165. package/src/widget/hooks/useIntentTransactionHistory.ts +788 -418
  166. package/src/widget/hooks/useIsConnectedWalletSmartContract.ts +43 -0
  167. package/src/widget/hooks/useIsSequenceWallet.ts +17 -0
  168. package/src/widget/hooks/useQuote.ts +16 -17
  169. package/src/widget/hooks/useRecentTokens.ts +2 -1
  170. package/src/widget/hooks/useSelectedFeeOption.tsx +257 -0
  171. package/src/widget/hooks/useSendForm.ts +172 -47
  172. package/src/widget/hooks/useTokenList.ts +15 -2
  173. package/src/widget/providers/TrailsProvider.tsx +53 -25
  174. package/src/widget/widget.tsx +119 -48
  175. package/dist/cctpqueue.d.ts +0 -18
  176. package/dist/cctpqueue.d.ts.map +0 -1
  177. package/dist/preconditions.d.ts +0 -12
  178. package/dist/preconditions.d.ts.map +0 -1
  179. package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts +0 -62
  180. package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts.map +0 -1
  181. package/dist/transactionIntent/utils/lifiHelpers.d.ts +0 -10
  182. package/dist/transactionIntent/utils/lifiHelpers.d.ts.map +0 -1
  183. package/dist/widget/hooks/useSelectedFeeToken.d.ts +0 -33
  184. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +0 -1
  185. package/src/cctpqueue.ts +0 -69
  186. package/src/preconditions.ts +0 -47
  187. package/src/transactionIntent/handlers/sameChainDifferentToken.ts +0 -323
  188. package/src/transactionIntent/utils/lifiHelpers.ts +0 -68
  189. package/src/widget/hooks/useSelectedFeeToken.tsx +0 -288
@@ -1,4 +1,5 @@
1
- import type { TokenPrice } from "@0xsequence/trails-api"
1
+ import type { TokenPrice, FeeOption } from "@0xsequence/trails-api"
2
+ import { JsonEncode } from "@0xsequence/trails-api"
2
3
  import type React from "react"
3
4
  import { useCallback, useEffect, useMemo, useState, useRef } from "react"
4
5
  import {
@@ -17,7 +18,6 @@ import {
17
18
  TradeType,
18
19
  type PrepareSendReturn,
19
20
  type PrepareSendQuote,
20
- type SelectedFeeToken,
21
21
  } from "../../prepareSend.js"
22
22
  import type { TransactionState } from "../../transactions.js"
23
23
  import { getTokenPrice, useTokenPrices, normalizeNumber } from "../../prices.js"
@@ -42,7 +42,10 @@ import { getIsContract } from "../../contractUtils.js"
42
42
  import { useTrailsClient } from "../../trailsClient.js"
43
43
  import { useTrails } from "../providers/TrailsProvider.js"
44
44
  import { useTokenList } from "./useTokenList.js"
45
- import { useSelectedFeeToken } from "./useSelectedFeeToken.js"
45
+ import {
46
+ useSelectedFeeOption,
47
+ type ProcessedFeeOption,
48
+ } from "./useSelectedFeeOption.js"
46
49
 
47
50
  export interface Token {
48
51
  id: number
@@ -118,17 +121,6 @@ export type UseSendProps = {
118
121
  refetchTrigger?: number
119
122
  }
120
123
 
121
- export type FeeOption = {
122
- tokenAddress: string
123
- tokenSymbol: string
124
- tokenDecimals: number
125
- amount: string
126
- amountUSD: number
127
- notEnoughBalance?: boolean
128
- tokenImageUrl?: string
129
- chainId?: number
130
- }
131
-
132
124
  export type UseSendReturn = {
133
125
  amount: string
134
126
  amountRaw: string
@@ -153,8 +145,8 @@ export type UseSendReturn = {
153
145
  setRecipientInput: (recipientInput: string) => void
154
146
  setSelectedDestinationChain: (chain: ChainInfo) => void
155
147
  setSelectedDestToken: (token: TokenInfo) => void
156
- setSelectedFeeToken: (token: FeeOption) => void
157
- feeOptions: FeeOption[]
148
+ setSelectedFeeOption: (token: FeeOption | null) => void
149
+ processedFeeOptions: ProcessedFeeOption[]
158
150
  supportedTokens: SupportedToken[]
159
151
  supportedChains: ChainInfo[]
160
152
  ensAddress: string | null
@@ -164,7 +156,7 @@ export type UseSendReturn = {
164
156
  destTokenPrices: TokenPrice[] | null
165
157
  sourceTokenPrices: TokenPrice[] | null
166
158
  selectedToken?: Token
167
- selectedFeeToken: SelectedFeeToken | null
159
+ selectedFeeOption: FeeOption | null
168
160
  setIsChainDropdownOpen: (isOpen: boolean) => void
169
161
  setIsTokenDropdownOpen: (isOpen: boolean) => void
170
162
  toAmountFormatted: string
@@ -434,7 +426,7 @@ export function useSendForm({
434
426
  }, [selectedDestToken, defaultDestToken])
435
427
 
436
428
  const trailsClient = useTrailsClient()
437
- const { sequenceIndexerUrl } = useTrails()
429
+ const { trailsApiKey, sequenceIndexerUrl } = useTrails()
438
430
 
439
431
  // Get user's token balances for balance checking
440
432
  const { filteredTokensFormatted } = useTokenList({
@@ -547,6 +539,10 @@ export function useSendForm({
547
539
  const [isLoadingQuote, setIsLoadingQuote] = useState(false)
548
540
  const [prepareSendResult, setPrepareSendResult] =
549
541
  useState<PrepareSendReturn | null>(null)
542
+ const latestResolvedIntentIdRef = useRef<string | null>(null)
543
+ const quoteInputsFingerprintRef = useRef<string | null>(null)
544
+ const preparedQuoteFingerprintRef = useRef<string | null>(null)
545
+
550
546
  // Create a stable callback for transaction state changes
551
547
  const handleTransactionStateChange = useCallback(
552
548
  (transactionStates: TransactionState[]) => {
@@ -585,11 +581,11 @@ export function useSendForm({
585
581
 
586
582
  // Use the fee token selection hook
587
583
  const {
588
- selectedFeeToken,
589
- setSelectedFeeToken,
590
- processedFeeOptions: feeOptions,
591
- setRawFeeOptions,
592
- } = useSelectedFeeToken()
584
+ selectedFeeOption,
585
+ setSelectedFeeOption,
586
+ processedFeeOptions,
587
+ setFeeOptions,
588
+ } = useSelectedFeeOption()
593
589
 
594
590
  const [isRecipientContract, setIsRecipientContract] = useState(false)
595
591
  const [isSenderContractOnOrigin, setIsSenderContractOnOrigin] =
@@ -686,6 +682,34 @@ export function useSendForm({
686
682
  tradeType,
687
683
  ])
688
684
 
685
+ const buildQuoteInputsFingerprint = useCallback(
686
+ () =>
687
+ JSON.stringify({
688
+ originTokenAddress: selectedToken?.contractAddress?.toLowerCase() ?? "",
689
+ originChainId: selectedToken?.chainId ?? "",
690
+ destinationTokenSymbol: selectedDestToken?.symbol ?? "",
691
+ destinationChainId: selectedDestinationChain?.id ?? "",
692
+ destinationTokenAddress: destinationTokenAddress?.toLowerCase() ?? "",
693
+ amount: amount || "",
694
+ amountRaw: amountRaw || "",
695
+ recipient: recipient?.toLowerCase() ?? "",
696
+ tradeType,
697
+ toCalldata: toCalldata ?? "",
698
+ }),
699
+ [
700
+ amount,
701
+ amountRaw,
702
+ destinationTokenAddress,
703
+ recipient,
704
+ selectedDestinationChain?.id,
705
+ selectedDestToken?.symbol,
706
+ selectedToken?.chainId,
707
+ selectedToken?.contractAddress,
708
+ toCalldata,
709
+ tradeType,
710
+ ],
711
+ )
712
+
689
713
  // Get quote automatically when inputs change
690
714
  const getQuote = useCallback(async () => {
691
715
  // Only get quote if all required inputs are present
@@ -715,9 +739,16 @@ export function useSendForm({
715
739
  )
716
740
  setQuoteError(null)
717
741
  setPrepareSendResult(null)
742
+ preparedQuoteFingerprintRef.current = null
718
743
  return
719
744
  }
720
745
 
746
+ latestResolvedIntentIdRef.current = null
747
+ let requestFingerprint = quoteInputsFingerprintRef.current
748
+ if (!requestFingerprint) {
749
+ requestFingerprint = buildQuoteInputsFingerprint()
750
+ quoteInputsFingerprintRef.current = requestFingerprint
751
+ }
721
752
  try {
722
753
  setIsLoadingQuote(true)
723
754
  setError(null)
@@ -735,6 +766,8 @@ export function useSendForm({
735
766
  tradeType,
736
767
  })
737
768
  setPrepareSendResult(null)
769
+ preparedQuoteFingerprintRef.current = null
770
+ latestResolvedIntentIdRef.current = null
738
771
  setIsLoadingQuote(false)
739
772
  return
740
773
  }
@@ -849,31 +882,66 @@ export function useSendForm({
849
882
  mode,
850
883
  fundMethod,
851
884
  checkoutOnHandlers,
852
- selectedFeeToken: selectedFeeToken ?? undefined,
885
+ selectedFeeOption: selectedFeeOption ?? null,
853
886
  walletId,
854
887
  sequenceIndexerUrl,
888
+ sequenceProjectAccessKey: trailsApiKey,
855
889
  }
856
890
 
857
891
  logger.console.log(
858
- "[trails-sdk] [FEE-SELECT] getQuote using selectedFeeToken:",
892
+ "[trails-sdk] [FEE-SELECT] getQuote using selectedFeeOption:",
859
893
  {
860
- selectedFeeToken,
894
+ selectedFeeOption,
861
895
  },
862
896
  )
863
897
 
864
898
  const result = await prepareSend(options)
865
899
 
900
+ if (requestFingerprint !== quoteInputsFingerprintRef.current) {
901
+ logger.console.log(
902
+ "[trails-sdk] Ignoring stale quote result (fingerprint mismatch)",
903
+ {
904
+ requestFingerprint,
905
+ currentFingerprint: quoteInputsFingerprintRef.current,
906
+ },
907
+ )
908
+ return
909
+ }
910
+
866
911
  logger.console.log("[trails-sdk] prepareSend quote:", result.quote)
867
912
 
913
+ // Track the intentId returned by the most recent quote so we never execute a stale CommitIntent
914
+ // even when the quote inputs (fingerprint) are identical between retries.
915
+ const quoteIntentId = result.quote.intentId ?? null
916
+
917
+ logger.console.log("[trails-sdk] Resolved quote intentId:", quoteIntentId)
918
+
919
+ latestResolvedIntentIdRef.current = quoteIntentId
868
920
  setPrepareSendResult(result)
921
+ preparedQuoteFingerprintRef.current = requestFingerprint
869
922
  setIsLoadingQuote(false)
870
923
  } catch (error) {
924
+ if (requestFingerprint !== quoteInputsFingerprintRef.current) {
925
+ logger.console.log(
926
+ "[trails-sdk] Ignoring quote error (fingerprint mismatch)",
927
+ {
928
+ requestFingerprint,
929
+ currentFingerprint: quoteInputsFingerprintRef.current,
930
+ },
931
+ )
932
+ return
933
+ }
934
+
871
935
  logger.console.error("[trails-sdk] Error getting quote:", error)
872
- setQuoteError(getFullErrorMessage(error))
936
+ const errorMessage = getFullErrorMessage(error)
937
+ setQuoteError(errorMessage)
873
938
  setPrepareSendResult(null)
939
+ preparedQuoteFingerprintRef.current = null
940
+ latestResolvedIntentIdRef.current = null
874
941
  setIsLoadingQuote(false)
875
942
  }
876
943
  }, [
944
+ trailsApiKey,
877
945
  tradeType,
878
946
  isDryMode,
879
947
  account,
@@ -904,8 +972,9 @@ export function useSendForm({
904
972
  amountRaw,
905
973
  checkoutOnHandlers,
906
974
  mode,
907
- selectedFeeToken,
975
+ selectedFeeOption,
908
976
  walletId,
977
+ buildQuoteInputsFingerprint,
909
978
  ])
910
979
 
911
980
  // Auto-fetch quotes when inputs change (debounced)
@@ -917,12 +986,29 @@ export function useSendForm({
917
986
  !destinationTokenAddress ||
918
987
  !isValidRecipient ||
919
988
  !selectedDestToken?.symbol ||
920
- !selectedDestinationChain?.id
989
+ !selectedDestinationChain?.id ||
990
+ !selectedToken
921
991
  ) {
922
992
  setPrepareSendResult(null)
993
+ latestResolvedIntentIdRef.current = null
994
+ quoteInputsFingerprintRef.current = null
995
+ preparedQuoteFingerprintRef.current = null
996
+ setIsLoadingQuote(false)
923
997
  return
924
998
  }
925
999
 
1000
+ const nextQuoteInputsFingerprint = buildQuoteInputsFingerprint()
1001
+
1002
+ // Fingerprint all quote-driving inputs so we can invalidate debounced quotes immediately when
1003
+ // the user switches tokens; this fixes the stale quote so that it stays loading until the new quote is ready.
1004
+ if (quoteInputsFingerprintRef.current !== nextQuoteInputsFingerprint) {
1005
+ quoteInputsFingerprintRef.current = nextQuoteInputsFingerprint
1006
+ latestResolvedIntentIdRef.current = null
1007
+ setPrepareSendResult(null)
1008
+ preparedQuoteFingerprintRef.current = null
1009
+ setIsLoadingQuote(true)
1010
+ }
1011
+
926
1012
  const timeoutId = setTimeout(() => {
927
1013
  getQuote()
928
1014
  }, 500) // Debounce by 500ms
@@ -936,12 +1022,15 @@ export function useSendForm({
936
1022
  selectedDestinationChain?.id,
937
1023
  toCalldata,
938
1024
  refetchTrigger,
1025
+ amountRaw,
1026
+ tradeType,
939
1027
  selectedToken?.contractAddress,
940
1028
  selectedToken?.chainId,
941
1029
  selectedToken?.balance,
942
1030
  selectedToken?.tokenPriceUsd,
943
1031
  recipient, // Add recipient to trigger quote re-fetch when it changes
944
- // selectedFeeToken is passed to send() at execution time, not needed here
1032
+ // selectedFeeOption is passed to send() at execution time, not needed here
1033
+ buildQuoteInputsFingerprint,
945
1034
  ])
946
1035
 
947
1036
  // Calculate destination amount from quote if available
@@ -971,12 +1060,12 @@ export function useSendForm({
971
1060
  },
972
1061
  )
973
1062
 
974
- setRawFeeOptions(
1063
+ setFeeOptions(
975
1064
  apiFeeOptions,
976
1065
  selectedToken?.chainId,
977
1066
  filteredTokensFormattedRef.current,
978
1067
  )
979
- }, [prepareSendResult, selectedToken?.chainId, setRawFeeOptions])
1068
+ }, [prepareSendResult, selectedToken?.chainId, setFeeOptions])
980
1069
 
981
1070
  const processSend = useCallback(async () => {
982
1071
  try {
@@ -985,6 +1074,41 @@ export function useSendForm({
985
1074
  return
986
1075
  }
987
1076
 
1077
+ const currentQuoteInputsFingerprint = buildQuoteInputsFingerprint()
1078
+ if (
1079
+ !preparedQuoteFingerprintRef.current ||
1080
+ preparedQuoteFingerprintRef.current !== currentQuoteInputsFingerprint
1081
+ ) {
1082
+ logger.console.warn(
1083
+ "[trails-sdk] Blocking send because quote fingerprint is stale",
1084
+ {
1085
+ prepared: preparedQuoteFingerprintRef.current,
1086
+ current: currentQuoteInputsFingerprint,
1087
+ },
1088
+ )
1089
+ setPrepareSendResult(null)
1090
+ preparedQuoteFingerprintRef.current = null
1091
+ latestResolvedIntentIdRef.current = null
1092
+ setIsLoadingQuote(true)
1093
+ return
1094
+ }
1095
+
1096
+ const quoteIntentId = prepareSendResult.quote.intentId ?? null
1097
+ if (
1098
+ quoteIntentId &&
1099
+ latestResolvedIntentIdRef.current !== quoteIntentId
1100
+ ) {
1101
+ logger.console.error(
1102
+ "[trails-sdk] Quote intentId has changed, waiting for latest quote",
1103
+ {
1104
+ current: latestResolvedIntentIdRef.current,
1105
+ prepareSend: quoteIntentId,
1106
+ },
1107
+ )
1108
+ setError("Quote is updating. Please wait for the latest quote.")
1109
+ return
1110
+ }
1111
+
988
1112
  setError(null)
989
1113
  setIsSubmitting(true)
990
1114
 
@@ -1063,28 +1187,28 @@ export function useSendForm({
1063
1187
  "[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] handleSend called, about to call send()",
1064
1188
  )
1065
1189
  logger.console.log(
1066
- "[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] selectedFeeToken value at send() call time:",
1190
+ "[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] selectedFeeOption value at send() call time:",
1067
1191
  {
1068
- selectedFeeToken,
1069
- isNull: selectedFeeToken === null,
1070
- isUndefined: selectedFeeToken === undefined,
1071
- type: typeof selectedFeeToken,
1072
- stringified: JSON.stringify(selectedFeeToken),
1192
+ selectedFeeOption,
1193
+ isNull: selectedFeeOption === null,
1194
+ isUndefined: selectedFeeOption === undefined,
1195
+ type: typeof selectedFeeOption,
1196
+ stringified: JsonEncode(selectedFeeOption),
1073
1197
  },
1074
1198
  )
1075
1199
  // Wait for full send to complete
1076
1200
  const {
1077
1201
  depositUserTxnReceipt,
1078
- originMetaTxnReceipt,
1079
- destinationMetaTxnReceipt,
1202
+ originIntentTransaction,
1203
+ destinationIntentTransaction,
1080
1204
  } = await send({
1081
1205
  onOriginSend,
1082
- selectedFeeToken: selectedFeeToken ?? undefined, // Pass current value at execution time
1206
+ selectedFeeOption: selectedFeeOption ?? null, // Pass current value at execution time
1083
1207
  })
1084
1208
  logger.console.log("[trails-sdk] send() completed, receipts:", {
1085
1209
  depositUserTxnReceipt,
1086
- originMetaTxnReceipt,
1087
- destinationMetaTxnReceipt,
1210
+ originIntentTransaction,
1211
+ destinationIntentTransaction,
1088
1212
  })
1089
1213
 
1090
1214
  // Move to receipt screen
@@ -1140,7 +1264,8 @@ export function useSendForm({
1140
1264
  onError,
1141
1265
  fundMethod,
1142
1266
  onNavigateToMeshConnect,
1143
- selectedFeeToken, // Include so handleSend captures latest value
1267
+ selectedFeeOption, // Include so handleSend captures latest value
1268
+ buildQuoteInputsFingerprint,
1144
1269
  ])
1145
1270
 
1146
1271
  const handleSubmit = async (e: React.FormEvent) => {
@@ -1329,8 +1454,8 @@ export function useSendForm({
1329
1454
  setRecipientInput,
1330
1455
  setSelectedDestinationChain,
1331
1456
  setSelectedDestToken,
1332
- setSelectedFeeToken,
1333
- feeOptions,
1457
+ setSelectedFeeOption,
1458
+ processedFeeOptions,
1334
1459
  supportedTokens,
1335
1460
  supportedChains,
1336
1461
  ensAddress: ensAddress ?? null,
@@ -1340,7 +1465,7 @@ export function useSendForm({
1340
1465
  destTokenPrices: destTokenPrices ?? null,
1341
1466
  sourceTokenPrices: sourceTokenPrices ?? null,
1342
1467
  selectedToken,
1343
- selectedFeeToken: selectedFeeToken ?? null,
1468
+ selectedFeeOption: selectedFeeOption ?? null,
1344
1469
  setIsChainDropdownOpen,
1345
1470
  setIsTokenDropdownOpen,
1346
1471
  toAmountFormatted: quotedDestinationAmount,
@@ -15,7 +15,11 @@ import {
15
15
  useHasSufficientBalanceUsd,
16
16
  useTokenBalances,
17
17
  } from "../../tokenBalances.js"
18
- import { getFormatttedTokenName, useSupportedTokens } from "../../tokens.js"
18
+ import {
19
+ getFormatttedTokenName,
20
+ useSupportedTokens,
21
+ useGetTokenImageUrl,
22
+ } from "../../tokens.js"
19
23
  import { useIndexerGatewayClient } from "../../indexerClient.js"
20
24
  import { useTrailsClient } from "../../trailsClient.js"
21
25
  import { useTokenPrices } from "../../prices.js"
@@ -137,6 +141,7 @@ export function useTokenList({
137
141
  const { address } = useAccount()
138
142
  const indexerGatewayClient = useIndexerGatewayClient()
139
143
  const trailsClient = useTrailsClient()
144
+ const { getTokenImageUrl } = useGetTokenImageUrl()
140
145
  const {
141
146
  sortedTokens: allSortedTokens,
142
147
  isLoadingSortedTokens,
@@ -536,7 +541,14 @@ export function useTokenList({
536
541
  if (tokenSymbol === "WETH") {
537
542
  imageContractAddress = zeroAddress
538
543
  }
539
- const imageUrl = `https://assets.sequence.info/images/tokens/small/${token.chainId}/${imageContractAddress}.webp`
544
+ const fallbackImageUrl =
545
+ (token as TokenBalanceWithPrice).contractInfo?.logoURI || ""
546
+ const imageUrl = getTokenImageUrl({
547
+ chainId: token.chainId,
548
+ contractAddress: imageContractAddress,
549
+ symbol: tokenSymbol,
550
+ fallbackImageUrl: fallbackImageUrl || undefined,
551
+ })
540
552
  const currentTokenName =
541
553
  (token as TokenBalanceWithPrice).contractInfo?.name || ""
542
554
  const tokenName = getFormatttedTokenName(
@@ -771,6 +783,7 @@ export function useTokenList({
771
783
  supportedTokens,
772
784
  supportedTokenPrices,
773
785
  searchQuery,
786
+ getTokenImageUrl,
774
787
  ])
775
788
 
776
789
  const showInsufficientBalance = useMemo(() => {
@@ -1,70 +1,98 @@
1
1
  import { SequenceHooksProvider } from "@0xsequence/hooks"
2
- import { createContext, useContext, useMemo } from "react"
2
+ import { createContext, useContext, useMemo, useEffect } from "react"
3
3
  import type { ComponentProps } from "react"
4
4
  import {
5
5
  DEFAULT_SLIPPAGE_TOLERANCE,
6
6
  DEFAULT_WALLETCONNECT_PROJECT_ID,
7
7
  PROD_SEQUENCE_INDEXER_URL,
8
8
  PROD_SEQUENCE_NODE_GATEWAY_URL,
9
- PROD_SEQUENCE_PROJECT_ACCESS_KEY,
9
+ PROD_SEQUENCE_METADATA_URL,
10
10
  PROD_TRAILS_API_URL,
11
11
  } from "../../constants.js"
12
+ import {
13
+ declareCommitHash,
14
+ setGlobalDebug,
15
+ updateGlobalConfig,
16
+ } from "../../config.js"
12
17
  import { logger } from "../../logger.js"
18
+ import { getQueryParam } from "../../queryParams.js"
13
19
 
14
20
  export type SequenceEnv = "prod" | "dev" | "local" | "cors-anywhere"
15
21
 
16
22
  export interface TrailsConfig {
17
23
  trailsApiKey: string
18
- trailsApiUrl?: string
19
- sequenceIndexerUrl?: string
20
- sequenceNodeGatewayUrl?: string
21
- walletConnectProjectId?: string
22
- slippageTolerance?: string | number
23
- debug?: boolean
24
- commitHash?: string
24
+ trailsApiUrl: string
25
+ sequenceIndexerUrl: string
26
+ sequenceNodeGatewayUrl: string
27
+ sequenceMetadataUrl: string
28
+ walletConnectProjectId: string
29
+ slippageTolerance: string | number
30
+ debug: boolean
31
+ commitHash: string | undefined
32
+ }
33
+
34
+ export interface TrailsProviderProps {
35
+ children: React.ReactNode
36
+ config: {
37
+ trailsApiKey: string
38
+ trailsApiUrl?: string
39
+ sequenceIndexerUrl?: string
40
+ sequenceNodeGatewayUrl?: string
41
+ sequenceMetadataUrl?: string
42
+ walletConnectProjectId?: string
43
+ slippageTolerance?: string | number
44
+ debug?: boolean
45
+ }
25
46
  }
26
47
 
27
48
  export const TrailsContext = createContext<TrailsConfig | null>(null)
28
49
 
29
- export const TrailsProvider = ({
30
- children,
31
- config = {
32
- trailsApiKey: PROD_SEQUENCE_PROJECT_ACCESS_KEY,
33
- },
34
- }: {
35
- children: React.ReactNode
36
- config?: TrailsConfig
37
- }) => {
50
+ export const TrailsProvider = ({ children, config }: TrailsProviderProps) => {
38
51
  // Populate the trails config with default values if not provided
39
52
  const trailsConfig: TrailsConfig = useMemo(() => {
40
53
  return {
41
- trailsApiKey: config.trailsApiKey || PROD_SEQUENCE_PROJECT_ACCESS_KEY,
54
+ trailsApiKey: config.trailsApiKey,
42
55
  trailsApiUrl: config.trailsApiUrl || PROD_TRAILS_API_URL,
43
56
  sequenceIndexerUrl:
44
57
  config.sequenceIndexerUrl || PROD_SEQUENCE_INDEXER_URL,
45
58
  sequenceNodeGatewayUrl:
46
59
  config.sequenceNodeGatewayUrl || PROD_SEQUENCE_NODE_GATEWAY_URL,
60
+ sequenceMetadataUrl:
61
+ config.sequenceMetadataUrl || PROD_SEQUENCE_METADATA_URL,
47
62
  walletConnectProjectId:
48
63
  config.walletConnectProjectId || DEFAULT_WALLETCONNECT_PROJECT_ID,
49
- slippageTolerance: config.slippageTolerance || DEFAULT_SLIPPAGE_TOLERANCE,
50
- debug: config.debug ?? false,
51
- commitHash: config.commitHash,
64
+ slippageTolerance: config.slippageTolerance ?? DEFAULT_SLIPPAGE_TOLERANCE,
65
+ debug: (config.debug ?? false) || getQueryParam("debug") === "true",
66
+ commitHash: declareCommitHash(),
52
67
  } satisfies TrailsConfig
53
68
  }, [
54
69
  config.trailsApiKey,
55
70
  config.trailsApiUrl,
56
71
  config.sequenceIndexerUrl,
72
+ config.sequenceMetadataUrl,
57
73
  config.sequenceNodeGatewayUrl,
58
74
  config.walletConnectProjectId,
59
75
  config.slippageTolerance,
60
76
  config.debug,
61
- config.commitHash,
62
77
  ])
63
78
 
79
+ // Set global debug mode for backward compatibility with getDebug()
80
+ useEffect(() => {
81
+ setGlobalDebug(trailsConfig.debug ?? false)
82
+ }, [trailsConfig.debug])
83
+
84
+ // Update global config when trails config changes (temporary solution)
85
+ useEffect(() => {
86
+ updateGlobalConfig({
87
+ trailsApiKey: trailsConfig.trailsApiKey,
88
+ sequenceNodeGatewayUrl: trailsConfig.sequenceNodeGatewayUrl,
89
+ })
90
+ }, [trailsConfig.trailsApiKey, trailsConfig.sequenceNodeGatewayUrl])
91
+
64
92
  // Create a sequence hooks config based on the trails config
65
93
  const sequenceHooksConfig = useMemo(() => {
66
94
  return {
67
- projectAccessKey: trailsConfig.trailsApiKey,
95
+ projectAccessKey: trailsConfig.trailsApiKey || "",
68
96
  env: {
69
97
  apiUrl: trailsConfig.trailsApiUrl,
70
98
  indexerUrl: trailsConfig.sequenceIndexerUrl,
@@ -84,7 +112,7 @@ export const TrailsProvider = ({
84
112
  )
85
113
  }
86
114
 
87
- export const useTrails = () => {
115
+ export const useTrails = (): Readonly<TrailsConfig> => {
88
116
  const context = useContext(TrailsContext)
89
117
 
90
118
  if (!context) {