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,13 +1,16 @@
1
1
  import { useQuery } from "@tanstack/react-query"
2
2
  import type React from "react"
3
- import { useEffect, useState } from "react"
3
+ import { useEffect, useState, useCallback, useMemo } from "react"
4
+ import { ChevronRight } from "lucide-react"
4
5
  import type { TransactionState } from "../../transactions.js"
5
6
  import { GreenCheckAnimation } from "./GreenCheckAnimation.js"
7
+ import { YellowWarningAnimation } from "./YellowWarningAnimation.js"
6
8
  import { getTxTimeDiff } from "../../transactions.js"
7
9
  import { QuoteDetails } from "./QuoteDetails.js"
8
10
  import type { PrepareSendQuote } from "../../prepareSend.js"
9
11
  import { truncateAddress } from "../../address.js"
10
12
  import { formatElapsed } from "../../utils.js"
13
+ import { ChainImage } from "./ChainImage.js"
11
14
 
12
15
  interface ReceiptProps {
13
16
  onSendAnother: () => void
@@ -55,8 +58,9 @@ function useTxTimeDiff(firstTx?: TransactionState, lastTx?: TransactionState) {
55
58
 
56
59
  // Custom hook to compute finalExplorerUrl and completionTimeSeconds
57
60
  function useReceipt(transactionStates?: TransactionState[]) {
58
- const finalExplorerUrl =
59
- transactionStates?.[transactionStates.length - 1]?.explorerUrl
61
+ const lastTx = transactionStates?.[transactionStates.length - 1]
62
+ const finalExplorerUrl = lastTx?.explorerUrl
63
+ const finalChainId = lastTx?.chainId
60
64
 
61
65
  // Only consider confirmed transactions
62
66
  const confirmedTxs = (transactionStates || []).filter(
@@ -67,10 +71,11 @@ function useReceipt(transactionStates?: TransactionState[]) {
67
71
  const last = confirmedTxs[confirmedTxs.length - 1]
68
72
 
69
73
  const completionTimeSeconds = useTxTimeDiff(first, last)
70
- return { finalExplorerUrl, completionTimeSeconds }
74
+ return { finalExplorerUrl, completionTimeSeconds, finalChainId }
71
75
  }
72
76
 
73
77
  export const Receipt: React.FC<ReceiptProps> = ({
78
+ onSendAnother,
74
79
  onClose,
75
80
  renderInline = false,
76
81
  transactionStates = [],
@@ -78,6 +83,8 @@ export const Receipt: React.FC<ReceiptProps> = ({
78
83
  quote,
79
84
  }) => {
80
85
  const [showContent, setShowContent] = useState(false)
86
+ const [showRefundInfo, setShowRefundInfo] = useState(false)
87
+ const [refundMessage, setRefundMessage] = useState<string | null>(null)
81
88
 
82
89
  useEffect(() => {
83
90
  const timer = setTimeout(() => {
@@ -87,13 +94,155 @@ export const Receipt: React.FC<ReceiptProps> = ({
87
94
  return () => clearTimeout(timer)
88
95
  }, [])
89
96
 
90
- const { finalExplorerUrl, completionTimeSeconds: calculatedCompletionTime } =
91
- useReceipt(transactionStates)
97
+ // Helper function to check if a specific transaction has meta tx errors
98
+ const hasMetaTxError = useCallback((tx: TransactionState) => {
99
+ return tx?.decodedGuestModuleEvents?.some(
100
+ (event) => event.type === "CallFailed",
101
+ )
102
+ }, [])
103
+
104
+ const showMetaTxError = useMemo(() => {
105
+ return transactionStates.some((tx) => hasMetaTxError(tx))
106
+ }, [transactionStates, hasMetaTxError])
107
+
108
+ const {
109
+ finalExplorerUrl,
110
+ finalChainId,
111
+ completionTimeSeconds: calculatedCompletionTime,
112
+ } = useReceipt(transactionStates)
92
113
 
93
114
  // Use provided totalCompletionSeconds if available, otherwise use calculated time
94
115
  const completionTimeSeconds =
95
116
  totalCompletionSeconds ?? calculatedCompletionTime
96
117
 
118
+ // Detect successful refund events on origin or sweep on destination
119
+ useEffect(() => {
120
+ if (!transactionStates || transactionStates.length === 0) return
121
+
122
+ const originTx = transactionStates[0]
123
+ const destinationTx = transactionStates[transactionStates.length - 1]
124
+
125
+ // Check if this is a same-chain transaction (all transactions on same chain)
126
+ const isSameChain =
127
+ transactionStates.length <= 2 &&
128
+ transactionStates.every((tx) => tx.chainId === originTx?.chainId)
129
+
130
+ const originRefund = originTx?.decodedTrailsTokenSweeperEvents?.find(
131
+ (e) => e.type === "Refund",
132
+ )
133
+
134
+ // Only check destination sweep for cross-chain transactions
135
+ const destinationSweep = !isSameChain
136
+ ? destinationTx?.decodedTrailsTokenSweeperEvents?.find(
137
+ (e) => e.type === "Sweep",
138
+ )
139
+ : null
140
+
141
+ if (originRefund) {
142
+ setRefundMessage(
143
+ "The origin call failed, but your funds were successfully refunded on the origin chain.",
144
+ )
145
+ setShowRefundInfo(true)
146
+ return
147
+ }
148
+
149
+ if (destinationSweep) {
150
+ setRefundMessage(
151
+ "The destination call failed, but a refund was successfully handled on the destination chain.",
152
+ )
153
+ setShowRefundInfo(true)
154
+ return
155
+ }
156
+
157
+ setShowRefundInfo(false)
158
+ setRefundMessage(null)
159
+ }, [transactionStates])
160
+
161
+ // Extract QuoteDetails section to reuse in both success and failure states
162
+ const quoteDetailsSection = quote && (
163
+ <div className="mt-2">
164
+ <QuoteDetails quote={quote} showContent={true}>
165
+ {transactionStates.length > 0 && (
166
+ <>
167
+ <div className="font-medium text-gray-700 dark:text-gray-300">
168
+ Transactions:
169
+ </div>
170
+ <div className="space-y-2">
171
+ {transactionStates.map((state, i) => (
172
+ <div
173
+ key={`${state.transactionHash}-${i}`}
174
+ className="p-2 rounded bg-gray-100 dark:bg-gray-700/50"
175
+ >
176
+ <div className="flex justify-between items-center">
177
+ <span className="text-gray-600 dark:text-gray-400">
178
+ {state.label}:
179
+ </span>
180
+ <span
181
+ className={`px-2 py-0.5 rounded-full text-xs ${
182
+ state.state === "confirmed"
183
+ ? "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-400"
184
+ : "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-400"
185
+ }`}
186
+ >
187
+ {state.state.charAt(0).toUpperCase() +
188
+ state.state.slice(1)}
189
+ </span>
190
+ </div>
191
+ <div className="mt-1 flex justify-between items-center">
192
+ <span className="text-gray-600 dark:text-gray-400">
193
+ Hash:
194
+ </span>
195
+ <a
196
+ href={state.explorerUrl}
197
+ target="_blank"
198
+ rel="noopener noreferrer"
199
+ className="flex items-center gap-1 hover:underline text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
200
+ >
201
+ {hasMetaTxError(state) && (
202
+ <svg
203
+ className="w-4 h-4 text-orange-500"
204
+ fill="none"
205
+ viewBox="0 0 24 24"
206
+ stroke="currentColor"
207
+ >
208
+ <title>
209
+ An internal meta transaction failure occurred
210
+ </title>
211
+ <path
212
+ strokeLinecap="round"
213
+ strokeLinejoin="round"
214
+ strokeWidth={2}
215
+ 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"
216
+ />
217
+ </svg>
218
+ )}
219
+ <ChainImage chainId={state.chainId} size={16} />
220
+ <span>{truncateAddress(state.transactionHash)}</span>
221
+ <svg
222
+ className="w-3 h-3"
223
+ fill="none"
224
+ viewBox="0 0 24 24"
225
+ stroke="currentColor"
226
+ >
227
+ <title>External Link</title>
228
+ <path
229
+ strokeLinecap="round"
230
+ strokeLinejoin="round"
231
+ strokeWidth={2}
232
+ d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
233
+ />
234
+ </svg>
235
+ </a>
236
+ </div>
237
+ </div>
238
+ ))}
239
+ </div>
240
+ </>
241
+ )}
242
+ </QuoteDetails>
243
+ </div>
244
+ )
245
+
97
246
  if (!finalExplorerUrl) {
98
247
  return (
99
248
  <div className="flex flex-col justify-center min-h-full space-y-6 pt-8">
@@ -126,6 +275,21 @@ export const Receipt: React.FC<ReceiptProps> = ({
126
275
  </p>
127
276
  </div>
128
277
 
278
+ {onSendAnother && (
279
+ <div className="flex justify-center">
280
+ <button
281
+ type="button"
282
+ onClick={onSendAnother}
283
+ className="inline-flex items-center gap-1 px-4 py-2 text-sm bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 trails-border-radius-button transition-colors duration-200 text-gray-600 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 cursor-pointer font-medium"
284
+ >
285
+ Try Again
286
+ </button>
287
+ </div>
288
+ )}
289
+
290
+ {/* Quote Details - Show even in failed state */}
291
+ {quoteDetailsSection}
292
+
129
293
  {!renderInline && (
130
294
  <div className="text-center">
131
295
  <button
@@ -145,15 +309,24 @@ export const Receipt: React.FC<ReceiptProps> = ({
145
309
  <div className="flex flex-col justify-center min-h-full space-y-6 pt-8">
146
310
  <div className="text-center mb-2">
147
311
  <div className={`mx-auto flex items-center justify-center`}>
148
- <GreenCheckAnimation />
312
+ {showRefundInfo || showMetaTxError ? (
313
+ <YellowWarningAnimation />
314
+ ) : (
315
+ <GreenCheckAnimation />
316
+ )}
149
317
  </div>
150
318
 
151
319
  <div
152
320
  className={`transition-all duration-500 ease-out ${showContent ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"}`}
153
321
  >
154
322
  <h2 className="mt-4 text-2xl font-bold text-gray-900 dark:text-white">
155
- Completed
323
+ {showRefundInfo ? "Unable to process." : "Completed"}
156
324
  </h2>
325
+ {showRefundInfo && (
326
+ <div className="mt-1 text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-1 justify-center animate-fade-in-up">
327
+ Refunded.
328
+ </div>
329
+ )}
157
330
  {completionTimeSeconds > 0 && (
158
331
  <div
159
332
  className="mt-1 text-sm text-gray-600 dark:text-gray-400 flex items-center gap-1 justify-center animate-fade-in-up"
@@ -162,28 +335,37 @@ export const Receipt: React.FC<ReceiptProps> = ({
162
335
  animationFillMode: "both",
163
336
  }}
164
337
  >
165
- <span className="inline-block animate-fade-in-up">
166
- Completed in{" "}
167
- <strong>{formatElapsed(completionTimeSeconds)}</strong>
168
- </span>
169
- {totalCompletionSeconds && totalCompletionSeconds < 15 && (
170
- <svg
171
- className="w-4 h-4 ml-1 text-yellow-400 inline"
172
- fill="none"
173
- viewBox="0 0 24 24"
174
- stroke="currentColor"
175
- aria-hidden="true"
176
- >
177
- <title>Lightning Fast</title>
178
- <path
179
- strokeLinecap="round"
180
- strokeLinejoin="round"
181
- strokeWidth={2}
182
- d="M13 10V3L4 14h7v7l9-11h-7z"
183
- fill="currentColor"
184
- />
185
- </svg>
186
- )}
338
+ <div className="inline-block animate-fade-in-up">
339
+ {showRefundInfo ? (
340
+ <span>Transaction confirmed</span>
341
+ ) : (
342
+ <span>
343
+ Completed in{" "}
344
+ <strong>{formatElapsed(completionTimeSeconds)}</strong>
345
+ </span>
346
+ )}
347
+ </div>
348
+ {totalCompletionSeconds &&
349
+ totalCompletionSeconds < 15 &&
350
+ !showMetaTxError &&
351
+ !showRefundInfo && (
352
+ <svg
353
+ className="w-4 h-4 ml-1 text-yellow-400 inline"
354
+ fill="none"
355
+ viewBox="0 0 24 24"
356
+ stroke="currentColor"
357
+ aria-hidden="true"
358
+ >
359
+ <title>Lightning Fast</title>
360
+ <path
361
+ strokeLinecap="round"
362
+ strokeLinejoin="round"
363
+ strokeWidth={2}
364
+ d="M13 10V3L4 14h7v7l9-11h-7z"
365
+ fill="currentColor"
366
+ />
367
+ </svg>
368
+ )}
187
369
  <style>{`
188
370
  @keyframes fadeInUp {
189
371
  0% { opacity: 0; transform: translateY(16px); }
@@ -192,6 +374,33 @@ export const Receipt: React.FC<ReceiptProps> = ({
192
374
  `}</style>
193
375
  </div>
194
376
  )}
377
+
378
+ {(showMetaTxError || showRefundInfo) && (
379
+ <div
380
+ className="mt-2 text-sm text-orange-600 dark:text-orange-400 flex items-center gap-2 justify-center animate-fade-in-up"
381
+ style={{
382
+ animation: "fadeInUp 0.7s cubic-bezier(0.23, 1, 0.32, 1)",
383
+ animationFillMode: "both",
384
+ }}
385
+ >
386
+ <svg
387
+ className="w-4 h-4 text-orange-500"
388
+ fill="none"
389
+ viewBox="0 0 24 24"
390
+ stroke="currentColor"
391
+ aria-hidden="true"
392
+ >
393
+ <title>Internal Meta Transaction Error</title>
394
+ <path
395
+ strokeLinecap="round"
396
+ strokeLinejoin="round"
397
+ strokeWidth={2}
398
+ 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"
399
+ />
400
+ </svg>
401
+ <span>Although one or more errors occurred</span>
402
+ </div>
403
+ )}
195
404
  </div>
196
405
  </div>
197
406
 
@@ -204,6 +413,7 @@ export const Receipt: React.FC<ReceiptProps> = ({
204
413
  rel="noopener noreferrer"
205
414
  className="inline-flex items-center gap-1 px-3 py-1.5 rounded-md font-medium transition-colors border border-solid text-sm bg-white border-gray-200 text-black hover:bg-gray-50 hover:text-gray-900 dark:bg-gray-900 dark:border-gray-700 dark:text-blue-300 dark:hover:bg-gray-800 dark:hover:text-blue-200"
206
415
  >
416
+ {finalChainId && <ChainImage chainId={finalChainId} size={16} />}
207
417
  View on Explorer
208
418
  <svg
209
419
  className="w-4 h-4 ml-1 text-black dark:text-blue-300"
@@ -222,9 +432,64 @@ export const Receipt: React.FC<ReceiptProps> = ({
222
432
  </a>
223
433
  </div>
224
434
 
435
+ {showRefundInfo && refundMessage && (
436
+ <div
437
+ className="mt-2 text-sm text-yellow-700 dark:text-yellow-400 flex items-center gap-2 justify-center animate-fade-in-up"
438
+ style={{
439
+ animation: "fadeInUp 0.7s cubic-bezier(0.23, 1, 0.32, 1)",
440
+ animationFillMode: "both",
441
+ }}
442
+ >
443
+ <div className="rounded-xl bg-warning">
444
+ <div className="flex bg-background-overlay rounded-xl p-4 w-full flex-col gap-3">
445
+ <div className="flex w-full gap-2 justify-between">
446
+ <div className="flex flex-col gap-1">
447
+ <div className="text-sm font-medium text-gray-900 dark:text-white text-left">
448
+ Refund Information
449
+ </div>
450
+ <div className="text-sm text-gray-700 dark:text-gray-300 text-left">
451
+ {refundMessage}
452
+ </div>
453
+ </div>
454
+ <div className="flex items-center">
455
+ <svg
456
+ className="w-5 h-5 text-yellow-600 dark:text-yellow-400"
457
+ fill="none"
458
+ viewBox="0 0 24 24"
459
+ stroke="currentColor"
460
+ aria-hidden="true"
461
+ >
462
+ <title>Information</title>
463
+ <path
464
+ strokeLinecap="round"
465
+ strokeLinejoin="round"
466
+ strokeWidth={2}
467
+ 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"
468
+ />
469
+ </svg>
470
+ </div>
471
+ </div>
472
+ </div>
473
+ </div>
474
+ </div>
475
+ )}
476
+
225
477
  <div
226
478
  className={`space-y-3 transition-all duration-500 ease-out delay-200 ${showContent ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"}`}
227
479
  >
480
+ {onSendAnother && (
481
+ <div className="flex justify-center">
482
+ <button
483
+ type="button"
484
+ onClick={onSendAnother}
485
+ className="inline-flex items-center gap-1 px-4 py-2 text-sm bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 trails-border-radius-button transition-colors duration-200 text-gray-600 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 cursor-pointer font-medium"
486
+ >
487
+ Send Again
488
+ <ChevronRight className="w-4 h-4" />
489
+ </button>
490
+ </div>
491
+ )}
492
+
228
493
  {!renderInline && (
229
494
  <button
230
495
  type="button"
@@ -236,79 +501,7 @@ export const Receipt: React.FC<ReceiptProps> = ({
236
501
  )}
237
502
 
238
503
  {/* Quote Details */}
239
- {quote && (
240
- <div className="mt-2">
241
- <QuoteDetails quote={quote} showContent={true}>
242
- {transactionStates.length > 0 && (
243
- <>
244
- <div className="font-medium text-gray-700 dark:text-gray-300">
245
- Transactions:
246
- </div>
247
- <div className="space-y-2">
248
- {transactionStates.map((state) => (
249
- <div
250
- key={state.transactionHash}
251
- className="p-2 rounded bg-gray-100 dark:bg-gray-700/50"
252
- >
253
- <div className="flex justify-between items-center">
254
- <span className="text-gray-600 dark:text-gray-400">
255
- {state.label}:
256
- </span>
257
- <span
258
- className={`px-2 py-0.5 rounded-full text-xs ${
259
- state.state === "confirmed"
260
- ? "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-400"
261
- : "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-400"
262
- }`}
263
- >
264
- {state.state}
265
- </span>
266
- </div>
267
- <div className="mt-1 flex justify-between items-center">
268
- <span className="text-gray-600 dark:text-gray-400">
269
- Chain ID:
270
- </span>
271
- <span className="text-gray-900 dark:text-white">
272
- {state.chainId}
273
- </span>
274
- </div>
275
- <div className="mt-1 flex justify-between items-center">
276
- <span className="text-gray-600 dark:text-gray-400">
277
- Hash:
278
- </span>
279
- <a
280
- href={state.explorerUrl}
281
- target="_blank"
282
- rel="noopener noreferrer"
283
- className="flex items-center gap-1 hover:underline text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
284
- >
285
- <span>
286
- {truncateAddress(state.transactionHash)}
287
- </span>
288
- <svg
289
- className="w-3 h-3"
290
- fill="none"
291
- viewBox="0 0 24 24"
292
- stroke="currentColor"
293
- >
294
- <title>External Link</title>
295
- <path
296
- strokeLinecap="round"
297
- strokeLinejoin="round"
298
- strokeWidth={2}
299
- d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
300
- />
301
- </svg>
302
- </a>
303
- </div>
304
- </div>
305
- ))}
306
- </div>
307
- </>
308
- )}
309
- </QuoteDetails>
310
- </div>
311
- )}
504
+ {quoteDetailsSection}
312
505
  </div>
313
506
  </div>
314
507
  )
@@ -0,0 +1,146 @@
1
+ import type React from "react"
2
+ import { useMemo } from "react"
3
+ import { QrCode } from "./QrCode.js"
4
+ import { ScreenHeader } from "./ScreenHeader.js"
5
+ import { getExplorerUrlForAddress } from "../../explorer.js"
6
+ import { ExternalLink } from "lucide-react"
7
+ import { truncateAddress } from "../../address.js"
8
+ import { useResolveEnsName } from "../../ens.js"
9
+
10
+ interface ReceiveProps {
11
+ accountAddress: string
12
+ onBack: () => void
13
+ isConnected?: boolean
14
+ onConnectWallet?: () => void
15
+ onPay?: () => void
16
+ toChainId?: number
17
+ }
18
+
19
+ export const Receive: React.FC<ReceiveProps> = ({
20
+ accountAddress,
21
+ onBack,
22
+ isConnected = false,
23
+ onConnectWallet,
24
+ onPay,
25
+ toChainId,
26
+ }) => {
27
+ // Use the provided address directly
28
+ const resolvedAddress = accountAddress
29
+
30
+ // Resolve ENS name from the address
31
+ const { ensName } = useResolveEnsName({
32
+ address: accountAddress,
33
+ })
34
+
35
+ // Generate QR code URL for the address
36
+ const qrCodeUrl = useMemo(() => {
37
+ if (!resolvedAddress) return ""
38
+ return `ethereum:${resolvedAddress}`
39
+ }, [resolvedAddress])
40
+
41
+ // Check if accountAddress is provided
42
+ if (!accountAddress) {
43
+ return (
44
+ <div className="space-y-6">
45
+ <ScreenHeader
46
+ onBack={onBack}
47
+ headerContent="Receive"
48
+ headerContentAlign="center"
49
+ />
50
+
51
+ <div className="flex flex-col justify-center min-h-full space-y-6 pt-8">
52
+ <div className="text-center p-6 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-700/50 rounded-lg">
53
+ <div className="flex items-center justify-center space-x-2 text-amber-600 dark:text-amber-400">
54
+ <svg
55
+ className="w-5 h-5"
56
+ fill="none"
57
+ viewBox="0 0 24 24"
58
+ stroke="currentColor"
59
+ >
60
+ <path
61
+ strokeLinecap="round"
62
+ strokeLinejoin="round"
63
+ strokeWidth={2}
64
+ 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"
65
+ />
66
+ </svg>
67
+ <span className="font-medium">Account address is required</span>
68
+ </div>
69
+ <p className="mt-2 text-sm text-amber-600 dark:text-amber-400">
70
+ Please provide an account address as <code>toAddress</code> to
71
+ generate the receive QR code.
72
+ </p>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ )
77
+ }
78
+
79
+ return (
80
+ <div className="space-y-2">
81
+ <ScreenHeader
82
+ onBack={onBack}
83
+ headerContent={`Pay ${ensName ? ensName : truncateAddress(resolvedAddress)}`}
84
+ headerContentAlign="center"
85
+ />
86
+
87
+ <div className="flex flex-col justify-center min-h-full space-y-6 pt-2">
88
+ {/* QR Code Section */}
89
+ <div className="space-y-3 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
90
+ <div className="flex justify-center">
91
+ <div className="flex flex-col items-center">
92
+ <QrCode url={qrCodeUrl} size={300} />
93
+ </div>
94
+ </div>
95
+
96
+ {/* Address Display */}
97
+ <div className="flex flex-col items-center justify-center gap-1 pt-2">
98
+ <div className="flex items-center gap-1">
99
+ <span className="text-xs text-gray-500 dark:text-gray-400">
100
+ Address:
101
+ </span>
102
+ <a
103
+ href={getExplorerUrlForAddress({
104
+ address: resolvedAddress,
105
+ chainId: toChainId || 1,
106
+ })}
107
+ target="_blank"
108
+ rel="noopener noreferrer"
109
+ className="flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400 hover:underline transition-all"
110
+ >
111
+ <span>{truncateAddress(resolvedAddress)}</span>
112
+ <ExternalLink className="w-3 h-3" />
113
+ </a>
114
+ </div>
115
+ </div>
116
+ </div>
117
+
118
+ {/* Connect Wallet Button - Only show when not connected */}
119
+ {!isConnected && onConnectWallet && (
120
+ <div className="flex justify-center">
121
+ <button
122
+ type="button"
123
+ onClick={onConnectWallet}
124
+ className="px-6 py-3 font-medium rounded-lg transition-colors duration-200 cursor-pointer bg-gray-100 hover:bg-gray-200 text-gray-600 hover:text-gray-900 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-300 dark:hover:text-white"
125
+ >
126
+ Connect Wallet
127
+ </button>
128
+ </div>
129
+ )}
130
+
131
+ {/* Pay Button - Only show when connected */}
132
+ {isConnected && onPay && (
133
+ <div className="flex justify-center">
134
+ <button
135
+ type="button"
136
+ onClick={onPay}
137
+ className="px-6 py-3 font-medium rounded-lg transition-colors duration-200 cursor-pointer bg-blue-500 hover:bg-blue-600 text-white"
138
+ >
139
+ Pay
140
+ </button>
141
+ </div>
142
+ )}
143
+ </div>
144
+ </div>
145
+ )
146
+ }
@@ -27,7 +27,7 @@ export const RecentTokens: React.FC<RecentTokensProps> = ({
27
27
 
28
28
  return (
29
29
  <div className="mb-0">
30
- <h3 className="text-xs font-medium text-gray-500 dark:text-gray-500">
30
+ <h3 className="text-xs font-medium text-gray-500 dark:text-gray-500 text-left">
31
31
  Recently Used
32
32
  </h3>
33
33
  <div className="flex gap-2 overflow-x-auto pb-1 trails-scrollbar">