0xtrails 0.2.5 → 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 (116) hide show
  1. package/dist/abortController.d.ts +8 -0
  2. package/dist/abortController.d.ts.map +1 -0
  3. package/dist/{ccip-CXlshvBY.js → ccip-Xjh9d1gb.js} +7 -7
  4. package/dist/constants.d.ts +2 -0
  5. package/dist/constants.d.ts.map +1 -1
  6. package/dist/fees.d.ts +19 -0
  7. package/dist/fees.d.ts.map +1 -0
  8. package/dist/{index-_QuyGrjU.js → index-BnhdZ8Ho.js} +34769 -34247
  9. package/dist/index.js +726 -520
  10. package/dist/prepareSend.d.ts +11 -77
  11. package/dist/prepareSend.d.ts.map +1 -1
  12. package/dist/transactions.d.ts +4 -2
  13. package/dist/transactions.d.ts.map +1 -1
  14. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts +4 -0
  15. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -0
  16. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  17. package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
  18. package/dist/widget/components/ClassicSwap.d.ts +2 -2
  19. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  20. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  21. package/dist/widget/components/DynamicInputStyles.d.ts +18 -0
  22. package/dist/widget/components/DynamicInputStyles.d.ts.map +1 -0
  23. package/dist/widget/components/Earn.d.ts +2 -2
  24. package/dist/widget/components/Earn.d.ts.map +1 -1
  25. package/dist/widget/components/ErrorAnimationIcon.d.ts +2 -0
  26. package/dist/widget/components/ErrorAnimationIcon.d.ts.map +1 -0
  27. package/dist/widget/components/FeeBreakdown.d.ts +9 -0
  28. package/dist/widget/components/FeeBreakdown.d.ts.map +1 -0
  29. package/dist/widget/components/Fund.d.ts +2 -2
  30. package/dist/widget/components/Fund.d.ts.map +1 -1
  31. package/dist/widget/components/FundSwap.d.ts +2 -2
  32. package/dist/widget/components/FundSwap.d.ts.map +1 -1
  33. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -1
  34. package/dist/widget/components/Identicon.d.ts.map +1 -1
  35. package/dist/widget/components/MeshConnectExchanges.d.ts +0 -3
  36. package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
  37. package/dist/widget/components/Modal.d.ts.map +1 -1
  38. package/dist/widget/components/Pay.d.ts +2 -2
  39. package/dist/widget/components/Pay.d.ts.map +1 -1
  40. package/dist/widget/components/PoolDeposit.d.ts +3 -2
  41. package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
  42. package/dist/widget/components/PoolWithdraw.d.ts +3 -2
  43. package/dist/widget/components/PoolWithdraw.d.ts.map +1 -1
  44. package/dist/widget/components/QuoteDetails.d.ts +1 -0
  45. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  46. package/dist/widget/components/Receipt.d.ts.map +1 -1
  47. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -1
  48. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  49. package/dist/widget/components/Swap.d.ts +2 -2
  50. package/dist/widget/components/Swap.d.ts.map +1 -1
  51. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  52. package/dist/widget/components/TokenDisplayNonSelectable.d.ts +11 -0
  53. package/dist/widget/components/TokenDisplayNonSelectable.d.ts.map +1 -0
  54. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  55. package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -1
  56. package/dist/widget/components/Tooltip.d.ts +9 -0
  57. package/dist/widget/components/Tooltip.d.ts.map +1 -0
  58. package/dist/widget/components/WaasFeeOptions.d.ts +1 -0
  59. package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -1
  60. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  61. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  62. package/dist/widget/css/compiled.css +1 -1
  63. package/dist/widget/hooks/useQuote.d.ts +83 -0
  64. package/dist/widget/hooks/useQuote.d.ts.map +1 -0
  65. package/dist/widget/hooks/useSendForm.d.ts +2 -2
  66. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  67. package/dist/widget/index.js +2 -2
  68. package/dist/widget/widget.d.ts +5 -0
  69. package/dist/widget/widget.d.ts.map +1 -1
  70. package/package.json +2 -2
  71. package/src/abortController.ts +35 -0
  72. package/src/constants.ts +3 -0
  73. package/src/fees.ts +199 -0
  74. package/src/prepareSend.ts +225 -398
  75. package/src/trails.ts +3 -3
  76. package/src/transactions.ts +62 -18
  77. package/src/widget/compiled.css +1 -1
  78. package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +22 -0
  79. package/src/widget/components/AccountSettings.tsx +48 -36
  80. package/src/widget/components/ChainFilterDropdown.tsx +24 -3
  81. package/src/widget/components/ClassicSwap.tsx +24 -62
  82. package/src/widget/components/ConnectWallet.tsx +4 -1
  83. package/src/widget/components/ConnectedWallets.tsx +21 -21
  84. package/src/widget/components/DynamicInputStyles.tsx +76 -0
  85. package/src/widget/components/Earn.tsx +34 -29
  86. package/src/widget/components/ErrorAnimationIcon.tsx +130 -0
  87. package/src/widget/components/FeeBreakdown.tsx +155 -0
  88. package/src/widget/components/Fund.tsx +10 -26
  89. package/src/widget/components/FundSwap.tsx +2 -2
  90. package/src/widget/components/FundingMethodSelectorButton.tsx +24 -14
  91. package/src/widget/components/Identicon.tsx +164 -95
  92. package/src/widget/components/MeshConnectExchanges.tsx +2 -15
  93. package/src/widget/components/Modal.tsx +0 -12
  94. package/src/widget/components/Pay.tsx +65 -63
  95. package/src/widget/components/PoolDeposit.tsx +206 -230
  96. package/src/widget/components/PoolWithdraw.tsx +219 -238
  97. package/src/widget/components/PriceImpactWarning.tsx +1 -1
  98. package/src/widget/components/QuoteDetails.tsx +25 -8
  99. package/src/widget/components/Receipt.tsx +16 -2
  100. package/src/widget/components/RecipientSelectorButton.tsx +7 -5
  101. package/src/widget/components/Recipients.tsx +1 -1
  102. package/src/widget/components/ScreenHeader.tsx +60 -36
  103. package/src/widget/components/Swap.tsx +2 -2
  104. package/src/widget/components/ThemeProvider.tsx +2 -1
  105. package/src/widget/components/TokenDisplayNonSelectable.tsx +40 -0
  106. package/src/widget/components/TokenImage.tsx +1 -1
  107. package/src/widget/components/TokenSelector.tsx +62 -53
  108. package/src/widget/components/TokenSelectorButton.tsx +38 -15
  109. package/src/widget/components/Tooltip.tsx +51 -0
  110. package/src/widget/components/TransferPendingVertical.tsx +1 -1
  111. package/src/widget/components/WaasFeeOptions.tsx +124 -5
  112. package/src/widget/components/WalletConfirmation.tsx +23 -13
  113. package/src/widget/components/WalletConnect.tsx +93 -29
  114. package/src/widget/hooks/useQuote.ts +413 -0
  115. package/src/widget/hooks/useSendForm.ts +8 -4
  116. package/src/widget/widget.tsx +175 -190
