0xtrails 0.1.3 → 0.1.4

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 (111) hide show
  1. package/dist/address.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +18 -0
  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-CWd4g9uZ.js → ccip-dLSEJjCf.js} +55 -55
  7. package/dist/cctpqueue.d.ts +1 -1
  8. package/dist/cctpqueue.d.ts.map +1 -1
  9. package/dist/constants.d.ts +1 -0
  10. package/dist/constants.d.ts.map +1 -1
  11. package/dist/decoders.d.ts +58 -0
  12. package/dist/decoders.d.ts.map +1 -0
  13. package/dist/ens.d.ts +6 -0
  14. package/dist/ens.d.ts.map +1 -1
  15. package/dist/error.d.ts +7 -0
  16. package/dist/error.d.ts.map +1 -1
  17. package/dist/{index-BTUBzx4R.js → index-BXbaLmtt.js} +25694 -24507
  18. package/dist/index.js +2 -2
  19. package/dist/intents.d.ts +4 -4
  20. package/dist/intents.d.ts.map +1 -1
  21. package/dist/metaTxns.d.ts +1 -1
  22. package/dist/metaTxns.d.ts.map +1 -1
  23. package/dist/mode.d.ts +1 -1
  24. package/dist/mode.d.ts.map +1 -1
  25. package/dist/preconditions.d.ts +1 -1
  26. package/dist/preconditions.d.ts.map +1 -1
  27. package/dist/prepareSend.d.ts +30 -24
  28. package/dist/prepareSend.d.ts.map +1 -1
  29. package/dist/prices.d.ts +1 -1
  30. package/dist/prices.d.ts.map +1 -1
  31. package/dist/proxyCaller.d.ts +0 -1
  32. package/dist/proxyCaller.d.ts.map +1 -1
  33. package/dist/tokenBalances.d.ts +1 -1
  34. package/dist/tokenBalances.d.ts.map +1 -1
  35. package/dist/tokens.d.ts.map +1 -1
  36. package/dist/trails.d.ts +1 -1
  37. package/dist/trails.d.ts.map +1 -1
  38. package/dist/transactions.d.ts +4 -0
  39. package/dist/transactions.d.ts.map +1 -1
  40. package/dist/utils.d.ts +6 -0
  41. package/dist/utils.d.ts.map +1 -1
  42. package/dist/widget/components/DebugScreensDropdown.d.ts.map +1 -1
  43. package/dist/widget/components/ErrorDisplay.d.ts +9 -0
  44. package/dist/widget/components/ErrorDisplay.d.ts.map +1 -0
  45. package/dist/widget/components/FundSendForm.d.ts +1 -1
  46. package/dist/widget/components/FundSendForm.d.ts.map +1 -1
  47. package/dist/widget/components/OriginTransferInformation.d.ts +10 -0
  48. package/dist/widget/components/OriginTransferInformation.d.ts.map +1 -0
  49. package/dist/widget/components/PaySendForm.d.ts +1 -1
  50. package/dist/widget/components/PaySendForm.d.ts.map +1 -1
  51. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  52. package/dist/widget/components/Receipt.d.ts.map +1 -1
  53. package/dist/widget/components/Receive.d.ts +12 -0
  54. package/dist/widget/components/Receive.d.ts.map +1 -0
  55. package/dist/widget/components/Swap.d.ts +5 -1
  56. package/dist/widget/components/Swap.d.ts.map +1 -1
  57. package/dist/widget/components/SwapDisplay.d.ts +9 -0
  58. package/dist/widget/components/SwapDisplay.d.ts.map +1 -0
  59. package/dist/widget/components/TransferPendingVertical.d.ts +2 -0
  60. package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -1
  61. package/dist/widget/components/YellowWarningAnimation.d.ts +2 -0
  62. package/dist/widget/components/YellowWarningAnimation.d.ts.map +1 -0
  63. package/dist/widget/hooks/useDebugScreens.d.ts +22 -0
  64. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -0
  65. package/dist/widget/hooks/useSendForm.d.ts +7 -3
  66. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  67. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  68. package/dist/widget/index.js +1 -1
  69. package/dist/widget/widget.d.ts.map +1 -1
  70. package/package.json +16 -15
  71. package/src/address.ts +3 -0
  72. package/src/analytics.ts +105 -4
  73. package/src/apiClient.ts +1 -1
  74. package/src/cctpqueue.ts +1 -1
  75. package/src/constants.ts +3 -0
  76. package/src/decoders.ts +310 -0
  77. package/src/ens.ts +16 -1
  78. package/src/error.ts +86 -1
  79. package/src/intents.ts +10 -2
  80. package/src/metaTxns.ts +1 -1
  81. package/src/mode.ts +1 -1
  82. package/src/preconditions.ts +1 -1
  83. package/src/prepareSend.ts +432 -114
  84. package/src/prices.ts +5 -1
  85. package/src/proxyCaller.ts +2 -14
  86. package/src/tokenBalances.ts +21 -17
  87. package/src/tokens.ts +62 -3
  88. package/src/trails.ts +2 -2
  89. package/src/transactions.ts +35 -18
  90. package/src/utils.ts +28 -0
  91. package/src/widget/compiled.css +2 -2
  92. package/src/widget/components/DebugScreensDropdown.tsx +3 -0
  93. package/src/widget/components/ErrorDisplay.tsx +150 -0
  94. package/src/widget/components/FundSendForm.tsx +62 -9
  95. package/src/widget/components/OriginTransferInformation.tsx +59 -0
  96. package/src/widget/components/PaySendForm.tsx +65 -12
  97. package/src/widget/components/QRCodeDeposit.tsx +5 -5
  98. package/src/widget/components/QrCode.tsx +1 -1
  99. package/src/widget/components/QuoteDetails.tsx +93 -25
  100. package/src/widget/components/Receipt.tsx +296 -103
  101. package/src/widget/components/Receive.tsx +146 -0
  102. package/src/widget/components/RecentTokens.tsx +1 -1
  103. package/src/widget/components/Swap.tsx +206 -85
  104. package/src/widget/components/SwapDisplay.tsx +68 -0
  105. package/src/widget/components/TokenSelector.tsx +1 -1
  106. package/src/widget/components/TransferPendingVertical.tsx +162 -112
  107. package/src/widget/components/YellowWarningAnimation.tsx +146 -0
  108. package/src/widget/hooks/useDebugScreens.ts +583 -0
  109. package/src/widget/hooks/useSendForm.ts +46 -4
  110. package/src/widget/hooks/useTokenList.ts +8 -27
  111. package/src/widget/widget.tsx +201 -484
