0xtrails 0.2.2 → 0.2.5

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 (194) hide show
  1. package/dist/aave.d.ts +8 -0
  2. package/dist/aave.d.ts.map +1 -1
  3. package/dist/{ccip-ConT1gDe.js → ccip-CXlshvBY.js} +1 -1
  4. package/dist/chains.d.ts +5 -1
  5. package/dist/chains.d.ts.map +1 -1
  6. package/dist/config.d.ts +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/constants.d.ts +5 -4
  9. package/dist/constants.d.ts.map +1 -1
  10. package/dist/error.d.ts +1 -0
  11. package/dist/error.d.ts.map +1 -1
  12. package/dist/estimate.d.ts +52 -0
  13. package/dist/estimate.d.ts.map +1 -1
  14. package/dist/{index-CMh8uEbV.js → index-_QuyGrjU.js} +86304 -83380
  15. package/dist/index.d.ts +4 -3
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2 -2
  18. package/dist/intentEntrypoint.d.ts +0 -8
  19. package/dist/intentEntrypoint.d.ts.map +1 -1
  20. package/dist/intents.d.ts +40 -0
  21. package/dist/intents.d.ts.map +1 -1
  22. package/dist/metaTxnMonitor.d.ts +5 -4
  23. package/dist/metaTxnMonitor.d.ts.map +1 -1
  24. package/dist/metaTxns.d.ts +3 -3
  25. package/dist/metaTxns.d.ts.map +1 -1
  26. package/dist/morpho.d.ts +8 -0
  27. package/dist/morpho.d.ts.map +1 -1
  28. package/dist/prepareSend.d.ts +16 -6
  29. package/dist/prepareSend.d.ts.map +1 -1
  30. package/dist/queryParams.d.ts.map +1 -1
  31. package/dist/relayer.d.ts +10 -7
  32. package/dist/relayer.d.ts.map +1 -1
  33. package/dist/sequenceWallet.d.ts +3 -2
  34. package/dist/sequenceWallet.d.ts.map +1 -1
  35. package/dist/tokenBalances.d.ts +7 -0
  36. package/dist/tokenBalances.d.ts.map +1 -1
  37. package/dist/tokens.d.ts +2 -1
  38. package/dist/tokens.d.ts.map +1 -1
  39. package/dist/trails.d.ts +2 -2
  40. package/dist/trails.d.ts.map +1 -1
  41. package/dist/wallets.d.ts.map +1 -1
  42. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  43. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  44. package/dist/widget/components/ClassicSwap.d.ts +2 -0
  45. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  46. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  47. package/dist/widget/components/ConnectedWallets.d.ts +4 -0
  48. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
  49. package/dist/widget/components/Earn.d.ts.map +1 -1
  50. package/dist/widget/components/EarnPools.d.ts.map +1 -1
  51. package/dist/widget/components/Fund.d.ts +1 -0
  52. package/dist/widget/components/Fund.d.ts.map +1 -1
  53. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  54. package/dist/widget/components/{FundSendForm.d.ts → FundSwap.d.ts} +11 -5
  55. package/dist/widget/components/FundSwap.d.ts.map +1 -0
  56. package/dist/widget/components/FundingMethodSelectorButton.d.ts +4 -0
  57. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -0
  58. package/dist/widget/components/Modal.d.ts.map +1 -1
  59. package/dist/widget/components/Pay.d.ts +1 -0
  60. package/dist/widget/components/Pay.d.ts.map +1 -1
  61. package/dist/widget/components/PercentageMaxButtons.d.ts +12 -0
  62. package/dist/widget/components/PercentageMaxButtons.d.ts.map +1 -0
  63. package/dist/widget/components/{PaySendForm.d.ts → PoolDeposit.d.ts} +11 -34
  64. package/dist/widget/components/PoolDeposit.d.ts.map +1 -0
  65. package/dist/widget/components/{SimpleSwap.d.ts → PoolWithdraw.d.ts} +16 -8
  66. package/dist/widget/components/PoolWithdraw.d.ts.map +1 -0
  67. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  68. package/dist/widget/components/Receive.d.ts.map +1 -1
  69. package/dist/widget/components/RecipientSelectorButton.d.ts +4 -0
  70. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -0
  71. package/dist/widget/components/Recipients.d.ts.map +1 -1
  72. package/dist/widget/components/RefundWarning.d.ts +1 -0
  73. package/dist/widget/components/RefundWarning.d.ts.map +1 -1
  74. package/dist/widget/components/RequiredPropsError.d.ts +8 -0
  75. package/dist/widget/components/RequiredPropsError.d.ts.map +1 -0
  76. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  77. package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
  78. package/dist/widget/components/Swap.d.ts +1 -0
  79. package/dist/widget/components/Swap.d.ts.map +1 -1
  80. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  81. package/dist/widget/components/TokenImage.d.ts +1 -0
  82. package/dist/widget/components/TokenImage.d.ts.map +1 -1
  83. package/dist/widget/components/TokenList.d.ts.map +1 -1
  84. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  85. package/dist/widget/components/TokenSelectorButton.d.ts +16 -0
  86. package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -0
  87. package/dist/widget/components/UserPreferences.d.ts.map +1 -1
  88. package/dist/widget/components/WaasFeeOptions.d.ts +8 -0
  89. package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -0
  90. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  91. package/dist/widget/components/WalletList.d.ts.map +1 -1
  92. package/dist/widget/css/compiled.css +2 -0
  93. package/dist/widget/css/index.css +554 -0
  94. package/dist/widget/hooks/useBack.d.ts +6 -0
  95. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  96. package/dist/widget/hooks/useCheckout.d.ts +1 -1
  97. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  98. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  99. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  100. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +3 -3
  101. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
  102. package/dist/widget/hooks/useInitialRedirect.d.ts +7 -0
  103. package/dist/widget/hooks/useInitialRedirect.d.ts.map +1 -0
  104. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -1
  105. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -1
  106. package/dist/widget/hooks/useSelectedFundMethod.d.ts +12 -0
  107. package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -0
  108. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -1
  109. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  110. package/dist/widget/index.js +1 -1
  111. package/dist/widget/widget.d.ts +4 -4
  112. package/dist/widget/widget.d.ts.map +1 -1
  113. package/package.json +30 -23
  114. package/src/aave.ts +32 -0
  115. package/src/chains.ts +23 -3
  116. package/src/config.ts +12 -4
  117. package/src/constants.ts +11 -16
  118. package/src/error.ts +20 -2
  119. package/src/estimate.ts +416 -5
  120. package/src/index.ts +8 -3
  121. package/src/intentEntrypoint.ts +0 -15
  122. package/src/intents.ts +161 -11
  123. package/src/metaTxnMonitor.ts +28 -22
  124. package/src/metaTxns.ts +3 -3
  125. package/src/morpho.ts +32 -0
  126. package/src/prepareSend.ts +710 -458
  127. package/src/queryParams.ts +2 -1
  128. package/src/relayer.ts +15 -16
  129. package/src/sequenceWallet.ts +7 -3
  130. package/src/tokenBalances.ts +47 -0
  131. package/src/tokens.ts +17 -1
  132. package/src/trails.ts +2 -2
  133. package/src/wallets.ts +8 -0
  134. package/src/widget/compiled.css +2 -2
  135. package/src/widget/components/AccountActionsDropdown.tsx +9 -15
  136. package/src/widget/components/AccountIntentTransactionHistory.tsx +1 -1
  137. package/src/widget/components/AccountSettings.tsx +10 -27
  138. package/src/widget/components/ChainFilterDropdown.tsx +1 -1
  139. package/src/widget/components/ChainList.tsx +1 -1
  140. package/src/widget/components/ClassicSwap.tsx +111 -155
  141. package/src/widget/components/ConnectWallet.tsx +10 -39
  142. package/src/widget/components/ConnectedWallets.tsx +113 -58
  143. package/src/widget/components/Earn.tsx +73 -589
  144. package/src/widget/components/EarnPools.tsx +2 -1
  145. package/src/widget/components/Fund.tsx +81 -109
  146. package/src/widget/components/FundMethods.tsx +82 -159
  147. package/src/widget/components/FundSwap.tsx +52 -0
  148. package/src/widget/components/FundingMethodSelectorButton.tsx +60 -0
  149. package/src/widget/components/Modal.tsx +6 -2
  150. package/src/widget/components/Pay.tsx +198 -200
  151. package/src/widget/components/PercentageMaxButtons.tsx +77 -0
  152. package/src/widget/components/PoolDeposit.tsx +593 -0
  153. package/src/widget/components/PoolWithdraw.tsx +903 -0
  154. package/src/widget/components/QuoteDetails.tsx +22 -8
  155. package/src/widget/components/Receive.tsx +1 -3
  156. package/src/widget/components/RecipientSelectorButton.tsx +42 -0
  157. package/src/widget/components/Recipients.tsx +64 -156
  158. package/src/widget/components/RefundWarning.tsx +5 -1
  159. package/src/widget/components/RequiredPropsError.tsx +33 -0
  160. package/src/widget/components/ScreenHeader.tsx +5 -1
  161. package/src/widget/components/SlippageToleranceSettings.tsx +2 -1
  162. package/src/widget/components/Swap.tsx +2 -43
  163. package/src/widget/components/SwapSettings.tsx +3 -15
  164. package/src/widget/components/TokenImage.tsx +21 -4
  165. package/src/widget/components/TokenList.tsx +0 -1
  166. package/src/widget/components/TokenSelector.tsx +2 -1
  167. package/src/widget/components/TokenSelectorButton.tsx +75 -0
  168. package/src/widget/components/UserPreferences.tsx +6 -24
  169. package/src/widget/components/WaasFeeOptions.tsx +331 -0
  170. package/src/widget/components/WalletConfirmation.tsx +55 -3
  171. package/src/widget/components/WalletList.tsx +7 -5
  172. package/src/widget/hooks/useBack.tsx +113 -9
  173. package/src/widget/hooks/useCheckout.ts +36 -20
  174. package/src/widget/hooks/useCurrentScreen.tsx +1 -0
  175. package/src/widget/hooks/useDefaultTokenSelection.tsx +104 -28
  176. package/src/widget/hooks/useInitialRedirect.tsx +70 -0
  177. package/src/widget/hooks/usePayMessage.tsx +86 -11
  178. package/src/widget/hooks/useSelectedFeeToken.tsx +10 -16
  179. package/src/widget/hooks/useSelectedFundMethod.tsx +41 -0
  180. package/src/widget/hooks/useSelectedRecipient.tsx +10 -0
  181. package/src/widget/hooks/useSendForm.ts +34 -12
  182. package/src/widget/hooks/useTokenList.ts +1 -1
  183. package/src/widget/index.css +27 -0
  184. package/src/widget/widget.tsx +245 -208
  185. package/dist/widget/components/FundSendForm.d.ts.map +0 -1
  186. package/dist/widget/components/PaySendForm.d.ts.map +0 -1
  187. package/dist/widget/components/SimpleSwap.d.ts.map +0 -1
  188. package/dist/widget/hooks/useSwapSettings.d.ts +0 -16
  189. package/dist/widget/hooks/useSwapSettings.d.ts.map +0 -1
  190. package/src/widget/components/FundSendForm.tsx +0 -903
  191. package/src/widget/components/PaySendForm.tsx +0 -869
  192. package/src/widget/components/SimpleSwap.tsx +0 -983
  193. package/src/widget/hooks/useSwapSettings.tsx +0 -100
  194. /package/dist/{style.css → 0xtrails.css} +0 -0
