0xtrails 0.9.2 → 0.9.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.
- package/dist/{ccip-g6lDdnrD.js → ccip-lAtzqne5.js} +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/{index-D-QngA_s.js → index-D5AG6huo.js} +22290 -21786
- package/dist/index.js +3 -3
- package/dist/intents.d.ts +1 -1
- package/dist/intents.d.ts.map +1 -1
- package/dist/mutations.d.ts +5 -2
- package/dist/mutations.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/transactionIntent/constants.d.ts +1 -0
- package/dist/transactionIntent/constants.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +3 -1
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/standardDeposit.d.ts +4 -1
- package/dist/transactionIntent/deposits/standardDeposit.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/quote/normalizeQuote.d.ts.map +1 -1
- package/dist/transactionIntent/types.d.ts +2 -0
- package/dist/transactionIntent/types.d.ts.map +1 -1
- package/dist/transactionIntent/utils/resilientDepositTracker.d.ts +25 -0
- package/dist/transactionIntent/utils/resilientDepositTracker.d.ts.map +1 -0
- package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
- package/dist/widget/components/DynamicInputStyles.d.ts +2 -2
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/EarnPools.d.ts.map +1 -1
- package/dist/widget/components/Fund.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receipt.d.ts.map +1 -1
- package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
- package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
- package/dist/widget/components/UserPreferences.d.ts.map +1 -1
- package/dist/widget/components/WalletConnect.d.ts.map +1 -1
- package/dist/widget/css/compiled.css +1 -1
- package/dist/widget/hooks/useQuote.d.ts +2 -0
- package/dist/widget/hooks/useQuote.d.ts.map +1 -1
- package/dist/widget/index.js +1 -1
- package/dist/widget/providers/TrailsProvider.d.ts +2 -0
- package/dist/widget/providers/TrailsProvider.d.ts.map +1 -1
- package/dist/widget/widget.d.ts +1 -0
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/config.ts +1 -0
- package/src/constants.ts +1 -0
- package/src/error.ts +6 -1
- package/src/intents.ts +22 -1
- package/src/prices.ts +1 -1
- package/src/tokens.ts +4 -3
- package/src/transactionIntent/constants.ts +2 -0
- package/src/transactionIntent/deposits/depositOrchestrator.ts +7 -0
- package/src/transactionIntent/deposits/standardDeposit.ts +194 -37
- package/src/transactionIntent/handlers/crossChain.ts +152 -105
- package/src/transactionIntent/handlers/sameChainSameToken.ts +1 -0
- package/src/transactionIntent/quote/normalizeQuote.ts +7 -4
- package/src/transactionIntent/types.ts +2 -0
- package/src/transactionIntent/utils/resilientDepositTracker.ts +281 -0
- package/src/widget/compiled.css +1 -1
- package/src/widget/components/AccountIntentTransactionHistory.tsx +170 -87
- package/src/widget/components/ClassicSwap.tsx +7 -1
- package/src/widget/components/ConfigDisplay.tsx +5 -0
- package/src/widget/components/Earn.tsx +14 -1
- package/src/widget/components/EarnPools.tsx +180 -59
- package/src/widget/components/Fund.tsx +3 -1
- package/src/widget/components/PoolWithdraw.tsx +1 -1
- package/src/widget/components/QuoteDetails.tsx +12 -35
- package/src/widget/components/Receipt.tsx +66 -40
- package/src/widget/components/SlippageToleranceSettings.tsx +86 -44
- package/src/widget/components/TransactionDetails.tsx +138 -218
- package/src/widget/components/UserPreferences.tsx +114 -41
- package/src/widget/components/WalletConnect.tsx +111 -48
- package/src/widget/hooks/useQuote.ts +389 -352
- package/src/widget/providers/TrailsProvider.tsx +5 -0
- package/src/widget/widget.tsx +2 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TrendingUp } from "lucide-react"
|
|
2
2
|
import { motion } from "motion/react"
|
|
3
3
|
import { useState, useEffect, useMemo } from "react"
|
|
4
|
+
import { useAccount } from "wagmi"
|
|
4
5
|
import { usePools, type Pool } from "../../pools.js"
|
|
5
6
|
import { TokenImage } from "./TokenImage.js"
|
|
6
7
|
import { getChainInfo } from "../../chains.js"
|
|
@@ -11,6 +12,10 @@ import { ScreenHeader } from "./ScreenHeader.js"
|
|
|
11
12
|
import { EarnPoolsFilters, type SortOption } from "./EarnPoolsFilters.js"
|
|
12
13
|
import { ChainList } from "./ChainList.js"
|
|
13
14
|
import { useChainFilter } from "../hooks/useChainFilter.js"
|
|
15
|
+
import {
|
|
16
|
+
useTokenBalances,
|
|
17
|
+
formatUsdAmountDisplay,
|
|
18
|
+
} from "../../tokenBalances.js"
|
|
14
19
|
import aaveLogo from "../assets/aave.svg"
|
|
15
20
|
import morphoLogo from "../assets/morpho.svg"
|
|
16
21
|
import { logger } from "../../logger.js"
|
|
@@ -25,6 +30,8 @@ export const EarnPools: React.FC<EarnPoolsProps> = ({
|
|
|
25
30
|
onPoolSelect,
|
|
26
31
|
}) => {
|
|
27
32
|
const { data: pools, loading, error } = usePools()
|
|
33
|
+
const { address } = useAccount()
|
|
34
|
+
const { sortedTokens: userTokens } = useTokenBalances(address || null)
|
|
28
35
|
const [selectedProtocol, setSelectedProtocol] = useState<string>("all")
|
|
29
36
|
const [searchFilter, setSearchFilter] = useState<string>("")
|
|
30
37
|
const [filterByChainId, setFilterByChainId] = useState<number | null>(null)
|
|
@@ -87,6 +94,18 @@ export const EarnPools: React.FC<EarnPoolsProps> = ({
|
|
|
87
94
|
}
|
|
88
95
|
}, [pools])
|
|
89
96
|
|
|
97
|
+
// Create a map to quickly lookup user balances by token address and chain
|
|
98
|
+
const userBalanceMap = useMemo(() => {
|
|
99
|
+
const map = new Map<string, (typeof userTokens)[0]>()
|
|
100
|
+
if (userTokens && Array.isArray(userTokens)) {
|
|
101
|
+
userTokens.forEach((token) => {
|
|
102
|
+
const key = `${token.contractAddress.toLowerCase()}-${token.chainId}`
|
|
103
|
+
map.set(key, token)
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
return map
|
|
107
|
+
}, [userTokens])
|
|
108
|
+
|
|
90
109
|
const filteredPools = useMemo(() => {
|
|
91
110
|
if (!pools || !Array.isArray(pools)) {
|
|
92
111
|
return []
|
|
@@ -130,13 +149,58 @@ export const EarnPools: React.FC<EarnPoolsProps> = ({
|
|
|
130
149
|
}
|
|
131
150
|
})
|
|
132
151
|
|
|
133
|
-
//
|
|
134
|
-
|
|
152
|
+
// Add user balance data to pools and sort
|
|
153
|
+
const poolsWithBalances = filtered.map((pool) => {
|
|
154
|
+
const key = `${pool.depositAddress.toLowerCase()}-${pool.chainId}`
|
|
155
|
+
const userBalance = userBalanceMap.get(key)
|
|
156
|
+
|
|
157
|
+
// Calculate USD value if we have balance and price
|
|
158
|
+
let calculatedUsdValue = userBalance?.balanceUsd || 0
|
|
159
|
+
if (userBalance?.balanceFormatted && userBalance?.priceUsd) {
|
|
160
|
+
calculatedUsdValue =
|
|
161
|
+
parseFloat(userBalance.balanceFormatted) * userBalance.priceUsd
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
...pool,
|
|
166
|
+
userBalance: userBalance?.balance,
|
|
167
|
+
userBalanceUsd: calculatedUsdValue,
|
|
168
|
+
userBalanceFormatted: userBalance?.balanceFormatted,
|
|
169
|
+
userBalanceDisplay: userBalance?.balanceDisplay,
|
|
170
|
+
userTokenSymbol: userBalance?.symbol,
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
// Sort the filtered pools - always prioritize by user balance
|
|
175
|
+
return poolsWithBalances.sort((a, b) => {
|
|
135
176
|
try {
|
|
177
|
+
const aHasBalance =
|
|
178
|
+
a.userBalanceFormatted && parseFloat(a.userBalanceFormatted) > 0
|
|
179
|
+
const bHasBalance =
|
|
180
|
+
b.userBalanceFormatted && parseFloat(b.userBalanceFormatted) > 0
|
|
181
|
+
|
|
182
|
+
// First prioritize pools with user balance
|
|
183
|
+
if (aHasBalance && !bHasBalance) return -1
|
|
184
|
+
if (!aHasBalance && bHasBalance) return 1
|
|
185
|
+
|
|
186
|
+
// If both have balance, sort by USD value first, then by token balance
|
|
187
|
+
if (aHasBalance && bHasBalance) {
|
|
188
|
+
// Sort by USD value if both have it
|
|
189
|
+
if (a.userBalanceUsd > 0 && b.userBalanceUsd > 0) {
|
|
190
|
+
return b.userBalanceUsd - a.userBalanceUsd
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Otherwise sort by token balance
|
|
194
|
+
const aBalance = parseFloat(a.userBalanceFormatted || "0")
|
|
195
|
+
const bBalance = parseFloat(b.userBalanceFormatted || "0")
|
|
196
|
+
return bBalance - aBalance
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// For pools without user balance, use the selected sort
|
|
136
200
|
if (sortBy === "tvl") {
|
|
137
|
-
return (b.tvl || 0) - (a.tvl || 0)
|
|
201
|
+
return (b.tvl || 0) - (a.tvl || 0)
|
|
138
202
|
} else if (sortBy === "apy") {
|
|
139
|
-
return (b.apy || 0) - (a.apy || 0)
|
|
203
|
+
return (b.apy || 0) - (a.apy || 0)
|
|
140
204
|
}
|
|
141
205
|
return 0
|
|
142
206
|
} catch (error) {
|
|
@@ -144,7 +208,94 @@ export const EarnPools: React.FC<EarnPoolsProps> = ({
|
|
|
144
208
|
return 0
|
|
145
209
|
}
|
|
146
210
|
})
|
|
147
|
-
}, [
|
|
211
|
+
}, [
|
|
212
|
+
pools,
|
|
213
|
+
selectedProtocol,
|
|
214
|
+
filterByChainId,
|
|
215
|
+
searchFilter,
|
|
216
|
+
sortBy,
|
|
217
|
+
userBalanceMap,
|
|
218
|
+
])
|
|
219
|
+
|
|
220
|
+
// Separate pools into user pools and other pools
|
|
221
|
+
const userPools = useMemo(() => {
|
|
222
|
+
return filteredPools.filter(
|
|
223
|
+
(pool) =>
|
|
224
|
+
pool.userBalanceFormatted && parseFloat(pool.userBalanceFormatted) > 0,
|
|
225
|
+
)
|
|
226
|
+
}, [filteredPools])
|
|
227
|
+
|
|
228
|
+
const otherPools = useMemo(() => {
|
|
229
|
+
return filteredPools.filter(
|
|
230
|
+
(pool) =>
|
|
231
|
+
!pool.userBalanceFormatted ||
|
|
232
|
+
parseFloat(pool.userBalanceFormatted) === 0,
|
|
233
|
+
)
|
|
234
|
+
}, [filteredPools])
|
|
235
|
+
|
|
236
|
+
// Function to render a pool item
|
|
237
|
+
const renderPoolItem = (pool: any) => (
|
|
238
|
+
<motion.div
|
|
239
|
+
key={pool.id}
|
|
240
|
+
whileHover={{ scale: 1 }}
|
|
241
|
+
whileTap={{ scale: 0.99 }}
|
|
242
|
+
onClick={() => onPoolSelect(pool)}
|
|
243
|
+
className="p-4 trails-border-radius-container trails-list-item cursor-pointer transition-all overflow-hidden"
|
|
244
|
+
>
|
|
245
|
+
<div className="flex items-center justify-between">
|
|
246
|
+
<div className="flex items-center space-x-3">
|
|
247
|
+
<div style={{ width: "32px", height: "32px" }}>
|
|
248
|
+
<TokenImage
|
|
249
|
+
symbol={pool.token.symbol}
|
|
250
|
+
imageUrl={pool.token.logoUrl}
|
|
251
|
+
chainId={pool.chainId}
|
|
252
|
+
size={32}
|
|
253
|
+
/>
|
|
254
|
+
</div>
|
|
255
|
+
<div>
|
|
256
|
+
<h3 className="font-medium text-gray-900 dark:text-white text-sm">
|
|
257
|
+
{pool.name}
|
|
258
|
+
</h3>
|
|
259
|
+
<div className="flex items-center space-x-1">
|
|
260
|
+
<span className="text-xs text-gray-500 dark:text-gray-400 flex items-center">
|
|
261
|
+
{pool.protocol === "Aave" && (
|
|
262
|
+
<img src={aaveLogo} alt="Aave" className="w-3 h-3 mr-1" />
|
|
263
|
+
)}
|
|
264
|
+
{pool.protocol === "Morpho" && (
|
|
265
|
+
<img src={morphoLogo} alt="Morpho" className="w-3 h-3 mr-1" />
|
|
266
|
+
)}
|
|
267
|
+
{pool.protocol}
|
|
268
|
+
</span>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
<div className="text-right">
|
|
273
|
+
<div className="flex items-center justify-end space-x-1 text-green-600 dark:text-green-400 mb-1 whitespace-nowrap">
|
|
274
|
+
<TrendingUp className="w-3 h-3" />
|
|
275
|
+
<span className="font-semibold text-sm">
|
|
276
|
+
{pool.apy.toFixed(1)}% APY
|
|
277
|
+
</span>
|
|
278
|
+
</div>
|
|
279
|
+
<p className="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">
|
|
280
|
+
TVL: {formatTvl(pool.tvl)}
|
|
281
|
+
</p>
|
|
282
|
+
{/* User balance display below TVL */}
|
|
283
|
+
{pool.userBalanceFormatted &&
|
|
284
|
+
parseFloat(pool.userBalanceFormatted) > 0 && (
|
|
285
|
+
<p
|
|
286
|
+
className="text-xs font-bold mt-1 whitespace-nowrap"
|
|
287
|
+
style={{ color: "#71717A" }}
|
|
288
|
+
>
|
|
289
|
+
Balance:{" "}
|
|
290
|
+
{pool.userBalanceUsd > 0
|
|
291
|
+
? formatUsdAmountDisplay(pool.userBalanceUsd)
|
|
292
|
+
: pool.userBalanceDisplay || pool.userBalanceFormatted}
|
|
293
|
+
</p>
|
|
294
|
+
)}
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
</motion.div>
|
|
298
|
+
)
|
|
148
299
|
|
|
149
300
|
// Show chain list screen
|
|
150
301
|
if (showChainList) {
|
|
@@ -223,63 +374,33 @@ export const EarnPools: React.FC<EarnPoolsProps> = ({
|
|
|
223
374
|
</p>
|
|
224
375
|
</div>
|
|
225
376
|
) : (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
<div className="flex items-center justify-between">
|
|
235
|
-
<div className="flex items-center space-x-3">
|
|
236
|
-
<div style={{ width: "32px", height: "32px" }}>
|
|
237
|
-
<TokenImage
|
|
238
|
-
symbol={pool.token.symbol}
|
|
239
|
-
imageUrl={pool.token.logoUrl}
|
|
240
|
-
chainId={pool.chainId}
|
|
241
|
-
size={32}
|
|
242
|
-
/>
|
|
243
|
-
</div>
|
|
244
|
-
<div>
|
|
245
|
-
<h3 className="font-medium text-gray-900 dark:text-white text-sm">
|
|
246
|
-
{pool.name}
|
|
247
|
-
</h3>
|
|
248
|
-
<div className="flex items-center space-x-1">
|
|
249
|
-
<span className="text-xs text-gray-500 dark:text-gray-400 flex items-center">
|
|
250
|
-
{pool.protocol === "Aave" && (
|
|
251
|
-
<img
|
|
252
|
-
src={aaveLogo}
|
|
253
|
-
alt="Aave"
|
|
254
|
-
className="w-3 h-3 mr-1"
|
|
255
|
-
/>
|
|
256
|
-
)}
|
|
257
|
-
{pool.protocol === "Morpho" && (
|
|
258
|
-
<img
|
|
259
|
-
src={morphoLogo}
|
|
260
|
-
alt="Morpho"
|
|
261
|
-
className="w-3 h-3 mr-1"
|
|
262
|
-
/>
|
|
263
|
-
)}
|
|
264
|
-
{pool.protocol}
|
|
265
|
-
</span>
|
|
266
|
-
</div>
|
|
267
|
-
</div>
|
|
377
|
+
<>
|
|
378
|
+
{/* My Vaults Section */}
|
|
379
|
+
{userPools.length > 0 && (
|
|
380
|
+
<>
|
|
381
|
+
<div className="px-2 py-1">
|
|
382
|
+
<p className="text-xs font-semibold text-gray-600 dark:text-gray-400 tracking-wider">
|
|
383
|
+
My Vaults
|
|
384
|
+
</p>
|
|
268
385
|
</div>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
386
|
+
{userPools.map(renderPoolItem)}
|
|
387
|
+
</>
|
|
388
|
+
)}
|
|
389
|
+
|
|
390
|
+
{/* Other Vaults Section */}
|
|
391
|
+
{otherPools.length > 0 && (
|
|
392
|
+
<>
|
|
393
|
+
<div
|
|
394
|
+
className={`px-2 py-1 ${userPools.length > 0 ? "mt-4" : ""}`}
|
|
395
|
+
>
|
|
396
|
+
<p className="text-xs font-semibold text-gray-600 dark:text-gray-400 tracking-wider">
|
|
397
|
+
{userPools.length > 0 ? "Other Vaults" : "Available Vaults"}
|
|
278
398
|
</p>
|
|
279
399
|
</div>
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
400
|
+
{otherPools.map(renderPoolItem)}
|
|
401
|
+
</>
|
|
402
|
+
)}
|
|
403
|
+
</>
|
|
283
404
|
)}
|
|
284
405
|
</div>
|
|
285
406
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Loader2, ArrowLeftRight } from "lucide-react"
|
|
2
2
|
import type React from "react"
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|
4
|
+
import { useBack } from "../hooks/useBack.js"
|
|
4
5
|
|
|
5
6
|
// Helper function to get centered window parameters
|
|
6
7
|
const getCenteredWindowParams = (width: number, height: number): string => {
|
|
@@ -146,6 +147,7 @@ const Fund: React.FC<FundProps> = ({
|
|
|
146
147
|
}) => {
|
|
147
148
|
const { mode } = useMode()
|
|
148
149
|
const { setCurrentScreen } = useCurrentScreen()
|
|
150
|
+
const { setCurrentScreenWithBack } = useBack()
|
|
149
151
|
const {
|
|
150
152
|
selectedToken: originToken,
|
|
151
153
|
setSelectedToken: setOriginToken,
|
|
@@ -1161,7 +1163,7 @@ const Fund: React.FC<FundProps> = ({
|
|
|
1161
1163
|
customActions={
|
|
1162
1164
|
<button
|
|
1163
1165
|
type="button"
|
|
1164
|
-
onClick={() =>
|
|
1166
|
+
onClick={() => setCurrentScreenWithBack("swap", "fund-form")}
|
|
1165
1167
|
className="flex h-8 px-3 justify-center items-center gap-1.5 rounded-full bg-gray-50 dark:bg-gray-700 cursor-pointer transition-colors text-gray-900 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-600"
|
|
1166
1168
|
title="Swap"
|
|
1167
1169
|
>
|
|
@@ -9,14 +9,13 @@ import { truncateAddress } from "../../utils.js"
|
|
|
9
9
|
import { PriceImpactWarning } from "./PriceImpactWarning.js"
|
|
10
10
|
import { usePriceImpactWarning } from "../hooks/usePriceImpactWarning.js"
|
|
11
11
|
import {
|
|
12
|
-
Copy,
|
|
13
|
-
Check,
|
|
14
12
|
Info,
|
|
15
13
|
ExternalLink,
|
|
16
14
|
Clock,
|
|
17
15
|
ChevronDown,
|
|
18
16
|
TriangleAlert,
|
|
19
17
|
} from "lucide-react"
|
|
18
|
+
import { useTrails } from "../providers/TrailsProvider.js"
|
|
20
19
|
import type { MeldQuote } from "../../meld/utils/meld.js"
|
|
21
20
|
import { logger } from "../../logger.js"
|
|
22
21
|
|
|
@@ -50,8 +49,8 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
|
|
|
50
49
|
const [showCalldata, setShowCalldata] = useState(false)
|
|
51
50
|
const [showOriginRate, setShowOriginRate] = useState(true)
|
|
52
51
|
const [isExpanded, setIsExpanded] = useState(initialExpanded)
|
|
53
|
-
const [intentIdCopied, setIntentIdCopied] = useState(false)
|
|
54
52
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
53
|
+
const { trailsAppUrl } = useTrails()
|
|
55
54
|
const calldataRef = useRef<HTMLDivElement>(null)
|
|
56
55
|
|
|
57
56
|
const priceImpactConfig = usePriceImpactWarning()
|
|
@@ -86,22 +85,6 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
|
|
|
86
85
|
onExpand?.(isExpanded)
|
|
87
86
|
}, [isExpanded, onExpand])
|
|
88
87
|
|
|
89
|
-
const handleCopyIntentId = useCallback(async () => {
|
|
90
|
-
if (!quote?.intentId) return
|
|
91
|
-
try {
|
|
92
|
-
await navigator.clipboard.writeText(quote.intentId)
|
|
93
|
-
setIntentIdCopied(true)
|
|
94
|
-
} catch (error) {
|
|
95
|
-
console.error("[trails-sdk] Failed to copy intentId", error)
|
|
96
|
-
}
|
|
97
|
-
}, [quote?.intentId])
|
|
98
|
-
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
if (!intentIdCopied) return
|
|
101
|
-
const timeout = setTimeout(() => setIntentIdCopied(false), 1500)
|
|
102
|
-
return () => clearTimeout(timeout)
|
|
103
|
-
}, [intentIdCopied])
|
|
104
|
-
|
|
105
88
|
const getFiatToCryptoRate = useCallback(() => {
|
|
106
89
|
if (!onRampQuote || !quote?.originTokenRate) {
|
|
107
90
|
return null
|
|
@@ -525,24 +508,18 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
|
|
|
525
508
|
|
|
526
509
|
{/* Intent ID */}
|
|
527
510
|
{hasIntentId && (
|
|
528
|
-
<Row
|
|
511
|
+
<Row>
|
|
529
512
|
<RowLabel>Intent ID</RowLabel>
|
|
530
|
-
<RowValue
|
|
531
|
-
<
|
|
532
|
-
{
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
onClick={handleCopyIntentId}
|
|
537
|
-
className="p-0.5 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
|
538
|
-
aria-label="Copy intent ID"
|
|
513
|
+
<RowValue>
|
|
514
|
+
<a
|
|
515
|
+
href={`${trailsAppUrl}/intent/${quote.intentId}`}
|
|
516
|
+
target="_blank"
|
|
517
|
+
rel="noopener noreferrer"
|
|
518
|
+
className="font-mono text-xs hover:underline flex items-center gap-1 text-blue-500"
|
|
539
519
|
>
|
|
540
|
-
{
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
<Copy className="size-3 text-gray-500 dark:text-gray-400" />
|
|
544
|
-
)}
|
|
545
|
-
</button>
|
|
520
|
+
{truncateAddress(quote?.intentId ?? "", 9, 3)}
|
|
521
|
+
<ExternalLink className="size-3" />
|
|
522
|
+
</a>
|
|
546
523
|
</RowValue>
|
|
547
524
|
</Row>
|
|
548
525
|
)}
|
|
@@ -2,7 +2,6 @@ import { useQuery } from "@tanstack/react-query"
|
|
|
2
2
|
import type React from "react"
|
|
3
3
|
import type { ReactNode } from "react"
|
|
4
4
|
import { useEffect, useState, useCallback, useMemo } from "react"
|
|
5
|
-
import { ChevronRight } from "lucide-react"
|
|
6
5
|
import type { TransactionState } from "../../transactions.js"
|
|
7
6
|
import { GreenCheckAnimation } from "./GreenCheckAnimation.js"
|
|
8
7
|
import { YellowWarningAnimation } from "./YellowWarningAnimation.js"
|
|
@@ -13,11 +12,11 @@ import { truncateAddress } from "../../utils.js"
|
|
|
13
12
|
import { formatElapsed } from "../../utils.js"
|
|
14
13
|
import { ChainImage } from "./ChainImage.js"
|
|
15
14
|
import { getChainInfo } from "../../chains.js"
|
|
16
|
-
import { useMode } from "../hooks/useMode.js"
|
|
17
15
|
import { ScreenHeader } from "./ScreenHeader.js"
|
|
18
16
|
import { getExplorerUrl } from "../../explorer.js"
|
|
19
17
|
import { logger } from "../../logger.js"
|
|
20
18
|
import type { OnrampQuote } from "../hooks/useOnRampQuote.js"
|
|
19
|
+
import { useTrails } from "../providers/TrailsProvider.js"
|
|
21
20
|
|
|
22
21
|
interface ReceiptProps {
|
|
23
22
|
onSendAnother?: () => void
|
|
@@ -110,7 +109,7 @@ export const Receipt: React.FC<ReceiptProps> = ({
|
|
|
110
109
|
actionButtonText,
|
|
111
110
|
onRampQuote,
|
|
112
111
|
}) => {
|
|
113
|
-
const {
|
|
112
|
+
const { trailsAppUrl } = useTrails()
|
|
114
113
|
const [showContent, setShowContent] = useState(false)
|
|
115
114
|
const [showRefundInfo, setShowRefundInfo] = useState(false)
|
|
116
115
|
const [refundMessage, setRefundMessage] = useState<string | ReactNode | null>(
|
|
@@ -142,20 +141,12 @@ export const Receipt: React.FC<ReceiptProps> = ({
|
|
|
142
141
|
}, [transactionStates])
|
|
143
142
|
|
|
144
143
|
const buttonText = useMemo(() => {
|
|
145
|
-
// Use custom actionButtonText if provided, otherwise use
|
|
144
|
+
// Use custom actionButtonText if provided, otherwise use "Start new transaction" for all modes
|
|
146
145
|
if (actionButtonText) {
|
|
147
146
|
return actionButtonText
|
|
148
147
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
} else if (mode === "fund") {
|
|
152
|
-
return "Fund Again"
|
|
153
|
-
} else if (mode === "swap") {
|
|
154
|
-
return "Swap Again"
|
|
155
|
-
} else {
|
|
156
|
-
return "Send Again"
|
|
157
|
-
}
|
|
158
|
-
}, [mode, actionButtonText])
|
|
148
|
+
return "Start new transaction"
|
|
149
|
+
}, [actionButtonText])
|
|
159
150
|
|
|
160
151
|
const {
|
|
161
152
|
finalExplorerUrl,
|
|
@@ -163,6 +154,11 @@ export const Receipt: React.FC<ReceiptProps> = ({
|
|
|
163
154
|
completionTimeSeconds: calculatedCompletionTime,
|
|
164
155
|
} = useReceipt(transactionStates)
|
|
165
156
|
|
|
157
|
+
const intentReceiptUrl = useMemo(() => {
|
|
158
|
+
if (!trailsAppUrl || !quote?.intentId) return null
|
|
159
|
+
return `${trailsAppUrl}/intent/${quote.intentId}`
|
|
160
|
+
}, [quote?.intentId, trailsAppUrl])
|
|
161
|
+
|
|
166
162
|
// Use provided totalCompletionSeconds if available, otherwise use calculated time
|
|
167
163
|
const completionTimeSeconds =
|
|
168
164
|
totalCompletionSeconds ?? calculatedCompletionTime
|
|
@@ -517,7 +513,7 @@ export const Receipt: React.FC<ReceiptProps> = ({
|
|
|
517
513
|
</div>
|
|
518
514
|
</div>
|
|
519
515
|
|
|
520
|
-
{finalExplorerUrl && (
|
|
516
|
+
{(finalExplorerUrl || intentReceiptUrl) && (
|
|
521
517
|
<div
|
|
522
518
|
className={`text-center transition-all duration-500 ease-out delay-100 ${
|
|
523
519
|
showContent
|
|
@@ -525,29 +521,60 @@ export const Receipt: React.FC<ReceiptProps> = ({
|
|
|
525
521
|
: "opacity-0 translate-y-4"
|
|
526
522
|
}`}
|
|
527
523
|
>
|
|
528
|
-
<
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
524
|
+
<div className="flex flex-col items-center gap-2">
|
|
525
|
+
{finalExplorerUrl && (
|
|
526
|
+
<a
|
|
527
|
+
href={finalExplorerUrl}
|
|
528
|
+
target="_blank"
|
|
529
|
+
rel="noopener noreferrer"
|
|
530
|
+
className="inline-flex items-center gap-1 text-sm font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 transition-colors"
|
|
531
|
+
>
|
|
532
|
+
{finalChainId && (
|
|
533
|
+
<ChainImage chainId={finalChainId} size={16} />
|
|
534
|
+
)}
|
|
535
|
+
View on Explorer
|
|
536
|
+
<svg
|
|
537
|
+
className="w-4 h-4 ml-1 text-blue-600 dark:text-blue-400"
|
|
538
|
+
fill="none"
|
|
539
|
+
viewBox="0 0 24 24"
|
|
540
|
+
stroke="currentColor"
|
|
541
|
+
>
|
|
542
|
+
<title>External Link</title>
|
|
543
|
+
<path
|
|
544
|
+
strokeLinecap="round"
|
|
545
|
+
strokeLinejoin="round"
|
|
546
|
+
strokeWidth={2}
|
|
547
|
+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
548
|
+
/>
|
|
549
|
+
</svg>
|
|
550
|
+
</a>
|
|
551
|
+
)}
|
|
552
|
+
|
|
553
|
+
{intentReceiptUrl && (
|
|
554
|
+
<a
|
|
555
|
+
href={intentReceiptUrl}
|
|
556
|
+
target="_blank"
|
|
557
|
+
rel="noopener noreferrer"
|
|
558
|
+
className="inline-flex items-center gap-1 text-sm font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 transition-colors"
|
|
559
|
+
>
|
|
560
|
+
View Transaction Receipt
|
|
561
|
+
<svg
|
|
562
|
+
className="w-4 h-4 ml-1 text-blue-600 dark:text-blue-400"
|
|
563
|
+
fill="none"
|
|
564
|
+
viewBox="0 0 24 24"
|
|
565
|
+
stroke="currentColor"
|
|
566
|
+
>
|
|
567
|
+
<title>External Link</title>
|
|
568
|
+
<path
|
|
569
|
+
strokeLinecap="round"
|
|
570
|
+
strokeLinejoin="round"
|
|
571
|
+
strokeWidth={2}
|
|
572
|
+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
573
|
+
/>
|
|
574
|
+
</svg>
|
|
575
|
+
</a>
|
|
576
|
+
)}
|
|
577
|
+
</div>
|
|
551
578
|
</div>
|
|
552
579
|
)}
|
|
553
580
|
|
|
@@ -603,10 +630,9 @@ export const Receipt: React.FC<ReceiptProps> = ({
|
|
|
603
630
|
<button
|
|
604
631
|
type="button"
|
|
605
632
|
onClick={onSendAnother}
|
|
606
|
-
className="inline-flex items-center gap-1 px-4 py-2 text-sm bg-
|
|
633
|
+
className="inline-flex items-center gap-1 px-4 py-2 text-sm bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700 trails-border-radius-button transition-colors duration-200 text-white cursor-pointer font-medium"
|
|
607
634
|
>
|
|
608
635
|
{buttonText}
|
|
609
|
-
<ChevronRight className="w-4 h-4" />
|
|
610
636
|
</button>
|
|
611
637
|
</div>
|
|
612
638
|
)}
|