@@ -1,4 +1,4 @@
1
- import { ChevronDown, Loader2 } from "lucide-react"
1
+ import { ChevronDown, Loader2, RefreshCcw } from "lucide-react"
2
2
  import type React from "react"
3
3
  import { useEffect, useRef, useState, useCallback, useMemo } from "react"
4
4
  import type { Account, WalletClient } from "viem"
@@ -16,6 +16,7 @@ import { TradeType } from "../../prepareSend.js"
16
16
  import type { PrepareSendQuote } from "../../prepareSend.js"
17
17
  import { formatAmount, formatUsdAmountDisplay } from "../../tokenBalances.js"
18
18
  import { ScreenHeader } from "./ScreenHeader.js"
19
+ import { ErrorDisplay } from "./ErrorDisplay.js"
19
20
 
20
21
  interface FundSendFormProps {
21
22
  selectedToken: Token
@@ -38,7 +39,7 @@ interface FundSendFormProps {
38
39
  setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
39
40
  quoteProvider?: string
40
41
  fundMethod?: string
41
- mode?: "pay" | "fund" | "earn" | "swap"
42
+ mode?: "pay" | "fund" | "earn" | "swap" | "receive"
42
43
  onNavigateToMeshConnect?: (
43
44
  props: {
44
45
  toTokenSymbol: string
@@ -78,6 +79,7 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
78
79
  }) => {
79
80
  // Local state for fund-specific functionality
80
81
  const [isInputTypeUsd, setIsInputTypeUsd] = useState(false)
82
+ const [refetchTrigger, setRefetchTrigger] = useState(0)
81
83
  // const [isRefundAddressOpen, setIsRefundAddressOpen] = useState(false)
82
84
  // const [refundAddress, setRefundAddress] = useState<string>(account.address)
83
85
 
@@ -122,6 +124,9 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
122
124
  setSelectedDestinationChain,
123
125
  setSelectedDestToken,
124
126
  prepareSendQuote,
127
+ quoteError,
128
+ quoteErrorPrettified,
129
+ isSameTokenWithoutCustomCalldata,
125
130
  } = useSendForm({
126
131
  account,
127
132
  // Don't pass toAmount for fund form - user enters input amount
@@ -147,6 +152,7 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
147
152
  mode,
148
153
  onNavigateToMeshConnect,
149
154
  checkoutOnHandlers,
155
+ refetchTrigger,
150
156
  })
151
157
 
152
158
  // Get source token price for USD conversions
@@ -344,6 +350,11 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
344
350
  }, 0)
