0xtrails 0.1.2 → 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 (158) hide show
  1. package/dist/address.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +86 -1
  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-BmFTEOaB.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/chains.d.ts +9 -3
  10. package/dist/chains.d.ts.map +1 -1
  11. package/dist/constants.d.ts +1 -0
  12. package/dist/constants.d.ts.map +1 -1
  13. package/dist/decoders.d.ts +58 -0
  14. package/dist/decoders.d.ts.map +1 -0
  15. package/dist/ens.d.ts +13 -0
  16. package/dist/ens.d.ts.map +1 -0
  17. package/dist/error.d.ts +9 -0
  18. package/dist/error.d.ts.map +1 -1
  19. package/dist/{index-BPsVj7zK.js → index-BXbaLmtt.js} +28779 -25738
  20. package/dist/index.js +2 -2
  21. package/dist/intents.d.ts +4 -4
  22. package/dist/intents.d.ts.map +1 -1
  23. package/dist/lifi.d.ts +4 -0
  24. package/dist/lifi.d.ts.map +1 -0
  25. package/dist/metaTxns.d.ts +1 -1
  26. package/dist/metaTxns.d.ts.map +1 -1
  27. package/dist/mode.d.ts +1 -1
  28. package/dist/mode.d.ts.map +1 -1
  29. package/dist/preconditions.d.ts +1 -1
  30. package/dist/preconditions.d.ts.map +1 -1
  31. package/dist/prepareSend.d.ts +32 -24
  32. package/dist/prepareSend.d.ts.map +1 -1
  33. package/dist/prices.d.ts +3 -1
  34. package/dist/prices.d.ts.map +1 -1
  35. package/dist/proxyCaller.d.ts +0 -1
  36. package/dist/proxyCaller.d.ts.map +1 -1
  37. package/dist/relaySdk.d.ts.map +1 -1
  38. package/dist/relayer.d.ts.map +1 -1
  39. package/dist/tokenBalances.d.ts +1 -1
  40. package/dist/tokenBalances.d.ts.map +1 -1
  41. package/dist/tokens.d.ts +2 -1
  42. package/dist/tokens.d.ts.map +1 -1
  43. package/dist/trails.d.ts +4 -4
  44. package/dist/trails.d.ts.map +1 -1
  45. package/dist/transactions.d.ts +4 -0
  46. package/dist/transactions.d.ts.map +1 -1
  47. package/dist/utils.d.ts +6 -0
  48. package/dist/utils.d.ts.map +1 -1
  49. package/dist/wallets.d.ts +247 -5
  50. package/dist/wallets.d.ts.map +1 -1
  51. package/dist/widget/components/ChainFilterDropdown.d.ts +2 -0
  52. package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
  53. package/dist/widget/components/ConnectWallet.d.ts +1 -0
  54. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  55. package/dist/widget/components/DebugScreensDropdown.d.ts.map +1 -1
  56. package/dist/widget/components/ErrorDisplay.d.ts +9 -0
  57. package/dist/widget/components/ErrorDisplay.d.ts.map +1 -0
  58. package/dist/widget/components/FundSendForm.d.ts +2 -2
  59. package/dist/widget/components/FundSendForm.d.ts.map +1 -1
  60. package/dist/widget/components/OriginTransferInformation.d.ts +10 -0
  61. package/dist/widget/components/OriginTransferInformation.d.ts.map +1 -0
  62. package/dist/widget/components/PaySendForm.d.ts +2 -2
  63. package/dist/widget/components/PaySendForm.d.ts.map +1 -1
  64. package/dist/widget/components/QrCode.d.ts +1 -1
  65. package/dist/widget/components/QrCode.d.ts.map +1 -1
  66. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  67. package/dist/widget/components/Receipt.d.ts.map +1 -1
  68. package/dist/widget/components/Receive.d.ts +12 -0
  69. package/dist/widget/components/Receive.d.ts.map +1 -0
  70. package/dist/widget/components/RefundAddressInput.d.ts +13 -0
  71. package/dist/widget/components/RefundAddressInput.d.ts.map +1 -0
  72. package/dist/widget/components/Swap.d.ts +47 -0
  73. package/dist/widget/components/Swap.d.ts.map +1 -0
  74. package/dist/widget/components/SwapDisplay.d.ts +9 -0
  75. package/dist/widget/components/SwapDisplay.d.ts.map +1 -0
  76. package/dist/widget/components/TokenList.d.ts +0 -2
  77. package/dist/widget/components/TokenList.d.ts.map +1 -1
  78. package/dist/widget/components/TokenSelector.d.ts +26 -0
  79. package/dist/widget/components/TokenSelector.d.ts.map +1 -0
  80. package/dist/widget/components/TransferPendingVertical.d.ts +2 -0
  81. package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -1
  82. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  83. package/dist/widget/components/WalletConnectionPending.d.ts +12 -0
  84. package/dist/widget/components/WalletConnectionPending.d.ts.map +1 -0
  85. package/dist/widget/components/WalletList.d.ts.map +1 -1
  86. package/dist/widget/components/YellowWarningAnimation.d.ts +2 -0
  87. package/dist/widget/components/YellowWarningAnimation.d.ts.map +1 -0
  88. package/dist/widget/hooks/useAmountUsd.d.ts +1 -3
  89. package/dist/widget/hooks/useAmountUsd.d.ts.map +1 -1
  90. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  91. package/dist/widget/hooks/useDebugScreens.d.ts +22 -0
  92. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -0
  93. package/dist/widget/hooks/useSendForm.d.ts +12 -6
  94. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  95. package/dist/widget/hooks/useTokenList.d.ts +2 -3
  96. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  97. package/dist/widget/index.js +1 -1
  98. package/dist/widget/widget.d.ts.map +1 -1
  99. package/package.json +19 -15
  100. package/src/aave.ts +13 -13
  101. package/src/address.ts +3 -0
  102. package/src/analytics.ts +192 -8
  103. package/src/apiClient.ts +1 -1
  104. package/src/cctpqueue.ts +1 -1
  105. package/src/chains.ts +45 -7
  106. package/src/constants.ts +7 -4
  107. package/src/decoders.ts +310 -0
  108. package/src/ens.ts +32 -0
  109. package/src/error.ts +101 -1
  110. package/src/intents.ts +10 -2
  111. package/src/lifi.ts +58 -0
  112. package/src/metaTxns.ts +1 -1
  113. package/src/mode.ts +1 -1
  114. package/src/morpho.ts +3 -3
  115. package/src/pools.ts +18 -18
  116. package/src/preconditions.ts +1 -1
  117. package/src/prepareSend.ts +463 -113
  118. package/src/prices.ts +26 -1
  119. package/src/proxyCaller.ts +2 -14
  120. package/src/relaySdk.ts +1 -0
  121. package/src/relayer.ts +8 -0
  122. package/src/tokenBalances.ts +24 -17
  123. package/src/tokens.ts +147 -22
  124. package/src/trails.ts +4 -4
  125. package/src/transactions.ts +35 -17
  126. package/src/utils.ts +28 -0
  127. package/src/wallets.ts +275 -35
  128. package/src/widget/compiled.css +2 -2
  129. package/src/widget/components/ChainFilterDropdown.tsx +42 -33
  130. package/src/widget/components/ChainImage.tsx +1 -1
  131. package/src/widget/components/ConnectWallet.tsx +92 -128
  132. package/src/widget/components/DebugScreensDropdown.tsx +6 -0
  133. package/src/widget/components/ErrorDisplay.tsx +150 -0
  134. package/src/widget/components/FundSendForm.tsx +78 -11
  135. package/src/widget/components/OriginTransferInformation.tsx +59 -0
  136. package/src/widget/components/PaySendForm.tsx +80 -13
  137. package/src/widget/components/QRCodeDeposit.tsx +6 -6
  138. package/src/widget/components/QrCode.tsx +278 -17
  139. package/src/widget/components/QuoteDetails.tsx +93 -25
  140. package/src/widget/components/Receipt.tsx +296 -103
  141. package/src/widget/components/Receive.tsx +146 -0
  142. package/src/widget/components/RecentTokens.tsx +1 -1
  143. package/src/widget/components/RefundAddressInput.tsx +149 -0
  144. package/src/widget/components/Swap.tsx +769 -0
  145. package/src/widget/components/SwapDisplay.tsx +68 -0
  146. package/src/widget/components/TokenList.tsx +27 -363
  147. package/src/widget/components/TokenSelector.tsx +405 -0
  148. package/src/widget/components/TransferPendingVertical.tsx +162 -112
  149. package/src/widget/components/WalletConnect.tsx +9 -7
  150. package/src/widget/components/WalletConnectionPending.tsx +157 -0
  151. package/src/widget/components/WalletList.tsx +6 -5
  152. package/src/widget/components/YellowWarningAnimation.tsx +146 -0
  153. package/src/widget/hooks/useAmountUsd.ts +3 -8
  154. package/src/widget/hooks/useCheckout.ts +3 -2
  155. package/src/widget/hooks/useDebugScreens.ts +583 -0
  156. package/src/widget/hooks/useSendForm.ts +111 -35
  157. package/src/widget/hooks/useTokenList.ts +155 -122
  158. package/src/widget/widget.tsx +503 -523
