0xtrails 0.6.0 → 0.6.2
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.
- package/dist/{ccip-Dw5AN7oU.js → ccip-CZfykYU7.js} +4 -4
- package/dist/chains.d.ts +9 -2
- package/dist/chains.d.ts.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/contractUtils.d.ts +2 -1
- package/dist/contractUtils.d.ts.map +1 -1
- package/dist/{index-BtVUTbEZ.js → index-S9pphnT9.js} +29732 -36767
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +300 -242
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
- package/dist/transactionIntent/types.d.ts +3 -1
- package/dist/transactionIntent/types.d.ts.map +1 -1
- package/dist/widget/components/ChainImage.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/DepositTracker.d.ts +12 -0
- package/dist/widget/components/DepositTracker.d.ts.map +1 -0
- package/dist/widget/components/Disconnect.d.ts.map +1 -1
- package/dist/widget/components/FeeBreakdown.d.ts.map +1 -1
- package/dist/widget/components/QRCodeDeposit.d.ts.map +1 -1
- package/dist/widget/components/QRCodeOptions.d.ts +9 -0
- package/dist/widget/components/QRCodeOptions.d.ts.map +1 -0
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receipt.d.ts.map +1 -1
- package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
- package/dist/widget/components/Toast.d.ts.map +1 -1
- package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -1
- package/dist/widget/components/WalletConnectionPending.d.ts.map +1 -1
- package/dist/widget/css/compiled.css +1 -1
- package/dist/widget/css/index.css +103 -38
- package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
- package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
- package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
- package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
- package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
- package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/hooks/useQuote.d.ts.map +1 -1
- package/dist/widget/hooks/useSelectedFeeOption.d.ts.map +1 -1
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/index.js +1 -1
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +6 -9
- package/src/chains.ts +37 -4
- package/src/constants.ts +1 -0
- package/src/contractUtils.ts +8 -7
- package/src/estimate.ts +2 -2
- package/src/index.ts +2 -0
- package/src/intents.ts +2 -2
- package/src/paymasterSend.ts +2 -2
- package/src/prepareSend.ts +34 -3
- package/src/sendUserOp.ts +2 -2
- package/src/tokens.ts +2 -2
- package/src/transactionIntent/deposits/gaslessDeposit.ts +2 -2
- package/src/transactionIntent/handlers/crossChain.ts +51 -2
- package/src/transactionIntent/handlers/sameChainSameToken.ts +52 -2
- package/src/transactionIntent/quote/normalizeQuote.ts +2 -2
- package/src/transactionIntent/types.ts +9 -1
- package/src/widget/compiled.css +1 -1
- package/src/widget/components/ChainImage.tsx +10 -7
- package/src/widget/components/ChainList.tsx +1 -1
- package/src/widget/components/ClassicSwap.tsx +8 -4
- package/src/widget/components/ConnectedWallets.tsx +1 -1
- package/src/widget/components/DepositTracker.tsx +298 -0
- package/src/widget/components/Disconnect.tsx +24 -3
- package/src/widget/components/FeeBreakdown.tsx +3 -3
- package/src/widget/components/QRCodeDeposit.tsx +29 -19
- package/src/widget/components/QRCodeOptions.tsx +65 -0
- package/src/widget/components/QuoteDetails.tsx +694 -803
- package/src/widget/components/Receipt.tsx +76 -40
- package/src/widget/components/ThemeProvider.tsx +7 -12
- package/src/widget/components/Toast.tsx +3 -2
- package/src/widget/components/TokenSelector.tsx +1 -1
- package/src/widget/components/Tooltip.tsx +1 -1
- package/src/widget/components/TransferPendingVertical.tsx +11 -2
- package/src/widget/components/WalletConnectionPending.tsx +28 -5
- package/src/widget/hooks/useCheckout.ts +10 -2
- package/src/widget/hooks/useCurrentScreen.tsx +1 -0
- package/src/widget/hooks/useDebugScreens.ts +1 -0
- package/src/widget/hooks/useIntentTransactionHistory.ts +114 -143
- package/src/widget/hooks/useQuote.ts +92 -6
- package/src/widget/hooks/useSelectedFeeOption.tsx +86 -29
- package/src/widget/hooks/useSendForm.ts +43 -7
- package/src/widget/index.css +103 -38
- package/src/widget/widget.tsx +48 -5
- package/dist/0xtrails.css +0 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import type React from "react"
|
|
2
|
+
import { useMemo, useEffect } from "react"
|
|
3
|
+
import { CheckCircle, Clock } from "lucide-react"
|
|
4
|
+
import { formatUnits, zeroAddress } from "viem"
|
|
5
|
+
import { TokenImage } from "./TokenImage.js"
|
|
6
|
+
import {
|
|
7
|
+
formatAmountDisplay,
|
|
8
|
+
useTokenBalances,
|
|
9
|
+
isNativeToken,
|
|
10
|
+
type TokenBalanceWithPrice,
|
|
11
|
+
} from "../../tokenBalances.js"
|
|
12
|
+
import { useQueryClient } from "@tanstack/react-query"
|
|
13
|
+
|
|
14
|
+
export interface DepositTrackerProps {
|
|
15
|
+
depositAddress: string
|
|
16
|
+
requiredAmount: string
|
|
17
|
+
tokenAddress: string
|
|
18
|
+
tokenSymbol: string
|
|
19
|
+
tokenDecimals: number
|
|
20
|
+
chainId: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const DepositTracker: React.FC<DepositTrackerProps> = ({
|
|
24
|
+
depositAddress,
|
|
25
|
+
requiredAmount,
|
|
26
|
+
tokenAddress,
|
|
27
|
+
tokenSymbol,
|
|
28
|
+
tokenDecimals,
|
|
29
|
+
chainId,
|
|
30
|
+
}) => {
|
|
31
|
+
const queryClient = useQueryClient()
|
|
32
|
+
|
|
33
|
+
// Fetch all token balances on the deposit address
|
|
34
|
+
const { sortedTokens } = useTokenBalances(depositAddress as `0x${string}`)
|
|
35
|
+
|
|
36
|
+
// Force refresh every 3 seconds to match backend polling
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const interval = setInterval(() => {
|
|
39
|
+
queryClient.invalidateQueries({
|
|
40
|
+
queryKey: ["getTokenBalancesWithPrices", "summary", depositAddress],
|
|
41
|
+
})
|
|
42
|
+
}, 3000)
|
|
43
|
+
|
|
44
|
+
return () => clearInterval(interval)
|
|
45
|
+
}, [depositAddress, queryClient])
|
|
46
|
+
|
|
47
|
+
// Find the required token deposit
|
|
48
|
+
const requiredTokenDeposit = useMemo(() => {
|
|
49
|
+
if (!sortedTokens || sortedTokens.length === 0) return null
|
|
50
|
+
|
|
51
|
+
const normalizedTokenAddress = tokenAddress.toLowerCase()
|
|
52
|
+
const isNativeDeposit = normalizedTokenAddress === zeroAddress.toLowerCase()
|
|
53
|
+
|
|
54
|
+
return sortedTokens.find((token) => {
|
|
55
|
+
const tokenIsNative = isNativeToken(token)
|
|
56
|
+
const tokenChainId = tokenIsNative
|
|
57
|
+
? token.chainId
|
|
58
|
+
: token.contractInfo?.chainId
|
|
59
|
+
|
|
60
|
+
if (tokenChainId !== chainId) return false
|
|
61
|
+
|
|
62
|
+
if (isNativeDeposit) {
|
|
63
|
+
return tokenIsNative
|
|
64
|
+
} else {
|
|
65
|
+
return (
|
|
66
|
+
!tokenIsNative &&
|
|
67
|
+
token.contractAddress?.toLowerCase() === normalizedTokenAddress
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}, [sortedTokens, tokenAddress, chainId])
|
|
72
|
+
|
|
73
|
+
// Get all deposits (including other tokens)
|
|
74
|
+
const allDeposits = useMemo(() => {
|
|
75
|
+
if (!sortedTokens || sortedTokens.length === 0) return []
|
|
76
|
+
|
|
77
|
+
return sortedTokens.filter((token) => {
|
|
78
|
+
const tokenIsNative = isNativeToken(token)
|
|
79
|
+
const tokenChainId = tokenIsNative
|
|
80
|
+
? token.chainId
|
|
81
|
+
: token.contractInfo?.chainId
|
|
82
|
+
|
|
83
|
+
// Only show tokens from the same chain with balance > 0
|
|
84
|
+
if (tokenChainId !== chainId) return false
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
return BigInt(token.balance) > 0n
|
|
88
|
+
} catch {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}, [sortedTokens, chainId])
|
|
93
|
+
|
|
94
|
+
// Determine if we should show the "Detected Deposits" section
|
|
95
|
+
// Only show if there's more than 1 deposit OR if there's a wrong token deposited
|
|
96
|
+
const shouldShowDetectedDeposits = useMemo(() => {
|
|
97
|
+
if (allDeposits.length === 0) return false
|
|
98
|
+
if (allDeposits.length > 1) return true
|
|
99
|
+
|
|
100
|
+
// If only 1 deposit, check if it's the required token
|
|
101
|
+
const singleDeposit = allDeposits[0]
|
|
102
|
+
if (!singleDeposit) return false
|
|
103
|
+
|
|
104
|
+
const normalizedTokenAddress = tokenAddress.toLowerCase()
|
|
105
|
+
const isNativeDeposit = normalizedTokenAddress === zeroAddress.toLowerCase()
|
|
106
|
+
const depositIsNative = isNativeToken(singleDeposit)
|
|
107
|
+
|
|
108
|
+
// Get contract address for ERC20 tokens
|
|
109
|
+
const depositTokenAddress = depositIsNative
|
|
110
|
+
? undefined
|
|
111
|
+
: (singleDeposit as TokenBalanceWithPrice).contractAddress?.toLowerCase()
|
|
112
|
+
|
|
113
|
+
// If both are native, don't show (it's the correct token)
|
|
114
|
+
if (isNativeDeposit && depositIsNative) return false
|
|
115
|
+
|
|
116
|
+
// If one is native and other isn't, show (wrong token)
|
|
117
|
+
if (isNativeDeposit !== depositIsNative) return true
|
|
118
|
+
|
|
119
|
+
// If both are ERC20, check if addresses match
|
|
120
|
+
if (!isNativeDeposit && depositTokenAddress === normalizedTokenAddress) {
|
|
121
|
+
return false // Same token, don't show
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Different token, show
|
|
125
|
+
return true
|
|
126
|
+
}, [allDeposits, tokenAddress])
|
|
127
|
+
|
|
128
|
+
// Calculate deposit progress
|
|
129
|
+
const depositStats = useMemo(() => {
|
|
130
|
+
const requiredBigInt = BigInt(requiredAmount)
|
|
131
|
+
const currentBalance = requiredTokenDeposit?.balance
|
|
132
|
+
? BigInt(requiredTokenDeposit.balance)
|
|
133
|
+
: 0n
|
|
134
|
+
|
|
135
|
+
const percentage =
|
|
136
|
+
requiredBigInt > 0n
|
|
137
|
+
? Number((currentBalance * 10000n) / requiredBigInt) / 100
|
|
138
|
+
: 0
|
|
139
|
+
|
|
140
|
+
const isComplete = currentBalance >= requiredBigInt
|
|
141
|
+
const isPartial = currentBalance > 0n && currentBalance < requiredBigInt
|
|
142
|
+
const isOverpaid = currentBalance > requiredBigInt
|
|
143
|
+
|
|
144
|
+
const currentFormatted = formatUnits(currentBalance, tokenDecimals)
|
|
145
|
+
const requiredFormatted = formatUnits(requiredBigInt, tokenDecimals)
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
currentBalance,
|
|
149
|
+
percentage: Math.min(percentage, 100),
|
|
150
|
+
isComplete,
|
|
151
|
+
isPartial,
|
|
152
|
+
isOverpaid,
|
|
153
|
+
currentFormatted,
|
|
154
|
+
requiredFormatted,
|
|
155
|
+
}
|
|
156
|
+
}, [requiredAmount, requiredTokenDeposit, tokenDecimals])
|
|
157
|
+
|
|
158
|
+
// Determine status icon and color
|
|
159
|
+
const getStatusDisplay = () => {
|
|
160
|
+
if (depositStats.isComplete) {
|
|
161
|
+
return {
|
|
162
|
+
icon: <CheckCircle className="w-4 h-4 text-green-500" />,
|
|
163
|
+
text: "Deposit Complete",
|
|
164
|
+
progressColor: "bg-green-500",
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (depositStats.isPartial) {
|
|
168
|
+
return {
|
|
169
|
+
icon: <Clock className="w-4 h-4 text-yellow-500" />,
|
|
170
|
+
text: "Deposit Detected",
|
|
171
|
+
progressColor: "bg-yellow-500",
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
icon: <Clock className="w-4 h-4 text-gray-400" />,
|
|
176
|
+
text: "Waiting for deposit...",
|
|
177
|
+
progressColor: "bg-gray-300",
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const statusDisplay = getStatusDisplay()
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<div className="space-y-3">
|
|
185
|
+
{/* Status Header */}
|
|
186
|
+
<div className="flex items-center gap-2">
|
|
187
|
+
{statusDisplay.icon}
|
|
188
|
+
<p className="text-xs font-medium text-gray-600 dark:text-gray-400">
|
|
189
|
+
{statusDisplay.text}
|
|
190
|
+
</p>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
{/* Progress Bar */}
|
|
194
|
+
<div className="space-y-2">
|
|
195
|
+
<div className="flex justify-between items-center text-xs">
|
|
196
|
+
<span className="text-gray-600 dark:text-gray-400">
|
|
197
|
+
Deposit Progress
|
|
198
|
+
</span>
|
|
199
|
+
<span className="font-semibold text-gray-900 dark:text-white">
|
|
200
|
+
{depositStats.percentage.toFixed(1)}%
|
|
201
|
+
</span>
|
|
202
|
+
</div>
|
|
203
|
+
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5 overflow-hidden">
|
|
204
|
+
<div
|
|
205
|
+
className={`${statusDisplay.progressColor} h-full transition-all duration-500 ease-out`}
|
|
206
|
+
style={{ width: `${Math.min(depositStats.percentage, 100)}%` }}
|
|
207
|
+
/>
|
|
208
|
+
</div>
|
|
209
|
+
<div className="flex justify-between items-center text-xs">
|
|
210
|
+
<div className="flex items-center gap-1">
|
|
211
|
+
<TokenImage
|
|
212
|
+
imageUrl={requiredTokenDeposit?.imageUrl}
|
|
213
|
+
symbol={tokenSymbol}
|
|
214
|
+
chainId={chainId}
|
|
215
|
+
contractAddress={tokenAddress}
|
|
216
|
+
size={16}
|
|
217
|
+
/>
|
|
218
|
+
<span className="text-gray-600 dark:text-gray-400">
|
|
219
|
+
{formatAmountDisplay(depositStats.currentFormatted, {
|
|
220
|
+
maxFractionDigits: 6,
|
|
221
|
+
minFractionDigits: 2,
|
|
222
|
+
})}{" "}
|
|
223
|
+
/{" "}
|
|
224
|
+
{formatAmountDisplay(depositStats.requiredFormatted, {
|
|
225
|
+
maxFractionDigits: 6,
|
|
226
|
+
minFractionDigits: 2,
|
|
227
|
+
})}{" "}
|
|
228
|
+
{tokenSymbol}
|
|
229
|
+
</span>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
{/* Current Deposits List */}
|
|
235
|
+
{shouldShowDetectedDeposits && (
|
|
236
|
+
<div className="space-y-1.5">
|
|
237
|
+
<p className="text-xs text-gray-600 dark:text-gray-400">
|
|
238
|
+
Detected Deposits:
|
|
239
|
+
</p>
|
|
240
|
+
<div className="trails-border-radius-container border trails-border-primary">
|
|
241
|
+
{allDeposits.map((token, index) => {
|
|
242
|
+
const tokenIsNative = isNativeToken(token)
|
|
243
|
+
const symbol = tokenIsNative
|
|
244
|
+
? token.symbol || "ETH"
|
|
245
|
+
: token.contractInfo?.symbol
|
|
246
|
+
const decimals = tokenIsNative
|
|
247
|
+
? token.decimals || 18
|
|
248
|
+
: token.contractInfo?.decimals || 18
|
|
249
|
+
const balance = formatUnits(BigInt(token.balance), decimals)
|
|
250
|
+
const tokenChainId = tokenIsNative
|
|
251
|
+
? token.chainId
|
|
252
|
+
: token.contractInfo?.chainId
|
|
253
|
+
|
|
254
|
+
const tokenKey = tokenIsNative
|
|
255
|
+
? `native-${tokenChainId}`
|
|
256
|
+
: `${token.contractAddress}-${tokenChainId}`
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<div key={tokenKey}>
|
|
260
|
+
<div className="px-2 py-1.5 flex items-center gap-2">
|
|
261
|
+
<TokenImage
|
|
262
|
+
imageUrl={token.imageUrl}
|
|
263
|
+
symbol={symbol}
|
|
264
|
+
chainId={tokenChainId || chainId}
|
|
265
|
+
contractAddress={
|
|
266
|
+
tokenIsNative ? undefined : token.contractAddress
|
|
267
|
+
}
|
|
268
|
+
size={16}
|
|
269
|
+
/>
|
|
270
|
+
<div className="flex flex-col flex-1 min-w-0">
|
|
271
|
+
<span className="text-xs text-gray-900 dark:text-white truncate">
|
|
272
|
+
{formatAmountDisplay(balance, {
|
|
273
|
+
maxFractionDigits: 6,
|
|
274
|
+
minFractionDigits: 2,
|
|
275
|
+
})}{" "}
|
|
276
|
+
{symbol}
|
|
277
|
+
</span>
|
|
278
|
+
{token.balanceUsdFormatted && (
|
|
279
|
+
<span className="text-xs text-gray-500 dark:text-gray-400">
|
|
280
|
+
{token.balanceUsdFormatted}
|
|
281
|
+
</span>
|
|
282
|
+
)}
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
{index < allDeposits.length - 1 && (
|
|
286
|
+
<div className="border-t trails-border-primary" />
|
|
287
|
+
)}
|
|
288
|
+
</div>
|
|
289
|
+
)
|
|
290
|
+
})}
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
)}
|
|
294
|
+
</div>
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export default DepositTracker
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from "react"
|
|
1
|
+
import { useEffect, useRef } from "react"
|
|
2
2
|
import type React from "react"
|
|
3
3
|
import { ScreenHeader } from "./ScreenHeader.js"
|
|
4
4
|
import { logger } from "../../logger.js"
|
|
@@ -12,18 +12,39 @@ export const Disconnect: React.FC<DisconnectProps> = ({
|
|
|
12
12
|
onBack,
|
|
13
13
|
disconnectHandler,
|
|
14
14
|
}) => {
|
|
15
|
+
const hasDisconnectedRef = useRef(false)
|
|
16
|
+
const disconnectHandlerRef = useRef(disconnectHandler)
|
|
17
|
+
|
|
18
|
+
// Keep the ref updated with the latest handler
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
disconnectHandlerRef.current = disconnectHandler
|
|
21
|
+
}, [disconnectHandler])
|
|
22
|
+
|
|
15
23
|
useEffect(() => {
|
|
24
|
+
// Only run once when component mounts
|
|
25
|
+
if (hasDisconnectedRef.current) {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
const handleDisconnect = async () => {
|
|
30
|
+
hasDisconnectedRef.current = true
|
|
17
31
|
try {
|
|
18
32
|
logger.console.log("[trails-sdk] Disconnecting user...")
|
|
19
|
-
|
|
33
|
+
// Ensure minimum 1 second delay to show disconnecting state
|
|
34
|
+
await Promise.all([
|
|
35
|
+
disconnectHandlerRef.current(),
|
|
36
|
+
new Promise((resolve) => setTimeout(resolve, 1000)),
|
|
37
|
+
])
|
|
20
38
|
} catch (error) {
|
|
39
|
+
// Still wait for minimum delay even if disconnect fails
|
|
40
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
21
41
|
logger.console.error("[trails-sdk] Failed to disconnect:", error)
|
|
22
42
|
}
|
|
23
43
|
}
|
|
24
44
|
|
|
25
45
|
handleDisconnect()
|
|
26
|
-
|
|
46
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
47
|
+
}, []) // Empty dependency array - only run once on mount
|
|
27
48
|
|
|
28
49
|
return (
|
|
29
50
|
<div className="space-y-6">
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type React from "react"
|
|
2
2
|
import { useState } from "react"
|
|
3
|
-
import { InfoIcon } from "@0xsequence/design-system"
|
|
4
3
|
import { Tooltip } from "./Tooltip.js"
|
|
5
4
|
import { TokenImage } from "./TokenImage.js"
|
|
6
5
|
import type { TrailsFeeBreakdown, FeeItem } from "../../fees.js"
|
|
6
|
+
import { Info } from "lucide-react"
|
|
7
7
|
|
|
8
8
|
interface FeeBreakdownProps {
|
|
9
9
|
feeBreakdown: TrailsFeeBreakdown
|
|
@@ -22,7 +22,7 @@ const FeeRow: React.FC<FeeRowProps> = ({ label, feeItem, tooltip }) => (
|
|
|
22
22
|
{label}
|
|
23
23
|
{tooltip && (
|
|
24
24
|
<Tooltip message={tooltip}>
|
|
25
|
-
<
|
|
25
|
+
<Info className="w-3 h-3 text-gray-500 dark:text-gray-400 cursor-pointer" />
|
|
26
26
|
</Tooltip>
|
|
27
27
|
)}
|
|
28
28
|
</span>
|
|
@@ -75,7 +75,7 @@ export const FeeBreakdown: React.FC<FeeBreakdownProps> = ({
|
|
|
75
75
|
children
|
|
76
76
|
) : (
|
|
77
77
|
<Tooltip message="Detailed breakdown of the Trails platform fees">
|
|
78
|
-
<
|
|
78
|
+
<Info className="w-3 h-3 text-gray-500 dark:text-gray-400 cursor-pointer" />
|
|
79
79
|
</Tooltip>
|
|
80
80
|
)}
|
|
81
81
|
</span>
|
|
@@ -10,6 +10,7 @@ import { formatUnits, getAddress, zeroAddress } from "viem"
|
|
|
10
10
|
import { getExplorerUrlForAddress } from "../../explorer.js"
|
|
11
11
|
import { ScreenHeader } from "./ScreenHeader.js"
|
|
12
12
|
import { useQRCodeWallets, type QRCodeWalletOption } from "../../wallets.js"
|
|
13
|
+
import { DepositTracker } from "./DepositTracker.js"
|
|
13
14
|
|
|
14
15
|
interface QRCodeDepositProps {
|
|
15
16
|
onBack?: () => void
|
|
@@ -102,18 +103,19 @@ export const QRCodeDeposit: React.FC<QRCodeDepositProps> = ({
|
|
|
102
103
|
</div>
|
|
103
104
|
|
|
104
105
|
{quote?.originAmount && (
|
|
105
|
-
<div className="flex flex-col items-center justify-center gap-
|
|
106
|
-
<div className="
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
<div className="flex flex-col items-center justify-center gap-2 pt-2">
|
|
107
|
+
<div className="text-xs text-gray-500 dark:text-gray-400">
|
|
108
|
+
Deposit exactly
|
|
109
|
+
</div>
|
|
110
|
+
<div className="flex items-center gap-2 px-3 py-2 bg-white dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600">
|
|
110
111
|
<TokenImage
|
|
111
112
|
imageUrl={quote.originToken.imageUrl}
|
|
112
113
|
symbol={quote.originToken.symbol}
|
|
113
114
|
chainId={quote.originChain.id}
|
|
114
|
-
|
|
115
|
+
contractAddress={quote.originToken.contractAddress}
|
|
116
|
+
size={24}
|
|
115
117
|
/>
|
|
116
|
-
<span className="text-
|
|
118
|
+
<span className="text-base font-bold text-gray-900 dark:text-white">
|
|
117
119
|
{formatUnits(
|
|
118
120
|
BigInt(quote.originAmount),
|
|
119
121
|
quote.originToken.decimals,
|
|
@@ -121,17 +123,11 @@ export const QRCodeDeposit: React.FC<QRCodeDepositProps> = ({
|
|
|
121
123
|
{quote.originToken.symbol}
|
|
122
124
|
</span>
|
|
123
125
|
</div>
|
|
124
|
-
<div className="flex items-center gap-1">
|
|
125
|
-
<span
|
|
126
|
-
|
|
127
|
-
</span>
|
|
128
|
-
<
|
|
129
|
-
<span className="text-xs font-bold text-gray-500 dark:text-gray-400">
|
|
130
|
-
{quote.originChain.name}
|
|
131
|
-
</span>
|
|
132
|
-
<span className="text-xs text-gray-500 dark:text-gray-400">
|
|
133
|
-
to
|
|
134
|
-
</span>
|
|
126
|
+
<div className="flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-400">
|
|
127
|
+
<span>on</span>
|
|
128
|
+
<ChainImage chainId={quote.originChain.id} size={14} />
|
|
129
|
+
<span className="font-medium">{quote.originChain.name}</span>
|
|
130
|
+
<span>to</span>
|
|
135
131
|
<a
|
|
136
132
|
href={getExplorerUrlForAddress({
|
|
137
133
|
address: quote.originDepositAddress,
|
|
@@ -139,7 +135,7 @@ export const QRCodeDeposit: React.FC<QRCodeDepositProps> = ({
|
|
|
139
135
|
})}
|
|
140
136
|
target="_blank"
|
|
141
137
|
rel="noopener noreferrer"
|
|
142
|
-
className="flex items-center gap-1
|
|
138
|
+
className="flex items-center gap-1 font-medium hover:text-blue-600 dark:hover:text-blue-400 transition-colors cursor-pointer"
|
|
143
139
|
>
|
|
144
140
|
<span>
|
|
145
141
|
{quote.originDepositAddress.slice(0, 6)}...
|
|
@@ -152,6 +148,20 @@ export const QRCodeDeposit: React.FC<QRCodeDepositProps> = ({
|
|
|
152
148
|
)}
|
|
153
149
|
</div>
|
|
154
150
|
|
|
151
|
+
{/* Deposit Tracker */}
|
|
152
|
+
{quote && (
|
|
153
|
+
<div className="space-y-2">
|
|
154
|
+
<DepositTracker
|
|
155
|
+
depositAddress={quote.originDepositAddress}
|
|
156
|
+
requiredAmount={quote.originAmount}
|
|
157
|
+
tokenAddress={quote.originToken.contractAddress}
|
|
158
|
+
tokenSymbol={quote.originToken.symbol}
|
|
159
|
+
tokenDecimals={quote.originToken.decimals}
|
|
160
|
+
chainId={quote.originChain.id}
|
|
161
|
+
/>
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
|
|
155
165
|
{/* Quote Details */}
|
|
156
166
|
{quote && (
|
|
157
167
|
<div className="space-y-2">
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type React from "react"
|
|
2
|
+
import { QrCode, Wallet, ChevronRight } from "lucide-react"
|
|
3
|
+
import { ScreenHeader } from "./ScreenHeader.js"
|
|
4
|
+
|
|
5
|
+
export interface QRCodeOptionsProps {
|
|
6
|
+
onBack?: () => void
|
|
7
|
+
onSelectWalletConnect: () => void
|
|
8
|
+
onSelectQRCode: () => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const QRCodeOptions: React.FC<QRCodeOptionsProps> = ({
|
|
12
|
+
onBack,
|
|
13
|
+
onSelectWalletConnect,
|
|
14
|
+
onSelectQRCode,
|
|
15
|
+
}) => {
|
|
16
|
+
return (
|
|
17
|
+
<div className="flex flex-col h-full">
|
|
18
|
+
<ScreenHeader
|
|
19
|
+
onBack={onBack}
|
|
20
|
+
headerContent="QR Code Options"
|
|
21
|
+
headerContentAlign="left"
|
|
22
|
+
rightSideContent={<div className="w-9" />}
|
|
23
|
+
/>
|
|
24
|
+
|
|
25
|
+
<div className="flex-1">
|
|
26
|
+
<div className="space-y-4">
|
|
27
|
+
{/* QR Code Options */}
|
|
28
|
+
<div className="trails-border-radius-container border trails-border-primary">
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
onClick={onSelectWalletConnect}
|
|
32
|
+
className="w-full text-left px-3 py-4 text-sm flex items-center justify-between cursor-pointer transition-colors trails-text-primary trails-hover-bg"
|
|
33
|
+
>
|
|
34
|
+
<div className="flex items-center gap-3">
|
|
35
|
+
<Wallet className="w-4 h-4" />
|
|
36
|
+
<span className="text-sm font-bold">Via WalletConnect</span>
|
|
37
|
+
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
|
38
|
+
Recommended
|
|
39
|
+
</span>
|
|
40
|
+
</div>
|
|
41
|
+
<ChevronRight className="w-5 h-5 text-gray-400" />
|
|
42
|
+
</button>
|
|
43
|
+
|
|
44
|
+
{/* Divider between buttons */}
|
|
45
|
+
<div className="border-b border-gray-200 dark:border-gray-700"></div>
|
|
46
|
+
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
onClick={onSelectQRCode}
|
|
50
|
+
className="w-full text-left px-3 py-4 text-sm flex items-center justify-between cursor-pointer transition-colors trails-text-primary trails-hover-bg"
|
|
51
|
+
>
|
|
52
|
+
<div className="flex items-center gap-3">
|
|
53
|
+
<QrCode className="w-4 h-4" />
|
|
54
|
+
<span className="text-sm font-bold">Via Deposit QR Code</span>
|
|
55
|
+
</div>
|
|
56
|
+
<ChevronRight className="w-5 h-5 text-gray-400" />
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default QRCodeOptions
|