345
351
  }, [tokenAmountForBackend, isInputTypeUsd, sourceTokenPrice])
346
352
 
353
+ // Handle manual quote refetch
354
+ const handleRefetchQuote = useCallback(() => {
355
+ setRefetchTrigger((prev) => prev + 1)
356
+ }, [])
357
+
347
358
  // Dynamic font size based on input length
348
359
  const inputStyles = useMemo(() => {
349
360
  const inputLength = displayAmount.length
@@ -501,7 +512,7 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
501
512
  <div className="mb-4">
502
513
  <label
503
514
  htmlFor="destination-chain"
504
- className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300"
515
+ className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300 text-left"
505
516
  >
506
517
  Destination Chain
507
518
  </label>
@@ -564,7 +575,7 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
564
575
  <div className="mb-4">
565
576
  <label
566
577
  htmlFor="token"
567
- className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300"
578
+ className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300 text-left"
568
579
  >
569
580
  Receive Token
570
581
  </label>
@@ -630,10 +641,37 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
630
641
 
631
642
  {/* Receive Section */}
632
643
  <div className="space-y-1">
633
- <div
634
- className={`text-lg font-semibold text-left ${"text-gray-900 dark:text-white"}`}
635
- >
636
- Receive
644
+ <div className="flex items-center justify-between">
645
+ <div
646
+ className={`text-lg font-semibold text-left ${"text-gray-900 dark:text-white"}`}
647
+ >
648
+ Receive
649
+ </div>
650
+ <button
651
+ type="button"
652
+ onClick={handleRefetchQuote}
653
+ disabled={
654
+ isLoadingQuote ||
655
+ !tokenAmountForBackend ||
656
+ !selectedDestToken ||
657
+ !selectedDestinationChain ||
658
+ !isValidRecipient
659
+ }
660
+ className={`p-2 rounded-md transition-colors cursor-pointer ${
661
+ isLoadingQuote ||
662
+ !tokenAmountForBackend ||
663
+ !selectedDestToken ||
664
+ !selectedDestinationChain ||
665
+ !isValidRecipient
666
+ ? "opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-500"
667
+ : "text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-200"
668
+ }`}
669
+ title="Refetch quote"
670
+ >
671
+ <RefreshCcw
672
+ className={`h-4 w-4 ${isLoadingQuote ? "animate-spin" : ""}`}
673
+ />
674
+ </button>
637
675
  </div>
638
676
 
639
677
  <div className="p-2">
@@ -703,6 +741,18 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
703
741
  /> */}
704
742
 
705
743
  {/* Warning Messages - Show only one at a time */}
744
+ {isSameTokenWithoutCustomCalldata ? (
745
+ <ErrorDisplay
746
+ errorPrettified="Cannot swap to the same token on the same chain without custom calldata. Please select a different destination token."
747
+ severity="error"
748
+ />
749
+ ) : (
750
+ <ErrorDisplay
751
+ errorPrettified={quoteErrorPrettified}
752
+ error={quoteError}
753
+ severity="warning"
754
+ />
755
+ )}
706
756
  {prepareSendQuote?.noSufficientBalance ? (
707
757
  <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
708
758
  <div className="flex items-center space-x-2">
@@ -760,7 +810,8 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
760
810
  !isValidRecipient ||
761
811
  buttonText === "No quote available" ||
762
812
  buttonText === "Getting quote..." ||
763
- prepareSendQuote?.noSufficientBalance
813
+ prepareSendQuote?.noSufficientBalance ||
814
+ isSameTokenWithoutCustomCalldata
764
815
  }
765
816
  className={`w-full font-semibold py-4 px-4 trails-border-radius-button transition-colors bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 text-white disabled:text-gray-500 disabled:cursor-not-allowed cursor-pointer relative`}
766
817
  >
@@ -769,6 +820,8 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
769
820
  <Loader2 className="w-5 h-5 animate-spin mr-2 text-white dark:text-gray-400" />
770
821
  <span>{buttonText}</span>
771
822
  </div>
823
+ ) : isSameTokenWithoutCustomCalldata ? (
824
+ "Select Different Tokens"
772
825
  ) : prepareSendQuote?.noSufficientBalance ? (
773
826
  "Insufficient Balance"
774
827
  ) : !tokenAmountForBackend ||
@@ -0,0 +1,59 @@
1
+ import type React from "react"
2
+ import { TokenImage } from "./TokenImage.js"
3
+ import type { PrepareSendQuote } from "../../prepareSend.js"
4
+ import { getTimeAgo } from "../../utils.js"
5
+
6
+ interface OriginTransferInformationProps {
7
+ quote: PrepareSendQuote
8
+ elapsedSeconds: number
9
+ timestamp?: number
10
+ }
11
+
12
+ export const OriginTransferInformation: React.FC<
13
+ OriginTransferInformationProps
14
+ > = ({ quote, elapsedSeconds, timestamp }) => {
15
+ return (
16
+ <div className="w-full max-w-sm p-3 trails-border-radius-container trails-bg-secondary trails-text-secondary">
17
+ <div className="flex items-start justify-between">
18
+ {/* Left side - Chain and Token images with token name */}
19
+ <div className="flex items-start space-x-2">
20
+ {/* Token Image and Name */}
21
+ <div className="flex items-center space-x-2">
22
+ <div style={{ width: "24px", height: "24px" }}>
23
+ <TokenImage
24
+ imageUrl={quote.originToken.imageUrl}
25
+ symbol={quote.originToken.symbol}
26
+ chainId={quote.originChain.id}
27
+ size={24}
28
+ />
29
+ </div>
30
+ <div className="flex flex-col">
31
+ <span className="text-xs font-medium text-left text-gray-900 dark:text-white">
32
+ {quote.originToken.name}
33
+ </span>
34
+ <span className="text-xs text-gray-500 dark:text-gray-400 flex items-center">
35
+ {getTimeAgo(timestamp)}
36
+ <span
37
+ className="ml-1 font-mono animate-pulse text-gray-500 dark:text-gray-400"
38
+ title="Elapsed time"
39
+ >
40
+ {elapsedSeconds}s
41
+ </span>
42
+ </span>
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ {/* Right side - USD value and amount */}
48
+ <div className="text-right">
49
+ <div className="text-xs font-medium text-right text-gray-900 dark:text-white">
50
+ {quote.originAmountUsdDisplay}
51
+ </div>
52
+ <div className="text-xs text-gray-600 dark:text-gray-400">
53
+ {quote.originAmountFormatted} {quote.originToken.symbol}
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ )
59
+ }
@@ -1,6 +1,6 @@
1
- import { ChevronDown, Loader2, TrendingUp } from "lucide-react"
1
+ import { ChevronDown, Loader2, RefreshCcw, TrendingUp } from "lucide-react"
2
2
  import type React from "react"
3
- import { useCallback, useEffect, useRef } from "react"
3
+ import { useCallback, useEffect, useRef, useState } from "react"
4
4
  import type { Account, WalletClient } from "viem"
5
5
  import { isAddress } from "viem"
6
6
  import type { TransactionState } from "../../transactions.js"
@@ -21,6 +21,7 @@ import morphoLogo from "../assets/morpho.svg"
21
21
  import { formatUsdAmountDisplay } from "../../tokenBalances.js"
22
22
  import { MINIMUM_USD_AMOUNT_FOR_SWAP } from "../../constants.js"
23
23
  import { ScreenHeader } from "./ScreenHeader.js"
24
+ import { ErrorDisplay } from "./ErrorDisplay.js"
24
25
 
25
26
  interface PaySendFormProps {
26
27
  selectedToken: Token
@@ -53,7 +54,7 @@ interface PaySendFormProps {
53
54
  quote?: PrepareSendQuote | null,
54
55
  ) => void
55
56
  onAmountUpdate?: (amount: string) => void
56
- mode?: "pay" | "fund" | "earn" | "swap"
57
+ mode?: "pay" | "fund" | "earn" | "swap" | "receive"
57
58
  selectedPool?: {
58
59
  id: string
59
60
  name: string
@@ -105,6 +106,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
105
106
  }) => {
106
107
  // const [isRefundAddressOpen, setIsRefundAddressOpen] = useState(false)
107
108
  // const [refundAddress, setRefundAddress] = useState<string>(account.address)
109
+ const [refetchTrigger, setRefetchTrigger] = useState(0)
108
110
  const {
109
111
  amount,
110
112
  amountRaw,
@@ -141,6 +143,9 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
141
143
  supportedChains,
142
144
  isValidCustomToken,
143
145
  prepareSendQuote,
146
+ quoteError,
147
+ quoteErrorPrettified,
148
+ isSameTokenWithoutCustomCalldata,
144
149
  } = useSendForm({
145
150
  account,
146
151
  toAmount,
@@ -166,6 +171,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
166
171
  mode,
167
172
  onNavigateToMeshConnect,
168
173
  checkoutOnHandlers,
174
+ refetchTrigger,
169
175
  })
170
176
 
171
177
  // Handle amount input changes with decimal validation
@@ -181,6 +187,11 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
181
187
  [setAmount],
182
188
  )
183
189
 
190
+ // Handle manual quote refetch
191
+ const handleRefetchQuote = useCallback(() => {
192
+ setRefetchTrigger((prev) => prev + 1)
193
+ }, [])
194
+
184
195
  // Call onAmountUpdate when amountRaw changes
185
196
  useEffect(() => {
186
197
  if (onAmountUpdate) {
@@ -377,7 +388,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
377
388
  <div className="mb-4">
378
389
  <label
379
390
  htmlFor="destination-chain"
380
- className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300"
391
+ className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300 text-left"
381
392
  >
382
393
  Destination Chain
383
394
  </label>
@@ -440,7 +451,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
440
451
  <div className="mb-4">
441
452
  <label
442
453
  htmlFor="token"
443
- className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300"
454
+ className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300 text-left"
444
455
  >
445
456
  Receive Token
446
457
  </label>
@@ -509,7 +520,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
509
520
  <div className="mb-2">
510
521
  <label
511
522
  htmlFor="amount"
512
- className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300"
523
+ className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300 text-left"
513
524
  >
514
525
  Amount to {mode === "earn" ? "Deposit" : "Receive"}
515
526
  </label>
@@ -530,7 +541,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
530
541
  </div>
531
542
  {amountUsdDisplay && selectedDestToken?.symbol && (
532
543
  <div className="h-6 mt-1">
533
- <div className="text-sm text-gray-400">
544
+ <div className="text-sm text-gray-400 text-left">
534
545
  ≈ {amountUsdDisplay}
535
546
  </div>
536
547
  </div>
@@ -541,10 +552,37 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
541
552
  {/* Receive Section - Similar to FundSendForm */}
542
553
  {(toAmount || toChainId || toToken) && (
543
554
  <div className="space-y-1">
544
- <div
545
- className={`text-lg font-semibold text-left ${"text-gray-900 dark:text-white"}`}
546
- >
547
- {mode === "earn" ? "Deposit" : "Receive"}
555
+ <div className="flex items-center justify-between">
556
+ <div
557
+ className={`text-lg font-semibold text-left ${"text-gray-900 dark:text-white"}`}
558
+ >
559
+ {mode === "earn" ? "Deposit" : "Receive"}
560
+ </div>
561
+ <button
562
+ type="button"
563
+ onClick={handleRefetchQuote}
564
+ disabled={
565
+ isLoadingQuote ||
566
+ !amount ||
567
+ !selectedDestToken ||
568
+ !selectedDestinationChain ||
569
+ !isValidRecipient
570
+ }
571
+ className={`p-2 rounded-md transition-colors cursor-pointer ${
572
+ isLoadingQuote ||
573
+ !amount ||
574
+ !selectedDestToken ||
575
+ !selectedDestinationChain ||
576
+ !isValidRecipient
577
+ ? "opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-500"
578
+ : "text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-200"
579
+ }`}
580
+ title="Refetch quote"
581
+ >
582
+ <RefreshCcw
583
+ className={`h-4 w-4 ${isLoadingQuote ? "animate-spin" : ""}`}
584
+ />
585
+ </button>
548
586
  </div>
549
587
 
550
588
  <div className="p-2">
@@ -675,6 +713,18 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
675
713
  />
676
714
 
677
715
  {/* Warning Messages - Show only one at a time */}
716
+ {isSameTokenWithoutCustomCalldata ? (
717
+ <ErrorDisplay
718
+ errorPrettified="Cannot swap to the same token on the same chain without custom calldata. Please select a different destination token."
719
+ severity="error"
720
+ />
721
+ ) : (
722
+ <ErrorDisplay
723
+ errorPrettified={quoteErrorPrettified}
724
+ error={quoteError}
725
+ severity="warning"
726
+ />
727
+ )}
678
728
  {prepareSendQuote?.noSufficientBalance ? (
679
729
  <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
680
730
  <div className="flex items-center space-x-2">
@@ -733,7 +783,8 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
733
783
  !isValidCustomToken ||
734
784
  isLoadingQuote ||
735
785
  !prepareSendQuote ||
736
- prepareSendQuote?.noSufficientBalance
786
+ prepareSendQuote?.noSufficientBalance ||
787
+ isSameTokenWithoutCustomCalldata
737
788
  }
738
789
  className={`w-full font-semibold py-4 px-4 trails-border-radius-button transition-colors bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 text-white disabled:text-gray-500 disabled:cursor-not-allowed cursor-pointer relative`}
739
790
  >
@@ -744,6 +795,8 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
744
795
  />
745
796
  <span>{buttonText}</span>
746
797
  </div>
798
+ ) : isSameTokenWithoutCustomCalldata ? (
799
+ "Select Different Tokens"
747
800
  ) : prepareSendQuote?.noSufficientBalance ? (
748
801
  "Insufficient Balance"
749
802
  ) : (
@@ -23,12 +23,12 @@ export const QRCodeDeposit: React.FC<QRCodeDepositProps> = ({
23
23
 
24
24
  const eip631Url = useMemo(() => {
25
25
  if (!quote) return ""
26
- return `ethereum:${quote.originAddress}@${quote.originChain.id}`
26
+ return `ethereum:${quote.originDepositAddress}@${quote.originChain.id}`
27
27
  }, [quote])
28
28
 
29
29
  const eip681Url = useMemo(() => {
30
30
  if (!quote) return ""
31
- return `ethereum:${quote.originToken.contractAddress}@${quote.originChain.id}/transfer?address=${quote.originAddress}&uint256=${quote.originAmount}`
31
+ return `ethereum:${quote.originToken.contractAddress}@${quote.originChain.id}/transfer?address=${quote.originDepositAddress}&uint256=${quote.originAmount}`
32
32
  }, [quote])
33
33
 
34
34
  return (
@@ -114,7 +114,7 @@ export const QRCodeDeposit: React.FC<QRCodeDepositProps> = ({
114
114
  </span>
115
115
  <a
116
116
  href={getExplorerUrlForAddress({
117
- address: quote.originAddress,
117
+ address: quote.originDepositAddress,
118
118
  chainId: quote.originChain.id,
119
119
  })}
120
120
  target="_blank"
@@ -122,8 +122,8 @@ export const QRCodeDeposit: React.FC<QRCodeDepositProps> = ({
122
122
  className="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400 hover:underline transition-all"
123
123
  >
124
124
  <span>
125
- {quote.originAddress.slice(0, 6)}...
126
- {quote.originAddress.slice(-4)}
125
+ {quote.originDepositAddress.slice(0, 6)}...
126
+ {quote.originDepositAddress.slice(-4)}
127
127
  </span>
128
128
  <ExternalLink className="w-3 h-3" />
129
129
  </a>
@@ -292,7 +292,7 @@ export const QrCode: React.FC<QrCodeProps> = ({
292
292
  width={size}
293
293
  height={size}
294
294
  viewBox={`0 0 ${size} ${size}`}
295
- className="rounded-lg border border-solid border-gray-200 dark:border-gray-700"
295
+ className="rounded-lg bg-white"
296
296
  />
297
297
  </div>
298
298
  )
@@ -390,7 +390,7 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
390
390
  </div>
391
391
  )}
392
392
 
393
- {quote?.originAddress && (
393
+ {quote?.originDepositAddress && (
394
394
  <div className="flex justify-between items-center">
395
395
  <span className="text-xs text-gray-600 dark:text-gray-400 flex items-center gap-1">
396
396
  Origin Deposit Address:
@@ -400,14 +400,14 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
400
400
  </span>
401
401
  <a
402
402
  href={getExplorerUrlForAddress({
403
- address: quote.originAddress,
403
+ address: quote.originDepositAddress,
404
404
  chainId: quote.originChain.id,
405
405
  })}
406
406
  target="_blank"
407
407
  rel="noopener noreferrer"
408
408
  className="font-mono text-xs hover:underline flex items-center gap-1 text-gray-700 dark:text-gray-300"
409
409
  >
410
- {truncateAddress(quote.originAddress)}
410
+ {truncateAddress(quote.originDepositAddress)}
411
411
  <svg
412
412
  className="w-3 h-3"
413
413
  fill="none"
@@ -426,11 +426,49 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
426
426
  </div>
427
427
  )}
428
428
 
429
+ {quote?.destinationDepositAddress &&
430
+ quote?.destinationDepositAddress !==
431
+ quote?.originDepositAddress && (
432
+ <div className="flex justify-between items-center">
433
+ <span className="text-xs text-gray-600 dark:text-gray-400 flex items-center gap-1">
434
+ Destination Deposit Address:
435
+ <Tooltip message="This is the address that will receive the tokens after any swap and/or bridge from the origin chain">
436
+ <InfoIcon className="w-3 h-3 text-gray-500 dark:text-gray-400 cursor-pointer" />
437
+ </Tooltip>
438
+ </span>
439
+ <a
440
+ href={getExplorerUrlForAddress({
441
+ address: quote.destinationDepositAddress,
442
+ chainId: quote.destinationChain.id,
443
+ })}
444
+ target="_blank"
445
+ rel="noopener noreferrer"
446
+ className="font-mono text-xs hover:underline flex items-center gap-1 text-gray-700 dark:text-gray-300"
447
+ >
448
+ {truncateAddress(quote.destinationDepositAddress)}
449
+ <svg
450
+ className="w-3 h-3"
451
+ fill="none"
452
+ stroke="currentColor"
453
+ viewBox="0 0 24 24"
454
+ aria-hidden="true"
455
+ >
456
+ <path
457
+ strokeLinecap="round"
458
+ strokeLinejoin="round"
459
+ strokeWidth={2}
460
+ d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
461
+ />
462
+ </svg>
463
+ </a>
464
+ </div>
465
+ )}
466
+
429
467
  {quote?.destinationAddress && (
430
468
  <div className="flex justify-between items-center">
431
469
  <span className="text-xs text-gray-600 dark:text-gray-400 flex items-center gap-1">
432
470
  Destination Target Address:
433
- <Tooltip message="This is the address that will receive the tokens after any swap and/or bridge">
471
+ <Tooltip message="This is the final execution address or recipient address">
434
472
  <InfoIcon className="w-3 h-3 text-gray-500 dark:text-gray-400 cursor-pointer" />
435
473
  </Tooltip>
436
474
  </span>
@@ -476,28 +514,58 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
476
514
  </div>
477
515
  )}
478
516
 
479
- {quote?.priceImpact != null && (
480
- <div className="space-y-1">
481
- <div className="flex justify-between items-center">
482
- <span className="text-xs text-gray-600 dark:text-gray-400 flex items-center gap-1">
483
- Price Impact:
484
- <Tooltip message="The percentage change in the token price caused by your trade. Higher impact means your trade affects the market price more, potentially resulting in worse rates.">
485
- <InfoIcon className="w-3 h-3 text-gray-500 dark:text-gray-400 cursor-pointer" />
486
- </Tooltip>
487
- </span>
488
- <span className="font-medium text-xs text-gray-900 dark:text-white">
489
- {quote.priceImpact}%
490
- </span>
491
- </div>
492
- {quote.priceImpactUsdDisplay && (
493
- <div className="text-right">
494
- <span className="text-xs text-gray-500 dark:text-gray-400">
495
- {quote.priceImpactUsdDisplay}
496
- </span>
517
+ {quote?.priceImpact != null &&
518
+ (() => {
519
+ const priceImpactNum = Math.abs(Number(quote.priceImpact))
520
+ return (
521
+ <div className="space-y-1">
522
+ <div className="flex justify-between items-center">
523
+ <span className="text-xs text-gray-600 dark:text-gray-400 flex items-center gap-1">
524
+ Price Impact:
525
+ <Tooltip message="The percentage change in the token price caused by your trade. Higher impact means your trade affects the market price more, potentially resulting in worse rates.">
526
+ <InfoIcon className="w-3 h-3 text-gray-500 dark:text-gray-400 cursor-pointer" />
527
+ </Tooltip>
528
+ </span>
529
+ <span
530
+ className={`font-medium text-xs flex items-center gap-1 ${
531
+ priceImpactNum > 5
532
+ ? "text-red-600 dark:text-red-400"
533
+ : priceImpactNum > 0.5
534
+ ? "text-orange-600 dark:text-orange-400"
535
+ : "text-gray-900 dark:text-white"
536
+ }`}
537
+ >
538
+ {priceImpactNum > 0.5 && (
539
+ <span title="High price impact">
540
+ <svg
541
+ className="w-3 h-3"
542
+ fill="none"
543
+ stroke="currentColor"
544
+ viewBox="0 0 24 24"
545
+ aria-hidden="true"
546
+ >
547
+ <path
548
+ strokeLinecap="round"
549
+ strokeLinejoin="round"
550
+ strokeWidth={2}
551
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
552
+ />
553
+ </svg>
554
+ </span>
555
+ )}
556
+ {quote.priceImpact}%
557
+ </span>
558
+ </div>
559
+ {quote.priceImpactUsdDisplay && (
560
+ <div className="text-right">
561
+ <span className="text-xs text-gray-500 dark:text-gray-400">
562
+ ≈ {quote.priceImpactUsdDisplay}
563
+ </span>
564
+ </div>
565
+ )}
497
566
  </div>
498
- )}
499
- </div>
500
- )}
567
+ )
568
+ })()}
501
569
 
502
570
  {quote?.fees?.totalFeeAmountUsd != null && (
503
571
  <div className="flex justify-between items-center">