@@ -1,12 +1,10 @@
1
1
  import { Settings } from "lucide-react"
2
2
  import type React from "react"
3
3
  import { useEffect, useRef, useState } from "react"
4
- import { SwapDisplayMode } from "./SwapDisplayMode.js"
4
+ // import { SwapDisplayMode } from "./SwapDisplayMode.js"
5
5
  import { SlippageToleranceSettings } from "./SlippageToleranceSettings.js"
6
- import { useSwapSettings } from "../hooks/useSwapSettings.js"
7
6
 
8
7
  export const SwapSettings: React.FC = () => {
9
- const { isSimpleSwapMode, setIsSimpleSwapModeWithStorage } = useSwapSettings()
10
8
  const [isSettingsDropdownOpen, setIsSettingsDropdownOpen] = useState(false)
11
9
  const settingsDropdownRef = useRef<HTMLDivElement>(null)
12
10
 
@@ -27,13 +25,8 @@ export const SwapSettings: React.FC = () => {
27
25
  }
28
26
  }, [isSettingsDropdownOpen])
29
27
 
30
- const handleModeSelect = (isSimple: boolean) => {
31
- setIsSimpleSwapModeWithStorage(isSimple)
32
- setIsSettingsDropdownOpen(false)
33
- }
34
-
35
28
  return (
36
- <div className="relative" ref={settingsDropdownRef}>
29
+ <div ref={settingsDropdownRef}>
37
30
  <button
38
31
  type="button"
39
32
  onClick={() => setIsSettingsDropdownOpen(!isSettingsDropdownOpen)}
@@ -47,12 +40,7 @@ export const SwapSettings: React.FC = () => {
47
40
  {isSettingsDropdownOpen && (
48
41
  <div className="absolute right-0 top-full mt-2 w-80 trails-bg-card rounded-lg shadow-lg border trails-border-primary z-20">
49
42
  <div className="p-4 space-y-4">
50
- <SwapDisplayMode
51
- isSimpleMode={isSimpleSwapMode}
52
- onModeChange={handleModeSelect}
53
- />
54
-
55
- <div className="border-t border-gray-200 dark:border-gray-700 pt-4">
43
+ <div>
56
44
  <SlippageToleranceSettings />
57
45
  </div>
58
46
  </div>
@@ -1,19 +1,28 @@
1
1
  import React, { useMemo } from "react"
2
2
  import { ChainImage } from "./ChainImage.js"
3
3
  import { getCommonTokenImageUrl } from "../../tokens.js"
4
+ import { logger } from "../../logger.js"
4
5
 
5
6
  function normalizeImageUrl(
6
7
  imageUrl?: string | null,
7
8
  symbol?: string | null,
8
9
  chainId?: number | null,
10
+ contractAddress?: string | null,
9
11
  ) {
10
12
  if (symbol === "ETH") {
11
13
  return "https://assets.sequence.info/images/tokens/large/1/0x0000000000000000000000000000000000000000.webp"
12
14
  }
15
+ const commonImageUrl = getCommonTokenImageUrl({
16
+ symbol,
17
+ chainId,
18
+ contractAddress,
19
+ })
20
+ if (commonImageUrl) {
21
+ return commonImageUrl
22
+ }
23
+
13
24
  if (imageUrl) {
14
25
  return imageUrl.replace("/small/", "/large/")
15
- } else if (symbol) {
16
- return getCommonTokenImageUrl({ symbol, chainId })
17
26
  }
18
27
 
19
28
  return null
@@ -23,6 +32,7 @@ interface TokenImageProps {
23
32
  imageUrl?: string | null
24
33
  symbol?: string | null
25
34
  chainId?: number | null
35
+ contractAddress?: string | null
26
36
  size?: number
27
37
  }
28
38
 
@@ -30,12 +40,19 @@ export const TokenImage: React.FC<TokenImageProps> = ({
30
40
  imageUrl,
31
41
  symbol,
32
42
  chainId,
43
+ contractAddress,
33
44
  size = 24,
34
45
  }) => {
35
46
  const [imageError, setImageError] = React.useState(false)
36
47
  const effectiveImageUrl = useMemo(() => {
37
- return normalizeImageUrl(imageUrl, symbol, chainId)
38
- }, [imageUrl, symbol, chainId])
48
+ logger.console.log("[TokenImage] normalizeImageUrl", {
49
+ imageUrl,
50
+ symbol,
51
+ chainId,
52
+ contractAddress,
53
+ })
54
+ return normalizeImageUrl(imageUrl, symbol, chainId, contractAddress)
55
+ }, [imageUrl, symbol, chainId, contractAddress])
39
56
 
40
57
  const displaySymbol = symbol?.[0]?.toUpperCase() || "?"
41
58
 
@@ -72,7 +72,6 @@ export const TokenList: React.FC<TokenListProps> = ({
72
72
  <div className="space-y-2">
73
73
  <ScreenHeader
74
74
  onBack={mode !== "fund" ? onBack : undefined}
75
- showAccountActions={true}
76
75
  headerContent={
77
76
  mode === "fund"
78
77
  ? "Fund with any token"
@@ -120,7 +120,7 @@ export const TokenSelector: React.FC<TokenSelectorProps> = ({
120
120
  // Then apply search filtering
121
121
  if (!searchQuery.trim()) return true
122
122
 
123
- const query = searchQuery.toLowerCase()
123
+ const query = searchQuery.trim().toLowerCase()
124
124
  return (
125
125
  recentToken.symbol.toLowerCase().includes(query) ||
126
126
  recentToken.name.toLowerCase().includes(query) ||
@@ -295,6 +295,7 @@ export const TokenSelector: React.FC<TokenSelectorProps> = ({
295
295
  symbol={symbol}
296
296
  imageUrl={imageUrl}
297
297
  chainId={chainId}
298
+ contractAddress={contractAddress}
298
299
  size={32}
299
300
  />
300
301
  ) : (
@@ -0,0 +1,75 @@
1
+ import { ChevronRight } from "lucide-react"
2
+ import type React from "react"
3
+ import { useMemo } from "react"
4
+ import { TokenImage } from "./TokenImage.js"
5
+ import { getChainInfo } from "../../chains.js"
6
+
7
+ interface TokenSelectorButtonProps {
8
+ token?: {
9
+ symbol: string
10
+ imageUrl?: string
11
+ contractAddress?: string
12
+ chainId?: number | null
13
+ } | null
14
+ chainId?: number | null
15
+ onSelect: () => void
16
+ className?: string
17
+ unselectable?: boolean
18
+ }
19
+
20
+ export const TokenSelectorButton: React.FC<TokenSelectorButtonProps> = ({
21
+ token,
22
+ chainId,
23
+ onSelect,
24
+ className = "flex items-center space-x-2 trails-border-radius-input px-2.5 py-1.5 border transition-colors",
25
+ unselectable = false,
26
+ }) => {
27
+ const displayChainId = token?.chainId || chainId
28
+
29
+ const chainInfo = useMemo(() => {
30
+ return displayChainId ? getChainInfo(displayChainId) : null
31
+ }, [displayChainId])
32
+
33
+ return (
34
+ <button
35
+ type="button"
36
+ onClick={unselectable ? undefined : onSelect}
37
+ disabled={unselectable}
38
+ className={`${className} ${
39
+ token
40
+ ? "trails-bg-card hover:trails-hover-bg trails-border-primary"
41
+ : "bg-blue-500 hover:bg-blue-600 border-blue-500 text-white"
42
+ } ${unselectable ? "cursor-default" : "cursor-pointer"}`}
43
+ >
44
+ {token ? (
45
+ <>
46
+ <TokenImage
47
+ symbol={token.symbol}
48
+ imageUrl={token.imageUrl}
49
+ chainId={displayChainId}
50
+ contractAddress={token.contractAddress}
51
+ size={28}
52
+ />
53
+ <div className="flex flex-col items-start">
54
+ <span className="font-bold trails-text-primary text-sm">
55
+ {token.symbol}
56
+ </span>
57
+ {chainInfo && (
58
+ <span className="text-xs trails-text-muted">
59
+ {chainInfo.name}
60
+ </span>
61
+ )}
62
+ </div>
63
+ {!unselectable && (
64
+ <ChevronRight className="w-3.5 h-3.5 trails-text-muted" />
65
+ )}
66
+ </>
67
+ ) : (
68
+ <>
69
+ <span className="font-medium text-sm text-white">Select Token</span>
70
+ {!unselectable && <ChevronRight className="w-3.5 h-3.5 text-white" />}
71
+ </>
72
+ )}
73
+ </button>
74
+ )
75
+ }
@@ -1,10 +1,10 @@
1
1
  import { ChevronDown, RotateCcw } from "lucide-react"
2
2
  import type React from "react"
3
3
  import { useBalanceVisible } from "../hooks/useBalanceVisible.js"
4
- import { useSwapSettings } from "../hooks/useSwapSettings.js"
5
4
  import { useThemePreference } from "../hooks/useTheme.js"
6
5
  import { useWidgetProps } from "../hooks/useWidgetProps.js"
7
6
  import { ScreenHeader } from "./ScreenHeader.js"
7
+ import { SlippageToleranceSettings } from "./SlippageToleranceSettings.js"
8
8
  import { logger } from "../../logger.js"
9
9
 
10
10
  interface UserPreferencesProps {
@@ -14,7 +14,6 @@ interface UserPreferencesProps {
14
14
  export const UserPreferences: React.FC<UserPreferencesProps> = ({ onBack }) => {
15
15
  const { isBalanceVisible, toggleBalanceVisible, resetBalanceVisible } =
16
16
  useBalanceVisible()
17
- const { resetSwapSettings } = useSwapSettings()
18
17
  const { selectedTheme, setSelectedTheme, resetThemePreference } =
19
18
  useThemePreference()
20
19
  const { customCss, theme: widgetTheme } = useWidgetProps()
@@ -31,7 +30,6 @@ export const UserPreferences: React.FC<UserPreferencesProps> = ({ onBack }) => {
31
30
  try {
32
31
  // Reset all preferences using their respective hook methods
33
32
  resetBalanceVisible()
34
- resetSwapSettings()
35
33
  resetThemePreference()
36
34
 
37
35
  logger.console.log(
@@ -51,7 +49,6 @@ export const UserPreferences: React.FC<UserPreferencesProps> = ({ onBack }) => {
51
49
  headerContent="User Preferences"
52
50
  headerContentAlign="left"
53
51
  onBack={onBack}
54
- showAccountActions={false}
55
52
  />
56
53
 
57
54
  <div className="space-y-6">
@@ -75,26 +72,6 @@ export const UserPreferences: React.FC<UserPreferencesProps> = ({ onBack }) => {
75
72
  </button>
76
73
  </div>
77
74
 
78
- {/* Simple Swap UI Setting */}
79
- {/* <div className="flex items-center justify-between">
80
- <span className="text-sm font-medium text-gray-900 dark:text-gray-100">
81
- Simple Swap UI
82
- </span>
83
- <button
84
- type="button"
85
- onClick={toggleSimpleSwapMode}
86
- className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${
87
- isSimpleSwapMode ? "bg-blue-600" : "bg-gray-200 dark:bg-gray-700"
88
- }`}
89
- >
90
- <span
91
- className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
92
- isSimpleSwapMode ? "translate-x-6" : "translate-x-1"
93
- }`}
94
- />
95
- </button>
96
- </div> */}
97
-
98
75
  {/* Theme Setting */}
99
76
  <div className="flex items-center justify-between">
100
77
  <span
@@ -136,6 +113,11 @@ export const UserPreferences: React.FC<UserPreferencesProps> = ({ onBack }) => {
136
113
  </div>
137
114
  </div>
138
115
 
116
+ {/* Slippage Tolerance Settings */}
117
+ <div className="pt-2">
118
+ <SlippageToleranceSettings />
119
+ </div>
120
+
139
121
  {/* Reset Preferences Button */}
140
122
  <div className="pt-4 dark:border-gray-700">
141
123
  <button
@@ -0,0 +1,331 @@
1
+ import { useEffect, useState } from "react"
2
+ import { useWaasFeeOptions } from "@0xsequence/connect"
3
+ import { TokenImage } from "./TokenImage.js"
4
+ import { ChevronDown, ChevronUp } from "lucide-react"
5
+ import { formatUnits } from "viem"
6
+
7
+ // Define types based on the @0xsequence/connect documentation
8
+ interface FeeToken {
9
+ symbol: string
10
+ contractAddress?: string
11
+ name?: string
12
+ decimals?: number
13
+ imageUrl?: string
14
+ }
15
+
16
+ interface FeeOption {
17
+ token: FeeToken
18
+ amount?: string
19
+ amountUSD?: number
20
+ }
21
+
22
+ interface FeeOptionExtended extends FeeOption {
23
+ balance: string
24
+ balanceFormatted: string
25
+ hasEnoughBalanceForFee: boolean
26
+ }
27
+
28
+ interface WaasFeeOptionsProps {
29
+ chainId?: number
30
+ setIsFeeOptionConfirmed: (isFeeOptionConfirmed: boolean) => void
31
+ onFeeOptionsLoaded?: () => void
32
+ }
33
+
34
+ export const WaasFeeOptions: React.FC<WaasFeeOptionsProps> = ({
35
+ chainId,
36
+ setIsFeeOptionConfirmed,
37
+ onFeeOptionsLoaded,
38
+ }) => {
39
+ const [
40
+ pendingFeeOptionConfirmation,
41
+ confirmPendingFeeOption,
42
+ rejectPendingFeeOption,
43
+ ] = useWaasFeeOptions({ chainIdOverride: chainId })
44
+
45
+ const [selectedFeeOptionTokenName, setSelectedFeeOptionTokenName] =
46
+ useState<string>()
47
+ const [isExpanded, setIsExpanded] = useState(false)
48
+ const [isProcessing, setIsProcessing] = useState(false)
49
+ const [isLoading, setIsLoading] = useState(false)
50
+
51
+ // Debug logging
52
+ useEffect(() => {
53
+ console.log("[trails-sdk] WaasFeeOptions component mounted/updated:", {
54
+ chainId,
55
+ pendingFeeOptionConfirmation: !!pendingFeeOptionConfirmation,
56
+ optionsCount: pendingFeeOptionConfirmation?.options?.length || 0,
57
+ hasOptions: !!pendingFeeOptionConfirmation?.options,
58
+ confirmationId: pendingFeeOptionConfirmation?.id,
59
+ isLoading,
60
+ isProcessing,
61
+ })
62
+ }, [chainId, pendingFeeOptionConfirmation, isLoading, isProcessing])
63
+
64
+ // Log when component renders but has no pending confirmation
65
+ useEffect(() => {
66
+ if (!pendingFeeOptionConfirmation) {
67
+ console.log(
68
+ "[trails-sdk] WaasFeeOptions: No pending fee confirmation - this is normal until a transaction requires fee payment",
69
+ )
70
+ }
71
+ }, [pendingFeeOptionConfirmation])
72
+
73
+ // Manage loading state
74
+ useEffect(() => {
75
+ if (
76
+ pendingFeeOptionConfirmation &&
77
+ pendingFeeOptionConfirmation.options?.length > 0
78
+ ) {
79
+ setIsLoading(false)
80
+ } else if (
81
+ pendingFeeOptionConfirmation &&
82
+ (!pendingFeeOptionConfirmation.options ||
83
+ pendingFeeOptionConfirmation.options.length === 0)
84
+ ) {
85
+ setIsLoading(true)
86
+ }
87
+ }, [pendingFeeOptionConfirmation])
88
+
89
+ // Initialize with first option when fee options become available
90
+ useEffect(() => {
91
+ if (pendingFeeOptionConfirmation) {
92
+ console.log(
93
+ "[trails-sdk] Pending fee options: ",
94
+ pendingFeeOptionConfirmation.options,
95
+ )
96
+
97
+ // Notify parent that fee options are loaded
98
+ if (
99
+ onFeeOptionsLoaded &&
100
+ pendingFeeOptionConfirmation.options?.length > 0
101
+ ) {
102
+ onFeeOptionsLoaded()
103
+ }
104
+
105
+ // Select the first fee option by default
106
+ if (pendingFeeOptionConfirmation.options.length > 0) {
107
+ const firstOption = pendingFeeOptionConfirmation.options.filter(
108
+ (option: any) => option.hasEnoughBalanceForFee,
109
+ )[0]
110
+ if (firstOption?.token?.symbol) {
111
+ setSelectedFeeOptionTokenName(firstOption.token.symbol)
112
+ }
113
+ }
114
+ }
115
+ }, [pendingFeeOptionConfirmation, onFeeOptionsLoaded])
116
+
117
+ // Handle fee option selection and confirmation
118
+ const handleConfirmFee = async (tokenAddress: string | null) => {
119
+ if (pendingFeeOptionConfirmation && !isProcessing) {
120
+ setIsProcessing(true)
121
+ try {
122
+ confirmPendingFeeOption(pendingFeeOptionConfirmation.id, tokenAddress)
123
+ setIsFeeOptionConfirmed(true)
124
+ } finally {
125
+ setIsProcessing(false)
126
+ }
127
+ }
128
+ }
129
+
130
+ // Handle fee option rejection
131
+ const handleRejectFee = async () => {
132
+ if (pendingFeeOptionConfirmation && !isProcessing) {
133
+ setIsProcessing(true)
134
+ try {
135
+ rejectPendingFeeOption(pendingFeeOptionConfirmation.id)
136
+ } finally {
137
+ setIsProcessing(false)
138
+ }
139
+ }
140
+ }
141
+
142
+ // Don't render if no pending fee confirmation
143
+ if (!pendingFeeOptionConfirmation) {
144
+ return null
145
+ }
146
+
147
+ // Show loading when we have confirmation but no options yet
148
+ if (isLoading) {
149
+ return (
150
+ <div className="flex items-center justify-center py-8">
151
+ <div className="flex flex-col items-center space-y-3">
152
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-trails-primary"></div>
153
+ <p className="text-sm trails-text-muted">Loading fee options...</p>
154
+ </div>
155
+ </div>
156
+ )
157
+ }
158
+
159
+ const selectedOption = pendingFeeOptionConfirmation.options
160
+ .filter((option: any) => option.hasEnoughBalanceForFee)
161
+ .find((option) => option.token.symbol === selectedFeeOptionTokenName) as
162
+ | FeeOptionExtended
163
+ | undefined
164
+
165
+ return (
166
+ <div className="space-y-2">
167
+ {/* Header */}
168
+ <div className="flex items-center justify-between">
169
+ <div className="text-sm font-medium trails-text-primary">
170
+ Select Fee Payment Token
171
+ </div>
172
+ <button
173
+ type="button"
174
+ onClick={() => setIsExpanded(!isExpanded)}
175
+ className="flex items-center space-x-1 text-xs trails-text-muted hover:trails-text-primary transition-colors cursor-pointer"
176
+ >
177
+ <span>{isExpanded ? "Hide" : "Show"} options</span>
178
+ {isExpanded ? (
179
+ <ChevronUp className="w-3 h-3" />
180
+ ) : (
181
+ <ChevronDown className="w-3 h-3" />
182
+ )}
183
+ </button>
184
+ </div>
185
+
186
+ {/* Selected Option Display */}
187
+ {selectedOption?.hasEnoughBalanceForFee && (
188
+ <div className="trails-bg-secondary trails-border-radius-container p-3">
189
+ <div className="flex items-center justify-between">
190
+ <div className="flex items-center space-x-2">
191
+ <TokenImage
192
+ symbol={selectedOption.token.symbol}
193
+ imageUrl={(selectedOption.token as any).logoURL}
194
+ chainId={chainId}
195
+ size={20}
196
+ />
197
+ <div className="text-left">
198
+ <div className="font-medium trails-text-primary text-sm">
199
+ {selectedOption.token.symbol}
200
+ </div>
201
+ <div className="text-xs trails-text-muted">
202
+ {selectedOption.token.contractAddress
203
+ ? `${selectedOption.token.contractAddress.slice(0, 6)}...${selectedOption.token.contractAddress.slice(-4)}`
204
+ : "Native Token"}
205
+ </div>
206
+ </div>
207
+ </div>
208
+ {/* Display balance info if available */}
209
+ {selectedOption && "balanceFormatted" in selectedOption && (
210
+ <div className="text-right">
211
+ <div className="text-xs trails-text-muted">
212
+ Balance: {String(selectedOption.balanceFormatted)}
213
+ </div>
214
+ {!selectedOption.hasEnoughBalanceForFee && (
215
+ <div className="text-xs text-red-500">
216
+ Insufficient balance
217
+ </div>
218
+ )}
219
+ </div>
220
+ )}
221
+ </div>
222
+ </div>
223
+ )}
224
+
225
+ {/* Expanded Options */}
226
+ {isExpanded && (
227
+ <div className="space-y-2">
228
+ {pendingFeeOptionConfirmation.options
229
+ .filter((option: any) => option.hasEnoughBalanceForFee)
230
+ .map((option) => (
231
+ <label
232
+ key={option.token.symbol || option.token.contractAddress}
233
+ className={`flex items-center space-x-3 p-3 rounded-lg border cursor-pointer transition-colors ${
234
+ selectedFeeOptionTokenName === option.token.symbol
235
+ ? "trails-bg-primary/10 border-trails-primary"
236
+ : "trails-bg-secondary hover:trails-hover-bg border-transparent"
237
+ }`}
238
+ >
239
+ <input
240
+ type="radio"
241
+ name="feeOption"
242
+ checked={selectedFeeOptionTokenName === option.token.symbol}
243
+ onChange={() =>
244
+ setSelectedFeeOptionTokenName(option.token.symbol)
245
+ }
246
+ className="w-4 h-4 text-trails-primary focus:ring-trails-primary"
247
+ />
248
+ <div className="flex items-center space-x-2 flex-1">
249
+ <TokenImage
250
+ symbol={option.token.symbol}
251
+ imageUrl={(option.token as any).logoURL}
252
+ chainId={chainId}
253
+ size={20}
254
+ />
255
+ <div className="flex-1 text-left">
256
+ <div className="font-medium trails-text-primary text-sm">
257
+ {option.token.symbol}
258
+ </div>
259
+ <div className="text-xs trails-text-muted">
260
+ {option.token.contractAddress
261
+ ? `${option.token.contractAddress.slice(0, 6)}...${option.token.contractAddress.slice(-4)}`
262
+ : "Native Token"}
263
+ </div>
264
+ </div>
265
+ </div>
266
+ {/* Display balance info if available */}
267
+ {"balanceFormatted" in option && (
268
+ <div className="text-right">
269
+ <div className="text-xs trails-text-muted">
270
+ Balance: {String(option.balanceFormatted)}
271
+ </div>
272
+ <div className="text-xs trails-text-muted">
273
+ Cost:{" "}
274
+ {String(
275
+ formatUnits(
276
+ BigInt(option.value),
277
+ option.token.decimals || 18,
278
+ ),
279
+ )}
280
+ </div>
281
+ {!("hasEnoughBalanceForFee" in option) ||
282
+ !option.hasEnoughBalanceForFee ? (
283
+ <div className="text-xs text-red-500">Insufficient</div>
284
+ ) : null}
285
+ </div>
286
+ )}
287
+ </label>
288
+ ))}
289
+ </div>
290
+ )}
291
+
292
+ {/* Action Buttons */}
293
+ <div className="flex space-x-3">
294
+ <button
295
+ type="button"
296
+ onClick={() =>
297
+ handleConfirmFee(selectedOption?.token.contractAddress || null)
298
+ }
299
+ disabled={!selectedOption || isProcessing}
300
+ className="flex-1 py-3 px-6 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-300 disabled:text-gray-500 text-white font-semibold rounded-lg transition-all duration-200 cursor-pointer disabled:cursor-not-allowed shadow-sm hover:shadow-md disabled:shadow-none flex items-center justify-center"
301
+ >
302
+ {isProcessing ? (
303
+ <>
304
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
305
+ Processing...
306
+ </>
307
+ ) : (
308
+ "Confirm Fee"
309
+ )}
310
+ </button>
311
+ <button
312
+ type="button"
313
+ onClick={handleRejectFee}
314
+ disabled={isProcessing}
315
+ className="px-6 py-3 bg-gray-100 hover:bg-gray-200 disabled:bg-gray-50 text-gray-700 font-semibold rounded-lg transition-all duration-200 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 flex items-center justify-center border border-gray-300"
316
+ >
317
+ {isProcessing ? (
318
+ <>
319
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-500 mr-2"></div>
320
+ Processing...
321
+ </>
322
+ ) : (
323
+ "Cancel"
324
+ )}
325
+ </button>
326
+ </div>
327
+ </div>
328
+ )
329
+ }
330
+
331
+ export default WaasFeeOptions