@@ -27,10 +27,12 @@ import { logger } from "../../logger.js"
27
27
  import { generateAaveWithdrawCalldata } from "../../aave.js"
28
28
  import { generateMorphoWithdrawCalldata } from "../../morpho.js"
29
29
  import { formatAmount } from "../../tokenBalances.js"
30
+ import { useDynamicInputStyles } from "./DynamicInputStyles.js"
31
+ import { TokenDisplayNonSelectable } from "./TokenDisplayNonSelectable.js"
30
32
 
31
33
  interface PoolWithdrawProps {
32
- account: Account
33
- walletClient: WalletClient
34
+ account?: Account
35
+ walletClient?: WalletClient
34
36
  onTransactionStateChange: (transactionStates: TransactionState[]) => void
35
37
  onError: (error: Error | string | null) => void
36
38
  onWaitingForWalletConfirm: (props: PrepareSendQuote) => void
@@ -55,6 +57,7 @@ interface PoolWithdrawProps {
55
57
  recentTokens?: SupportedToken[]
56
58
  onRecentTokenSelect?: (token: SupportedToken) => void
57
59
  onTrackToken?: (token: any) => void
60
+ onPoolSelectorStateChange?: (isShowing: boolean) => void
58
61
  }
59
62
 
60
63
  export const PoolWithdraw: React.FC<PoolWithdrawProps> = ({
@@ -73,6 +76,7 @@ export const PoolWithdraw: React.FC<PoolWithdrawProps> = ({
73
76
  fundMethod,
74
77
  onNavigateToMeshConnect,
75
78
  checkoutOnHandlers,
79
+ onPoolSelectorStateChange,
76
80
  }) => {
77
81
  const { mode } = useMode()
78
82
  const { isBalanceVisible } = useBalanceVisible()
@@ -301,6 +305,13 @@ export const PoolWithdraw: React.FC<PoolWithdrawProps> = ({
301
305
 
302
306
  // Generate withdraw calldata dynamically based on user's entered amount
303
307
  const withdrawCalldata = useMemo(() => {
308
+ if (!account?.address) {
309
+ logger.console.log(
310
+ "[pool-withdraw] No account found, skipping calldata generation",
311
+ )
312
+ return undefined
313
+ }
314
+
304
315
  const isAave = selectedPool?.protocol === "Aave"
305
316
  const isMorpho = selectedPool?.protocol === "Morpho"
306
317
 
@@ -356,7 +367,7 @@ export const PoolWithdraw: React.FC<PoolWithdrawProps> = ({
356
367
  )
357
368
  return undefined
358
369
  }
359
- }, [selectedPool, amount, account.address])
370
+ }, [selectedPool, amount, account?.address])
360
371
 
361
372
  // Convert pool to token format for useSendForm
362
373
  // For Aave, use the aToken address
@@ -539,6 +550,14 @@ export const PoolWithdraw: React.FC<PoolWithdrawProps> = ({
539
550
  }
540
551
  }, [])
541
552
 
553
+ // Notify parent component about pool selector visibility
554
+ useEffect(() => {
555
+ if (onPoolSelectorStateChange) {
556
+ // Only hide tabs when showing EarnPools selector
557
+ onPoolSelectorStateChange(showEarnPools)
558
+ }
559
+ }, [showEarnPools, onPoolSelectorStateChange])
560
+
542
561
  const handleAmountChange = (value: string) => {
543
562
  // Validate decimal places (max 8 decimals)
544
563
  const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
@@ -561,6 +580,17 @@ export const PoolWithdraw: React.FC<PoolWithdrawProps> = ({
561
580
  setSendFormAmount(selectedAmount)
562
581
  }
563
582
 
583
+ // Dynamic font size based on input length
584
+ const inputStyles = useDynamicInputStyles({
585
+ inputValue: amount,
586
+ variant: "default",
587
+ })
588
+
589
+ // Get chain info for selected pool
590
+ const chainInfo = useMemo(() => {
591
+ return selectedPool ? getChainInfo(selectedPool.chainId) : null
592
+ }, [selectedPool])
593
+
564
594
  if (showEarnPools) {
565
595
  return (
566
596
  <EarnPools
@@ -572,271 +602,222 @@ export const PoolWithdraw: React.FC<PoolWithdrawProps> = ({
572
602
 
573
603
  return (
574
604
  <div className="space-y-2">
575
- <div className="space-y-1">
576
- {/* Pool Selection (Primary Section) */}
577
- <div className="trails-bg-secondary trails-border-radius-container transition-all duration-200 border border-transparent hover:!bg-white dark:hover:!bg-white hover:border-gray-400 dark:hover:border-gray-500">
578
- {selectedPool ? (
579
- <div className="p-3 trails-border-radius-container trails-bg-secondary transition-all overflow-hidden">
580
- {/* Vault Label */}
581
- <div className="flex justify-between items-center mb-2">
582
- <div className="text-sm font-semibold trails-text-secondary text-left">
583
- Withdraw From
584
- </div>
605
+ {/* Pool Selection (Primary Section) */}
606
+ <div className="trails-bg-secondary trails-border-radius-container transition-all duration-200 border border-transparent hover:!bg-white dark:hover:!bg-white hover:border-gray-400 dark:hover:border-gray-500">
607
+ {selectedPool ? (
608
+ <div className="p-3 trails-border-radius-container trails-bg-secondary transition-all overflow-hidden">
609
+ {/* Vault Label */}
610
+ <div className="flex justify-between items-center mb-2">
611
+ <div className="text-sm font-semibold trails-text-secondary text-left">
612
+ From
585
613
  </div>
614
+ </div>
586
615
 
587
- <div className="px-1">
588
- <div className="flex items-center justify-between">
589
- <div className="flex items-center space-x-3">
590
- <div style={{ width: "32px", height: "32px" }}>
591
- <a
592
- href={getExplorerUrlForAddress({
593
- address: selectedPool.token.address,
594
- chainId: selectedPool.chainId,
595
- })}
596
- target="_blank"
597
- rel="noopener noreferrer"
598
- className="cursor-pointer"
599
- >
600
- <TokenImage
601
- symbol={selectedPool.token.symbol}
602
- imageUrl={selectedPool.token.logoUrl}
603
- chainId={selectedPool.chainId}
604
- contractAddress={selectedPool.token.address}
605
- size={32}
606
- />
607
- </a>
608
- </div>
609
- <div>
610
- <h3 className="font-medium text-gray-900 dark:text-white text-sm">
611
- {selectedPool.poolUrl ? (
616
+ <div className="px-1">
617
+ <div className="flex items-center justify-between">
618
+ <div className="flex items-center space-x-3">
619
+ <div style={{ width: "32px", height: "32px" }}>
620
+ <a
621
+ href={getExplorerUrlForAddress({
622
+ address: selectedPool.token.address,
623
+ chainId: selectedPool.chainId,
624
+ })}
625
+ target="_blank"
626
+ rel="noopener noreferrer"
627
+ className="cursor-pointer"
628
+ >
629
+ <TokenImage
630
+ symbol={selectedPool.token.symbol}
631
+ imageUrl={selectedPool.token.logoUrl}
632
+ chainId={selectedPool.chainId}
633
+ contractAddress={selectedPool.token.address}
634
+ size={32}
635
+ />
636
+ </a>
637
+ </div>
638
+ <div>
639
+ <h3 className="font-medium text-gray-900 dark:text-white text-sm">
640
+ {selectedPool.poolUrl ? (
641
+ <a
642
+ href={selectedPool.poolUrl}
643
+ target="_blank"
644
+ rel="noopener noreferrer"
645
+ className="hover:underline cursor-pointer"
646
+ >
647
+ {selectedPool.name}
648
+ </a>
649
+ ) : (
650
+ selectedPool.name
651
+ )}
652
+ </h3>
653
+ <div className="flex items-center space-x-2">
654
+ <span className="text-xs text-gray-500 dark:text-gray-400 flex items-center">
655
+ {selectedPool.protocol === "Aave" && (
656
+ <img
657
+ src={aaveLogo}
658
+ alt="Aave"
659
+ className="w-3 h-3 mr-1"
660
+ />
661
+ )}
662
+ {selectedPool.protocol === "Morpho" && (
663
+ <img
664
+ src={morphoLogo}
665
+ alt="Morpho"
666
+ className="w-3 h-3 mr-1"
667
+ />
668
+ )}
669
+ {selectedPool.protocolUrl ? (
612
670
  <a
613
- href={selectedPool.poolUrl}
671
+ href={selectedPool.protocolUrl}
614
672
  target="_blank"
615
673
  rel="noopener noreferrer"
616
674
  className="hover:underline cursor-pointer"
617
675
  >
618
- {selectedPool.name}
676
+ {selectedPool.protocol}
619
677
  </a>
620
678
  ) : (
621
- selectedPool.name
679
+ selectedPool.protocol
622
680
  )}
623
- </h3>
624
- <div className="flex items-center space-x-2">
625
- <span className="text-xs text-gray-500 dark:text-gray-400 flex items-center">
626
- {selectedPool.protocol === "Aave" && (
627
- <img
628
- src={aaveLogo}
629
- alt="Aave"
630
- className="w-3 h-3 mr-1"
631
- />
632
- )}
633
- {selectedPool.protocol === "Morpho" && (
634
- <img
635
- src={morphoLogo}
636
- alt="Morpho"
637
- className="w-3 h-3 mr-1"
638
- />
639
- )}
640
- {selectedPool.protocolUrl ? (
641
- <a
642
- href={selectedPool.protocolUrl}
643
- target="_blank"
644
- rel="noopener noreferrer"
645
- className="hover:underline cursor-pointer"
646
- >
647
- {selectedPool.protocol}
648
- </a>
649
- ) : (
650
- selectedPool.protocol
651
- )}
652
- </span>
653
- </div>
681
+ </span>
654
682
  </div>
655
683
  </div>
656
- <button
657
- type="button"
658
- title="Select Vault"
659
- onClick={() => setShowEarnPools(true)}
660
- className="text-right flex items-center space-x-3 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 rounded p-2 transition-colors"
661
- >
662
- <div>
663
- <div className="flex items-center justify-end space-x-1 text-green-600 dark:text-green-400 mb-1 whitespace-nowrap">
664
- <TrendingUp className="w-3 h-3" />
665
- <span className="font-semibold text-sm">
666
- {selectedPool.apy.toFixed(1)}% APY
667
- </span>
668
- </div>
669
- <p className="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">
670
- TVL: {formatTvl(selectedPool.tvl)}
671
- </p>
672
- </div>
673
- <ChevronRight className="w-4 h-4 text-gray-400" />
674
- </button>
675
684
  </div>
685
+ <button
686
+ type="button"
687
+ title="Select Vault"
688
+ onClick={() => setShowEarnPools(true)}
689
+ className="text-right flex items-center space-x-3 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 rounded p-2 transition-colors"
690
+ >
691
+ <div>
692
+ <div className="flex items-center justify-end space-x-1 text-green-600 dark:text-green-400 mb-1 whitespace-nowrap">
693
+ <TrendingUp className="w-3 h-3" />
694
+ <span className="font-semibold text-sm">
695
+ {selectedPool.apy.toFixed(1)}% APY
696
+ </span>
697
+ </div>
698
+ <p className="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">
699
+ TVL: {formatTvl(selectedPool.tvl)}
700
+ </p>
701
+ </div>
702
+ <ChevronRight className="w-4 h-4 text-gray-400" />
703
+ </button>
676
704
  </div>
677
705
  </div>
678
- ) : (
679
- <button
680
- type="button"
681
- onClick={() => setShowEarnPools(true)}
682
- className="w-full py-6 px-4 trails-list-item trails-border-radius-container transition-all duration-200 cursor-pointer"
683
- >
684
- <div className="flex items-center justify-between">
685
- <div className="flex items-center space-x-3 flex-1">
686
- <div className="w-8 h-8 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
687
- <Search className="w-4 h-4 text-gray-400" />
688
- </div>
689
- <div className="text-left flex-1">
690
- <div className="font-semibold text-gray-900 dark:text-white text-sm">
691
- Select vault to withdraw from
692
- </div>
706
+ </div>
707
+ ) : (
708
+ <button
709
+ type="button"
710
+ onClick={() => setShowEarnPools(true)}
711
+ className="w-full py-6 px-4 trails-list-item trails-border-radius-container transition-all duration-200 cursor-pointer"
712
+ >
713
+ <div className="flex items-center justify-between">
714
+ <div className="flex items-center space-x-3 flex-1">
715
+ <div className="w-8 h-8 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
716
+ <Search className="w-4 h-4 text-gray-400" />
717
+ </div>
718
+ <div className="text-left flex-1">
719
+ <div className="font-semibold text-gray-900 dark:text-white text-sm">
720
+ Select vault to withdraw from
693
721
  </div>
694
722
  </div>
695
- <ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
696
723
  </div>
697
- </button>
698
- )}
699
- </div>
724
+ <ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
725
+ </div>
726
+ </button>
727
+ )}
728
+ </div>
700
729
 
701
- {/* Amount Input Section */}
702
- {selectedPool && (
703
- <div className="trails-bg-secondary trails-bg-secondary-hover trails-border-radius-container p-3 group transition-all duration-200 border border-transparent focus-within:!bg-white dark:focus-within:!bg-gray-800 trails-focus-border-secondary">
704
- {/* Withdraw Label */}
705
- <div className="flex justify-between items-center mb-2">
706
- <div className="text-sm font-semibold trails-text-secondary text-left">
707
- Amount
730
+ {/* Amount Input Section */}
731
+ {selectedPool && (
732
+ <div className="pt-4 pb-4 mb-4 trails-bg-secondary trails-bg-secondary-hover trails-border-radius-container p-3 group transition-all duration-200 border border-transparent focus-within:!bg-white dark:focus-within:!bg-gray-800 trails-focus-border-secondary min-h-[120px] flex flex-col">
733
+ {/* Withdraw Label */}
734
+ <div className="mb-4 flex justify-between items-center">
735
+ <div className="text-sm font-medium trails-text-secondary text-left m-0">
736
+ Amount
737
+ </div>
738
+ </div>
739
+
740
+ <div className="flex items-center space-x-2 flex-1">
741
+ {/* Amount Input */}
742
+ <div className="flex-1">
743
+ <div className="flex items-center space-x-2">
744
+ <input
745
+ ref={inputRef}
746
+ id="amount"
747
+ type="text"
748
+ value={amount}
749
+ onChange={(e) => handleAmountChange(e.target.value)}
750
+ placeholder={"0"}
751
+ className={`w-full bg-transparent font-bold trails-text-primary placeholder:trails-text-muted border-none outline-none ${
752
+ isLoadingQuote ? "animate-pulse" : ""
753
+ }`}
754
+ style={inputStyles}
755
+ inputMode="decimal"
756
+ />
757
+ {isLoadingQuote && (
758
+ <div className="animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
759
+ )}
708
760
  </div>
709
761
  </div>
710
762
 
711
- <div className="flex items-center space-x-2">
712
- {/* Amount Input */}
713
- <div className="flex-1">
763
+ {/* Token Display (Not Selectable) */}
764
+ <TokenDisplayNonSelectable
765
+ symbol={selectedPool.token.symbol}
766
+ imageUrl={selectedPool.token.logoUrl}
767
+ chainId={selectedPool.chainId}
768
+ contractAddress={selectedPool.token.address}
769
+ chainName={chainInfo?.name}
770
+ />
771
+ </div>
772
+
773
+ {/* Bottom Info Row */}
774
+ <div className="mt-4 flex justify-between items-center">
775
+ {/* USD Amount */}
776
+ <div className="text-xs text-gray-500 dark:text-gray-400">
777
+ {selectedPool && amount ? (
778
+ <>≈ {underlyingTokenUsdDisplay || "$0.00"}</>
779
+ ) : (
780
+ <span>&nbsp;</span>
781
+ )}
782
+ </div>
783
+
784
+ {/* Pool Balance and Percentage Buttons */}
785
+ {poolBalance && (
786
+ <div className="flex items-center space-x-2">
714
787
  <button
715
788
  type="button"
716
- className="flex items-center justify-start cursor-text bg-transparent border-none p-0 w-full"
717
- onClick={() => inputRef.current?.focus()}
789
+ className="text-xs text-gray-500 dark:text-gray-400 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 transition-colors bg-transparent border-none p-0"
790
+ onClick={() => handleAmountSelect(poolBalance || "0")}
791
+ onKeyDown={(e) => {
792
+ if (e.key === "Enter" || e.key === " ") {
793
+ e.preventDefault()
794
+ handleAmountSelect(poolBalance || "0")
795
+ }
796
+ }}
797
+ title="Click to withdraw full balance"
718
798
  >
719
- <div className="flex items-center">
720
- <input
721
- ref={inputRef}
722
- type="text"
723
- value={amount}
724
- onChange={(e) => handleAmountChange(e.target.value)}
725
- placeholder="0"
726
- className={`bg-transparent border-none outline-none font-bold text-left trails-text-primary placeholder-trails-text-primary ${
727
- isLoadingQuote ? "animate-pulse" : ""
728
- }`}
729
- style={{
730
- fontSize:
731
- amount.length > 12
732
- ? "0.875rem"
733
- : amount.length > 9
734
- ? "1rem"
735
- : amount.length > 6
736
- ? "1.125rem"
737
- : amount.length > 3
738
- ? "1.25rem"
739
- : "1.5rem",
740
- width: `${Math.max((amount || "0").length, 1)}ch`,
741
- minWidth: "1ch",
742
- maxWidth: "270px",
743
- padding: "0",
744
- margin: "0",
745
- transition: "all 0.1s ease-in-out",
746
- }}
747
- inputMode="decimal"
748
- />
749
- <span
750
- className="font-bold text-gray-400 dark:text-gray-500"
751
- style={{
752
- fontSize:
753
- amount.length > 12
754
- ? "0.875rem"
755
- : amount.length > 9
756
- ? "1rem"
757
- : amount.length > 6
758
- ? "1.125rem"
759
- : amount.length > 3
760
- ? "1.25rem"
761
- : "1.5rem",
762
- marginLeft: "0.1em",
763
- padding: "0",
764
- transition: "all 0.2s ease-in-out",
765
- }}
766
- >
767
- {selectedPool?.token.symbol.slice(0, 4) || ""}
768
- </span>
769
- {isLoadingQuote && (
770
- <div className="ml-2 animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
771
- )}
772
- </div>
799
+ Pool Balance:{" "}
800
+ {isBalanceVisible
801
+ ? isLoadingBalance
802
+ ? "Loading..."
803
+ : poolBalance || "0.00"
804
+ : "••••••"}
773
805
  </button>
774
- </div>
775
806
 
776
- {/* Token Display (Not Selectable) */}
777
- <div className="flex items-center space-x-2 trails-bg-card trails-border-radius-input px-2.5 py-1.5 border trails-border-primary">
778
- <TokenImage
779
- symbol={selectedPool.token.symbol}
780
- imageUrl={selectedPool.token.logoUrl}
807
+ {/* Percentage Buttons */}
808
+ <PercentageMaxButtons
809
+ userBalance={poolBalance || undefined}
810
+ isNativeToken={false} // Pool tokens are never native tokens
811
+ gasCostFormatted={prepareSendQuote?.gasCostFormatted}
781
812
  chainId={selectedPool.chainId}
782
- contractAddress={selectedPool.token.address}
783
- size={20}
813
+ onAmountSelect={handleAmountSelect}
814
+ className="opacity-100"
784
815
  />
785
- <span className="font-medium trails-text-primary text-sm">
786
- {selectedPool.token.symbol}
787
- </span>
788
816
  </div>
789
- </div>
790
-
791
- {/* Bottom Info Row */}
792
- <div className="mt-2 flex justify-between items-center">
793
- {/* USD Amount */}
794
- <div className="text-xs trails-text-muted">
795
- {selectedPool && amount ? (
796
- <>≈ {underlyingTokenUsdDisplay || "$0.00"}</>
797
- ) : (
798
- <span>&nbsp;</span>
799
- )}
800
- </div>
801
-
802
- {/* Pool Balance and Percentage Buttons */}
803
- {poolBalance && (
804
- <div className="flex items-center space-x-2">
805
- <button
806
- type="button"
807
- className="text-xs trails-text-muted cursor-pointer hover:trails-hover-text transition-colors bg-transparent border-none p-0"
808
- onClick={() => handleAmountSelect(poolBalance || "0")}
809
- onKeyDown={(e) => {
810
- if (e.key === "Enter" || e.key === " ") {
811
- e.preventDefault()
812
- handleAmountSelect(poolBalance || "0")
813
- }
814
- }}
815
- title="Click to withdraw full balance"
816
- >
817
- Pool Balance:{" "}
818
- {isBalanceVisible
819
- ? isLoadingBalance
820
- ? "Loading..."
821
- : poolBalance || "0.00"
822
- : "••••••"}
823
- </button>
824
-
825
- {/* Percentage Buttons */}
826
- <PercentageMaxButtons
827
- userBalance={poolBalance || undefined}
828
- isNativeToken={false} // Pool tokens are never native tokens
829
- gasCostFormatted={prepareSendQuote?.gasCostFormatted}
830
- chainId={selectedPool.chainId}
831
- onAmountSelect={handleAmountSelect}
832
- className="opacity-100"
833
- />
834
- </div>
835
- )}
836
- </div>
817
+ )}
837
818
  </div>
838
- )}
839
- </div>
819
+ </div>
820
+ )}
840
821
 
841
822
  {prepareSendQuote?.noSufficientBalance ? (
842
823
  <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
@@ -864,7 +845,7 @@ export const PoolWithdraw: React.FC<PoolWithdrawProps> = ({
864
845
 
865
846
  {/* Quote Details */}
866
847
  {prepareSendQuote && (
867
- <div className="space-y-2">
848
+ <div className="mb-4">
868
849
  <QuoteDetails quote={prepareSendQuote} showContent={true} />
869
850
  </div>
870
851
  )}
@@ -1,6 +1,6 @@
1
1
  import type React from "react"
2
2
  import type { PrepareSendQuote } from "../../prepareSend.js"
3
- import { Tooltip } from "@0xsequence/design-system"
3
+ import { Tooltip } from "./Tooltip.js"
4
4
 
5
5
  interface PriceImpactWarningProps {
6
6
  quote?: PrepareSendQuote | null
@@ -1,5 +1,6 @@
1
1
  import { TokenImage } from "./TokenImage.js"
2
- import { InfoIcon, Tooltip } from "@0xsequence/design-system"
2
+ import { InfoIcon } from "@0xsequence/design-system"
3
+ import { Tooltip } from "./Tooltip.js"
3
4
  import type React from "react"
4
5
  import { getExplorerUrlForAddress } from "../../explorer.js"
5
6
  import type { PrepareSendQuote } from "../../prepareSend.js"
@@ -7,6 +8,7 @@ import { useState, useEffect, useRef } from "react"
7
8
  import { truncateAddress } from "../../utils.js"
8
9
  import { PriceImpactWarning } from "./PriceImpactWarning.js"
9
10
  import { usePriceImpactWarning } from "../hooks/usePriceImpactWarning.js"
11
+ import { FeeBreakdown } from "./FeeBreakdown.js"
10
12
 
11
13
  interface QuoteDetailsProps {
12
14
  quote?: PrepareSendQuote | null
@@ -14,6 +16,7 @@ interface QuoteDetailsProps {
14
16
  children?: React.ReactNode
15
17
  onExpand?: (isExpanded: boolean) => void
16
18
  swapMode?: boolean
19
+ compact?: boolean
17
20
  }
18
21
 
19
22
  // Helper function to format completion time
@@ -44,6 +47,7 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
44
47
  children,
45
48
  onExpand,
46
49
  swapMode,
50
+ compact = false,
47
51
  }) => {
48
52
  const [showCalldata, setShowCalldata] = useState(false)
49
53
  const [showOriginRate, setShowOriginRate] = useState(true)
@@ -88,7 +92,7 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
88
92
  <button
89
93
  type="button"
90
94
  onClick={() => setIsExpanded(true)}
91
- className="w-full flex items-center justify-between py-2 px-4 trails-border-radius-button transition-colors cursor-pointer text-xs bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border border-gray-200 dark:border-gray-600"
95
+ className="w-full flex items-center justify-between py-2 px-4 trails-border-radius-button transition-colors cursor-pointer text-xs"
92
96
  aria-label="Show more details"
93
97
  >
94
98
  <div className="flex items-center gap-2 text-gray-600 dark:text-gray-400 whitespace-nowrap max-w-48 truncate">
@@ -161,7 +165,7 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
161
165
  <button
162
166
  type="button"
163
167
  onClick={() => setIsExpanded(true)}
164
- className="w-full max-w-md flex items-center justify-between gap-2 py-1 px-1 trails-border-radius-button transition-colors cursor-pointer text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
168
+ className={`w-full max-w-md flex items-center ${compact ? "justify-center" : "justify-between"} gap-2 py-1 px-1 trails-border-radius-button transition-colors cursor-pointer text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300`}
165
169
  aria-label="Show transaction details"
166
170
  >
167
171
  <span>Transaction details</span>
@@ -195,9 +199,7 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
195
199
  className={`text-sm ${swapMode ? "p-2 space-y-2" : "p-4 rounded-lg space-y-4 trails-bg-secondary"}`}
196
200
  >
197
201
  {/* Close Button - only visible when expanded, at top center */}
198
- <div
199
- className={`flex justify-center ${swapMode ? "mb-2 -mt-1" : "mb-4 -mt-2"}`}
200
- >
202
+ <div className={`flex justify-center mb-4 -mt-2`}>
201
203
  {swapMode ? (
202
204
  <button
203
205
  type="button"
@@ -665,7 +667,22 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
665
667
  )
666
668
  })()}
667
669
 
668
- {quote?.fees?.totalFeeAmountUsd != null && (
670
+ {/* Total Fees with optional breakdown */}
671
+ {quote?.trailsFeeBreakdown ? (
672
+ <FeeBreakdown feeBreakdown={quote.trailsFeeBreakdown}>
673
+ <div className="flex justify-between items-center w-full">
674
+ <span className="text-xs text-gray-600 dark:text-gray-400 flex items-center gap-1 w-full">
675
+ Total Fees:
676
+ <Tooltip message="The total fees charged for this transaction, including gas fees, bridge fees, and any platform fees. These fees are deducted from your transaction.">
677
+ <InfoIcon className="w-3 h-3 text-gray-500 dark:text-gray-400 cursor-pointer" />
678
+ </Tooltip>
679
+ </span>
680
+ <span className="font-medium text-xs text-gray-900 dark:text-white">
681
+ {quote.fees.totalFeeAmountUsdDisplay}
682
+ </span>
683
+ </div>
684
+ </FeeBreakdown>
685
+ ) : quote?.fees?.totalFeeAmountUsd != null ? (
669
686
  <div className="flex justify-between items-center">
670
687
  <span className="text-xs text-gray-600 dark:text-gray-400 flex items-center gap-1">
671
688
  Total Fees:
@@ -677,7 +694,7 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
677
694
  {quote.fees.totalFeeAmountUsdDisplay}
678
695
  </span>
679
696
  </div>
680
- )}
697
+ ) : null}
681
698
 
682
699
  {quote?.quoteProvider?.name && (
683
700
  <div className="flex justify-between items-center">