0xtrails 0.8.2 → 0.8.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/aave.d.ts.map +1 -1
- package/dist/{ccip-ru_Yzdas.js → ccip-BKavX04a.js} +13 -13
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/fees.d.ts +11 -17
- package/dist/fees.d.ts.map +1 -1
- package/dist/gasless.d.ts.map +1 -1
- package/dist/{index-Si7cO9V7.js → index-D5kULpIU.js} +20430 -20133
- package/dist/index.js +425 -847
- package/dist/intents.d.ts +1 -2
- package/dist/intents.d.ts.map +1 -1
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/recover.d.ts +8 -9
- package/dist/recover.d.ts.map +1 -1
- package/dist/tokenBalances.d.ts +51 -0
- package/dist/tokenBalances.d.ts.map +1 -1
- package/dist/trailsRouter.d.ts +15 -0
- package/dist/trailsRouter.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +1 -3
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/standardDeposit.d.ts +1 -3
- package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/crossChain.d.ts +2 -4
- package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +5 -4
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
- package/dist/transactionIntent/quote/normalizeQuote.d.ts +1 -1
- package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
- package/dist/transactionIntent/quote/quoteHelpers.d.ts +1 -1
- package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
- package/dist/transactionIntent/types.d.ts +11 -18
- package/dist/transactionIntent/types.d.ts.map +1 -1
- package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts +1 -0
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/DynamicSizeInputField.d.ts.map +1 -1
- package/dist/widget/components/Fund.d.ts.map +1 -1
- package/dist/widget/components/FundSwap.d.ts +1 -0
- package/dist/widget/components/FundSwap.d.ts.map +1 -1
- package/dist/widget/components/Pay.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/SlippageToleranceSettings.d.ts +2 -1
- package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
- package/dist/widget/components/Swap.d.ts +1 -0
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/WidgetProviders.d.ts.map +1 -1
- package/dist/widget/css/compiled.css +1 -1
- package/dist/widget/hooks/useDefaultDestinationToken.d.ts +20 -0
- package/dist/widget/hooks/useDefaultDestinationToken.d.ts.map +1 -0
- package/dist/widget/hooks/{useDefaultTokenSelection.d.ts → useDefaultOriginToken.d.ts} +4 -16
- package/dist/widget/hooks/useDefaultOriginToken.d.ts.map +1 -0
- package/dist/widget/hooks/useQuote.d.ts +94 -35
- package/dist/widget/hooks/useQuote.d.ts.map +1 -1
- package/dist/widget/hooks/useSendForm.d.ts +2 -2
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/hooks/useTrailsSendTransaction.d.ts.map +1 -1
- package/dist/widget/index.js +1 -1
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/aave.ts +4 -0
- package/src/constants.ts +4 -0
- package/src/fees.ts +47 -72
- package/src/gasless.ts +62 -32
- package/src/intents.ts +1 -3
- package/src/morpho.ts +1 -1
- package/src/prepareSend.ts +42 -6
- package/src/recover.ts +116 -172
- package/src/tokenBalances.ts +301 -1
- package/src/trailsRouter.ts +77 -0
- package/src/transactionIntent/deposits/depositOrchestrator.ts +0 -6
- package/src/transactionIntent/deposits/standardDeposit.ts +167 -184
- package/src/transactionIntent/handlers/crossChain.ts +8 -11
- package/src/transactionIntent/handlers/sameChainSameToken.ts +619 -608
- package/src/transactionIntent/quote/normalizeQuote.ts +32 -46
- package/src/transactionIntent/quote/quoteHelpers.ts +4 -2
- package/src/transactionIntent/types.ts +11 -18
- package/src/widget/compiled.css +1 -1
- package/src/widget/components/AccountIntentTransactionHistory.tsx +50 -18
- package/src/widget/components/ClassicSwap.tsx +158 -63
- package/src/widget/components/DynamicSizeInputField.tsx +2 -0
- package/src/widget/components/Fund.tsx +12 -11
- package/src/widget/components/FundSwap.tsx +1 -0
- package/src/widget/components/Pay.tsx +15 -14
- package/src/widget/components/QuoteDetails.tsx +18 -27
- package/src/widget/components/SlippageToleranceSettings.tsx +55 -25
- package/src/widget/components/Swap.tsx +1 -0
- package/src/widget/components/WidgetProviders.tsx +1 -6
- package/src/widget/hooks/useDefaultDestinationToken.tsx +173 -0
- package/src/widget/hooks/{useDefaultTokenSelection.tsx → useDefaultOriginToken.tsx} +58 -191
- package/src/widget/hooks/useQuote.ts +317 -79
- package/src/widget/hooks/useSendForm.ts +123 -764
- package/src/widget/hooks/useTrailsSendTransaction.ts +0 -2
- package/src/widget/widget.tsx +2 -0
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +0 -1
|
@@ -280,7 +280,7 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
|
|
|
280
280
|
/>
|
|
281
281
|
</RowLabel>
|
|
282
282
|
<RowValue>
|
|
283
|
-
<span title={`$${quote.
|
|
283
|
+
<span title={`$${quote.destinationGasUsd ?? 0}`}>
|
|
284
284
|
{
|
|
285
285
|
quote.trailsFeeBreakdown.destinationRelayFee
|
|
286
286
|
.usdValue
|
|
@@ -301,37 +301,28 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
|
|
|
301
301
|
if (!quote?.trailsFeeBreakdown) return null
|
|
302
302
|
|
|
303
303
|
// Check if provider fee is non-zero
|
|
304
|
-
const hasProviderFee = (quote.
|
|
304
|
+
const hasProviderFee = (quote.providerFeeUsd ?? 0) > 0
|
|
305
305
|
|
|
306
306
|
// Check if trails fee is non-zero
|
|
307
|
-
const hasTrailsFee = (quote.
|
|
307
|
+
const hasTrailsFee = (quote.trailsFeeUsd ?? 0) > 0
|
|
308
308
|
|
|
309
309
|
if (!hasProviderFee && !hasTrailsFee) return null
|
|
310
310
|
|
|
311
311
|
return (
|
|
312
312
|
<RowGroup>
|
|
313
|
-
{/* All Provider Fees
|
|
314
|
-
{(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
</RowLabel>
|
|
327
|
-
<RowValue>
|
|
328
|
-
<span title={`$${quote.allProviderFeesUsd ?? 0}`}>
|
|
329
|
-
{quote.allProviderFeesUsdDisplay}
|
|
330
|
-
</span>
|
|
331
|
-
</RowValue>
|
|
332
|
-
</Row>
|
|
333
|
-
)
|
|
334
|
-
})()}
|
|
313
|
+
{/* All Provider Fees (swap spread is shown in priceImpact) */}
|
|
314
|
+
{(quote.totalProviderFeesUsd ?? 0) > 0 && (
|
|
315
|
+
<Row>
|
|
316
|
+
<RowLabel tooltip="Total provider fees for bridge and routing">
|
|
317
|
+
All provider fees
|
|
318
|
+
</RowLabel>
|
|
319
|
+
<RowValue>
|
|
320
|
+
<span title={`$${quote.totalProviderFeesUsd ?? 0}`}>
|
|
321
|
+
{quote.totalProviderFeesUsdDisplay}
|
|
322
|
+
</span>
|
|
323
|
+
</RowValue>
|
|
324
|
+
</Row>
|
|
325
|
+
)}
|
|
335
326
|
|
|
336
327
|
{hasProviderFee &&
|
|
337
328
|
quote.trailsFeeBreakdown.providerFee &&
|
|
@@ -394,7 +385,7 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
|
|
|
394
385
|
)}
|
|
395
386
|
</RowLabel>
|
|
396
387
|
<RowValue>
|
|
397
|
-
<span title={`$${quote.
|
|
388
|
+
<span title={`$${quote.providerFeeUsd ?? 0}`}>
|
|
398
389
|
{providerFee.usdValue}
|
|
399
390
|
</span>
|
|
400
391
|
</RowValue>
|
|
@@ -497,7 +488,7 @@ export const QuoteDetails: React.FC<QuoteDetailsProps> = ({
|
|
|
497
488
|
<TriangleAlert className="size-3" />
|
|
498
489
|
</span>
|
|
499
490
|
)}
|
|
500
|
-
{quote.priceImpact}%
|
|
491
|
+
{Number(quote.priceImpact).toFixed(2)}%
|
|
501
492
|
</RowValue>
|
|
502
493
|
</Row>
|
|
503
494
|
{quote.priceImpactUsdDisplay && (
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type React from "react"
|
|
2
2
|
import { useState, useEffect } from "react"
|
|
3
3
|
import { HelpCircle } from "lucide-react"
|
|
4
|
-
import {
|
|
4
|
+
import { SLIPPAGE_AUTO } from "../../constants.js"
|
|
5
5
|
|
|
6
6
|
// Convert decimal format to percentage for display
|
|
7
7
|
// Format: "0.05" (5%) -> "5"
|
|
8
8
|
const decimalToPercentage = (decimal: string): string => {
|
|
9
|
+
if (decimal === SLIPPAGE_AUTO) return SLIPPAGE_AUTO
|
|
9
10
|
const num = parseFloat(decimal)
|
|
10
11
|
return (num * 100).toString()
|
|
11
12
|
}
|
|
@@ -13,6 +14,7 @@ const decimalToPercentage = (decimal: string): string => {
|
|
|
13
14
|
// Convert percentage to decimal format for storage
|
|
14
15
|
// Format: "5" (5%) -> "0.05"
|
|
15
16
|
const percentageToDecimal = (percentage: string): string => {
|
|
17
|
+
if (percentage === SLIPPAGE_AUTO) return SLIPPAGE_AUTO
|
|
16
18
|
const num = parseFloat(percentage)
|
|
17
19
|
return (num / 100).toString()
|
|
18
20
|
}
|
|
@@ -20,11 +22,23 @@ const percentageToDecimal = (percentage: string): string => {
|
|
|
20
22
|
// Local storage key for user's slippage preference
|
|
21
23
|
const SLIPPAGE_STORAGE_KEY = "trails-slippage-tolerance"
|
|
22
24
|
|
|
25
|
+
// Helper function to check if slippage is in AUTO mode
|
|
26
|
+
export const isSlippageAuto = (value: string | null | undefined): boolean => {
|
|
27
|
+
return value === SLIPPAGE_AUTO || value === null || value === undefined
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
// Helper function to get current slippage tolerance (for use in SDK functions)
|
|
24
31
|
// Returns the user's stored preference or falls back to the provided default
|
|
25
|
-
|
|
32
|
+
// Returns null if AUTO mode is selected (backend will calculate optimal slippage)
|
|
33
|
+
export const getSlippageToleranceValue = (
|
|
34
|
+
defaultValue: string,
|
|
35
|
+
): string | null => {
|
|
26
36
|
try {
|
|
27
37
|
const stored = localStorage.getItem(SLIPPAGE_STORAGE_KEY)
|
|
38
|
+
if (stored === SLIPPAGE_AUTO || stored === null) {
|
|
39
|
+
// AUTO mode - return null so backend calculates optimal slippage
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
28
42
|
return stored || defaultValue
|
|
29
43
|
} catch (_error) {
|
|
30
44
|
return defaultValue
|
|
@@ -38,7 +52,6 @@ interface SlippageToleranceSettingsProps {
|
|
|
38
52
|
export const SlippageToleranceSettings: React.FC<
|
|
39
53
|
SlippageToleranceSettingsProps
|
|
40
54
|
> = ({ className = "" }) => {
|
|
41
|
-
const config = useTrails()
|
|
42
55
|
const [displayValue, setDisplayValue] = useState("")
|
|
43
56
|
const [showTooltip, setShowTooltip] = useState(false)
|
|
44
57
|
|
|
@@ -49,14 +62,14 @@ export const SlippageToleranceSettings: React.FC<
|
|
|
49
62
|
if (stored) {
|
|
50
63
|
setDisplayValue(decimalToPercentage(stored))
|
|
51
64
|
} else {
|
|
52
|
-
// Default to
|
|
53
|
-
setDisplayValue(
|
|
65
|
+
// Default to AUTO mode
|
|
66
|
+
setDisplayValue(SLIPPAGE_AUTO)
|
|
54
67
|
}
|
|
55
68
|
} catch (_error) {
|
|
56
|
-
// Fallback to
|
|
57
|
-
setDisplayValue(
|
|
69
|
+
// Fallback to AUTO if localStorage fails
|
|
70
|
+
setDisplayValue(SLIPPAGE_AUTO)
|
|
58
71
|
}
|
|
59
|
-
}, [
|
|
72
|
+
}, [])
|
|
60
73
|
|
|
61
74
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
62
75
|
const value = e.target.value
|
|
@@ -73,17 +86,13 @@ export const SlippageToleranceSettings: React.FC<
|
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
const handleInputBlur = () => {
|
|
76
|
-
if (displayValue === "") {
|
|
77
|
-
// Reset to
|
|
89
|
+
if (displayValue === "" || displayValue === SLIPPAGE_AUTO) {
|
|
90
|
+
// Reset to AUTO mode
|
|
91
|
+
setDisplayValue(SLIPPAGE_AUTO)
|
|
78
92
|
try {
|
|
79
|
-
|
|
80
|
-
if (stored) {
|
|
81
|
-
setDisplayValue(decimalToPercentage(stored))
|
|
82
|
-
} else {
|
|
83
|
-
setDisplayValue(decimalToPercentage(String(config.slippageTolerance)))
|
|
84
|
-
}
|
|
93
|
+
localStorage.setItem(SLIPPAGE_STORAGE_KEY, SLIPPAGE_AUTO)
|
|
85
94
|
} catch (_error) {
|
|
86
|
-
|
|
95
|
+
// Ignore localStorage errors
|
|
87
96
|
}
|
|
88
97
|
return
|
|
89
98
|
}
|
|
@@ -91,17 +100,17 @@ export const SlippageToleranceSettings: React.FC<
|
|
|
91
100
|
const percentage = parseFloat(displayValue)
|
|
92
101
|
|
|
93
102
|
// Validate range (0.01% to 50%)
|
|
94
|
-
if (percentage < 0.01 || percentage > 50) {
|
|
95
|
-
// Reset to stored value or
|
|
103
|
+
if (Number.isNaN(percentage) || percentage < 0.01 || percentage > 50) {
|
|
104
|
+
// Reset to stored value or AUTO default
|
|
96
105
|
try {
|
|
97
106
|
const stored = localStorage.getItem(SLIPPAGE_STORAGE_KEY)
|
|
98
|
-
if (stored) {
|
|
107
|
+
if (stored && stored !== SLIPPAGE_AUTO) {
|
|
99
108
|
setDisplayValue(decimalToPercentage(stored))
|
|
100
109
|
} else {
|
|
101
|
-
setDisplayValue(
|
|
110
|
+
setDisplayValue(SLIPPAGE_AUTO)
|
|
102
111
|
}
|
|
103
112
|
} catch (_error) {
|
|
104
|
-
setDisplayValue(
|
|
113
|
+
setDisplayValue(SLIPPAGE_AUTO)
|
|
105
114
|
}
|
|
106
115
|
return
|
|
107
116
|
}
|
|
@@ -124,6 +133,15 @@ export const SlippageToleranceSettings: React.FC<
|
|
|
124
133
|
}
|
|
125
134
|
}
|
|
126
135
|
|
|
136
|
+
const handleAutoClick = () => {
|
|
137
|
+
setDisplayValue(SLIPPAGE_AUTO)
|
|
138
|
+
try {
|
|
139
|
+
localStorage.setItem(SLIPPAGE_STORAGE_KEY, SLIPPAGE_AUTO)
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.warn("Failed to save slippage tolerance to localStorage:", error)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
127
145
|
const handlePresetClick = (percentage: number) => {
|
|
128
146
|
const percentageStr = percentage.toString()
|
|
129
147
|
setDisplayValue(percentageStr)
|
|
@@ -152,8 +170,9 @@ export const SlippageToleranceSettings: React.FC<
|
|
|
152
170
|
</button>
|
|
153
171
|
{showTooltip && (
|
|
154
172
|
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 w-64 p-2 text-xs text-white bg-gray-900 dark:bg-gray-700 rounded-md shadow-lg z-10">
|
|
155
|
-
|
|
156
|
-
|
|
173
|
+
Auto mode calculates optimal slippage based on transaction size
|
|
174
|
+
and routing. Higher slippage allows for larger price movements but
|
|
175
|
+
may result in less favorable rates.
|
|
157
176
|
<div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-900 dark:border-t-gray-700"></div>
|
|
158
177
|
</div>
|
|
159
178
|
)}
|
|
@@ -162,7 +181,18 @@ export const SlippageToleranceSettings: React.FC<
|
|
|
162
181
|
|
|
163
182
|
{/* Preset buttons */}
|
|
164
183
|
<div className="flex items-center space-x-2">
|
|
165
|
-
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
onClick={handleAutoClick}
|
|
187
|
+
className={`px-2 py-1 text-xs font-medium trails-border-radius-container border border-solid transition-colors cursor-pointer ${
|
|
188
|
+
displayValue === SLIPPAGE_AUTO
|
|
189
|
+
? "bg-blue-500 text-white border-blue-500"
|
|
190
|
+
: "border-gray-300 text-gray-600 dark:border-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:trails-hover-bg hover:border-gray-400 dark:hover:border-gray-500"
|
|
191
|
+
}`}
|
|
192
|
+
>
|
|
193
|
+
Auto
|
|
194
|
+
</button>
|
|
195
|
+
{[0.5, 1, 3, 5].map((preset) => (
|
|
166
196
|
<button
|
|
167
197
|
key={preset}
|
|
168
198
|
type="button"
|
|
@@ -15,7 +15,6 @@ import { BalanceVisibleProvider } from "../hooks/useBalanceVisible.js"
|
|
|
15
15
|
import { ThemeProvider as ThemePreferenceProvider } from "../hooks/useTheme.js"
|
|
16
16
|
import { SelectedFundMethodProvider } from "../hooks/useSelectedFundMethod.js"
|
|
17
17
|
import { EarnPoolProvider } from "../hooks/useEarnPool.js"
|
|
18
|
-
import { DefaultTokenSelectionProvider } from "../hooks/useDefaultTokenSelection.js"
|
|
19
18
|
|
|
20
19
|
// Default props for hook modal (minimal required config)
|
|
21
20
|
const DEFAULT_HOOK_MODAL_PROPS: TrailsWidgetProps = {
|
|
@@ -52,11 +51,7 @@ export function WidgetProviders({
|
|
|
52
51
|
<BalanceVisibleProvider>
|
|
53
52
|
<ThemePreferenceProvider>
|
|
54
53
|
<SelectedFundMethodProvider>
|
|
55
|
-
<EarnPoolProvider>
|
|
56
|
-
<DefaultTokenSelectionProvider>
|
|
57
|
-
{children}
|
|
58
|
-
</DefaultTokenSelectionProvider>
|
|
59
|
-
</EarnPoolProvider>
|
|
54
|
+
<EarnPoolProvider>{children}</EarnPoolProvider>
|
|
60
55
|
</SelectedFundMethodProvider>
|
|
61
56
|
</ThemePreferenceProvider>
|
|
62
57
|
</BalanceVisibleProvider>
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { useMemo, useRef } from "react"
|
|
2
|
+
import { useAccount } from "wagmi"
|
|
3
|
+
import { base, arbitrum } from "viem/chains"
|
|
4
|
+
import type { Address } from "ox"
|
|
5
|
+
import { useSupportedTokens } from "../../tokens.js"
|
|
6
|
+
import type { Token } from "../../tokens.js"
|
|
7
|
+
import { useTokenBalances } from "../../tokenBalances.js"
|
|
8
|
+
import { logger } from "../../logger.js"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hook for intelligent default destination token selection.
|
|
12
|
+
*
|
|
13
|
+
* This hook calculates the best destination token based on:
|
|
14
|
+
* - Origin token's chain (to minimize bridging costs)
|
|
15
|
+
* - Token availability on destination chain
|
|
16
|
+
* - User's existing balances for accurate balance display
|
|
17
|
+
*
|
|
18
|
+
* Only calculates destination when toToken/toChainId are NOT specified.
|
|
19
|
+
*
|
|
20
|
+
* @param originToken - The selected origin token
|
|
21
|
+
* @returns defaultDestinationToken - Best destination token or null
|
|
22
|
+
* @returns isLoading - Whether token data is still loading
|
|
23
|
+
*/
|
|
24
|
+
export function useDefaultDestinationToken(originToken: Token | null) {
|
|
25
|
+
const { address } = useAccount()
|
|
26
|
+
|
|
27
|
+
const { supportedTokens, isLoadingTokens } = useSupportedTokens()
|
|
28
|
+
|
|
29
|
+
const { sortedTokens, isLoadingSortedTokens } = useTokenBalances(
|
|
30
|
+
address as Address.Address,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const isLoading = isLoadingTokens || isLoadingSortedTokens
|
|
34
|
+
|
|
35
|
+
// Use a ref to prevent unnecessary recalculations on balance-only updates
|
|
36
|
+
const stableDestTokenRef = useRef<Token | null>(null)
|
|
37
|
+
const lastOriginTokenKey = useRef<string>("")
|
|
38
|
+
|
|
39
|
+
const defaultDestinationToken = useMemo(() => {
|
|
40
|
+
// Don't compute destination if no origin token or still loading
|
|
41
|
+
if (!originToken || !supportedTokens?.length || !sortedTokens?.length) {
|
|
42
|
+
stableDestTokenRef.current = null
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Create a key based on origin token identity (not balance)
|
|
47
|
+
const originTokenKey = `${originToken.symbol}-${originToken.chainId}-${originToken.contractAddress || "native"}`
|
|
48
|
+
|
|
49
|
+
// Only recalculate if origin token has actually changed
|
|
50
|
+
const shouldRecalculate =
|
|
51
|
+
originTokenKey !== lastOriginTokenKey.current ||
|
|
52
|
+
!stableDestTokenRef.current
|
|
53
|
+
|
|
54
|
+
if (!shouldRecalculate && stableDestTokenRef.current) {
|
|
55
|
+
return stableDestTokenRef.current
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
lastOriginTokenKey.current = originTokenKey
|
|
59
|
+
|
|
60
|
+
// Determine destination chain: Base if origin is not Base, Arbitrum if origin is Base
|
|
61
|
+
const defaultDestChainId =
|
|
62
|
+
originToken.chainId === base.id ? arbitrum.id : base.id
|
|
63
|
+
|
|
64
|
+
// Find USDC on destination chain first (preferred stable coin)
|
|
65
|
+
const usdcOnDestChain = supportedTokens.find(
|
|
66
|
+
(token: Token) =>
|
|
67
|
+
token.chainId === defaultDestChainId && token.symbol === "USDC",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
let destToken: Token | null = null
|
|
71
|
+
|
|
72
|
+
if (usdcOnDestChain) {
|
|
73
|
+
destToken = usdcOnDestChain
|
|
74
|
+
logger.console.log(
|
|
75
|
+
"[trails-sdk] Selected USDC as destination token on chain:",
|
|
76
|
+
defaultDestChainId,
|
|
77
|
+
)
|
|
78
|
+
} else {
|
|
79
|
+
// Fallback: Find matching token on destination chain
|
|
80
|
+
const destChainTokens = supportedTokens.filter(
|
|
81
|
+
(token: Token) => token.chainId === defaultDestChainId,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
// Try to find same symbol on destination chain first
|
|
85
|
+
const sameSymbolOnDestChain = destChainTokens.find((token: Token) => {
|
|
86
|
+
return token.symbol.toUpperCase() === originToken.symbol.toUpperCase()
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
if (sameSymbolOnDestChain) {
|
|
90
|
+
destToken = sameSymbolOnDestChain
|
|
91
|
+
logger.console.log(
|
|
92
|
+
"[trails-sdk] Selected same symbol token on destination chain:",
|
|
93
|
+
sameSymbolOnDestChain.symbol,
|
|
94
|
+
)
|
|
95
|
+
} else {
|
|
96
|
+
// Fallback: ETH on destination chain > first available token on destination chain
|
|
97
|
+
destToken =
|
|
98
|
+
destChainTokens.find((token: Token) => token.symbol === "ETH") ||
|
|
99
|
+
destChainTokens[0] ||
|
|
100
|
+
null
|
|
101
|
+
|
|
102
|
+
if (destToken) {
|
|
103
|
+
logger.console.log(
|
|
104
|
+
"[trails-sdk] Selected fallback token on destination chain:",
|
|
105
|
+
destToken.symbol,
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!destToken) {
|
|
112
|
+
logger.console.log(
|
|
113
|
+
"[trails-sdk] No suitable destination token found on chain:",
|
|
114
|
+
defaultDestChainId,
|
|
115
|
+
)
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const decimals = destToken.decimals
|
|
120
|
+
if (!decimals) {
|
|
121
|
+
logger.console.warn(
|
|
122
|
+
"[trails-sdk] Missing decimals for destination token, skipping:",
|
|
123
|
+
destToken,
|
|
124
|
+
)
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Try to find this token in user's balances to get the actual balance
|
|
129
|
+
const matchingUserToken = sortedTokens.find((t) => {
|
|
130
|
+
if (t.chainId !== destToken.chainId) return false
|
|
131
|
+
|
|
132
|
+
const isDestNative = destToken.isNativeToken ?? false
|
|
133
|
+
const isUserNative = t.isNativeToken ?? false
|
|
134
|
+
|
|
135
|
+
if (isDestNative && isUserNative) return true
|
|
136
|
+
if (isDestNative || isUserNative) return false
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
t.contractAddress?.toLowerCase() ===
|
|
140
|
+
destToken.contractAddress?.toLowerCase()
|
|
141
|
+
)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const finalDestinationToken = {
|
|
145
|
+
...destToken,
|
|
146
|
+
balance: matchingUserToken?.balance ?? "0",
|
|
147
|
+
balanceUsdFormatted: matchingUserToken?.balanceUsdFormatted ?? "0",
|
|
148
|
+
priceUsd: matchingUserToken?.priceUsd ?? 0,
|
|
149
|
+
decimals,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
logger.console.log("[trails-sdk] Default destination token selection:", {
|
|
153
|
+
destination: {
|
|
154
|
+
symbol: finalDestinationToken.symbol,
|
|
155
|
+
chainId: finalDestinationToken.chainId,
|
|
156
|
+
hasUserBalance: !!matchingUserToken,
|
|
157
|
+
userBalanceUsd: matchingUserToken?.balanceUsdFormatted ?? "0",
|
|
158
|
+
},
|
|
159
|
+
originChain: originToken.chainId,
|
|
160
|
+
destinationChain: defaultDestChainId,
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// Store the calculated result in ref for stability
|
|
164
|
+
stableDestTokenRef.current = finalDestinationToken
|
|
165
|
+
|
|
166
|
+
return finalDestinationToken
|
|
167
|
+
}, [originToken, supportedTokens, sortedTokens])
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
defaultDestinationToken,
|
|
171
|
+
isLoading,
|
|
172
|
+
}
|
|
173
|
+
}
|