0xtrails 0.1.13 → 0.2.1

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 (256) hide show
  1. package/dist/aave.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +12 -2
  3. package/dist/analytics.d.ts.map +1 -1
  4. package/dist/apiClient.d.ts +1 -1
  5. package/dist/apiClient.d.ts.map +1 -1
  6. package/dist/{ccip-D3gTQONK.js → ccip-BbfANth7.js} +12 -12
  7. package/dist/cctp.d.ts.map +1 -1
  8. package/dist/cctpqueue.d.ts +3 -3
  9. package/dist/cctpqueue.d.ts.map +1 -1
  10. package/dist/chains.d.ts.map +1 -1
  11. package/dist/config.d.ts +18 -5
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/constants.d.ts +6 -5
  14. package/dist/constants.d.ts.map +1 -1
  15. package/dist/contractUtils.d.ts +2 -0
  16. package/dist/contractUtils.d.ts.map +1 -1
  17. package/dist/customChains.d.ts +24 -0
  18. package/dist/customChains.d.ts.map +1 -0
  19. package/dist/gasless.d.ts +19 -7
  20. package/dist/gasless.d.ts.map +1 -1
  21. package/dist/{index-CnUM7lKf.js → index-WpIVoh3X.js} +36741 -31761
  22. package/dist/index.d.ts +5 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +405 -394
  25. package/dist/indexerClient.d.ts +10 -0
  26. package/dist/indexerClient.d.ts.map +1 -1
  27. package/dist/intentEntrypoint.d.ts +122 -0
  28. package/dist/intentEntrypoint.d.ts.map +1 -0
  29. package/dist/intents.d.ts +5 -3
  30. package/dist/intents.d.ts.map +1 -1
  31. package/dist/metaTxnMonitor.d.ts.map +1 -1
  32. package/dist/morpho.d.ts.map +1 -1
  33. package/dist/pools.d.ts +3 -1
  34. package/dist/pools.d.ts.map +1 -1
  35. package/dist/prepareSend.d.ts +18 -9
  36. package/dist/prepareSend.d.ts.map +1 -1
  37. package/dist/prices.d.ts +1 -1
  38. package/dist/prices.d.ts.map +1 -1
  39. package/dist/relaySdk.d.ts.map +1 -1
  40. package/dist/relayer.d.ts.map +1 -1
  41. package/dist/toast.d.ts +9 -0
  42. package/dist/toast.d.ts.map +1 -0
  43. package/dist/tokenBalances.d.ts +6 -2
  44. package/dist/tokenBalances.d.ts.map +1 -1
  45. package/dist/tokens.d.ts.map +1 -1
  46. package/dist/trails.d.ts +6 -5
  47. package/dist/trails.d.ts.map +1 -1
  48. package/dist/trailsClient.d.ts +12 -0
  49. package/dist/trailsClient.d.ts.map +1 -0
  50. package/dist/trailsRouter.d.ts +22 -0
  51. package/dist/trailsRouter.d.ts.map +1 -0
  52. package/dist/transactions.d.ts +8 -1
  53. package/dist/transactions.d.ts.map +1 -1
  54. package/dist/wallets.d.ts.map +1 -1
  55. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  56. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  57. package/dist/widget/components/AccountSettings.d.ts +7 -0
  58. package/dist/widget/components/AccountSettings.d.ts.map +1 -0
  59. package/dist/widget/components/ChainList.d.ts +0 -1
  60. package/dist/widget/components/ChainList.d.ts.map +1 -1
  61. package/dist/widget/components/ClassicSwap.d.ts +46 -0
  62. package/dist/widget/components/ClassicSwap.d.ts.map +1 -0
  63. package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
  64. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  65. package/dist/widget/components/ConnectedWallets.d.ts +9 -0
  66. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -0
  67. package/dist/widget/components/DebugMenu.d.ts.map +1 -1
  68. package/dist/widget/components/DebugScreensList.d.ts.map +1 -1
  69. package/dist/widget/components/DebugToast.d.ts +3 -0
  70. package/dist/widget/components/DebugToast.d.ts.map +1 -0
  71. package/dist/widget/components/Earn.d.ts.map +1 -1
  72. package/dist/widget/components/EarnPools.d.ts.map +1 -1
  73. package/dist/widget/components/FeeOption.d.ts +22 -0
  74. package/dist/widget/components/FeeOption.d.ts.map +1 -0
  75. package/dist/widget/components/FeeOptions.d.ts +13 -17
  76. package/dist/widget/components/FeeOptions.d.ts.map +1 -1
  77. package/dist/widget/components/Fund.d.ts +44 -0
  78. package/dist/widget/components/Fund.d.ts.map +1 -0
  79. package/dist/widget/components/FundMethods.d.ts +1 -1
  80. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  81. package/dist/widget/components/FundSendForm.d.ts.map +1 -1
  82. package/dist/widget/components/Identicon.d.ts +9 -0
  83. package/dist/widget/components/Identicon.d.ts.map +1 -0
  84. package/dist/widget/components/MeshConnectExchanges.d.ts +5 -2
  85. package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
  86. package/dist/widget/components/MeshConnectFlow.d.ts +2 -0
  87. package/dist/widget/components/MeshConnectFlow.d.ts.map +1 -1
  88. package/dist/widget/components/NativeGasOption.d.ts +12 -0
  89. package/dist/widget/components/NativeGasOption.d.ts.map +1 -0
  90. package/dist/widget/components/Pay.d.ts +46 -0
  91. package/dist/widget/components/Pay.d.ts.map +1 -0
  92. package/dist/widget/components/PaySendForm.d.ts.map +1 -1
  93. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  94. package/dist/widget/components/Receive.d.ts.map +1 -1
  95. package/dist/widget/components/RecentTokens.d.ts.map +1 -1
  96. package/dist/widget/components/Recipients.d.ts +9 -0
  97. package/dist/widget/components/Recipients.d.ts.map +1 -0
  98. package/dist/widget/components/RefundWarning.d.ts +9 -0
  99. package/dist/widget/components/RefundWarning.d.ts.map +1 -0
  100. package/dist/widget/components/SimpleSwap.d.ts.map +1 -1
  101. package/dist/widget/components/Swap.d.ts.map +1 -1
  102. package/dist/widget/components/SwapSettings.d.ts +1 -5
  103. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  104. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  105. package/dist/widget/components/ThemeSyncer.d.ts +6 -0
  106. package/dist/widget/components/ThemeSyncer.d.ts.map +1 -0
  107. package/dist/widget/components/Toast.d.ts +24 -0
  108. package/dist/widget/components/Toast.d.ts.map +1 -0
  109. package/dist/widget/components/TokenList.d.ts.map +1 -1
  110. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  111. package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
  112. package/dist/widget/components/TruncatedAddress.d.ts +2 -0
  113. package/dist/widget/components/TruncatedAddress.d.ts.map +1 -1
  114. package/dist/widget/components/UserPreferences.d.ts +7 -0
  115. package/dist/widget/components/UserPreferences.d.ts.map +1 -0
  116. package/dist/widget/hooks/useBack.d.ts +2 -0
  117. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  118. package/dist/widget/hooks/useBalanceVisible.d.ts +1 -0
  119. package/dist/widget/hooks/useBalanceVisible.d.ts.map +1 -1
  120. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  121. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  122. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  123. package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
  124. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  125. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +54 -0
  126. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -0
  127. package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
  128. package/dist/widget/hooks/usePayMessage.d.ts +34 -0
  129. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -0
  130. package/dist/widget/hooks/useRecipients.d.ts +17 -0
  131. package/dist/widget/hooks/useRecipients.d.ts.map +1 -0
  132. package/dist/widget/hooks/useSelectedFeeToken.d.ts +32 -0
  133. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -0
  134. package/dist/widget/hooks/useSelectedMeshExchange.d.ts +14 -0
  135. package/dist/widget/hooks/useSelectedMeshExchange.d.ts.map +1 -0
  136. package/dist/widget/hooks/useSelectedRecipient.d.ts +12 -0
  137. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -0
  138. package/dist/widget/hooks/useSendForm.d.ts +10 -13
  139. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  140. package/dist/widget/hooks/useSwapAmount.d.ts +13 -0
  141. package/dist/widget/hooks/useSwapAmount.d.ts.map +1 -0
  142. package/dist/widget/hooks/useSwapSettings.d.ts +16 -0
  143. package/dist/widget/hooks/useSwapSettings.d.ts.map +1 -0
  144. package/dist/widget/hooks/useTargetAmount.d.ts +5 -0
  145. package/dist/widget/hooks/useTargetAmount.d.ts.map +1 -0
  146. package/dist/widget/hooks/useTheme.d.ts +14 -0
  147. package/dist/widget/hooks/useTheme.d.ts.map +1 -0
  148. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  149. package/dist/widget/index.js +2 -2
  150. package/dist/widget/widget.d.ts +9 -0
  151. package/dist/widget/widget.d.ts.map +1 -1
  152. package/package.json +38 -36
  153. package/src/aave.ts +6 -1
  154. package/src/analytics.ts +109 -53
  155. package/src/apiClient.ts +1 -1
  156. package/src/cctp.ts +6 -2
  157. package/src/cctpqueue.ts +7 -7
  158. package/src/chains.ts +18 -0
  159. package/src/config.ts +63 -17
  160. package/src/constants.ts +20 -16
  161. package/src/contractUtils.ts +33 -2
  162. package/src/customChains.ts +24 -0
  163. package/src/gasless.ts +162 -109
  164. package/src/index.ts +11 -1
  165. package/src/indexerClient.ts +73 -1
  166. package/src/intentEntrypoint.ts +218 -0
  167. package/src/intents.ts +85 -54
  168. package/src/metaTxnMonitor.ts +1 -0
  169. package/src/morpho.ts +13 -2
  170. package/src/pools.ts +68 -86
  171. package/src/prepareSend.ts +1719 -967
  172. package/src/prices.ts +51 -7
  173. package/src/relaySdk.ts +6 -4
  174. package/src/relayer.ts +6 -3
  175. package/src/toast.ts +110 -0
  176. package/src/tokenBalances.ts +112 -20
  177. package/src/tokens.ts +70 -7
  178. package/src/trails.ts +81 -80
  179. package/src/trailsClient.ts +48 -0
  180. package/src/{proxyCaller.ts → trailsRouter.ts} +25 -20
  181. package/src/transactions.ts +30 -88
  182. package/src/umd.tsx +1 -1
  183. package/src/wallets.ts +2 -1
  184. package/src/widget/assets/sequence-logo.svg +15 -0
  185. package/src/widget/compiled.css +2 -2
  186. package/src/widget/components/AccountActionsDropdown.tsx +18 -159
  187. package/src/widget/components/AccountIntentTransactionHistory.tsx +346 -63
  188. package/src/widget/components/AccountSettings.tsx +102 -0
  189. package/src/widget/components/ChainFilterDropdown.tsx +1 -1
  190. package/src/widget/components/ChainList.tsx +10 -20
  191. package/src/widget/components/ClassicSwap.tsx +921 -0
  192. package/src/widget/components/ConfigDisplay.tsx +41 -5
  193. package/src/widget/components/ConnectWallet.tsx +168 -11
  194. package/src/widget/components/ConnectedWallets.tsx +342 -0
  195. package/src/widget/components/DebugMenu.tsx +2 -0
  196. package/src/widget/components/DebugScreensList.tsx +3 -0
  197. package/src/widget/components/DebugToast.tsx +63 -0
  198. package/src/widget/components/Earn.tsx +112 -143
  199. package/src/widget/components/EarnPools.tsx +2 -4
  200. package/src/widget/components/EarnPoolsFilters.tsx +6 -6
  201. package/src/widget/components/FeeOption.tsx +78 -0
  202. package/src/widget/components/FeeOptions.tsx +192 -127
  203. package/src/widget/components/Fund.tsx +1236 -0
  204. package/src/widget/components/FundMethods.tsx +4 -4
  205. package/src/widget/components/FundSendForm.tsx +1 -34
  206. package/src/widget/components/Identicon.tsx +158 -0
  207. package/src/widget/components/MeshConnectExchanges.tsx +32 -3
  208. package/src/widget/components/MeshConnectFlow.tsx +23 -4
  209. package/src/widget/components/NativeGasOption.tsx +99 -0
  210. package/src/widget/components/Pay.tsx +1092 -0
  211. package/src/widget/components/PaySendForm.tsx +1 -38
  212. package/src/widget/components/QuoteDetails.tsx +1 -30
  213. package/src/widget/components/Receipt.tsx +1 -1
  214. package/src/widget/components/Receive.tsx +4 -2
  215. package/src/widget/components/RecentTokens.tsx +2 -1
  216. package/src/widget/components/Recipients.tsx +448 -0
  217. package/src/widget/components/RefundWarning.tsx +61 -0
  218. package/src/widget/components/ScreenHeader.tsx +1 -1
  219. package/src/widget/components/SimpleSwap.tsx +74 -58
  220. package/src/widget/components/Swap.tsx +35 -853
  221. package/src/widget/components/SwapSettings.tsx +5 -11
  222. package/src/widget/components/ThemeProvider.tsx +32 -0
  223. package/src/widget/components/ThemeSyncer.tsx +47 -0
  224. package/src/widget/components/Toast.tsx +315 -0
  225. package/src/widget/components/TokenList.tsx +2 -34
  226. package/src/widget/components/TokenSelector.tsx +14 -3
  227. package/src/widget/components/TransactionDetails.tsx +153 -13
  228. package/src/widget/components/TransferPendingVertical.tsx +1 -1
  229. package/src/widget/components/TruncatedAddress.tsx +5 -1
  230. package/src/widget/components/UserPreferences.tsx +155 -0
  231. package/src/widget/components/WalletList.tsx +1 -1
  232. package/src/widget/hooks/useBack.tsx +4 -0
  233. package/src/widget/hooks/useBalanceVisible.tsx +40 -2
  234. package/src/widget/hooks/useCheckout.ts +13 -0
  235. package/src/widget/hooks/useCurrentScreen.tsx +4 -0
  236. package/src/widget/hooks/useDebugScreens.ts +12 -2
  237. package/src/widget/hooks/useDefaultTokenSelection.tsx +471 -0
  238. package/src/widget/hooks/useIntentTransactionHistory.ts +212 -0
  239. package/src/widget/hooks/usePayMessage.tsx +370 -0
  240. package/src/widget/hooks/useRecipients.ts +168 -0
  241. package/src/widget/hooks/useSelectedFeeToken.tsx +299 -0
  242. package/src/widget/hooks/useSelectedMeshExchange.tsx +46 -0
  243. package/src/widget/hooks/useSelectedRecipient.tsx +48 -0
  244. package/src/widget/hooks/useSendForm.ts +257 -49
  245. package/src/widget/hooks/useSwapAmount.tsx +50 -0
  246. package/src/widget/hooks/useSwapSettings.tsx +100 -0
  247. package/src/widget/hooks/useTargetAmount.ts +23 -0
  248. package/src/widget/hooks/useTheme.tsx +80 -0
  249. package/src/widget/hooks/useTokenList.ts +20 -11
  250. package/src/widget/index.css +45 -21
  251. package/src/widget/widget.tsx +294 -136
  252. package/dist/address.d.ts +0 -2
  253. package/dist/address.d.ts.map +0 -1
  254. package/dist/proxyCaller.d.ts +0 -21
  255. package/dist/proxyCaller.d.ts.map +0 -1
  256. package/src/address.ts +0 -6
