0xtrails 0.2.4 → 0.2.6

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 (212) hide show
  1. package/dist/aave.d.ts +8 -0
  2. package/dist/aave.d.ts.map +1 -1
  3. package/dist/abortController.d.ts +8 -0
  4. package/dist/abortController.d.ts.map +1 -0
  5. package/dist/{ccip-BlV1Mry3.js → ccip-Xjh9d1gb.js} +7 -7
  6. package/dist/config.d.ts +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/constants.d.ts +3 -0
  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/fees.d.ts +19 -0
  15. package/dist/fees.d.ts.map +1 -0
  16. package/dist/{index-BNWCIGfQ.js → index-BnhdZ8Ho.js} +76406 -75798
  17. package/dist/index.js +726 -520
  18. package/dist/intents.d.ts +40 -0
  19. package/dist/intents.d.ts.map +1 -1
  20. package/dist/metaTxnMonitor.d.ts +3 -3
  21. package/dist/metaTxnMonitor.d.ts.map +1 -1
  22. package/dist/metaTxns.d.ts +3 -3
  23. package/dist/metaTxns.d.ts.map +1 -1
  24. package/dist/morpho.d.ts +8 -0
  25. package/dist/morpho.d.ts.map +1 -1
  26. package/dist/prepareSend.d.ts +19 -75
  27. package/dist/prepareSend.d.ts.map +1 -1
  28. package/dist/queryParams.d.ts.map +1 -1
  29. package/dist/relayer.d.ts +6 -6
  30. package/dist/relayer.d.ts.map +1 -1
  31. package/dist/sequenceWallet.d.ts +2 -2
  32. package/dist/sequenceWallet.d.ts.map +1 -1
  33. package/dist/tokens.d.ts.map +1 -1
  34. package/dist/transactions.d.ts +4 -2
  35. package/dist/transactions.d.ts.map +1 -1
  36. package/dist/wallets.d.ts.map +1 -1
  37. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  38. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts +4 -0
  39. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -0
  40. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  41. package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
  42. package/dist/widget/components/ClassicSwap.d.ts +4 -2
  43. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  44. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  45. package/dist/widget/components/ConnectedWallets.d.ts +4 -0
  46. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
  47. package/dist/widget/components/DynamicInputStyles.d.ts +18 -0
  48. package/dist/widget/components/DynamicInputStyles.d.ts.map +1 -0
  49. package/dist/widget/components/Earn.d.ts +2 -2
  50. package/dist/widget/components/Earn.d.ts.map +1 -1
  51. package/dist/widget/components/ErrorAnimationIcon.d.ts +2 -0
  52. package/dist/widget/components/ErrorAnimationIcon.d.ts.map +1 -0
  53. package/dist/widget/components/FeeBreakdown.d.ts +9 -0
  54. package/dist/widget/components/FeeBreakdown.d.ts.map +1 -0
  55. package/dist/widget/components/Fund.d.ts +2 -2
  56. package/dist/widget/components/Fund.d.ts.map +1 -1
  57. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  58. package/dist/widget/components/{FundSendForm.d.ts → FundSwap.d.ts} +13 -7
  59. package/dist/widget/components/FundSwap.d.ts.map +1 -0
  60. package/dist/widget/components/FundingMethodSelectorButton.d.ts +4 -0
  61. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -0
  62. package/dist/widget/components/Identicon.d.ts.map +1 -1
  63. package/dist/widget/components/MeshConnectExchanges.d.ts +0 -3
  64. package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
  65. package/dist/widget/components/Modal.d.ts.map +1 -1
  66. package/dist/widget/components/Pay.d.ts +2 -2
  67. package/dist/widget/components/Pay.d.ts.map +1 -1
  68. package/dist/widget/components/PercentageMaxButtons.d.ts +12 -0
  69. package/dist/widget/components/PercentageMaxButtons.d.ts.map +1 -0
  70. package/dist/widget/components/{PaySendForm.d.ts → PoolDeposit.d.ts} +14 -36
  71. package/dist/widget/components/PoolDeposit.d.ts.map +1 -0
  72. package/dist/widget/components/{SimpleSwap.d.ts → PoolWithdraw.d.ts} +19 -10
  73. package/dist/widget/components/PoolWithdraw.d.ts.map +1 -0
  74. package/dist/widget/components/QuoteDetails.d.ts +1 -0
  75. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  76. package/dist/widget/components/Receipt.d.ts.map +1 -1
  77. package/dist/widget/components/Receive.d.ts.map +1 -1
  78. package/dist/widget/components/RecipientSelectorButton.d.ts +4 -0
  79. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -0
  80. package/dist/widget/components/Recipients.d.ts.map +1 -1
  81. package/dist/widget/components/RequiredPropsError.d.ts +8 -0
  82. package/dist/widget/components/RequiredPropsError.d.ts.map +1 -0
  83. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  84. package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
  85. package/dist/widget/components/Swap.d.ts +3 -2
  86. package/dist/widget/components/Swap.d.ts.map +1 -1
  87. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  88. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  89. package/dist/widget/components/TokenDisplayNonSelectable.d.ts +11 -0
  90. package/dist/widget/components/TokenDisplayNonSelectable.d.ts.map +1 -0
  91. package/dist/widget/components/TokenImage.d.ts +1 -0
  92. package/dist/widget/components/TokenImage.d.ts.map +1 -1
  93. package/dist/widget/components/TokenList.d.ts.map +1 -1
  94. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  95. package/dist/widget/components/TokenSelectorButton.d.ts +16 -0
  96. package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -0
  97. package/dist/widget/components/Tooltip.d.ts +9 -0
  98. package/dist/widget/components/Tooltip.d.ts.map +1 -0
  99. package/dist/widget/components/UserPreferences.d.ts.map +1 -1
  100. package/dist/widget/components/WaasFeeOptions.d.ts +9 -0
  101. package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -0
  102. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  103. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  104. package/dist/widget/components/WalletList.d.ts.map +1 -1
  105. package/dist/widget/css/compiled.css +2 -0
  106. package/dist/widget/css/index.css +554 -0
  107. package/dist/widget/hooks/useBack.d.ts +1 -0
  108. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  109. package/dist/widget/hooks/useCheckout.d.ts +1 -1
  110. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  111. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  112. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  113. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +3 -3
  114. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
  115. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -1
  116. package/dist/widget/hooks/useQuote.d.ts +83 -0
  117. package/dist/widget/hooks/useQuote.d.ts.map +1 -0
  118. package/dist/widget/hooks/useSelectedFundMethod.d.ts +12 -0
  119. package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -0
  120. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -1
  121. package/dist/widget/hooks/useSendForm.d.ts +2 -2
  122. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  123. package/dist/widget/index.js +2 -2
  124. package/dist/widget/widget.d.ts +9 -4
  125. package/dist/widget/widget.d.ts.map +1 -1
  126. package/package.json +18 -12
  127. package/src/aave.ts +32 -0
  128. package/src/abortController.ts +35 -0
  129. package/src/config.ts +12 -4
  130. package/src/constants.ts +5 -0
  131. package/src/error.ts +19 -1
  132. package/src/estimate.ts +416 -5
  133. package/src/fees.ts +199 -0
  134. package/src/intents.ts +161 -11
  135. package/src/metaTxnMonitor.ts +3 -3
  136. package/src/metaTxns.ts +3 -5
  137. package/src/morpho.ts +32 -0
  138. package/src/prepareSend.ts +714 -550
  139. package/src/queryParams.ts +2 -1
  140. package/src/relayer.ts +11 -11
  141. package/src/sequenceWallet.ts +2 -2
  142. package/src/tokens.ts +7 -1
  143. package/src/trails.ts +3 -3
  144. package/src/transactions.ts +62 -18
  145. package/src/wallets.ts +8 -0
  146. package/src/widget/compiled.css +2 -2
  147. package/src/widget/components/AccountActionsDropdown.tsx +3 -13
  148. package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +22 -0
  149. package/src/widget/components/AccountSettings.tsx +48 -54
  150. package/src/widget/components/ChainFilterDropdown.tsx +24 -3
  151. package/src/widget/components/ClassicSwap.tsx +131 -213
  152. package/src/widget/components/ConnectWallet.tsx +8 -38
  153. package/src/widget/components/ConnectedWallets.tsx +132 -77
  154. package/src/widget/components/DynamicInputStyles.tsx +76 -0
  155. package/src/widget/components/Earn.tsx +82 -593
  156. package/src/widget/components/ErrorAnimationIcon.tsx +130 -0
  157. package/src/widget/components/FeeBreakdown.tsx +155 -0
  158. package/src/widget/components/Fund.tsx +41 -108
  159. package/src/widget/components/FundMethods.tsx +82 -159
  160. package/src/widget/components/FundSwap.tsx +52 -0
  161. package/src/widget/components/FundingMethodSelectorButton.tsx +70 -0
  162. package/src/widget/components/Identicon.tsx +164 -95
  163. package/src/widget/components/MeshConnectExchanges.tsx +2 -15
  164. package/src/widget/components/Modal.tsx +0 -8
  165. package/src/widget/components/Pay.tsx +214 -237
  166. package/src/widget/components/PercentageMaxButtons.tsx +77 -0
  167. package/src/widget/components/PoolDeposit.tsx +569 -0
  168. package/src/widget/components/PoolWithdraw.tsx +884 -0
  169. package/src/widget/components/PriceImpactWarning.tsx +1 -1
  170. package/src/widget/components/QuoteDetails.tsx +43 -12
  171. package/src/widget/components/Receipt.tsx +16 -2
  172. package/src/widget/components/Receive.tsx +0 -2
  173. package/src/widget/components/RecipientSelectorButton.tsx +44 -0
  174. package/src/widget/components/Recipients.tsx +63 -157
  175. package/src/widget/components/RequiredPropsError.tsx +33 -0
  176. package/src/widget/components/ScreenHeader.tsx +62 -34
  177. package/src/widget/components/SlippageToleranceSettings.tsx +2 -1
  178. package/src/widget/components/Swap.tsx +4 -45
  179. package/src/widget/components/SwapSettings.tsx +2 -14
  180. package/src/widget/components/ThemeProvider.tsx +2 -1
  181. package/src/widget/components/TokenDisplayNonSelectable.tsx +40 -0
  182. package/src/widget/components/TokenImage.tsx +22 -5
  183. package/src/widget/components/TokenList.tsx +0 -1
  184. package/src/widget/components/TokenSelector.tsx +63 -53
  185. package/src/widget/components/TokenSelectorButton.tsx +98 -0
  186. package/src/widget/components/Tooltip.tsx +51 -0
  187. package/src/widget/components/TransferPendingVertical.tsx +1 -1
  188. package/src/widget/components/UserPreferences.tsx +6 -24
  189. package/src/widget/components/WaasFeeOptions.tsx +450 -0
  190. package/src/widget/components/WalletConfirmation.tsx +76 -14
  191. package/src/widget/components/WalletConnect.tsx +93 -29
  192. package/src/widget/components/WalletList.tsx +4 -2
  193. package/src/widget/hooks/useBack.tsx +2 -0
  194. package/src/widget/hooks/useCheckout.ts +36 -20
  195. package/src/widget/hooks/useCurrentScreen.tsx +1 -0
  196. package/src/widget/hooks/useDefaultTokenSelection.tsx +104 -28
  197. package/src/widget/hooks/usePayMessage.tsx +86 -11
  198. package/src/widget/hooks/useQuote.ts +413 -0
  199. package/src/widget/hooks/useSelectedFundMethod.tsx +41 -0
  200. package/src/widget/hooks/useSelectedRecipient.tsx +10 -0
  201. package/src/widget/hooks/useSendForm.ts +32 -6
  202. package/src/widget/index.css +27 -0
  203. package/src/widget/widget.tsx +326 -283
  204. package/dist/widget/components/FundSendForm.d.ts.map +0 -1
  205. package/dist/widget/components/PaySendForm.d.ts.map +0 -1
  206. package/dist/widget/components/SimpleSwap.d.ts.map +0 -1
  207. package/dist/widget/hooks/useSwapSettings.d.ts +0 -16
  208. package/dist/widget/hooks/useSwapSettings.d.ts.map +0 -1
  209. package/src/widget/components/FundSendForm.tsx +0 -903
  210. package/src/widget/components/PaySendForm.tsx +0 -869
  211. package/src/widget/components/SimpleSwap.tsx +0 -983
  212. package/src/widget/hooks/useSwapSettings.tsx +0 -100
