0xtrails 0.1.13 → 0.2.1
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/analytics.d.ts +12 -2
- package/dist/analytics.d.ts.map +1 -1
- package/dist/apiClient.d.ts +1 -1
- package/dist/apiClient.d.ts.map +1 -1
- package/dist/{ccip-D3gTQONK.js → ccip-BbfANth7.js} +12 -12
- package/dist/cctp.d.ts.map +1 -1
- package/dist/cctpqueue.d.ts +3 -3
- package/dist/cctpqueue.d.ts.map +1 -1
- package/dist/chains.d.ts.map +1 -1
- package/dist/config.d.ts +18 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/constants.d.ts +6 -5
- package/dist/constants.d.ts.map +1 -1
- package/dist/contractUtils.d.ts +2 -0
- package/dist/contractUtils.d.ts.map +1 -1
- package/dist/customChains.d.ts +24 -0
- package/dist/customChains.d.ts.map +1 -0
- package/dist/gasless.d.ts +19 -7
- package/dist/gasless.d.ts.map +1 -1
- package/dist/{index-CnUM7lKf.js → index-WpIVoh3X.js} +36741 -31761
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +405 -394
- package/dist/indexerClient.d.ts +10 -0
- package/dist/indexerClient.d.ts.map +1 -1
- package/dist/intentEntrypoint.d.ts +122 -0
- package/dist/intentEntrypoint.d.ts.map +1 -0
- package/dist/intents.d.ts +5 -3
- package/dist/intents.d.ts.map +1 -1
- package/dist/metaTxnMonitor.d.ts.map +1 -1
- package/dist/morpho.d.ts.map +1 -1
- package/dist/pools.d.ts +3 -1
- package/dist/pools.d.ts.map +1 -1
- package/dist/prepareSend.d.ts +18 -9
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/prices.d.ts +1 -1
- package/dist/prices.d.ts.map +1 -1
- package/dist/relaySdk.d.ts.map +1 -1
- package/dist/relayer.d.ts.map +1 -1
- package/dist/toast.d.ts +9 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/tokenBalances.d.ts +6 -2
- package/dist/tokenBalances.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/trails.d.ts +6 -5
- package/dist/trails.d.ts.map +1 -1
- package/dist/trailsClient.d.ts +12 -0
- package/dist/trailsClient.d.ts.map +1 -0
- package/dist/trailsRouter.d.ts +22 -0
- package/dist/trailsRouter.d.ts.map +1 -0
- package/dist/transactions.d.ts +8 -1
- package/dist/transactions.d.ts.map +1 -1
- package/dist/wallets.d.ts.map +1 -1
- package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
- package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/components/AccountSettings.d.ts +7 -0
- package/dist/widget/components/AccountSettings.d.ts.map +1 -0
- package/dist/widget/components/ChainList.d.ts +0 -1
- package/dist/widget/components/ChainList.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts +46 -0
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -0
- package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
- package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
- package/dist/widget/components/ConnectedWallets.d.ts +9 -0
- package/dist/widget/components/ConnectedWallets.d.ts.map +1 -0
- package/dist/widget/components/DebugMenu.d.ts.map +1 -1
- package/dist/widget/components/DebugScreensList.d.ts.map +1 -1
- package/dist/widget/components/DebugToast.d.ts +3 -0
- package/dist/widget/components/DebugToast.d.ts.map +1 -0
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/EarnPools.d.ts.map +1 -1
- package/dist/widget/components/FeeOption.d.ts +22 -0
- package/dist/widget/components/FeeOption.d.ts.map +1 -0
- package/dist/widget/components/FeeOptions.d.ts +13 -17
- package/dist/widget/components/FeeOptions.d.ts.map +1 -1
- package/dist/widget/components/Fund.d.ts +44 -0
- package/dist/widget/components/Fund.d.ts.map +1 -0
- package/dist/widget/components/FundMethods.d.ts +1 -1
- package/dist/widget/components/FundMethods.d.ts.map +1 -1
- package/dist/widget/components/FundSendForm.d.ts.map +1 -1
- package/dist/widget/components/Identicon.d.ts +9 -0
- package/dist/widget/components/Identicon.d.ts.map +1 -0
- package/dist/widget/components/MeshConnectExchanges.d.ts +5 -2
- package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
- package/dist/widget/components/MeshConnectFlow.d.ts +2 -0
- package/dist/widget/components/MeshConnectFlow.d.ts.map +1 -1
- package/dist/widget/components/NativeGasOption.d.ts +12 -0
- package/dist/widget/components/NativeGasOption.d.ts.map +1 -0
- package/dist/widget/components/Pay.d.ts +46 -0
- package/dist/widget/components/Pay.d.ts.map +1 -0
- package/dist/widget/components/PaySendForm.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receive.d.ts.map +1 -1
- package/dist/widget/components/RecentTokens.d.ts.map +1 -1
- package/dist/widget/components/Recipients.d.ts +9 -0
- package/dist/widget/components/Recipients.d.ts.map +1 -0
- package/dist/widget/components/RefundWarning.d.ts +9 -0
- package/dist/widget/components/RefundWarning.d.ts.map +1 -0
- package/dist/widget/components/SimpleSwap.d.ts.map +1 -1
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/SwapSettings.d.ts +1 -5
- package/dist/widget/components/SwapSettings.d.ts.map +1 -1
- package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
- package/dist/widget/components/ThemeSyncer.d.ts +6 -0
- package/dist/widget/components/ThemeSyncer.d.ts.map +1 -0
- package/dist/widget/components/Toast.d.ts +24 -0
- package/dist/widget/components/Toast.d.ts.map +1 -0
- package/dist/widget/components/TokenList.d.ts.map +1 -1
- package/dist/widget/components/TokenSelector.d.ts.map +1 -1
- package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
- package/dist/widget/components/TruncatedAddress.d.ts +2 -0
- package/dist/widget/components/TruncatedAddress.d.ts.map +1 -1
- package/dist/widget/components/UserPreferences.d.ts +7 -0
- package/dist/widget/components/UserPreferences.d.ts.map +1 -0
- package/dist/widget/hooks/useBack.d.ts +2 -0
- package/dist/widget/hooks/useBack.d.ts.map +1 -1
- package/dist/widget/hooks/useBalanceVisible.d.ts +1 -0
- package/dist/widget/hooks/useBalanceVisible.d.ts.map +1 -1
- 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/useDefaultTokenSelection.d.ts +54 -0
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -0
- package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/hooks/usePayMessage.d.ts +34 -0
- package/dist/widget/hooks/usePayMessage.d.ts.map +1 -0
- package/dist/widget/hooks/useRecipients.d.ts +17 -0
- package/dist/widget/hooks/useRecipients.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedFeeToken.d.ts +32 -0
- package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedMeshExchange.d.ts +14 -0
- package/dist/widget/hooks/useSelectedMeshExchange.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedRecipient.d.ts +12 -0
- package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -0
- package/dist/widget/hooks/useSendForm.d.ts +10 -13
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/hooks/useSwapAmount.d.ts +13 -0
- package/dist/widget/hooks/useSwapAmount.d.ts.map +1 -0
- package/dist/widget/hooks/useSwapSettings.d.ts +16 -0
- package/dist/widget/hooks/useSwapSettings.d.ts.map +1 -0
- package/dist/widget/hooks/useTargetAmount.d.ts +5 -0
- package/dist/widget/hooks/useTargetAmount.d.ts.map +1 -0
- package/dist/widget/hooks/useTheme.d.ts +14 -0
- package/dist/widget/hooks/useTheme.d.ts.map +1 -0
- package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
- package/dist/widget/index.js +2 -2
- package/dist/widget/widget.d.ts +9 -0
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +38 -36
- package/src/aave.ts +6 -1
- package/src/analytics.ts +109 -53
- package/src/apiClient.ts +1 -1
- package/src/cctp.ts +6 -2
- package/src/cctpqueue.ts +7 -7
- package/src/chains.ts +18 -0
- package/src/config.ts +63 -17
- package/src/constants.ts +20 -16
- package/src/contractUtils.ts +33 -2
- package/src/customChains.ts +24 -0
- package/src/gasless.ts +162 -109
- package/src/index.ts +11 -1
- package/src/indexerClient.ts +73 -1
- package/src/intentEntrypoint.ts +218 -0
- package/src/intents.ts +85 -54
- package/src/metaTxnMonitor.ts +1 -0
- package/src/morpho.ts +13 -2
- package/src/pools.ts +68 -86
- package/src/prepareSend.ts +1719 -967
- package/src/prices.ts +51 -7
- package/src/relaySdk.ts +6 -4
- package/src/relayer.ts +6 -3
- package/src/toast.ts +110 -0
- package/src/tokenBalances.ts +112 -20
- package/src/tokens.ts +70 -7
- package/src/trails.ts +81 -80
- package/src/trailsClient.ts +48 -0
- package/src/{proxyCaller.ts → trailsRouter.ts} +25 -20
- package/src/transactions.ts +30 -88
- package/src/umd.tsx +1 -1
- package/src/wallets.ts +2 -1
- package/src/widget/assets/sequence-logo.svg +15 -0
- package/src/widget/compiled.css +2 -2
- package/src/widget/components/AccountActionsDropdown.tsx +18 -159
- package/src/widget/components/AccountIntentTransactionHistory.tsx +346 -63
- package/src/widget/components/AccountSettings.tsx +102 -0
- package/src/widget/components/ChainFilterDropdown.tsx +1 -1
- package/src/widget/components/ChainList.tsx +10 -20
- package/src/widget/components/ClassicSwap.tsx +921 -0
- package/src/widget/components/ConfigDisplay.tsx +41 -5
- package/src/widget/components/ConnectWallet.tsx +168 -11
- package/src/widget/components/ConnectedWallets.tsx +342 -0
- package/src/widget/components/DebugMenu.tsx +2 -0
- package/src/widget/components/DebugScreensList.tsx +3 -0
- package/src/widget/components/DebugToast.tsx +63 -0
- package/src/widget/components/Earn.tsx +112 -143
- package/src/widget/components/EarnPools.tsx +2 -4
- package/src/widget/components/EarnPoolsFilters.tsx +6 -6
- package/src/widget/components/FeeOption.tsx +78 -0
- package/src/widget/components/FeeOptions.tsx +192 -127
- package/src/widget/components/Fund.tsx +1236 -0
- package/src/widget/components/FundMethods.tsx +4 -4
- package/src/widget/components/FundSendForm.tsx +1 -34
- package/src/widget/components/Identicon.tsx +158 -0
- package/src/widget/components/MeshConnectExchanges.tsx +32 -3
- package/src/widget/components/MeshConnectFlow.tsx +23 -4
- package/src/widget/components/NativeGasOption.tsx +99 -0
- package/src/widget/components/Pay.tsx +1092 -0
- package/src/widget/components/PaySendForm.tsx +1 -38
- package/src/widget/components/QuoteDetails.tsx +1 -30
- package/src/widget/components/Receipt.tsx +1 -1
- package/src/widget/components/Receive.tsx +4 -2
- package/src/widget/components/RecentTokens.tsx +2 -1
- package/src/widget/components/Recipients.tsx +448 -0
- package/src/widget/components/RefundWarning.tsx +61 -0
- package/src/widget/components/ScreenHeader.tsx +1 -1
- package/src/widget/components/SimpleSwap.tsx +74 -58
- package/src/widget/components/Swap.tsx +35 -853
- package/src/widget/components/SwapSettings.tsx +5 -11
- package/src/widget/components/ThemeProvider.tsx +32 -0
- package/src/widget/components/ThemeSyncer.tsx +47 -0
- package/src/widget/components/Toast.tsx +315 -0
- package/src/widget/components/TokenList.tsx +2 -34
- package/src/widget/components/TokenSelector.tsx +14 -3
- package/src/widget/components/TransactionDetails.tsx +153 -13
- package/src/widget/components/TransferPendingVertical.tsx +1 -1
- package/src/widget/components/TruncatedAddress.tsx +5 -1
- package/src/widget/components/UserPreferences.tsx +155 -0
- package/src/widget/components/WalletList.tsx +1 -1
- package/src/widget/hooks/useBack.tsx +4 -0
- package/src/widget/hooks/useBalanceVisible.tsx +40 -2
- package/src/widget/hooks/useCheckout.ts +13 -0
- package/src/widget/hooks/useCurrentScreen.tsx +4 -0
- package/src/widget/hooks/useDebugScreens.ts +12 -2
- package/src/widget/hooks/useDefaultTokenSelection.tsx +471 -0
- package/src/widget/hooks/useIntentTransactionHistory.ts +212 -0
- package/src/widget/hooks/usePayMessage.tsx +370 -0
- package/src/widget/hooks/useRecipients.ts +168 -0
- package/src/widget/hooks/useSelectedFeeToken.tsx +299 -0
- package/src/widget/hooks/useSelectedMeshExchange.tsx +46 -0
- package/src/widget/hooks/useSelectedRecipient.tsx +48 -0
- package/src/widget/hooks/useSendForm.ts +257 -49
- package/src/widget/hooks/useSwapAmount.tsx +50 -0
- package/src/widget/hooks/useSwapSettings.tsx +100 -0
- package/src/widget/hooks/useTargetAmount.ts +23 -0
- package/src/widget/hooks/useTheme.tsx +80 -0
- package/src/widget/hooks/useTokenList.ts +20 -11
- package/src/widget/index.css +45 -21
- package/src/widget/widget.tsx +294 -136
- package/dist/address.d.ts +0 -2
- package/dist/address.d.ts.map +0 -1
- package/dist/proxyCaller.d.ts +0 -21
- package/dist/proxyCaller.d.ts.map +0 -1
- package/src/address.ts +0 -6
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo } from "react"
|
|
2
|
+
import type { ReactNode } from "react"
|
|
3
|
+
import { useAccount } from "wagmi"
|
|
4
|
+
import type { Address } from "ox"
|
|
5
|
+
import { base, arbitrum } from "viem/chains"
|
|
6
|
+
import { zeroAddress } from "viem"
|
|
7
|
+
import { useWidgetProps } from "./useWidgetProps.js"
|
|
8
|
+
import { useTargetAmount } from "./useTargetAmount.js"
|
|
9
|
+
import { useTokenBalances } from "../../tokenBalances.js"
|
|
10
|
+
import type { TokenBalanceExtended } from "../../tokenBalances.js"
|
|
11
|
+
import { useIndexerGatewayClient } from "../../indexerClient.js"
|
|
12
|
+
import { useSupportedTokens } from "../../tokens.js"
|
|
13
|
+
import { getChainInfo } from "../../chains.js"
|
|
14
|
+
import { logger } from "../../logger.js"
|
|
15
|
+
|
|
16
|
+
const MINIMUM_24H_VOLUME_USD = 1_000_000 // $1M
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Hook for intelligent default token selection in 0xtrails widget.
|
|
20
|
+
*
|
|
21
|
+
* When toToken and toChainId are specified (user wants specific destination):
|
|
22
|
+
* 1. Same-chain, same-token with balance >= targetAmountUsd (no swap, no bridge) - FASTEST
|
|
23
|
+
* 2. Same-chain, different-token with balance >= targetAmountUsd (swap only) - FAST
|
|
24
|
+
* - Ordered by: 24h volume > $1M first, then highest balance USD
|
|
25
|
+
* 3. Different-chain, same-token with balance >= targetAmountUsd (bridge only) - SLOWER
|
|
26
|
+
* - Ordered by: 24h volume > $1M first, then highest balance USD
|
|
27
|
+
* 4. Different-chain, different-token with balance >= targetAmountUsd (swap + bridge) - SLOWEST
|
|
28
|
+
* - Ordered by: 24h volume > $1M first, then highest balance USD
|
|
29
|
+
* 5. Fallback: Any token with balance > 0 if none can cover target amount
|
|
30
|
+
*
|
|
31
|
+
* When toToken/toChainId NOT specified (general send/bridge):
|
|
32
|
+
* - Select highest balance token with 24h volume > $1M
|
|
33
|
+
* - Fallback to any token with balance > 0
|
|
34
|
+
*
|
|
35
|
+
* Destination Token Logic (only when toToken/toChainId not provided):
|
|
36
|
+
* - Matches origin token symbol on different chain when possible
|
|
37
|
+
* - Default destination chain: Base (unless origin is Base, then Arbitrum)
|
|
38
|
+
* - Fallback priority: USDC > ETH > first available token
|
|
39
|
+
*
|
|
40
|
+
* @returns defaultOriginToken - Best origin token that can cover targetAmountUsd (if specified)
|
|
41
|
+
* @returns defaultDestinationToken - Matching token on different chain (null if toToken/toChainId set)
|
|
42
|
+
* @returns isLoading - Whether token data is still loading
|
|
43
|
+
*/
|
|
44
|
+
export interface DefaultToken {
|
|
45
|
+
id: number
|
|
46
|
+
name: string
|
|
47
|
+
symbol: string
|
|
48
|
+
balance: string
|
|
49
|
+
imageUrl: string
|
|
50
|
+
chainId: number
|
|
51
|
+
contractAddress: string
|
|
52
|
+
balanceUsdFormatted: string
|
|
53
|
+
tokenPriceUsd: number
|
|
54
|
+
decimals: number // Top-level decimals for compatibility
|
|
55
|
+
contractInfo?: {
|
|
56
|
+
decimals: number
|
|
57
|
+
symbol: string
|
|
58
|
+
name: string
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface UseDefaultTokenSelectionReturn {
|
|
63
|
+
defaultOriginToken: DefaultToken | null
|
|
64
|
+
defaultDestinationToken: DefaultToken | null
|
|
65
|
+
isLoading: boolean
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getToken24hVolume(token: TokenBalanceExtended): number {
|
|
69
|
+
const price = token.price as any
|
|
70
|
+
if (!price) return 0
|
|
71
|
+
|
|
72
|
+
return Number(
|
|
73
|
+
price.price24hVol?.value ||
|
|
74
|
+
price.volume24h ||
|
|
75
|
+
price.volume_24h ||
|
|
76
|
+
price.vol24h ||
|
|
77
|
+
price.dailyVolume ||
|
|
78
|
+
0,
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function tokenMatches(
|
|
83
|
+
token: TokenBalanceExtended,
|
|
84
|
+
targetSymbolOrAddress: string,
|
|
85
|
+
targetChainId?: number,
|
|
86
|
+
): boolean {
|
|
87
|
+
const isNative =
|
|
88
|
+
!("contractAddress" in token) || token.contractAddress === zeroAddress
|
|
89
|
+
|
|
90
|
+
// Check chain match first
|
|
91
|
+
const chainMatches = !targetChainId || token.chainId === targetChainId
|
|
92
|
+
if (!chainMatches) return false
|
|
93
|
+
|
|
94
|
+
// Check if target is an address (starts with 0x and is longer than 5 chars)
|
|
95
|
+
const isTargetAddress =
|
|
96
|
+
targetSymbolOrAddress.startsWith("0x") && targetSymbolOrAddress.length > 5
|
|
97
|
+
|
|
98
|
+
if (isNative) {
|
|
99
|
+
const chainInfo = getChainInfo(token.chainId)
|
|
100
|
+
const nativeSymbol = chainInfo?.nativeCurrency.symbol || "ETH"
|
|
101
|
+
|
|
102
|
+
// For native tokens, check if target is zero address or matches symbol
|
|
103
|
+
if (isTargetAddress) {
|
|
104
|
+
return targetSymbolOrAddress.toLowerCase() === zeroAddress.toLowerCase()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return nativeSymbol.toUpperCase() === targetSymbolOrAddress.toUpperCase()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// For non-native tokens, check both address and symbol
|
|
111
|
+
if (isTargetAddress) {
|
|
112
|
+
return (
|
|
113
|
+
token.contractAddress.toLowerCase() ===
|
|
114
|
+
targetSymbolOrAddress.toLowerCase()
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
token.contractInfo?.symbol.toUpperCase() ===
|
|
120
|
+
targetSymbolOrAddress.toUpperCase()
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function useDefaultTokenSelectionInternal(): UseDefaultTokenSelectionReturn {
|
|
125
|
+
const { toToken, toChainId } = useWidgetProps()
|
|
126
|
+
const { targetAmountUsd } = useTargetAmount()
|
|
127
|
+
const { address } = useAccount()
|
|
128
|
+
const indexerGatewayClient = useIndexerGatewayClient()
|
|
129
|
+
|
|
130
|
+
const { sortedTokens, isLoadingSortedTokens } = useTokenBalances(
|
|
131
|
+
address as Address.Address,
|
|
132
|
+
indexerGatewayClient,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
const { supportedTokens, isLoadingTokens: isLoadingSupportedTokens } =
|
|
136
|
+
useSupportedTokens()
|
|
137
|
+
|
|
138
|
+
const isLoading = isLoadingSortedTokens || isLoadingSupportedTokens
|
|
139
|
+
|
|
140
|
+
const { defaultOriginToken, defaultDestinationToken } = useMemo(() => {
|
|
141
|
+
if (!sortedTokens?.length || !supportedTokens?.length) {
|
|
142
|
+
return {
|
|
143
|
+
defaultOriginToken: null,
|
|
144
|
+
defaultDestinationToken: null,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Determine if we should compute destination token
|
|
149
|
+
// Only compute destination defaults if toToken and toChainId are NOT set
|
|
150
|
+
const shouldComputeDestination = !toToken && !toChainId
|
|
151
|
+
|
|
152
|
+
// Helper to convert TokenBalanceExtended to DefaultToken
|
|
153
|
+
const toDefaultToken = (
|
|
154
|
+
token: TokenBalanceExtended,
|
|
155
|
+
): DefaultToken | null => {
|
|
156
|
+
const isNative =
|
|
157
|
+
!("contractAddress" in token) || token.contractAddress === zeroAddress
|
|
158
|
+
const chainInfo = getChainInfo(token.chainId)
|
|
159
|
+
|
|
160
|
+
if (isNative) {
|
|
161
|
+
// Native tokens - use token properties as-is
|
|
162
|
+
const tokenWithInfo = token as any
|
|
163
|
+
const imageUrl = tokenWithInfo.logoURI || tokenWithInfo.imageUrl || ""
|
|
164
|
+
const decimals = 18
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
id: token.chainId,
|
|
168
|
+
name: chainInfo?.nativeCurrency.name || "Native Token",
|
|
169
|
+
symbol: chainInfo?.nativeCurrency.symbol || "ETH",
|
|
170
|
+
balance: token.balance,
|
|
171
|
+
imageUrl,
|
|
172
|
+
chainId: token.chainId,
|
|
173
|
+
contractAddress: zeroAddress,
|
|
174
|
+
balanceUsdFormatted: token.balanceUsdFormatted ?? "0",
|
|
175
|
+
tokenPriceUsd: token.price?.value ?? 0,
|
|
176
|
+
decimals, // Top-level decimals
|
|
177
|
+
contractInfo: {
|
|
178
|
+
decimals,
|
|
179
|
+
symbol: chainInfo?.nativeCurrency.symbol || "ETH",
|
|
180
|
+
name: chainInfo?.nativeCurrency.name || "Native Token",
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
// Non-native tokens - use contractInfo properties
|
|
185
|
+
const tokenWithInfo = token as any
|
|
186
|
+
const imageUrl =
|
|
187
|
+
tokenWithInfo.contractInfo?.logoURI || tokenWithInfo.imageUrl || ""
|
|
188
|
+
const decimals = token.contractInfo?.decimals
|
|
189
|
+
if (!decimals) {
|
|
190
|
+
throw new Error("Decimals not found")
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
id: token.chainId,
|
|
195
|
+
name: token.contractInfo?.name || "Unknown Token",
|
|
196
|
+
symbol: token.contractInfo?.symbol || "???",
|
|
197
|
+
balance: token.balance,
|
|
198
|
+
imageUrl,
|
|
199
|
+
chainId: token.chainId,
|
|
200
|
+
contractAddress: token.contractAddress,
|
|
201
|
+
balanceUsdFormatted: token.balanceUsdFormatted ?? "0",
|
|
202
|
+
tokenPriceUsd: token.price?.value ?? 0,
|
|
203
|
+
decimals, // Top-level decimals
|
|
204
|
+
contractInfo: {
|
|
205
|
+
decimals,
|
|
206
|
+
symbol: token.contractInfo?.symbol ?? "???",
|
|
207
|
+
name: token.contractInfo?.name ?? "Unknown Token",
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Filter tokens with balance > 0
|
|
214
|
+
const tokensWithBalance = sortedTokens.filter((token) => {
|
|
215
|
+
const balanceUsd = token.balanceUsd ?? 0
|
|
216
|
+
return balanceUsd > 0
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
if (tokensWithBalance.length === 0) {
|
|
220
|
+
logger.console.log(
|
|
221
|
+
"[trails-sdk] No tokens with balance found for default selection",
|
|
222
|
+
)
|
|
223
|
+
return {
|
|
224
|
+
defaultOriginToken: null,
|
|
225
|
+
defaultDestinationToken: null,
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Helper to sort tokens by balance USD and volume
|
|
230
|
+
const sortTokensByBalanceAndVolume = (tokens: TokenBalanceExtended[]) => {
|
|
231
|
+
return [...tokens].sort((a, b) => {
|
|
232
|
+
const aVolume = getToken24hVolume(a)
|
|
233
|
+
const bVolume = getToken24hVolume(b)
|
|
234
|
+
const aHighVolume = aVolume >= MINIMUM_24H_VOLUME_USD
|
|
235
|
+
const bHighVolume = bVolume >= MINIMUM_24H_VOLUME_USD
|
|
236
|
+
|
|
237
|
+
// Prioritize high volume tokens first
|
|
238
|
+
if (aHighVolume && !bHighVolume) return -1
|
|
239
|
+
if (!aHighVolume && bHighVolume) return 1
|
|
240
|
+
|
|
241
|
+
// Then sort by balance USD (highest first)
|
|
242
|
+
const aBalanceUsd = a.balanceUsd ?? 0
|
|
243
|
+
const bBalanceUsd = b.balanceUsd ?? 0
|
|
244
|
+
return bBalanceUsd - aBalanceUsd
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Find the best origin token using intelligent selection
|
|
249
|
+
let bestOriginToken: TokenBalanceExtended | null = null
|
|
250
|
+
|
|
251
|
+
// When toToken and toChainId are specified (user wants specific destination)
|
|
252
|
+
if (toToken && toChainId) {
|
|
253
|
+
const targetChainId = Number(toChainId)
|
|
254
|
+
|
|
255
|
+
// Use targetAmountUsd if available, otherwise use a minimal threshold when toToken is set
|
|
256
|
+
const effectiveTargetAmount = targetAmountUsd ?? 0.01
|
|
257
|
+
|
|
258
|
+
// Filter tokens that can cover the target amount
|
|
259
|
+
const tokensWithSufficientBalance = tokensWithBalance.filter(
|
|
260
|
+
(token) => (token.balanceUsd ?? 0) >= effectiveTargetAmount,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
// Priority 1: Same-chain, same-token (no swap, no bridge) - FASTEST
|
|
264
|
+
const sameChainSameToken = tokensWithSufficientBalance.find((token) =>
|
|
265
|
+
tokenMatches(token, toToken, targetChainId),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if (sameChainSameToken) {
|
|
269
|
+
bestOriginToken = sameChainSameToken
|
|
270
|
+
logger.console.log(
|
|
271
|
+
"[trails-sdk] Selected same-chain, same-token (fastest)",
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Priority 2: Same-chain, different-token (swap only, no bridge) - FAST
|
|
276
|
+
if (!bestOriginToken) {
|
|
277
|
+
const sameChainTokens = tokensWithSufficientBalance.filter(
|
|
278
|
+
(token) => token.chainId === targetChainId,
|
|
279
|
+
)
|
|
280
|
+
const sortedSameChain = sortTokensByBalanceAndVolume(sameChainTokens)
|
|
281
|
+
bestOriginToken = sortedSameChain[0] ?? null
|
|
282
|
+
if (bestOriginToken) {
|
|
283
|
+
logger.console.log(
|
|
284
|
+
"[trails-sdk] Selected same-chain, different-token (fast)",
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Priority 3: Different-chain, same-token (bridge only, no swap) - SLOWER
|
|
290
|
+
if (!bestOriginToken) {
|
|
291
|
+
const sameTokenDiffChain = tokensWithSufficientBalance.filter(
|
|
292
|
+
(token) =>
|
|
293
|
+
tokenMatches(token, toToken) && token.chainId !== targetChainId,
|
|
294
|
+
)
|
|
295
|
+
const sortedSameToken = sortTokensByBalanceAndVolume(sameTokenDiffChain)
|
|
296
|
+
bestOriginToken = sortedSameToken[0] ?? null
|
|
297
|
+
if (bestOriginToken) {
|
|
298
|
+
logger.console.log(
|
|
299
|
+
"[trails-sdk] Selected different-chain, same-token (slower)",
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Priority 4: Different-chain, different-token (swap + bridge) - SLOWEST
|
|
305
|
+
if (!bestOriginToken) {
|
|
306
|
+
const diffChainDiffToken = tokensWithSufficientBalance.filter(
|
|
307
|
+
(token) => token.chainId !== targetChainId,
|
|
308
|
+
)
|
|
309
|
+
const sortedDiffChainDiffToken =
|
|
310
|
+
sortTokensByBalanceAndVolume(diffChainDiffToken)
|
|
311
|
+
bestOriginToken = sortedDiffChainDiffToken[0] ?? null
|
|
312
|
+
if (bestOriginToken) {
|
|
313
|
+
logger.console.log(
|
|
314
|
+
"[trails-sdk] Selected different-chain, different-token (slowest)",
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Fallback: If no token can cover target amount, use any token (sorted by balance/volume)
|
|
320
|
+
if (!bestOriginToken) {
|
|
321
|
+
const allTokensSorted = sortTokensByBalanceAndVolume(tokensWithBalance)
|
|
322
|
+
bestOriginToken = allTokensSorted[0] ?? null
|
|
323
|
+
logger.console.log(
|
|
324
|
+
"[trails-sdk] Fallback: No token can cover target amount, using highest balance token",
|
|
325
|
+
)
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
// When no specific destination is set, select highest value token with good liquidity
|
|
329
|
+
const sortedByBalanceAndVolume =
|
|
330
|
+
sortTokensByBalanceAndVolume(tokensWithBalance)
|
|
331
|
+
bestOriginToken = sortedByBalanceAndVolume[0] ?? null
|
|
332
|
+
logger.console.log(
|
|
333
|
+
"[trails-sdk] No destination specified, selected highest value token with best liquidity",
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!bestOriginToken) {
|
|
338
|
+
return {
|
|
339
|
+
defaultOriginToken: null,
|
|
340
|
+
defaultDestinationToken: null,
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const originToken = toDefaultToken(bestOriginToken)
|
|
345
|
+
|
|
346
|
+
if (!originToken) {
|
|
347
|
+
return {
|
|
348
|
+
defaultOriginToken: null,
|
|
349
|
+
defaultDestinationToken: null,
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Compute destination token only when toToken and toChainId are not provided
|
|
354
|
+
let destinationToken: DefaultToken | null = null
|
|
355
|
+
|
|
356
|
+
if (shouldComputeDestination) {
|
|
357
|
+
// Determine default destination chain: Base if origin is not Base, Arbitrum if origin is Base
|
|
358
|
+
const defaultDestChainId =
|
|
359
|
+
originToken.chainId === base.id ? arbitrum.id : base.id
|
|
360
|
+
|
|
361
|
+
// Find matching token on destination chain (prefer same symbol)
|
|
362
|
+
const destChainTokens = supportedTokens.filter(
|
|
363
|
+
(token: any) => token.chainId === defaultDestChainId,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
let destToken: any = null
|
|
367
|
+
|
|
368
|
+
// Try to find same symbol on dest chain
|
|
369
|
+
const sameSymbolToken = destChainTokens.find((token: any) => {
|
|
370
|
+
return token.symbol.toUpperCase() === originToken.symbol.toUpperCase()
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
if (sameSymbolToken) {
|
|
374
|
+
destToken = sameSymbolToken
|
|
375
|
+
} else {
|
|
376
|
+
// Fallback: USDC > ETH > first available token
|
|
377
|
+
destToken =
|
|
378
|
+
destChainTokens.find((token: any) => token.symbol === "USDC") ||
|
|
379
|
+
destChainTokens.find((token: any) => token.symbol === "ETH") ||
|
|
380
|
+
destChainTokens[0]
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const decimals = destToken?.decimals
|
|
384
|
+
if (!decimals) {
|
|
385
|
+
throw new Error("Decimals not found")
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
destinationToken = destToken
|
|
389
|
+
? {
|
|
390
|
+
id: destToken.chainId,
|
|
391
|
+
name: destToken.name,
|
|
392
|
+
symbol: destToken.symbol,
|
|
393
|
+
balance: "0", // Destination token doesn't have balance yet
|
|
394
|
+
imageUrl: destToken.imageUrl || "",
|
|
395
|
+
chainId: destToken.chainId,
|
|
396
|
+
contractAddress: destToken.contractAddress || zeroAddress,
|
|
397
|
+
balanceUsdFormatted: "0",
|
|
398
|
+
tokenPriceUsd: 0,
|
|
399
|
+
decimals, // Top-level decimals
|
|
400
|
+
contractInfo: {
|
|
401
|
+
decimals,
|
|
402
|
+
symbol: destToken.symbol,
|
|
403
|
+
name: destToken.name,
|
|
404
|
+
},
|
|
405
|
+
}
|
|
406
|
+
: null
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
logger.console.log("[trails-sdk] Default token selection:", {
|
|
410
|
+
origin: originToken
|
|
411
|
+
? {
|
|
412
|
+
symbol: originToken.symbol,
|
|
413
|
+
chainId: originToken.chainId,
|
|
414
|
+
balanceUsd: originToken.balanceUsdFormatted,
|
|
415
|
+
volume24h: getToken24hVolume(bestOriginToken),
|
|
416
|
+
}
|
|
417
|
+
: null,
|
|
418
|
+
destination: destinationToken
|
|
419
|
+
? {
|
|
420
|
+
symbol: destinationToken.symbol,
|
|
421
|
+
chainId: destinationToken.chainId,
|
|
422
|
+
}
|
|
423
|
+
: null,
|
|
424
|
+
shouldComputeDestination,
|
|
425
|
+
toToken,
|
|
426
|
+
toChainId,
|
|
427
|
+
targetAmountUsd,
|
|
428
|
+
effectiveTargetAmount:
|
|
429
|
+
toToken && toChainId ? (targetAmountUsd ?? 0.01) : null,
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
defaultOriginToken: originToken,
|
|
434
|
+
defaultDestinationToken: destinationToken,
|
|
435
|
+
}
|
|
436
|
+
}, [toToken, toChainId, sortedTokens, supportedTokens, targetAmountUsd])
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
defaultOriginToken,
|
|
440
|
+
defaultDestinationToken,
|
|
441
|
+
isLoading,
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Context for sharing default token selection across components
|
|
446
|
+
const DefaultTokenSelectionContext =
|
|
447
|
+
createContext<UseDefaultTokenSelectionReturn | null>(null)
|
|
448
|
+
|
|
449
|
+
export function DefaultTokenSelectionProvider({
|
|
450
|
+
children,
|
|
451
|
+
}: {
|
|
452
|
+
children: ReactNode
|
|
453
|
+
}) {
|
|
454
|
+
const value = useDefaultTokenSelectionInternal()
|
|
455
|
+
|
|
456
|
+
return (
|
|
457
|
+
<DefaultTokenSelectionContext.Provider value={value}>
|
|
458
|
+
{children}
|
|
459
|
+
</DefaultTokenSelectionContext.Provider>
|
|
460
|
+
)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export function useDefaultTokenSelection(): UseDefaultTokenSelectionReturn {
|
|
464
|
+
const context = useContext(DefaultTokenSelectionContext)
|
|
465
|
+
if (!context) {
|
|
466
|
+
throw new Error(
|
|
467
|
+
"useDefaultTokenSelection must be used within a DefaultTokenSelectionProvider",
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
return context
|
|
471
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from "react"
|
|
2
2
|
import {
|
|
3
3
|
getIntentTransactionHistory,
|
|
4
|
+
getAccountTransactionHistory,
|
|
4
5
|
type IntentTransaction,
|
|
5
6
|
type IntentTransactionHistoryResponse,
|
|
6
7
|
} from "../../transactions.js"
|
|
7
8
|
import { getTokenInfo, getSupportedTokens } from "../../tokens.js"
|
|
9
|
+
import { getExplorerUrl } from "../../explorer.js"
|
|
8
10
|
import { logger } from "../../logger.js"
|
|
9
11
|
|
|
10
12
|
export type UseIntentTransactionHistoryParams = {
|
|
@@ -241,10 +243,220 @@ export function useIntentTransactionHistory({
|
|
|
241
243
|
}
|
|
242
244
|
}
|
|
243
245
|
|
|
246
|
+
// Check if origin and destination are the same
|
|
247
|
+
const isSameIntentAddress =
|
|
248
|
+
transaction.originIntentAddress?.toLowerCase() ===
|
|
249
|
+
transaction.destinationIntentAddress?.toLowerCase() &&
|
|
250
|
+
transaction.originChainId === transaction.destinationChainId
|
|
251
|
+
|
|
252
|
+
// Fetch intent wallet transaction hashes
|
|
253
|
+
let originIntentTxHash: string | undefined
|
|
254
|
+
let destinationIntentTxHash: string | undefined
|
|
255
|
+
let originIntentTxExplorerUrl: string | undefined
|
|
256
|
+
let destinationIntentTxExplorerUrl: string | undefined
|
|
257
|
+
let originIntentDepositTxHash: string | undefined
|
|
258
|
+
let destinationIntentDepositTxHash: string | undefined
|
|
259
|
+
let originIntentDepositTxExplorerUrl: string | undefined
|
|
260
|
+
let destinationIntentDepositTxExplorerUrl: string | undefined
|
|
261
|
+
|
|
262
|
+
// Get origin intent transaction hashes (both deposit and action)
|
|
263
|
+
if (transaction.originIntentAddress && transaction.originChainId) {
|
|
264
|
+
try {
|
|
265
|
+
const originHistory = await getAccountTransactionHistory({
|
|
266
|
+
chainId: transaction.originChainId,
|
|
267
|
+
accountAddress: transaction.originIntentAddress,
|
|
268
|
+
pageSize: 10, // Fetch more to find both deposit and action txs
|
|
269
|
+
page: 0,
|
|
270
|
+
includeMetadata: true, // Need metadata to check transfer direction
|
|
271
|
+
})
|
|
272
|
+
if (
|
|
273
|
+
originHistory?.transactions &&
|
|
274
|
+
originHistory.transactions.length > 0
|
|
275
|
+
) {
|
|
276
|
+
const intentAddress =
|
|
277
|
+
transaction.originIntentAddress.toLowerCase()
|
|
278
|
+
|
|
279
|
+
// Find first deposit tx (transfer TO intent address)
|
|
280
|
+
const depositTx = originHistory.transactions.find((tx) =>
|
|
281
|
+
tx.transfers?.some(
|
|
282
|
+
(transfer) => transfer.to.toLowerCase() === intentAddress,
|
|
283
|
+
),
|
|
284
|
+
)
|
|
285
|
+
if (depositTx) {
|
|
286
|
+
originIntentDepositTxHash = depositTx.txnHash
|
|
287
|
+
originIntentDepositTxExplorerUrl = getExplorerUrl({
|
|
288
|
+
txHash: depositTx.txnHash,
|
|
289
|
+
chainId: transaction.originChainId,
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Find first action tx (transfer FROM intent address)
|
|
294
|
+
const actionTx = originHistory.transactions.find((tx) =>
|
|
295
|
+
tx.transfers?.some(
|
|
296
|
+
(transfer) =>
|
|
297
|
+
transfer.from.toLowerCase() === intentAddress,
|
|
298
|
+
),
|
|
299
|
+
)
|
|
300
|
+
if (actionTx) {
|
|
301
|
+
originIntentTxHash = actionTx.txnHash
|
|
302
|
+
originIntentTxExplorerUrl = getExplorerUrl({
|
|
303
|
+
txHash: actionTx.txnHash,
|
|
304
|
+
chainId: transaction.originChainId,
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} catch (error) {
|
|
309
|
+
logger.console.warn(
|
|
310
|
+
"[trails-sdk] Failed to get origin intent transaction hashes for:",
|
|
311
|
+
transaction.originIntentAddress,
|
|
312
|
+
"on chain",
|
|
313
|
+
transaction.originChainId,
|
|
314
|
+
error,
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Get destination intent transaction hashes (only if different from origin)
|
|
320
|
+
if (
|
|
321
|
+
!isSameIntentAddress &&
|
|
322
|
+
transaction.destinationIntentAddress &&
|
|
323
|
+
transaction.destinationChainId
|
|
324
|
+
) {
|
|
325
|
+
try {
|
|
326
|
+
const destinationHistory = await getAccountTransactionHistory({
|
|
327
|
+
chainId: transaction.destinationChainId,
|
|
328
|
+
accountAddress: transaction.destinationIntentAddress,
|
|
329
|
+
pageSize: 10, // Fetch more to find both deposit and action txs
|
|
330
|
+
page: 0,
|
|
331
|
+
includeMetadata: true, // Need metadata to check transfer direction
|
|
332
|
+
})
|
|
333
|
+
if (
|
|
334
|
+
destinationHistory?.transactions &&
|
|
335
|
+
destinationHistory.transactions.length > 0
|
|
336
|
+
) {
|
|
337
|
+
const intentAddress =
|
|
338
|
+
transaction.destinationIntentAddress.toLowerCase()
|
|
339
|
+
|
|
340
|
+
// Find first deposit tx (transfer TO intent address)
|
|
341
|
+
const depositTx = destinationHistory.transactions.find((tx) =>
|
|
342
|
+
tx.transfers?.some(
|
|
343
|
+
(transfer) => transfer.to.toLowerCase() === intentAddress,
|
|
344
|
+
),
|
|
345
|
+
)
|
|
346
|
+
if (depositTx) {
|
|
347
|
+
destinationIntentDepositTxHash = depositTx.txnHash
|
|
348
|
+
destinationIntentDepositTxExplorerUrl = getExplorerUrl({
|
|
349
|
+
txHash: depositTx.txnHash,
|
|
350
|
+
chainId: transaction.destinationChainId,
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Find first action tx (transfer FROM intent address)
|
|
355
|
+
const actionTx = destinationHistory.transactions.find((tx) =>
|
|
356
|
+
tx.transfers?.some(
|
|
357
|
+
(transfer) =>
|
|
358
|
+
transfer.from.toLowerCase() === intentAddress,
|
|
359
|
+
),
|
|
360
|
+
)
|
|
361
|
+
if (actionTx) {
|
|
362
|
+
destinationIntentTxHash = actionTx.txnHash
|
|
363
|
+
destinationIntentTxExplorerUrl = getExplorerUrl({
|
|
364
|
+
txHash: actionTx.txnHash,
|
|
365
|
+
chainId: transaction.destinationChainId,
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} catch (error) {
|
|
370
|
+
logger.console.warn(
|
|
371
|
+
"[trails-sdk] Failed to get destination intent transaction hashes for:",
|
|
372
|
+
transaction.destinationIntentAddress,
|
|
373
|
+
"on chain",
|
|
374
|
+
transaction.destinationChainId,
|
|
375
|
+
error,
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Calculate effective execution status
|
|
381
|
+
const getEffectiveStatus = (): string => {
|
|
382
|
+
const {
|
|
383
|
+
executionStatus,
|
|
384
|
+
originChainId,
|
|
385
|
+
destinationChainId,
|
|
386
|
+
originIntentAddress,
|
|
387
|
+
destinationIntentAddress,
|
|
388
|
+
createdAt,
|
|
389
|
+
} = transaction
|
|
390
|
+
|
|
391
|
+
// Check if this is a same-chain transaction (same address and chain)
|
|
392
|
+
const isSameChain =
|
|
393
|
+
originIntentAddress?.toLowerCase() ===
|
|
394
|
+
destinationIntentAddress?.toLowerCase() &&
|
|
395
|
+
originChainId === destinationChainId
|
|
396
|
+
|
|
397
|
+
// Check if there's no destination (same-chain or no destination intent)
|
|
398
|
+
const hasNoDestination =
|
|
399
|
+
!destinationIntentAddress || !destinationChainId || isSameChain
|
|
400
|
+
|
|
401
|
+
// Check if it's been over 5 minutes since creation
|
|
402
|
+
const isOver5Minutes = createdAt
|
|
403
|
+
? Date.now() - new Date(createdAt).getTime() > 5 * 60 * 1000
|
|
404
|
+
: false
|
|
405
|
+
|
|
406
|
+
// Rule 1: If there's a destination intent tx, it's completed
|
|
407
|
+
if (destinationIntentTxHash) {
|
|
408
|
+
return "completed"
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Rule 2: Only origin tx hash exists and no real destination (same-chain case)
|
|
412
|
+
if (originIntentTxHash && originChainId && hasNoDestination) {
|
|
413
|
+
return "completed"
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Rule 3: If there's a deposit but no action yet, status is pending
|
|
417
|
+
if (originIntentDepositTxHash && !originIntentTxHash) {
|
|
418
|
+
return "pending"
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Rule 4: Cross-chain with origin tx but no destination tx
|
|
422
|
+
// If over 5 minutes, consider it failed; otherwise pending
|
|
423
|
+
if (
|
|
424
|
+
originIntentTxHash &&
|
|
425
|
+
!destinationIntentTxHash &&
|
|
426
|
+
originChainId &&
|
|
427
|
+
destinationChainId &&
|
|
428
|
+
!isSameChain &&
|
|
429
|
+
destinationIntentAddress
|
|
430
|
+
) {
|
|
431
|
+
return isOver5Minutes ? "failed" : "pending"
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Otherwise, use the original execution status
|
|
435
|
+
return executionStatus || "unknown"
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const effectiveExecutionStatus = getEffectiveStatus()
|
|
439
|
+
|
|
440
|
+
// If same intent address, omit destination address/chain to avoid duplication
|
|
244
441
|
return {
|
|
245
442
|
...transaction,
|
|
443
|
+
...(isSameIntentAddress
|
|
444
|
+
? {
|
|
445
|
+
destinationIntentAddress: "",
|
|
446
|
+
destinationChainId: undefined,
|
|
447
|
+
}
|
|
448
|
+
: {}),
|
|
246
449
|
originToken,
|
|
247
450
|
destinationToken,
|
|
451
|
+
originIntentTxHash,
|
|
452
|
+
destinationIntentTxHash,
|
|
453
|
+
originIntentTxExplorerUrl,
|
|
454
|
+
destinationIntentTxExplorerUrl,
|
|
455
|
+
originIntentDepositTxHash,
|
|
456
|
+
destinationIntentDepositTxHash,
|
|
457
|
+
originIntentDepositTxExplorerUrl,
|
|
458
|
+
destinationIntentDepositTxExplorerUrl,
|
|
459
|
+
executionStatus: effectiveExecutionStatus,
|
|
248
460
|
}
|
|
249
461
|
}),
|
|
250
462
|
)
|