@@ -39,6 +39,9 @@ import { useResolveEnsAddress } from "../../ens.js"
39
39
  import { etherlink } from "viem/chains"
40
40
  import { logger } from "../../logger.js"
41
41
  import { getIsContract } from "../../contractUtils.js"
42
+ import { useTrailsClient } from "../../trailsClient.js"
43
+ import { useTokenList } from "./useTokenList.js"
44
+ import { useSelectedFeeToken } from "./useSelectedFeeToken.js"
42
45
 
43
46
  export interface Token {
44
47
  id: number
@@ -116,19 +119,14 @@ export type UseSendProps = {
116
119
  }
117
120
 
118
121
  export type FeeOption = {
119
- token: {
120
- chainId: number
121
- name: string
122
- symbol: string
123
- type: string
124
- decimals: number
125
- logoURL: string
126
- contractAddress: string | null
127
- tokenID: string | null
128
- }
129
- to: string
130
- value: string
131
- gasLimit: number
122
+ tokenAddress: string
123
+ tokenSymbol: string
124
+ tokenDecimals: number
125
+ amount: string
126
+ amountUSD: number
127
+ notEnoughBalance?: boolean
128
+ tokenImageUrl?: string
129
+ chainId?: number
132
130
  }
133
131
 
134
132
  export type UseSendReturn = {
@@ -178,6 +176,8 @@ export type UseSendReturn = {
178
176
  quoteErrorPrettified: string | null
179
177
  isSameTokenWithoutCustomCalldata: boolean
180
178
  isRecipientContract: boolean
179
+ isSenderContractOnOrigin: boolean
180
+ isSenderContractOnDestination: boolean
181
181
  }
182
182
 
183
183
  export function useSendForm({
@@ -277,6 +277,7 @@ export function useSendForm({
277
277
  recipient as `0x${string}`,
278
278
  selectedDestinationChain.id,
279
279
  )
280
+ logger.console.log("[trails-sdk] isRecipientContract:", isContract)
280
281
  setIsRecipientContract(isContract)
281
282
  } catch (error) {
282
283
  logger.console.error(
@@ -293,6 +294,64 @@ export function useSendForm({
293
294
  checkRecipientContract()
294
295
  }, [recipient, selectedDestinationChain?.id])
295
296
 
297
+ // Check if sender is a contract address on origin chain
298
+ useEffect(() => {
299
+ const checkSenderContractOnOrigin = async () => {
300
+ if (account?.address && selectedToken?.chainId) {
301
+ try {
302
+ const isContract = await getIsContract(
303
+ account.address as `0x${string}`,
304
+ selectedToken.chainId,
305
+ )
306
+ logger.console.log(
307
+ "[trails-sdk] isSenderContractOnOrigin:",
308
+ isContract,
309
+ )
310
+ setIsSenderContractOnOrigin(isContract)
311
+ } catch (error) {
312
+ logger.console.error(
313
+ "[trails-sdk] Error checking if sender is contract on origin:",
314
+ error,
315
+ )
316
+ setIsSenderContractOnOrigin(false)
317
+ }
318
+ } else {
319
+ setIsSenderContractOnOrigin(false)
320
+ }
321
+ }
322
+
323
+ checkSenderContractOnOrigin()
324
+ }, [account?.address, selectedToken?.chainId])
325
+
326
+ // Check if sender is a contract address on destination chain
327
+ useEffect(() => {
328
+ const checkSenderContractOnDestination = async () => {
329
+ if (account?.address && selectedDestinationChain?.id) {
330
+ try {
331
+ const isContract = await getIsContract(
332
+ account.address as `0x${string}`,
333
+ selectedDestinationChain.id,
334
+ )
335
+ logger.console.log(
336
+ "[trails-sdk] isSenderContractOnDestination:",
337
+ isContract,
338
+ )
339
+ setIsSenderContractOnDestination(isContract)
340
+ } catch (error) {
341
+ logger.console.error(
342
+ "[trails-sdk] Error checking if sender is contract on destination:",
343
+ error,
344
+ )
345
+ setIsSenderContractOnDestination(false)
346
+ }
347
+ } else {
348
+ setIsSenderContractOnDestination(false)
349
+ }
350
+ }
351
+
352
+ checkSenderContractOnDestination()
353
+ }, [account?.address, selectedDestinationChain?.id])
354
+
296
355
  const isCustomToken = useMemo(() => toToken?.startsWith("0x"), [toToken])
297
356
 
298
357
  const {
@@ -354,7 +413,7 @@ export function useSendForm({
354
413
  token = supportedTokens.find(
355
414
  (token) =>
356
415
  (isToTokenAddress // Match by specified destination token address or symbol
357
- ? token.contractAddress === toToken
416
+ ? token.contractAddress.toLowerCase() === toToken.toLowerCase()
358
417
  : token.symbol === toToken) &&
359
418
  (toChainId // Match by specified destination chain id
360
419
  ? token.chainId === toChainId
@@ -372,22 +431,38 @@ export function useSendForm({
372
431
  }, [selectedDestToken, defaultDestToken])
373
432
 
374
433
  const apiClient = useAPIClient()
434
+ const trailsClient = useTrailsClient()
435
+
436
+ // Get user's token balances for balance checking
437
+ const { filteredTokensFormatted } = useTokenList({
438
+ onContinue: () => {}, // Not used for balance checking
439
+ onError: () => {}, // Not used for balance checking
440
+ fundMethod: undefined,
441
+ allSupportedTokens: true, // Get all tokens for balance checking
442
+ })
375
443
 
376
444
  const destTokenAddress = useTokenAddress({
377
445
  chainId: selectedDestinationChain?.id,
378
446
  tokenSymbol: selectedDestToken?.symbol,
379
447
  })
380
448
 
449
+ const destTokenPricesInput = useMemo(() => {
450
+ const input =
451
+ selectedDestToken && destTokenAddress && selectedDestinationChain?.id
452
+ ? [
453
+ {
454
+ tokenId: selectedDestToken.symbol,
455
+ contractAddress: destTokenAddress,
456
+ chainId: selectedDestinationChain.id,
457
+ },
458
+ ]
459
+ : []
460
+
461
+ return input
462
+ }, [selectedDestToken, destTokenAddress, selectedDestinationChain?.id])
463
+
381
464
  const { tokenPrices: destTokenPrices } = useTokenPrices(
382
- selectedDestToken && destTokenAddress
383
- ? [
384
- {
385
- tokenId: selectedDestToken.symbol,
386
- contractAddress: destTokenAddress,
387
- chainId: selectedDestinationChain.id,
388
- },
389
- ]
390
- : [],
465
+ destTokenPricesInput,
391
466
  apiClient,
392
467
  )
393
468
 
@@ -421,7 +496,7 @@ export function useSendForm({
421
496
  const newToken = supportedTokens.find(
422
497
  (token) =>
423
498
  (isToTokenAddress // Match by specified destination token address or symbol
424
- ? token.contractAddress === toToken
499
+ ? token.contractAddress.toLowerCase() === toToken.toLowerCase()
425
500
  : token.symbol === toToken) &&
426
501
  (toChainId // Match by specified destination chain id
427
502
  ? token.chainId === toChainId
@@ -479,12 +554,13 @@ export function useSendForm({
479
554
  [onTransactionStateChange],
480
555
  )
481
556
 
482
- const balanceFormatted = selectedToken
483
- ? formatRawAmount(
484
- selectedToken.balance,
485
- selectedToken.contractInfo?.decimals,
486
- )
487
- : "0"
557
+ const balanceFormatted =
558
+ selectedToken?.balance && selectedToken?.contractInfo?.decimals
559
+ ? formatRawAmount(
560
+ selectedToken.balance,
561
+ selectedToken.contractInfo?.decimals!,
562
+ )
563
+ : "0"
488
564
  const balanceUsdDisplay = selectedToken?.balanceUsdFormatted ?? ""
489
565
  const isValidRecipient = Boolean(recipient && isAddress(recipient))
490
566
 
@@ -498,10 +574,19 @@ export function useSendForm({
498
574
  return formatUsdAmountDisplay(amountUsd)
499
575
  }, [amount, destTokenPrices, sourceTokenPrices, tradeType])
500
576
 
501
- const [selectedFeeToken, setSelectedFeeToken] = useState<FeeOption | null>(
502
- null,
503
- )
577
+ // Use the fee token selection hook
578
+ const {
579
+ selectedFeeToken,
580
+ setSelectedFeeToken,
581
+ processedFeeOptions: feeOptions,
582
+ setRawFeeOptions,
583
+ } = useSelectedFeeToken()
584
+
504
585
  const [isRecipientContract, setIsRecipientContract] = useState(false)
586
+ const [isSenderContractOnOrigin, setIsSenderContractOnOrigin] =
587
+ useState(false)
588
+ const [isSenderContractOnDestination, setIsSenderContractOnDestination] =
589
+ useState(false)
505
590
 
506
591
  const { hasParam } = useQueryParams()
507
592
  const isDryMode = hasParam("dryMode", "true")
@@ -520,7 +605,39 @@ export function useSendForm({
520
605
 
521
606
  // Calculate raw amount (in wei/smallest unit)
522
607
  const amountRaw = useMemo(() => {
523
- if (!amount || !selectedToken?.contractInfo?.decimals) {
608
+ if (
609
+ !amount &&
610
+ !(selectedToken?.contractInfo?.decimals || selectedDestToken?.decimals)
611
+ ) {
612
+ logger.console.warn("[trails-sdk] Missing token decimals for quote", {
613
+ amount,
614
+ selectedToken,
615
+ selectedDestToken,
616
+ tradeType,
617
+ })
618
+ return "0"
619
+ }
620
+
621
+ if (
622
+ tradeType === TradeType.EXACT_INPUT &&
623
+ !selectedToken?.contractInfo?.decimals
624
+ ) {
625
+ logger.console.warn(
626
+ "[trails-sdk] Missing source token decimals for quote",
627
+ {
628
+ selectedToken,
629
+ tradeType,
630
+ },
631
+ )
632
+ return "0"
633
+ } else if (!selectedDestToken?.decimals) {
634
+ logger.console.warn(
635
+ "[trails-sdk] Missing destination token decimals for quote",
636
+ {
637
+ selectedDestToken,
638
+ tradeType,
639
+ },
640
+ )
524
641
  return "0"
525
642
  }
526
643
 
@@ -528,7 +645,7 @@ export function useSendForm({
528
645
  // For EXACT_OUTPUT: use destination token decimals (user enters destination amount)
529
646
  const decimals =
530
647
  tradeType === TradeType.EXACT_INPUT
531
- ? selectedToken.contractInfo?.decimals
648
+ ? selectedToken?.contractInfo?.decimals
532
649
  : selectedDestToken?.decimals
533
650
 
534
651
  if (!decimals) {
@@ -569,6 +686,18 @@ export function useSendForm({
569
686
  amountRaw === "0" ||
570
687
  !selectedToken
571
688
  ) {
689
+ logger.console.log(
690
+ "[trails-sdk] Skipping quote because of missing inputs",
691
+ {
692
+ amount,
693
+ destinationTokenAddress,
694
+ isValidRecipient,
695
+ selectedDestToken,
696
+ selectedDestinationChain,
697
+ amountRaw,
698
+ selectedToken,
699
+ },
700
+ )
572
701
  setQuoteError(null)
573
702
  setPrepareSendResult(null)
574
703
  return
@@ -589,7 +718,13 @@ export function useSendForm({
589
718
  const destinationTokenDecimals = selectedDestToken.decimals
590
719
 
591
720
  if (!sourceTokenDecimals || !destinationTokenDecimals) {
592
- logger.console.warn("[trails-sdk] Missing token decimals for quote")
721
+ logger.console.warn("[trails-sdk] Missing token decimals for quote", {
722
+ sourceTokenDecimals,
723
+ destinationTokenDecimals,
724
+ selectedToken,
725
+ selectedDestToken,
726
+ tradeType,
727
+ })
593
728
  setPrepareSendResult(null)
594
729
  setIsLoadingQuote(false)
595
730
  return
@@ -669,7 +804,10 @@ export function useSendForm({
669
804
  originChainId: selectedToken.chainId,
670
805
  originTokenBalance:
671
806
  fundMethod === "qr-code" || fundMethod === "exchange"
672
- ? "1"
807
+ ? parseUnits(
808
+ "100",
809
+ selectedToken.contractInfo?.decimals ?? 18,
810
+ ).toString() // needs to be an amount that is greater than the minimum amount for the swap
673
811
  : selectedToken.balance,
674
812
  destinationChainId: selectedDestinationChain.id,
675
813
  recipient,
@@ -681,6 +819,7 @@ export function useSendForm({
681
819
  fee: "0",
682
820
  client: walletClient,
683
821
  apiClient,
822
+ trailsClient,
684
823
  originRelayer,
685
824
  destinationRelayer,
686
825
  destinationCalldata: toCalldata,
@@ -701,8 +840,16 @@ export function useSendForm({
701
840
  mode,
702
841
  fundMethod,
703
842
  checkoutOnHandlers,
843
+ selectedFeeToken,
704
844
  }
705
845
 
846
+ logger.console.log(
847
+ "[trails-sdk] [FEE-SELECT] getQuote using selectedFeeToken:",
848
+ {
849
+ selectedFeeToken,
850
+ },
851
+ )
852
+
706
853
  const result = await prepareSend(options)
707
854
 
708
855
  logger.console.log("[trails-sdk] prepareSend quote:", result.quote)
@@ -721,6 +868,7 @@ export function useSendForm({
721
868
  account,
722
869
  walletClient,
723
870
  apiClient,
871
+ trailsClient,
724
872
  selectedDestToken?.decimals,
725
873
  recipient,
726
874
  destinationTokenAddress,
@@ -746,6 +894,7 @@ export function useSendForm({
746
894
  amountRaw,
747
895
  checkoutOnHandlers,
748
896
  mode,
897
+ selectedFeeToken,
749
898
  ])
750
899
 
751
900
  // Auto-fetch quotes when inputs change (debounced)
@@ -776,6 +925,11 @@ export function useSendForm({
776
925
  selectedDestinationChain?.id,
777
926
  toCalldata,
778
927
  refetchTrigger,
928
+ selectedToken?.contractAddress,
929
+ selectedToken?.chainId,
930
+ selectedToken?.balance,
931
+ selectedToken?.tokenPriceUsd,
932
+ // selectedFeeToken is passed to send() at execution time, not needed here
779
933
  ])
780
934
 
781
935
  // Calculate destination amount from quote if available
@@ -791,9 +945,31 @@ export function useSendForm({
791
945
  return formatAmountDisplay(quotedDestinationAmount || "0")
792
946
  }, [quotedDestinationAmount])
793
947
 
794
- const feeOptions = useMemo(() => {
795
- return prepareSendResult?.feeOptions?.options ?? []
796
- }, [prepareSendResult])
948
+ // Set raw fee options in the hook whenever prepareSendResult or tokens change
949
+ useEffect(() => {
950
+ const apiFeeOptions = prepareSendResult?.feeOptions?.feeOptions ?? []
951
+ logger.console.log(
952
+ "[trails-sdk] [FEE-SELECT] useSendForm setting raw fee options:",
953
+ {
954
+ prepareSendResult,
955
+ feeOptions: prepareSendResult?.feeOptions,
956
+ apiFeeOptions,
957
+ length: apiFeeOptions.length,
958
+ },
959
+ )
960
+
961
+ setRawFeeOptions(
962
+ apiFeeOptions,
963
+ selectedToken?.chainId,
964
+ filteredTokensFormatted,
965
+ )
966
+ // eslint-disable-next-line react-hooks/exhaustive-deps
967
+ }, [
968
+ prepareSendResult,
969
+ selectedToken?.chainId,
970
+ filteredTokensFormatted,
971
+ setRawFeeOptions,
972
+ ])
797
973
 
798
974
  const processSend = useCallback(async () => {
799
975
  try {
@@ -877,7 +1053,17 @@ export function useSendForm({
877
1053
 
878
1054
  async function handleSend() {
879
1055
  logger.console.log(
880
- "[trails-sdk] handleRetry called, about to call send()",
1056
+ "[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] handleSend called, about to call send()",
1057
+ )
1058
+ logger.console.log(
1059
+ "[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] selectedFeeToken value at send() call time:",
1060
+ {
1061
+ selectedFeeToken,
1062
+ isNull: selectedFeeToken === null,
1063
+ isUndefined: selectedFeeToken === undefined,
1064
+ type: typeof selectedFeeToken,
1065
+ stringified: JSON.stringify(selectedFeeToken),
1066
+ },
881
1067
  )
882
1068
  // Wait for full send to complete
883
1069
  const {
@@ -886,7 +1072,7 @@ export function useSendForm({
886
1072
  destinationMetaTxnReceipt,
887
1073
  } = await send({
888
1074
  onOriginSend,
889
- feeTokenAddress: selectedFeeToken?.token.contractAddress,
1075
+ selectedFeeToken, // Pass current value at execution time
890
1076
  })
891
1077
  logger.console.log("[trails-sdk] send() completed, receipts:", {
892
1078
  originUserTxReceipt,
@@ -947,7 +1133,7 @@ export function useSendForm({
947
1133
  onError,
948
1134
  fundMethod,
949
1135
  onNavigateToMeshConnect,
950
- selectedFeeToken?.token.contractAddress,
1136
+ selectedFeeToken, // Include so handleSend captures latest value
951
1137
  ])
952
1138
 
953
1139
  const handleSubmit = async (e: React.FormEvent) => {
@@ -991,9 +1177,13 @@ export function useSendForm({
991
1177
  const checksummedRecipient = getAddress(recipient)
992
1178
  const checksummedAccount = getAddress(account.address)
993
1179
 
994
- if (mode === "swap") {
995
- return `Swap ${amountDisplay} ${tokenSymbol}`
996
- }
1180
+ logger.console.log("[trails-sdk] buttonText:", {
1181
+ mode,
1182
+ fundMethod,
1183
+ isSameChain,
1184
+ isSameToken,
1185
+ checksummedRecipient,
1186
+ })
997
1187
 
998
1188
  if (fundMethod === "exchange") {
999
1189
  return `Continue to Exchange`
@@ -1003,6 +1193,14 @@ export function useSendForm({
1003
1193
  return `Continue to QR Code`
1004
1194
  }
1005
1195
 
1196
+ if (!fundMethod || fundMethod === "wallet") {
1197
+ return `Continue on wallet`
1198
+ }
1199
+
1200
+ if (mode === "swap") {
1201
+ return `Swap ${amountDisplay} ${tokenSymbol}`
1202
+ }
1203
+
1006
1204
  if (mode === "earn") {
1007
1205
  return `Deposit ${destinationAmountDisplay} ${destinationTokenSymbol}`
1008
1206
  }
@@ -1054,7 +1252,7 @@ export function useSendForm({
1054
1252
  }, [quoteError])
1055
1253
 
1056
1254
  // Check if origin and destination tokens are the same (same contract address on same chain)
1057
- // Only block same-token transactions when there's no custom calldata
1255
+ // Only block same-token transactions when there's no custom calldata AND recipient is the same as sender
1058
1256
  const isSameTokenWithoutCustomCalldata = useMemo(() => {
1059
1257
  if (
1060
1258
  !selectedToken ||
@@ -1069,14 +1267,22 @@ export function useSendForm({
1069
1267
  selectedToken.contractAddress.toLowerCase() ===
1070
1268
  destinationTokenAddress.toLowerCase() &&
1071
1269
  selectedToken.chainId === selectedDestinationChain?.id
1072
- // Allow same-token transactions if there's custom calldata (e.g., NFT minting)
1073
- return isSameChainAndToken && !toCalldata
1270
+
1271
+ // Allow same-token transactions if:
1272
+ // 1. There's custom calldata (e.g., NFT minting)
1273
+ // 2. Recipient is different from sender (simple transfer to another address)
1274
+ const recipientIsSameAsSender =
1275
+ recipient?.toLowerCase() === account?.address?.toLowerCase()
1276
+
1277
+ return isSameChainAndToken && !toCalldata && recipientIsSameAsSender
1074
1278
  }, [
1075
1279
  selectedToken,
1076
1280
  destinationTokenAddress,
1077
1281
  selectedDestinationChain,
1078
1282
  toCalldata,
1079
1283
  selectedToken?.chainId,
1284
+ recipient,
1285
+ account?.address,
1080
1286
  ])
1081
1287
 
1082
1288
  return {
@@ -1126,5 +1332,7 @@ export function useSendForm({
1126
1332
  quoteErrorPrettified,
1127
1333
  isSameTokenWithoutCustomCalldata,
1128
1334
  isRecipientContract,
1335
+ isSenderContractOnOrigin,
1336
+ isSenderContractOnDestination,
1129
1337
  }
1130
1338
  }
@@ -0,0 +1,50 @@
1
+ import React, {
2
+ createContext,
3
+ useContext,
4
+ useState,
5
+ type ReactNode,
6
+ } from "react"
7
+
8
+ interface SwapAmountContextType {
9
+ amount: string
10
+ setAmount: (amount: string) => void
11
+ clearAmount: () => void
12
+ }
13
+
14
+ const SwapAmountContext = createContext<SwapAmountContextType | undefined>(
15
+ undefined,
16
+ )
17
+
18
+ interface SwapAmountProviderProps {
19
+ children: ReactNode
20
+ }
21
+
22
+ export const SwapAmountProvider: React.FC<SwapAmountProviderProps> = ({
23
+ children,
24
+ }) => {
25
+ const [amount, setAmount] = useState<string>("")
26
+
27
+ const clearAmount = () => {
28
+ setAmount("")
29
+ }
30
+
31
+ return (
32
+ <SwapAmountContext.Provider
33
+ value={{
34
+ amount,
35
+ setAmount,
36
+ clearAmount,
37
+ }}
38
+ >
39
+ {children}
40
+ </SwapAmountContext.Provider>
41
+ )
42
+ }
43
+
44
+ export const useSwapAmount = () => {
45
+ const context = useContext(SwapAmountContext)
46
+ if (context === undefined) {
47
+ throw new Error("useSwapAmount must be used within a SwapAmountProvider")
48
+ }
49
+ return context
50
+ }
@@ -0,0 +1,100 @@
1
+ import React, {
2
+ createContext,
3
+ useContext,
4
+ useState,
5
+ useCallback,
6
+ type ReactNode,
7
+ } from "react"
8
+ import { logger } from "../../logger.js"
9
+
10
+ interface SwapSettingsContextType {
11
+ isSimpleSwapMode: boolean
12
+ setIsSimpleSwapMode: (simple: boolean) => void
13
+ setIsSimpleSwapModeWithStorage: (simple: boolean) => void
14
+ toggleSimpleSwapMode: () => void
15
+ resetSwapSettings: () => void
16
+ }
17
+
18
+ const SwapSettingsContext = createContext<SwapSettingsContextType | undefined>(
19
+ undefined,
20
+ )
21
+
22
+ interface SwapSettingsProviderProps {
23
+ children: ReactNode
24
+ initialSimpleMode?: boolean
25
+ }
26
+
27
+ const SIMPLE_SWAP_MODE_KEY = "trails-simple-swap-mode"
28
+
29
+ export const SwapSettingsProvider: React.FC<SwapSettingsProviderProps> = ({
30
+ children,
31
+ initialSimpleMode = false,
32
+ }) => {
33
+ // Initialize state from localStorage or use initialSimpleMode as fallback
34
+ const [isSimpleSwapMode, setIsSimpleSwapMode] = useState<boolean>(() => {
35
+ try {
36
+ const stored = localStorage.getItem(SIMPLE_SWAP_MODE_KEY)
37
+ return stored !== null ? JSON.parse(stored) : initialSimpleMode
38
+ } catch (error) {
39
+ logger.console.warn(
40
+ "Failed to read simple swap mode from localStorage:",
41
+ error,
42
+ )
43
+ return initialSimpleMode
44
+ }
45
+ })
46
+
47
+ // Function that updates state and saves to localStorage (for user preferences)
48
+ const setIsSimpleSwapModeWithStorage = useCallback((simple: boolean) => {
49
+ setIsSimpleSwapMode(simple)
50
+ try {
51
+ localStorage.setItem(SIMPLE_SWAP_MODE_KEY, JSON.stringify(simple))
52
+ } catch (error) {
53
+ logger.console.warn(
54
+ "Failed to save simple swap mode to localStorage:",
55
+ error,
56
+ )
57
+ }
58
+ }, [])
59
+
60
+ const toggleSimpleSwapMode = useCallback(() => {
61
+ const newMode = !isSimpleSwapMode
62
+ setIsSimpleSwapModeWithStorage(newMode)
63
+ }, [isSimpleSwapMode, setIsSimpleSwapModeWithStorage])
64
+
65
+ const resetSwapSettings = useCallback(() => {
66
+ try {
67
+ localStorage.removeItem(SIMPLE_SWAP_MODE_KEY)
68
+ setIsSimpleSwapMode(false) // Default: advanced swap mode
69
+ } catch (error) {
70
+ logger.console.warn("Failed to reset swap settings:", error)
71
+ setIsSimpleSwapMode(false) // Still reset state even if localStorage fails
72
+ }
73
+ }, [])
74
+
75
+ const value: SwapSettingsContextType = {
76
+ isSimpleSwapMode,
77
+ setIsSimpleSwapMode, // This one doesn't save to localStorage
78
+ setIsSimpleSwapModeWithStorage, // This one saves to localStorage
79
+ toggleSimpleSwapMode,
80
+ resetSwapSettings,
81
+ }
82
+
83
+ return (
84
+ <SwapSettingsContext.Provider value={value}>
85
+ {children}
86
+ </SwapSettingsContext.Provider>
87
+ )
88
+ }
89
+
90
+ export const useSwapSettings = (): SwapSettingsContextType => {
91
+ const context = useContext(SwapSettingsContext)
92
+
93
+ if (context === undefined) {
94
+ throw new Error(
95
+ "useSwapSettings must be used within a SwapSettingsProvider",
96
+ )
97
+ }
98
+
99
+ return context
100
+ }
@@ -0,0 +1,23 @@
1
+ import { useAmountUsd } from "./useAmountUsd.js"
2
+ import { useWidgetProps } from "./useWidgetProps.js"
3
+
4
+ export function useTargetAmount(): {
5
+ targetAmountUsd: number | null
6
+ targetAmountUsdFormatted: string
7
+ } {
8
+ const { toAmount, toToken, toChainId } = useWidgetProps()
9
+
10
+ const {
11
+ amountUsd: targetAmountUsd,
12
+ amountUsdFormatted: targetAmountUsdFormatted,
13
+ } = useAmountUsd({
14
+ amount: toAmount,
15
+ token: toToken,
16
+ chainId: Number(toChainId),
17
+ })
18
+
19
+ return {
20
+ targetAmountUsd,
21
+ targetAmountUsdFormatted,
22
+ }
23
+ }