@@ -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"
@@ -12,6 +12,7 @@ import { FeeOptions } from "./FeeOptions.js"
12
12
  import { TokenImage } from "./TokenImage.js"
13
13
  import { QuoteDetails } from "./QuoteDetails.js"
14
14
  import { TruncatedAddress } from "./TruncatedAddress.js"
15
+ // import { RefundAddressInput } from "./RefundAddressInput.js"
15
16
  import { type PrepareSendQuote, TradeType } from "../../prepareSend.js"
16
17
  import { getChainInfo, getChainColor } from "../../chains.js"
17
18
  import { formatTvl } from "../../prices.js"
@@ -20,6 +21,7 @@ import morphoLogo from "../assets/morpho.svg"
20
21
  import { formatUsdAmountDisplay } from "../../tokenBalances.js"
21
22
  import { MINIMUM_USD_AMOUNT_FOR_SWAP } from "../../constants.js"
22
23
  import { ScreenHeader } from "./ScreenHeader.js"
24
+ import { ErrorDisplay } from "./ErrorDisplay.js"
23
25
 
24
26
  interface PaySendFormProps {
25
27
  selectedToken: Token
@@ -41,7 +43,7 @@ interface PaySendFormProps {
41
43
  gasless?: boolean
42
44
  setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
43
45
  quoteProvider?: string
44
- fundMethod?: string | null
46
+ fundMethod?: string
45
47
  onNavigateToMeshConnect?: (
46
48
  props: {
47
49
  toTokenSymbol: string
@@ -52,7 +54,7 @@ interface PaySendFormProps {
52
54
  quote?: PrepareSendQuote | null,
53
55
  ) => void
54
56
  onAmountUpdate?: (amount: string) => void
55
- mode?: "pay" | "fund" | "earn"
57
+ mode?: "pay" | "fund" | "earn" | "swap" | "receive"
56
58
  selectedPool?: {
57
59
  id: string
58
60
  name: string
@@ -102,6 +104,9 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
102
104
  selectedPool,
103
105
  checkoutOnHandlers,
104
106
  }) => {
107
+ // const [isRefundAddressOpen, setIsRefundAddressOpen] = useState(false)
108
+ // const [refundAddress, setRefundAddress] = useState<string>(account.address)
109
+ const [refetchTrigger, setRefetchTrigger] = useState(0)
105
110
  const {
106
111
  amount,
107
112
  amountRaw,
@@ -138,6 +143,9 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
138
143
  supportedChains,
139
144
  isValidCustomToken,
140
145
  prepareSendQuote,
146
+ quoteError,
147
+ quoteErrorPrettified,
148
+ isSameTokenWithoutCustomCalldata,
141
149
  } = useSendForm({
142
150
  account,
143
151
  toAmount,
@@ -145,6 +153,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
145
153
  toChainId,
146
154
  toToken,
147
155
  toCalldata,
156
+ // refundAddress,
148
157
  walletClient,
149
158
  onTransactionStateChange,
150
159
  onError,
@@ -162,6 +171,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
162
171
  mode,
163
172
  onNavigateToMeshConnect,
164
173
  checkoutOnHandlers,
174
+ refetchTrigger,
165
175
  })
166
176
 
167
177
  // Handle amount input changes with decimal validation
@@ -177,6 +187,11 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
177
187
  [setAmount],
178
188
  )
179
189
 
190
+ // Handle manual quote refetch
191
+ const handleRefetchQuote = useCallback(() => {
192
+ setRefetchTrigger((prev) => prev + 1)
193
+ }, [])
194
+
180
195
  // Call onAmountUpdate when amountRaw changes
181
196
  useEffect(() => {
182
197
  if (onAmountUpdate) {
@@ -373,7 +388,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
373
388
  <div className="mb-4">
374
389
  <label
375
390
  htmlFor="destination-chain"
376
- 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"
377
392
  >
378
393
  Destination Chain
379
394
  </label>
@@ -436,7 +451,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
436
451
  <div className="mb-4">
437
452
  <label
438
453
  htmlFor="token"
439
- 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"
440
455
  >
441
456
  Receive Token
442
457
  </label>
@@ -505,7 +520,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
505
520
  <div className="mb-2">
506
521
  <label
507
522
  htmlFor="amount"
508
- 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"
509
524
  >
510
525
  Amount to {mode === "earn" ? "Deposit" : "Receive"}
511
526
  </label>
@@ -526,7 +541,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
526
541
  </div>
527
542
  {amountUsdDisplay && selectedDestToken?.symbol && (
528
543
  <div className="h-6 mt-1">
529
- <div className="text-sm text-gray-400">
544
+ <div className="text-sm text-gray-400 text-left">
530
545
  ≈ {amountUsdDisplay}
531
546
  </div>
532
547
  </div>
@@ -537,10 +552,37 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
537
552
  {/* Receive Section - Similar to FundSendForm */}
538
553
  {(toAmount || toChainId || toToken) && (
539
554
  <div className="space-y-1">
540
- <div
541
- className={`text-lg font-semibold text-left ${"text-gray-900 dark:text-white"}`}
542
- >
543
- {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>
544
586
  </div>
545
587
 
546
588
  <div className="p-2">
@@ -653,6 +695,16 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
653
695
  </div>
654
696
  )}
655
697
 
698
+ {/* Refund Address Input */}
699
+ {/* <RefundAddressInput
700
+ account={account}
701
+ isOpen={isRefundAddressOpen}
702
+ onToggle={() => setIsRefundAddressOpen(!isRefundAddressOpen)}
703
+ refundAddress={refundAddress}
704
+ onRefundAddressChange={setRefundAddress}
705
+ chainId={selectedDestinationChain.id}
706
+ /> */}
707
+
656
708
  {/* Fee Options */}
657
709
  <FeeOptions
658
710
  options={FEE_TOKENS}
@@ -661,6 +713,18 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
661
713
  />
662
714
 
663
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
+ )}
664
728
  {prepareSendQuote?.noSufficientBalance ? (
665
729
  <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
666
730
  <div className="flex items-center space-x-2">
@@ -719,7 +783,8 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
719
783
  !isValidCustomToken ||
720
784
  isLoadingQuote ||
721
785
  !prepareSendQuote ||
722
- prepareSendQuote?.noSufficientBalance
786
+ prepareSendQuote?.noSufficientBalance ||
787
+ isSameTokenWithoutCustomCalldata
723
788
  }
724
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`}
725
790
  >
@@ -730,6 +795,8 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
730
795
  />
731
796
  <span>{buttonText}</span>
732
797
  </div>
798
+ ) : isSameTokenWithoutCustomCalldata ? (
799
+ "Select Different Tokens"
733
800
  ) : prepareSendQuote?.noSufficientBalance ? (
734
801
  "Insufficient Balance"
735
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 (
@@ -64,7 +64,7 @@ export const QRCodeDeposit: React.FC<QRCodeDepositProps> = ({
64
64
  >
65
65
  <QrCode
66
66
  url={useSimpleQrCode ? eip631Url : eip681Url}
67
- size={200}
67
+ size={300}
68
68
  />
69
69
  </button>
70
70
  {!useSimpleQrCode && (
@@ -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>
@@ -1,5 +1,6 @@
1
1
  import type React from "react"
2
- import { QRCodeSVG } from "qrcode.react"
2
+ import { useEffect, useRef } from "react"
3
+ import QRCodeUtil from "qrcode"
3
4
 
4
5
  interface QrCodeProps {
5
6
  url: string
@@ -8,31 +9,291 @@ interface QrCodeProps {
8
9
  className?: string
9
10
  }
10
11
 
12
+ const CONNECTING_ERROR_MARGIN = 0.1
13
+ const CIRCLE_SIZE_MODIFIER = 2.5
14
+ const QRCODE_MATRIX_MARGIN = 7
15
+
16
+ function isAdjecentDots(cy: number, otherCy: number, cellSize: number) {
17
+ if (cy === otherCy) {
18
+ return false
19
+ }
20
+ const diff = cy - otherCy < 0 ? otherCy - cy : cy - otherCy
21
+ return diff <= cellSize + CONNECTING_ERROR_MARGIN
22
+ }
23
+
24
+ function getMatrix(
25
+ value: string,
26
+ errorCorrectionLevel: QRCodeUtil.QRCodeErrorCorrectionLevel,
27
+ ) {
28
+ const arr = Array.prototype.slice.call(
29
+ QRCodeUtil.create(value, {
30
+ errorCorrectionLevel,
31
+ }).modules.data,
32
+ 0,
33
+ )
34
+ const sqrt = Math.sqrt(arr.length)
35
+ return arr.reduce(
36
+ (rows, key, index) =>
37
+ (index % sqrt === 0
38
+ ? rows.push([key])
39
+ : rows[rows.length - 1].push(key)) && rows,
40
+ [],
41
+ )
42
+ }
43
+
11
44
  export const QrCode: React.FC<QrCodeProps> = ({
12
45
  url,
13
46
  imageUrl,
14
47
  size = 250,
15
48
  className = "",
16
49
  }) => {
50
+ const svgRef = useRef<SVGSVGElement>(null)
51
+
52
+ useEffect(() => {
53
+ if (!svgRef.current) return
54
+
55
+ // Clear existing content
56
+ while (svgRef.current.firstChild) {
57
+ svgRef.current.removeChild(svgRef.current.firstChild)
58
+ }
59
+
60
+ const strokeWidth = 5
61
+ const padding = 8
62
+ const logoSize = size * 0.16 // 16% of size for logo
63
+ const matrix = getMatrix(url, "Q")
64
+ const cellSize = (size - 2 * padding) / matrix.length
65
+
66
+ // Corner squares positions
67
+ const qrList = [
68
+ { x: 0, y: 0 }, // Top-left
69
+ { x: 1, y: 0 }, // Top-right
70
+ { x: 0, y: 1 }, // Bottom-left
71
+ ]
72
+
73
+ // Draw corner squares
74
+ qrList.forEach(({ x, y }) => {
75
+ const x1 = (matrix.length - QRCODE_MATRIX_MARGIN) * cellSize * x + padding
76
+ const y1 = (matrix.length - QRCODE_MATRIX_MARGIN) * cellSize * y + padding
77
+ const borderRadius = 0.45
78
+
79
+ for (let i = 0; i < qrList.length; i += 1) {
80
+ const dotSize = cellSize * (QRCODE_MATRIX_MARGIN - i * 2)
81
+ const rect = document.createElementNS(
82
+ "http://www.w3.org/2000/svg",
83
+ "rect",
84
+ )
85
+
86
+ if (i === 0) {
87
+ // Outer square: transparent with dark stroke
88
+ rect.setAttribute("fill", "transparent")
89
+ rect.setAttribute("stroke", "#141414")
90
+ rect.setAttribute("stroke-width", strokeWidth.toString())
91
+ rect.setAttribute("width", (dotSize - strokeWidth).toString())
92
+ rect.setAttribute("height", (dotSize - strokeWidth).toString())
93
+ rect.setAttribute(
94
+ "rx",
95
+ ((dotSize - strokeWidth) * borderRadius).toString(),
96
+ )
97
+ rect.setAttribute(
98
+ "ry",
99
+ ((dotSize - strokeWidth) * borderRadius).toString(),
100
+ )
101
+ rect.setAttribute(
102
+ "x",
103
+ (y1 + cellSize * i + strokeWidth / 2).toString(),
104
+ )
105
+ rect.setAttribute(
106
+ "y",
107
+ (x1 + cellSize * i + strokeWidth / 2).toString(),
108
+ )
109
+ } else if (i === 1) {
110
+ // Middle square: transparent with no stroke
111
+ rect.setAttribute("fill", "transparent")
112
+ rect.setAttribute("stroke", "#141414")
113
+ rect.setAttribute("stroke-width", "0")
114
+ rect.setAttribute("width", dotSize.toString())
115
+ rect.setAttribute("height", dotSize.toString())
116
+ rect.setAttribute("rx", (dotSize * borderRadius).toString())
117
+ rect.setAttribute("ry", (dotSize * borderRadius).toString())
118
+ rect.setAttribute("x", (y1 + cellSize * i).toString())
119
+ rect.setAttribute("y", (x1 + cellSize * i).toString())
120
+ } else {
121
+ // Inner square: dark fill with no stroke
122
+ rect.setAttribute("fill", "#141414")
123
+ rect.setAttribute("stroke", "#141414")
124
+ rect.setAttribute("stroke-width", "0")
125
+ rect.setAttribute("width", dotSize.toString())
126
+ rect.setAttribute("height", dotSize.toString())
127
+ rect.setAttribute("rx", (dotSize * borderRadius).toString())
128
+ rect.setAttribute("ry", (dotSize * borderRadius).toString())
129
+ rect.setAttribute("x", (y1 + cellSize * i).toString())
130
+ rect.setAttribute("y", (x1 + cellSize * i).toString())
131
+ }
132
+
133
+ if (svgRef.current) {
134
+ svgRef.current.appendChild(rect)
135
+ }
136
+ }
137
+ })
138
+
139
+ // Calculate logo area to exclude from data dots
140
+ const clearArenaSize = Math.floor((logoSize + 25) / cellSize)
141
+ const matrixMiddleStart = matrix.length / 2 - clearArenaSize / 2
142
+ const matrixMiddleEnd = matrix.length / 2 + clearArenaSize / 2 - 1
143
+
144
+ const circles: [number, number][] = []
145
+
146
+ // Get coordinates for each QR code dot
147
+ matrix.forEach((row: QRCodeUtil.QRCode[], i: number) => {
148
+ row.forEach((_, j: number) => {
149
+ if (matrix[i][j]) {
150
+ // Skip corner areas
151
+ if (
152
+ !(
153
+ (i < QRCODE_MATRIX_MARGIN && j < QRCODE_MATRIX_MARGIN) ||
154
+ (i > matrix.length - (QRCODE_MATRIX_MARGIN + 1) &&
155
+ j < QRCODE_MATRIX_MARGIN) ||
156
+ (i < QRCODE_MATRIX_MARGIN &&
157
+ j > matrix.length - (QRCODE_MATRIX_MARGIN + 1))
158
+ )
159
+ ) {
160
+ // Skip logo area
161
+ if (
162
+ !(
163
+ i > matrixMiddleStart &&
164
+ i < matrixMiddleEnd &&
165
+ j > matrixMiddleStart &&
166
+ j < matrixMiddleEnd
167
+ )
168
+ ) {
169
+ const cx = i * cellSize + cellSize / 2 + padding
170
+ const cy = j * cellSize + cellSize / 2 + padding
171
+ circles.push([cx, cy])
172
+ }
173
+ }
174
+ }
175
+ })
176
+ })
177
+
178
+ // Group dots by x-coordinate
179
+ const circlesToConnect: Record<number, number[]> = {}
180
+ circles.forEach(([cx, cy]) => {
181
+ if (circlesToConnect[cx]) {
182
+ circlesToConnect[cx]?.push(cy)
183
+ } else {
184
+ circlesToConnect[cx] = [cy]
185
+ }
186
+ })
187
+
188
+ // Draw individual dots (those without neighbors)
189
+ Object.entries(circlesToConnect)
190
+ .map(([cx, cys]) => {
191
+ const newCys = cys.filter((cy) =>
192
+ cys.every((otherCy) => !isAdjecentDots(cy, otherCy, cellSize)),
193
+ )
194
+ return [Number(cx), newCys] as [number, number[]]
195
+ })
196
+ .forEach(([cx, cys]) => {
197
+ cys.forEach((cy) => {
198
+ const circle = document.createElementNS(
199
+ "http://www.w3.org/2000/svg",
200
+ "circle",
201
+ )
202
+ circle.setAttribute("cx", cx.toString())
203
+ circle.setAttribute("cy", cy.toString())
204
+ circle.setAttribute("fill", "#141414")
205
+ circle.setAttribute("r", (cellSize / CIRCLE_SIZE_MODIFIER).toString())
206
+ if (svgRef.current) {
207
+ svgRef.current.appendChild(circle)
208
+ }
209
+ })
210
+ })
211
+
212
+ // Draw dots for connected groups (instead of lines)
213
+ Object.entries(circlesToConnect)
214
+ .filter(([_, cys]) => cys.length > 1)
215
+ .map(([cx, cys]) => {
216
+ const newCys = cys.filter((cy) =>
217
+ cys.some((otherCy) => isAdjecentDots(cy, otherCy, cellSize)),
218
+ )
219
+ return [Number(cx), newCys] as [number, number[]]
220
+ })
221
+ .map(([cx, cys]) => {
222
+ cys.sort((a, b) => (a < b ? -1 : 1))
223
+ const groups: number[][] = []
224
+ for (const cy of cys) {
225
+ const group = groups.find((item) =>
226
+ item.some((otherCy) => isAdjecentDots(cy, otherCy, cellSize)),
227
+ )
228
+ if (group) {
229
+ group.push(cy)
230
+ } else {
231
+ groups.push([cy])
232
+ }
233
+ }
234
+ return [cx, groups.map((item) => [item[0], item[item.length - 1]])] as [
235
+ number,
236
+ number[][],
237
+ ]
238
+ })
239
+ .forEach(([cx, groups]) => {
240
+ groups.forEach(([y1, y2]) => {
241
+ if (y1 !== undefined && y2 !== undefined) {
242
+ // Instead of drawing a line, draw individual dots at each position
243
+ const positions = []
244
+ for (let y = y1; y <= y2; y += cellSize) {
245
+ positions.push(y)
246
+ }
247
+
248
+ positions.forEach((y) => {
249
+ const circle = document.createElementNS(
250
+ "http://www.w3.org/2000/svg",
251
+ "circle",
252
+ )
253
+ circle.setAttribute("cx", cx.toString())
254
+ circle.setAttribute("cy", y.toString())
255
+ circle.setAttribute("fill", "#141414")
256
+ circle.setAttribute(
257
+ "r",
258
+ (cellSize / CIRCLE_SIZE_MODIFIER).toString(),
259
+ )
260
+ if (svgRef.current) {
261
+ svgRef.current.appendChild(circle)
262
+ }
263
+ })
264
+ }
265
+ })
266
+ })
267
+
268
+ // Add logo if provided
269
+ if (imageUrl) {
270
+ const logoX = (size - logoSize) / 2
271
+ const logoY = (size - logoSize) / 2
272
+
273
+ const logo = document.createElementNS(
274
+ "http://www.w3.org/2000/svg",
275
+ "image",
276
+ )
277
+ logo.setAttribute("href", imageUrl)
278
+ logo.setAttribute("x", logoX.toString())
279
+ logo.setAttribute("y", logoY.toString())
280
+ logo.setAttribute("width", logoSize.toString())
281
+ logo.setAttribute("height", logoSize.toString())
282
+ if (svgRef.current) {
283
+ svgRef.current.appendChild(logo)
284
+ }
285
+ }
286
+ }, [url, imageUrl, size])
287
+
17
288
  return (
18
289
  <div className={`inline-block ${className}`}>
19
- <QRCodeSVG
20
- value={url}
21
- size={size}
22
- bgColor="#FFFFFF"
23
- fgColor="#000000"
24
- level="M"
25
- marginSize={2}
26
- imageSettings={{
27
- src: imageUrl || "https://trails.build/favicon.ico",
28
- height: size * 0.16,
29
- width: size * 0.16,
30
- excavate: true,
31
- }}
32
- className="rounded-lg border border-solid border-gray-200 dark:border-gray-700"
290
+ <svg
291
+ ref={svgRef}
292
+ width={size}
293
+ height={size}
294
+ viewBox={`0 0 ${size} ${size}`}
295
+ className="rounded-lg bg-white"
33
296
  />
34
297
  </div>
35
298
  )
36
299
  }
37
-
38
- export default QrCode