@@ -0,0 +1,51 @@
1
+ import type React from "react"
2
+ import { TooltipPrimitive } from "@0xsequence/design-system"
3
+ import { useTheme } from "./ThemeProvider.js"
4
+
5
+ interface TooltipProps {
6
+ message: string
7
+ children: React.ReactNode
8
+ className?: string
9
+ }
10
+
11
+ export const Tooltip: React.FC<TooltipProps> = ({
12
+ message,
13
+ children,
14
+ className = "",
15
+ }) => {
16
+ const { isDark } = useTheme()
17
+
18
+ return (
19
+ <TooltipPrimitive.Provider>
20
+ <TooltipPrimitive.Root delayDuration={0}>
21
+ <TooltipPrimitive.Trigger asChild>
22
+ <div className={className}>{children}</div>
23
+ </TooltipPrimitive.Trigger>
24
+ <TooltipPrimitive.Portal>
25
+ <TooltipPrimitive.Content
26
+ className="trails-border-radius-container px-3 py-2 text-xs font-medium shadow-lg z-50 animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
27
+ style={{
28
+ maxWidth: "400px",
29
+ wordWrap: "break-word",
30
+ whiteSpace: "normal",
31
+ overflowWrap: "break-word",
32
+ backgroundColor: isDark ? "#111827" : "#ffffff", // gray-900 or white
33
+ color: isDark ? "#ffffff" : "#000000", // white or black
34
+ }}
35
+ side="top"
36
+ align="center"
37
+ sideOffset={4}
38
+ alignOffset={0}
39
+ >
40
+ {message}
41
+ <TooltipPrimitive.Arrow
42
+ style={{ fill: isDark ? "#111827" : "#ffffff" }} // gray-900 or white
43
+ />
44
+ </TooltipPrimitive.Content>
45
+ </TooltipPrimitive.Portal>
46
+ </TooltipPrimitive.Root>
47
+ </TooltipPrimitive.Provider>
48
+ )
49
+ }
50
+
51
+ export default Tooltip
@@ -483,7 +483,7 @@ export const TransferPending: React.FC<TransferPendingProps> = ({
483
483
  }`}
484
484
  >
485
485
  <div className="mt-4">
486
- <QuoteDetails quote={quote} showContent={true} />
486
+ <QuoteDetails quote={quote} showContent={true} compact={true} />
487
487
  </div>
488
488
  </div>
489
489
 
@@ -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,450 @@
1
+ import { useEffect, useState } from "react"
2
+ import { useWaasFeeOptions } from "@0xsequence/connect"
3
+ import { TokenImage } from "./TokenImage.js"
4
+ import { ChevronDown, ChevronUp, Copy } 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
+ setIsError?: (isError: boolean) => void
33
+ }
34
+
35
+ export const WaasFeeOptions: React.FC<WaasFeeOptionsProps> = ({
36
+ chainId,
37
+ setIsFeeOptionConfirmed,
38
+ onFeeOptionsLoaded,
39
+ setIsError,
40
+ }) => {
41
+ const [
42
+ pendingFeeOptionConfirmation,
43
+ confirmPendingFeeOption,
44
+ rejectPendingFeeOption,
45
+ ] = useWaasFeeOptions({ chainIdOverride: chainId })
46
+
47
+ const [selectedFeeOptionTokenName, setSelectedFeeOptionTokenName] =
48
+ useState<string>()
49
+ const [isExpanded, setIsExpanded] = useState(false)
50
+ const [isProcessing, setIsProcessing] = useState(false)
51
+ const [isLoading, setIsLoading] = useState(false)
52
+ const [copiedAddress, setCopiedAddress] = useState<string | null>(null)
53
+
54
+ // Debug logging
55
+ useEffect(() => {
56
+ console.log("[trails-sdk] WaasFeeOptions component mounted/updated:", {
57
+ chainId,
58
+ pendingFeeOptionConfirmation: !!pendingFeeOptionConfirmation,
59
+ optionsCount: pendingFeeOptionConfirmation?.options?.length || 0,
60
+ hasOptions: !!pendingFeeOptionConfirmation?.options,
61
+ confirmationId: pendingFeeOptionConfirmation?.id,
62
+ isLoading,
63
+ isProcessing,
64
+ })
65
+ }, [chainId, pendingFeeOptionConfirmation, isLoading, isProcessing])
66
+
67
+ // Log when component renders but has no pending confirmation
68
+ useEffect(() => {
69
+ if (!pendingFeeOptionConfirmation) {
70
+ console.log(
71
+ "[trails-sdk] WaasFeeOptions: No pending fee confirmation - this is normal until a transaction requires fee payment",
72
+ )
73
+ }
74
+ }, [pendingFeeOptionConfirmation])
75
+
76
+ // Manage loading state
77
+ useEffect(() => {
78
+ if (
79
+ pendingFeeOptionConfirmation &&
80
+ pendingFeeOptionConfirmation.options?.length > 0
81
+ ) {
82
+ setIsLoading(false)
83
+ } else if (
84
+ pendingFeeOptionConfirmation &&
85
+ (!pendingFeeOptionConfirmation.options ||
86
+ pendingFeeOptionConfirmation.options.length === 0)
87
+ ) {
88
+ setIsLoading(true)
89
+ }
90
+ }, [pendingFeeOptionConfirmation])
91
+
92
+ // Initialize with first option when fee options become available
93
+ useEffect(() => {
94
+ setIsError?.(false)
95
+ if (pendingFeeOptionConfirmation) {
96
+ console.log(
97
+ "[trails-sdk] Pending fee options: ",
98
+ pendingFeeOptionConfirmation.options,
99
+ )
100
+
101
+ // Notify parent that fee options are loaded
102
+ if (
103
+ onFeeOptionsLoaded &&
104
+ pendingFeeOptionConfirmation.options?.length > 0 &&
105
+ pendingFeeOptionConfirmation.options.find(
106
+ (option: any) => option.hasEnoughBalanceForFee,
107
+ )
108
+ ) {
109
+ onFeeOptionsLoaded()
110
+ }
111
+
112
+ // Select the first fee option by default
113
+ if (pendingFeeOptionConfirmation.options.length > 0) {
114
+ const firstOption = pendingFeeOptionConfirmation.options.filter(
115
+ (option: any) => option.hasEnoughBalanceForFee,
116
+ )[0]
117
+ if (firstOption?.token?.symbol) {
118
+ setSelectedFeeOptionTokenName(firstOption.token.symbol)
119
+ }
120
+ }
121
+ }
122
+ }, [pendingFeeOptionConfirmation, onFeeOptionsLoaded, setIsError])
123
+
124
+ // Handle fee option selection and confirmation
125
+ const handleConfirmFee = async (tokenAddress: string | null) => {
126
+ if (pendingFeeOptionConfirmation && !isProcessing) {
127
+ setIsProcessing(true)
128
+ try {
129
+ confirmPendingFeeOption(pendingFeeOptionConfirmation.id, tokenAddress)
130
+ setIsFeeOptionConfirmed(true)
131
+ } finally {
132
+ setIsProcessing(false)
133
+ }
134
+ }
135
+ }
136
+
137
+ // Handle fee option rejection
138
+ const handleRejectFee = async () => {
139
+ if (pendingFeeOptionConfirmation && !isProcessing) {
140
+ setIsProcessing(true)
141
+ try {
142
+ rejectPendingFeeOption(pendingFeeOptionConfirmation.id)
143
+ } finally {
144
+ setIsProcessing(false)
145
+ }
146
+ }
147
+ }
148
+
149
+ // Handle copy address to clipboard
150
+ const handleCopyAddress = async (address: string, e: React.MouseEvent) => {
151
+ e.preventDefault()
152
+ e.stopPropagation()
153
+ try {
154
+ await navigator.clipboard.writeText(address)
155
+ setCopiedAddress(address)
156
+ setTimeout(() => setCopiedAddress(null), 2000)
157
+ } catch (error) {
158
+ try {
159
+ const textArea = document.createElement("textarea")
160
+ textArea.value = address
161
+ document.body.appendChild(textArea)
162
+ textArea.select()
163
+ document.execCommand("copy")
164
+ document.body.removeChild(textArea)
165
+ setCopiedAddress(address)
166
+ setTimeout(() => setCopiedAddress(null), 2000)
167
+ } catch (fallbackError) {
168
+ console.error("Fallback copy failed:", fallbackError)
169
+ }
170
+ }
171
+ }
172
+
173
+ // Don't render if no pending fee confirmation
174
+ if (!pendingFeeOptionConfirmation) {
175
+ return null
176
+ }
177
+
178
+ // Show loading when we have confirmation but no options yet
179
+ if (isLoading) {
180
+ return (
181
+ <div className="flex items-center justify-center py-8">
182
+ <div className="flex flex-col items-center space-y-3">
183
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-trails-primary"></div>
184
+ <p className="text-sm trails-text-muted">Loading fee options...</p>
185
+ </div>
186
+ </div>
187
+ )
188
+ }
189
+
190
+ const selectedOption = pendingFeeOptionConfirmation.options
191
+ .filter((option: any) => option.hasEnoughBalanceForFee)
192
+ .find((option) => option.token.symbol === selectedFeeOptionTokenName) as
193
+ | FeeOptionExtended
194
+ | undefined
195
+
196
+ const hasTokenForFee = pendingFeeOptionConfirmation.options.find(
197
+ (option: any) => option.hasEnoughBalanceForFee,
198
+ )
199
+
200
+ if (!hasTokenForFee) {
201
+ setIsError?.(true)
202
+ }
203
+
204
+ if (!hasTokenForFee) {
205
+ return (
206
+ <div className="space-y-4 py-6">
207
+ <div className="text-center space-y-1">
208
+ <p className="text-xs trails-text-muted">
209
+ Fund your wallet with one of the following tokens
210
+ </p>
211
+ </div>
212
+
213
+ <div className="flex flex-col space-y-2">
214
+ {pendingFeeOptionConfirmation.options.map((option: any) => {
215
+ const contractAddress = option.token.contractAddress
216
+
217
+ return (
218
+ <div
219
+ key={option.token.symbol || contractAddress}
220
+ className="flex items-center space-x-3 p-3 trails-bg-secondary trails-border-radius-container border border-transparent"
221
+ >
222
+ <div className="flex items-center shrink-0">
223
+ <TokenImage
224
+ symbol={option.token.symbol}
225
+ imageUrl={(option.token as any).logoURL}
226
+ chainId={chainId}
227
+ size={32}
228
+ />
229
+ </div>
230
+ <div className="flex-1 min-w-0 flex flex-col items-start">
231
+ <span className="text-sm font-bold trails-text-primary leading-tight">
232
+ {option.token.symbol}
233
+ </span>
234
+ {contractAddress ? (
235
+ <div className="flex items-center space-x-1.5 mt-0.5">
236
+ <span className="text-xs trails-text-muted leading-tight">
237
+ {contractAddress.slice(0, 4)}...
238
+ {contractAddress.slice(-4)}
239
+ </span>
240
+ <button
241
+ type="button"
242
+ onClick={(e) => handleCopyAddress(contractAddress, e)}
243
+ className="shrink-0 p-0.5 rounded transition-colors cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 flex items-center justify-center"
244
+ title={
245
+ copiedAddress === contractAddress
246
+ ? "Copied!"
247
+ : "Copy address"
248
+ }
249
+ >
250
+ {copiedAddress === contractAddress ? (
251
+ <svg
252
+ className="w-3 h-3 text-green-600 dark:text-green-400"
253
+ fill="none"
254
+ viewBox="0 0 24 24"
255
+ stroke="currentColor"
256
+ aria-label="Copied"
257
+ >
258
+ <path
259
+ strokeLinecap="round"
260
+ strokeLinejoin="round"
261
+ strokeWidth={2}
262
+ d="M5 13l4 4L19 7"
263
+ />
264
+ </svg>
265
+ ) : (
266
+ <Copy className="w-3 h-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300" />
267
+ )}
268
+ </button>
269
+ </div>
270
+ ) : (
271
+ <span className="text-xs trails-text-muted mt-0.5 leading-tight">
272
+ Native Token
273
+ </span>
274
+ )}
275
+ </div>
276
+ </div>
277
+ )
278
+ })}
279
+ </div>
280
+ </div>
281
+ )
282
+ }
283
+
284
+ return (
285
+ <div className="space-y-2">
286
+ {/* Header */}
287
+ <div className="flex items-center justify-between">
288
+ <div className="text-sm font-medium trails-text-primary">
289
+ Select Fee Payment Token
290
+ </div>
291
+ <button
292
+ type="button"
293
+ onClick={() => setIsExpanded(!isExpanded)}
294
+ className="flex items-center space-x-1 text-xs trails-text-muted hover:trails-text-primary transition-colors cursor-pointer"
295
+ >
296
+ <span>{isExpanded ? "Hide" : "Show"} options</span>
297
+ {isExpanded ? (
298
+ <ChevronUp className="w-3 h-3" />
299
+ ) : (
300
+ <ChevronDown className="w-3 h-3" />
301
+ )}
302
+ </button>
303
+ </div>
304
+
305
+ {/* Selected Option Display */}
306
+ {selectedOption?.hasEnoughBalanceForFee && (
307
+ <div className="trails-bg-secondary trails-border-radius-container p-3">
308
+ <div className="flex items-center justify-between">
309
+ <div className="flex items-center space-x-2">
310
+ <TokenImage
311
+ symbol={selectedOption.token.symbol}
312
+ imageUrl={(selectedOption.token as any).logoURL}
313
+ chainId={chainId}
314
+ size={20}
315
+ />
316
+ <div className="text-left">
317
+ <div className="font-medium trails-text-primary text-sm">
318
+ {selectedOption.token.symbol}
319
+ </div>
320
+ <div className="text-xs trails-text-muted">
321
+ {selectedOption.token.contractAddress
322
+ ? `${selectedOption.token.contractAddress.slice(0, 6)}...${selectedOption.token.contractAddress.slice(-4)}`
323
+ : "Native Token"}
324
+ </div>
325
+ </div>
326
+ </div>
327
+ {/* Display balance info if available */}
328
+ {selectedOption && "balanceFormatted" in selectedOption && (
329
+ <div className="text-right">
330
+ <div className="text-xs text-gray-500 dark:text-gray-400">
331
+ Balance: {String(selectedOption.balanceFormatted)}
332
+ </div>
333
+ {!selectedOption.hasEnoughBalanceForFee && (
334
+ <div className="text-xs text-red-500">
335
+ Insufficient balance
336
+ </div>
337
+ )}
338
+ </div>
339
+ )}
340
+ </div>
341
+ </div>
342
+ )}
343
+
344
+ {/* Expanded Options */}
345
+ {isExpanded && (
346
+ <div className="space-y-2">
347
+ {pendingFeeOptionConfirmation.options
348
+ .filter((option: any) => option.hasEnoughBalanceForFee)
349
+ .map((option) => (
350
+ <label
351
+ key={option.token.symbol || option.token.contractAddress}
352
+ className={`flex items-center space-x-3 p-3 rounded-lg border cursor-pointer transition-colors ${
353
+ selectedFeeOptionTokenName === option.token.symbol
354
+ ? "trails-bg-primary/10 border-trails-primary"
355
+ : "trails-bg-secondary hover:trails-hover-bg border-transparent"
356
+ }`}
357
+ >
358
+ <input
359
+ type="radio"
360
+ name="feeOption"
361
+ checked={selectedFeeOptionTokenName === option.token.symbol}
362
+ onChange={() =>
363
+ setSelectedFeeOptionTokenName(option.token.symbol)
364
+ }
365
+ className="w-4 h-4 text-trails-primary focus:ring-trails-primary"
366
+ />
367
+ <div className="flex items-center space-x-2 flex-1">
368
+ <TokenImage
369
+ symbol={option.token.symbol}
370
+ imageUrl={(option.token as any).logoURL}
371
+ chainId={chainId}
372
+ size={20}
373
+ />
374
+ <div className="flex-1 text-left">
375
+ <div className="font-medium trails-text-primary text-sm">
376
+ {option.token.symbol}
377
+ </div>
378
+ <div className="text-xs trails-text-muted">
379
+ {option.token.contractAddress
380
+ ? `${option.token.contractAddress.slice(0, 6)}...${option.token.contractAddress.slice(-4)}`
381
+ : "Native Token"}
382
+ </div>
383
+ </div>
384
+ </div>
385
+ {/* Display balance info if available */}
386
+ {"balanceFormatted" in option && (
387
+ <div className="text-right">
388
+ <div className="text-xs text-gray-500 dark:text-gray-400">
389
+ Balance: {String(option.balanceFormatted)}
390
+ </div>
391
+ <div className="text-xs trails-text-muted">
392
+ Cost:{" "}
393
+ {String(
394
+ formatUnits(
395
+ BigInt(option.value),
396
+ option.token.decimals || 18,
397
+ ),
398
+ )}
399
+ </div>
400
+ {!("hasEnoughBalanceForFee" in option) ||
401
+ !option.hasEnoughBalanceForFee ? (
402
+ <div className="text-xs text-red-500">Insufficient</div>
403
+ ) : null}
404
+ </div>
405
+ )}
406
+ </label>
407
+ ))}
408
+ </div>
409
+ )}
410
+
411
+ {/* Action Buttons */}
412
+ <div className="flex space-x-3">
413
+ <button
414
+ type="button"
415
+ onClick={() =>
416
+ handleConfirmFee(selectedOption?.token.contractAddress || null)
417
+ }
418
+ disabled={!selectedOption || isProcessing}
419
+ 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"
420
+ >
421
+ {isProcessing ? (
422
+ <>
423
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
424
+ Processing...
425
+ </>
426
+ ) : (
427
+ "Confirm Fee"
428
+ )}
429
+ </button>
430
+ <button
431
+ type="button"
432
+ onClick={handleRejectFee}
433
+ disabled={isProcessing}
434
+ 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"
435
+ >
436
+ {isProcessing ? (
437
+ <>
438
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-500 mr-2"></div>
439
+ Processing...
440
+ </>
441
+ ) : (
442
+ "Cancel"
443
+ )}
444
+ </button>
445
+ </div>
446
+ </div>
447
+ )
448
+ }
449
+
450
+ export default WaasFeeOptions