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.
Files changed (94) hide show
  1. package/dist/aave.d.ts.map +1 -1
  2. package/dist/{ccip-ru_Yzdas.js → ccip-BKavX04a.js} +13 -13
  3. package/dist/constants.d.ts +2 -0
  4. package/dist/constants.d.ts.map +1 -1
  5. package/dist/fees.d.ts +11 -17
  6. package/dist/fees.d.ts.map +1 -1
  7. package/dist/gasless.d.ts.map +1 -1
  8. package/dist/{index-Si7cO9V7.js → index-D5kULpIU.js} +20430 -20133
  9. package/dist/index.js +425 -847
  10. package/dist/intents.d.ts +1 -2
  11. package/dist/intents.d.ts.map +1 -1
  12. package/dist/prepareSend.d.ts.map +1 -1
  13. package/dist/recover.d.ts +8 -9
  14. package/dist/recover.d.ts.map +1 -1
  15. package/dist/tokenBalances.d.ts +51 -0
  16. package/dist/tokenBalances.d.ts.map +1 -1
  17. package/dist/trailsRouter.d.ts +15 -0
  18. package/dist/trailsRouter.d.ts.map +1 -1
  19. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +1 -3
  20. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
  21. package/dist/transactionIntent/deposits/standardDeposit.d.ts +1 -3
  22. package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
  23. package/dist/transactionIntent/handlers/crossChain.d.ts +2 -4
  24. package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
  25. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +5 -4
  26. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
  27. package/dist/transactionIntent/quote/normalizeQuote.d.ts +1 -1
  28. package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
  29. package/dist/transactionIntent/quote/quoteHelpers.d.ts +1 -1
  30. package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
  31. package/dist/transactionIntent/types.d.ts +11 -18
  32. package/dist/transactionIntent/types.d.ts.map +1 -1
  33. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  34. package/dist/widget/components/ClassicSwap.d.ts +1 -0
  35. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  36. package/dist/widget/components/DynamicSizeInputField.d.ts.map +1 -1
  37. package/dist/widget/components/Fund.d.ts.map +1 -1
  38. package/dist/widget/components/FundSwap.d.ts +1 -0
  39. package/dist/widget/components/FundSwap.d.ts.map +1 -1
  40. package/dist/widget/components/Pay.d.ts.map +1 -1
  41. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  42. package/dist/widget/components/SlippageToleranceSettings.d.ts +2 -1
  43. package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
  44. package/dist/widget/components/Swap.d.ts +1 -0
  45. package/dist/widget/components/Swap.d.ts.map +1 -1
  46. package/dist/widget/components/WidgetProviders.d.ts.map +1 -1
  47. package/dist/widget/css/compiled.css +1 -1
  48. package/dist/widget/hooks/useDefaultDestinationToken.d.ts +20 -0
  49. package/dist/widget/hooks/useDefaultDestinationToken.d.ts.map +1 -0
  50. package/dist/widget/hooks/{useDefaultTokenSelection.d.ts → useDefaultOriginToken.d.ts} +4 -16
  51. package/dist/widget/hooks/useDefaultOriginToken.d.ts.map +1 -0
  52. package/dist/widget/hooks/useQuote.d.ts +94 -35
  53. package/dist/widget/hooks/useQuote.d.ts.map +1 -1
  54. package/dist/widget/hooks/useSendForm.d.ts +2 -2
  55. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  56. package/dist/widget/hooks/useTrailsSendTransaction.d.ts.map +1 -1
  57. package/dist/widget/index.js +1 -1
  58. package/dist/widget/widget.d.ts.map +1 -1
  59. package/package.json +2 -2
  60. package/src/aave.ts +4 -0
  61. package/src/constants.ts +4 -0
  62. package/src/fees.ts +47 -72
  63. package/src/gasless.ts +62 -32
  64. package/src/intents.ts +1 -3
  65. package/src/morpho.ts +1 -1
  66. package/src/prepareSend.ts +42 -6
  67. package/src/recover.ts +116 -172
  68. package/src/tokenBalances.ts +301 -1
  69. package/src/trailsRouter.ts +77 -0
  70. package/src/transactionIntent/deposits/depositOrchestrator.ts +0 -6
  71. package/src/transactionIntent/deposits/standardDeposit.ts +167 -184
  72. package/src/transactionIntent/handlers/crossChain.ts +8 -11
  73. package/src/transactionIntent/handlers/sameChainSameToken.ts +619 -608
  74. package/src/transactionIntent/quote/normalizeQuote.ts +32 -46
  75. package/src/transactionIntent/quote/quoteHelpers.ts +4 -2
  76. package/src/transactionIntent/types.ts +11 -18
  77. package/src/widget/compiled.css +1 -1
  78. package/src/widget/components/AccountIntentTransactionHistory.tsx +50 -18
  79. package/src/widget/components/ClassicSwap.tsx +158 -63
  80. package/src/widget/components/DynamicSizeInputField.tsx +2 -0
  81. package/src/widget/components/Fund.tsx +12 -11
  82. package/src/widget/components/FundSwap.tsx +1 -0
  83. package/src/widget/components/Pay.tsx +15 -14
  84. package/src/widget/components/QuoteDetails.tsx +18 -27
  85. package/src/widget/components/SlippageToleranceSettings.tsx +55 -25
  86. package/src/widget/components/Swap.tsx +1 -0
  87. package/src/widget/components/WidgetProviders.tsx +1 -6
  88. package/src/widget/hooks/useDefaultDestinationToken.tsx +173 -0
  89. package/src/widget/hooks/{useDefaultTokenSelection.tsx → useDefaultOriginToken.tsx} +58 -191
  90. package/src/widget/hooks/useQuote.ts +317 -79
  91. package/src/widget/hooks/useSendForm.ts +123 -764
  92. package/src/widget/hooks/useTrailsSendTransaction.ts +0 -2
  93. package/src/widget/widget.tsx +2 -0
  94. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +0 -1
package/src/gasless.ts CHANGED
@@ -147,51 +147,81 @@ export async function getPermitSignature({
147
147
  args: [signer],
148
148
  })
149
149
 
150
- const name = (await publicClient.readContract({
151
- address: tokenAddress as `0x${string}`,
152
- abi: [
153
- {
154
- name: "name",
155
- type: "function",
156
- stateMutability: "view",
157
- inputs: [],
158
- outputs: [{ name: "", type: "string" }],
159
- },
160
- ],
161
- functionName: "name",
162
- })) as string
150
+ // Fetch EIP-5267 eip712Domain() first - this returns the exact domain parameters used by the contract
151
+ let name: string
152
+ let version: string
163
153
 
164
- let version = "1" // fallback default
165
154
  try {
166
- version = (await publicClient.readContract({
155
+ const eip712Domain = (await publicClient.readContract({
167
156
  address: tokenAddress as `0x${string}`,
168
157
  abi: [
169
158
  {
170
- name: "version",
159
+ name: "eip712Domain",
171
160
  type: "function",
172
161
  stateMutability: "view",
173
162
  inputs: [],
174
- outputs: [{ name: "", type: "string" }],
163
+ outputs: [
164
+ { name: "fields", type: "bytes1" },
165
+ { name: "name", type: "string" },
166
+ { name: "version", type: "string" },
167
+ { name: "chainId", type: "uint256" },
168
+ { name: "verifyingContract", type: "address" },
169
+ { name: "salt", type: "bytes32" },
170
+ { name: "extensions", type: "uint256[]" },
171
+ ],
175
172
  },
176
173
  ],
177
- functionName: "version",
178
- })) as string
179
- logger.console.log("[trails-sdk] Token version from contract:", version)
180
- } catch (error) {
181
- logger.console.warn(
182
- "[trails-sdk] Token does not implement version(), defaulting to '1'",
183
- error,
184
- )
185
- }
174
+ functionName: "eip712Domain",
175
+ })) as [string, string, string, bigint, string, string, bigint[]]
186
176
 
187
- // Special handling for Katana chain (747474)
188
- // Katana USDC contract returns "0.5.0" from version() but uses "1" for EIP-712 domain
189
- // The contract's version() is the implementation version, not the EIP-712 domain version
190
- if (chain.id === 747474) {
177
+ name = eip712Domain[1]
178
+ version = eip712Domain[2]
179
+ logger.console.log(
180
+ "[trails-sdk] EIP-5267 eip712Domain() supported, using domain:",
181
+ { name, version },
182
+ )
183
+ } catch {
184
+ // EIP-5267 not supported, fall back to reading name() and version()
191
185
  logger.console.log(
192
- "[trails-sdk] Katana chain detected, overriding version to '1' for EIP-712 domain compatibility",
186
+ "[trails-sdk] EIP-5267 not supported, falling back to name()/version()",
193
187
  )
194
- version = "1"
188
+
189
+ name = (await publicClient.readContract({
190
+ address: tokenAddress as `0x${string}`,
191
+ abi: [
192
+ {
193
+ name: "name",
194
+ type: "function",
195
+ stateMutability: "view",
196
+ inputs: [],
197
+ outputs: [{ name: "", type: "string" }],
198
+ },
199
+ ],
200
+ functionName: "name",
201
+ })) as string
202
+
203
+ version = "1" // fallback default EIP-712 version
204
+ try {
205
+ version = (await publicClient.readContract({
206
+ address: tokenAddress as `0x${string}`,
207
+ abi: [
208
+ {
209
+ name: "version",
210
+ type: "function",
211
+ stateMutability: "view",
212
+ inputs: [],
213
+ outputs: [{ name: "", type: "string" }],
214
+ },
215
+ ],
216
+ functionName: "version",
217
+ })) as string
218
+ logger.console.log("[trails-sdk] Token version from contract:", version)
219
+ } catch (error) {
220
+ logger.console.warn(
221
+ "[trails-sdk] Token does not implement version(), defaulting to '1'",
222
+ error,
223
+ )
224
+ }
195
225
  }
196
226
 
197
227
  // Log domain parameters for debugging
package/src/intents.ts CHANGED
@@ -822,18 +822,16 @@ export function buildCrossChainDepositParams({
822
822
  originTokenAddress,
823
823
  originIntentAddress,
824
824
  depositAmount,
825
- fee,
826
825
  originChainId,
827
826
  chain,
828
827
  }: {
829
828
  originTokenAddress: string
830
829
  originIntentAddress: string
831
830
  depositAmount: string
832
- fee: string
833
831
  originChainId: number
834
832
  chain: Chain
835
833
  }) {
836
- const totalAmount = BigInt(depositAmount) + BigInt(fee)
834
+ const totalAmount = BigInt(depositAmount)
837
835
 
838
836
  return {
839
837
  to: isNativeToken(originTokenAddress)
package/src/morpho.ts CHANGED
@@ -63,7 +63,7 @@ async function fetchAllVaults(): Promise<any[]> {
63
63
  vaults(
64
64
  first: 1000,
65
65
  where: {
66
- chainId_in: [1, 8453, 42161, 137, 10, 100]
66
+ chainId_in: [1, 8453, 42161, 42170, 137, 10, 100, 81457, 56, 660279, 33139, 747474]
67
67
  },
68
68
  orderBy: TotalAssetsUsd,
69
69
  orderDirection: Desc
@@ -7,6 +7,8 @@ import { getTokenPrice } from "./prices.js"
7
7
  import {
8
8
  TRAILS_ROUTER_PLACEHOLDER_AMOUNT,
9
9
  wrapCalldataWithTrailsRouterIfNeeded,
10
+ calldataHasPlaceholder,
11
+ replacePlaceholderInCalldata,
10
12
  } from "./trailsRouter.js"
11
13
  import type { TransactionState } from "./transactions.js"
12
14
  import { logger } from "./logger.js"
@@ -62,9 +64,7 @@ export async function prepareSend(
62
64
  tradeType = TradeType.EXACT_OUTPUT,
63
65
  originTokenSymbol,
64
66
  destinationTokenSymbol,
65
- fee,
66
67
  client: walletClient,
67
- dryMode = false,
68
68
  trailsClient,
69
69
  destinationCalldata,
70
70
  onTransactionStateChange,
@@ -149,6 +149,44 @@ export async function prepareSend(
149
149
  const isToSameToken = isSameToken(originTokenAddress, destinationTokenAddress)
150
150
  const isSameChainSameToken = isToSameChain && isToSameToken
151
151
 
152
+ // For same-chain same-token with custom calldata:
153
+ // 1. Replace any placeholder in the calldata with the actual swapAmount
154
+ // 2. Use EXACT_OUTPUT trade type to ensure consistent amount handling
155
+ // This handles vault deposits where calldata may have been generated with a placeholder
156
+ const originalHadPlaceholder = calldataHasPlaceholder(destinationCalldata)
157
+ let placeholderWasReplaced = false
158
+
159
+ if (isSameChainSameToken && hasCustomCalldata && originalHadPlaceholder) {
160
+ const calldataBefore = effectiveDestinationCalldata
161
+ // Replace placeholder with actual amount in calldata
162
+ effectiveDestinationCalldata = replacePlaceholderInCalldata(
163
+ effectiveDestinationCalldata || "",
164
+ swapAmount,
165
+ )
166
+ // Verify the placeholder was actually replaced
167
+ placeholderWasReplaced = !calldataHasPlaceholder(
168
+ effectiveDestinationCalldata,
169
+ )
170
+
171
+ logger.console.log(
172
+ "[trails-sdk] Same-chain same-token: Replaced placeholder with actual amount in calldata",
173
+ {
174
+ swapAmount,
175
+ placeholderWasReplaced,
176
+ calldataBefore,
177
+ calldataAfter: effectiveDestinationCalldata,
178
+ },
179
+ )
180
+ }
181
+
182
+ logger.console.log("[trails-sdk] Trade type and calldata info:", {
183
+ tradeType,
184
+ isSameChainSameToken,
185
+ hasCustomCalldata,
186
+ originalHadPlaceholder,
187
+ placeholderWasReplaced,
188
+ })
189
+
152
190
  if (
153
191
  !hasCustomCalldata &&
154
192
  tradeType === TradeType.EXACT_INPUT &&
@@ -414,7 +452,6 @@ export async function prepareSend(
414
452
  originChainId,
415
453
  walletClient,
416
454
  onTransactionStateChange,
417
- dryMode,
418
455
  account,
419
456
  chain,
420
457
  transactionStates,
@@ -437,6 +474,7 @@ export async function prepareSend(
437
474
  isSmartWallet,
438
475
  trailsApiKey,
439
476
  trailsApiUrl,
477
+ tradeType: tradeType,
440
478
  })
441
479
  }
442
480
 
@@ -462,12 +500,10 @@ export async function prepareSend(
462
500
  publicClient,
463
501
  chain,
464
502
  account,
465
- fee,
466
- dryMode,
467
503
  onTransactionStateChange,
468
504
  transactionStates,
469
505
  slippageTolerance,
470
- tradeType,
506
+ tradeType: tradeType,
471
507
  originNativeTokenPriceUsd,
472
508
  swapProvider,
473
509
  bridgeProvider,
package/src/recover.ts CHANGED
@@ -17,13 +17,14 @@ import { splitSignature } from "./gasless.js"
17
17
  import { useMemo, useCallback } from "react"
18
18
  import { useGetIntent } from "./widget/hooks/useGetIntent.js"
19
19
  import {
20
- useTokenBalances,
20
+ useTokenBalancesForMultipleAccounts,
21
21
  getTokenBalanceUsd,
22
22
  type Price,
23
23
  } from "./tokenBalances.js"
24
24
  import { getERC20TransferData } from "./encoders.js"
25
25
  import { zeroAddress } from "viem"
26
26
  import { useTokenPrices } from "./prices.js"
27
+ import type { Token } from "./tokens.js"
27
28
  import type { Payload as WalletPayload } from "@0xsequence/wallet-primitives"
28
29
  import { attemptSwitchChain } from "./chainSwitch.js"
29
30
  import { decodeGuestModuleEvents } from "./decoders.js"
@@ -440,15 +441,17 @@ export async function buildRefundTransaction(
440
441
 
441
442
  /**
442
443
  * Determines the refund call based on token balances at the intent address
444
+ * Uses Token type which has symbol, name, decimals, chainId directly (no contractInfo)
443
445
  */
444
446
  export function determineRefundCall(
445
447
  tokenBalancesData:
446
448
  | {
447
- nativeBalances?: Array<{ balance?: string; chainId?: number }>
448
- balances?: Array<{
449
+ tokens?: Array<{
449
450
  balance?: string
450
451
  contractAddress?: string
451
- contractInfo?: { decimals?: number; chainId?: number }
452
+ decimals?: number
453
+ chainId?: number
454
+ isNativeToken?: boolean
452
455
  }>
453
456
  }
454
457
  | undefined,
@@ -467,34 +470,18 @@ export function determineRefundCall(
467
470
  chainId?: number
468
471
  } | null = null
469
472
 
470
- // Check native token balances
471
- if (tokenBalancesData?.nativeBalances) {
472
- for (const nativeBalance of tokenBalancesData.nativeBalances) {
473
- const balance = BigInt(nativeBalance.balance || "0")
473
+ // Check all tokens (both native and ERC20)
474
+ if (tokenBalancesData?.tokens) {
475
+ for (const token of tokenBalancesData.tokens) {
476
+ const balance = BigInt(token.balance || "0")
474
477
  if (balance > 0n) {
475
478
  if (!highestBalanceToken || balance > highestBalanceToken.balance) {
476
479
  highestBalanceToken = {
477
- type: "native",
480
+ type: token.isNativeToken ? "native" : "erc20",
478
481
  balance,
479
- chainId: nativeBalance.chainId,
480
- }
481
- }
482
- }
483
- }
484
- }
485
-
486
- // Check ERC20 token balances
487
- if (tokenBalancesData?.balances) {
488
- for (const tokenBalance of tokenBalancesData.balances) {
489
- const balance = BigInt(tokenBalance.balance || "0")
490
- if (balance > 0n) {
491
- if (!highestBalanceToken || balance > highestBalanceToken.balance) {
492
- highestBalanceToken = {
493
- type: "erc20",
494
- balance,
495
- tokenAddress: tokenBalance.contractAddress,
496
- decimals: tokenBalance.contractInfo?.decimals,
497
- chainId: tokenBalance.contractInfo?.chainId,
482
+ tokenAddress: token.contractAddress,
483
+ decimals: token.decimals,
484
+ chainId: token.chainId,
498
485
  }
499
486
  }
500
487
  }
@@ -581,6 +568,8 @@ export interface UseIntentRecoverReturn {
581
568
  intentError: Error | null
582
569
  balancesError: Error | null
583
570
  hasIntentBalance: boolean
571
+ /** Token that will be recovered (the one with highest balance) */
572
+ recoverToken: Token | null
584
573
  refetchIntent: () => void
585
574
  signPayload: () => Promise<{
586
575
  signature: string
@@ -655,49 +644,41 @@ export function useIntentRecover({
655
644
  return Address.from(intentData.destinationIntentAddress)
656
645
  }, [intentData?.destinationIntentAddress])
657
646
 
647
+ // Fetch balances for both origin and destination in a single API call
658
648
  const {
659
- tokenBalancesData: originTokenBalancesData,
660
- isLoadingBalances: isLoadingOriginBalances,
661
- balanceError: originBalanceError,
662
- } = useTokenBalances(originIntentAddressForBalances)
649
+ balancesByAccount,
650
+ isLoading: isLoadingBalances,
651
+ error: balanceError,
652
+ } = useTokenBalancesForMultipleAccounts([
653
+ originIntentAddressForBalances,
654
+ destinationIntentAddressForBalances,
655
+ ])
663
656
 
664
- const {
665
- tokenBalancesData: destinationTokenBalancesData,
666
- isLoadingBalances: isLoadingDestinationBalances,
667
- balanceError: destinationBalanceError,
668
- } = useTokenBalances(destinationIntentAddressForBalances)
657
+ // Extract individual account balances
658
+ const originTokenBalancesData = originIntentAddressForBalances
659
+ ? balancesByAccount[originIntentAddressForBalances.toLowerCase()]
660
+ ?.tokenBalancesData
661
+ : undefined
662
+ const destinationTokenBalancesData = destinationIntentAddressForBalances
663
+ ? balancesByAccount[destinationIntentAddressForBalances.toLowerCase()]
664
+ ?.tokenBalancesData
665
+ : undefined
666
+
667
+ // balancesError alias for backward compatibility in component return
668
+ const balancesError = balanceError
669
669
 
670
670
  // Get token prices for origin balances
671
+ // Note: Token type already has balance/price fields populated, but we fetch prices
672
+ // separately for more accurate/up-to-date pricing
671
673
  const originTokensForPricing = useMemo(() => {
672
- if (!originTokenBalancesData) return []
673
- const tokens: Array<{
674
- tokenSymbol: string
675
- tokenAddress: string
676
- chainId: number
677
- }> = []
678
- if (originTokenBalancesData.nativeBalances) {
679
- for (const nativeBalance of originTokenBalancesData.nativeBalances) {
680
- if (nativeBalance.chainId) {
681
- tokens.push({
682
- tokenSymbol: (nativeBalance as any).symbol || "ETH",
683
- tokenAddress: zeroAddress,
684
- chainId: nativeBalance.chainId,
685
- })
686
- }
687
- }
688
- }
689
- if (originTokenBalancesData.balances) {
690
- for (const tokenBalance of originTokenBalancesData.balances) {
691
- if (tokenBalance.contractInfo?.chainId) {
692
- tokens.push({
693
- tokenSymbol: tokenBalance.contractInfo?.symbol || "",
694
- tokenAddress: tokenBalance.contractAddress || zeroAddress,
695
- chainId: tokenBalance.contractInfo.chainId,
696
- })
697
- }
698
- }
699
- }
700
- return tokens
674
+ if (!originTokenBalancesData?.tokens) return []
675
+ return originTokenBalancesData.tokens
676
+ .filter((token) => token.chainId !== undefined)
677
+ .map((token) => ({
678
+ tokenSymbol: token.symbol,
679
+ tokenAddress: token.contractAddress || zeroAddress,
680
+ chainId: token.chainId!,
681
+ }))
701
682
  }, [originTokenBalancesData])
702
683
 
703
684
  const { tokenPrices: originTokenPrices } = useTokenPrices(
@@ -706,35 +687,14 @@ export function useIntentRecover({
706
687
 
707
688
  // Get token prices for destination balances
708
689
  const destinationTokensForPricing = useMemo(() => {
709
- if (!destinationTokenBalancesData) return []
710
- const tokens: Array<{
711
- tokenSymbol: string
712
- tokenAddress: string
713
- chainId: number
714
- }> = []
715
- if (destinationTokenBalancesData.nativeBalances) {
716
- for (const nativeBalance of destinationTokenBalancesData.nativeBalances) {
717
- if (nativeBalance.chainId) {
718
- tokens.push({
719
- tokenSymbol: (nativeBalance as any).symbol || "ETH",
720
- tokenAddress: zeroAddress,
721
- chainId: nativeBalance.chainId,
722
- })
723
- }
724
- }
725
- }
726
- if (destinationTokenBalancesData.balances) {
727
- for (const tokenBalance of destinationTokenBalancesData.balances) {
728
- if (tokenBalance.contractInfo?.chainId) {
729
- tokens.push({
730
- tokenSymbol: tokenBalance.contractInfo?.symbol || "",
731
- tokenAddress: tokenBalance.contractAddress || zeroAddress,
732
- chainId: tokenBalance.contractInfo.chainId,
733
- })
734
- }
735
- }
736
- }
737
- return tokens
690
+ if (!destinationTokenBalancesData?.tokens) return []
691
+ return destinationTokenBalancesData.tokens
692
+ .filter((token) => token.chainId !== undefined)
693
+ .map((token) => ({
694
+ tokenSymbol: token.symbol,
695
+ tokenAddress: token.contractAddress || zeroAddress,
696
+ chainId: token.chainId!,
697
+ }))
738
698
  }, [destinationTokenBalancesData])
739
699
 
740
700
  const { tokenPrices: destinationTokenPrices } = useTokenPrices(
@@ -746,19 +706,13 @@ export function useIntentRecover({
746
706
  (
747
707
  balancesData:
748
708
  | {
749
- nativeBalances?: Array<{
709
+ tokens?: Array<{
750
710
  balance?: string
751
711
  chainId?: number
752
- symbol?: string
753
- }>
754
- balances?: Array<{
755
- balance?: string
756
712
  contractAddress?: string
757
- contractInfo?: {
758
- decimals?: number
759
- chainId?: number
760
- symbol?: string
761
- }
713
+ decimals?: number
714
+ isNativeToken?: boolean
715
+ balanceUsd?: number
762
716
  }>
763
717
  }
764
718
  | undefined,
@@ -773,56 +727,44 @@ export function useIntentRecover({
773
727
 
774
728
  let totalUsd = 0
775
729
 
776
- // Sum native token USD values
777
- if (balancesData.nativeBalances) {
778
- for (const nativeBalance of balancesData.nativeBalances) {
779
- const priceData = tokenPrices.find(
780
- (p) =>
781
- p.token.tokenAddress === zeroAddress &&
782
- p.token.chainId === nativeBalance.chainId &&
783
- p.priceUsd !== undefined,
784
- )
785
- if (priceData?.priceUsd && nativeBalance.balance) {
786
- const price: Price = {
787
- value: priceData.priceUsd,
788
- currency: "USD",
789
- }
790
- const usdValue = getTokenBalanceUsd(
791
- {
792
- balance: nativeBalance.balance,
793
- chainId: nativeBalance.chainId,
794
- } as any,
795
- price,
796
- )
797
- totalUsd += usdValue
798
- }
799
- }
800
- }
730
+ // Sum all token USD values (both native and ERC20)
731
+ if (balancesData.tokens) {
732
+ for (const token of balancesData.tokens) {
733
+ const tokenAddress = token.isNativeToken
734
+ ? zeroAddress
735
+ : token.contractAddress || zeroAddress
801
736
 
802
- // Sum ERC20 token USD values
803
- if (balancesData.balances) {
804
- for (const tokenBalance of balancesData.balances) {
805
737
  const priceData = tokenPrices.find(
806
738
  (p) =>
807
739
  p.token.tokenAddress?.toLowerCase() ===
808
- (tokenBalance.contractAddress || zeroAddress).toLowerCase() &&
809
- p.token.chainId === tokenBalance.contractInfo?.chainId &&
740
+ tokenAddress.toLowerCase() &&
741
+ p.token.chainId === token.chainId &&
810
742
  p.priceUsd !== undefined,
811
743
  )
812
- if (priceData?.priceUsd && tokenBalance.balance) {
744
+
745
+ if (priceData?.priceUsd && token.balance) {
813
746
  const price: Price = {
814
747
  value: priceData.priceUsd,
815
748
  currency: "USD",
816
749
  }
817
- const usdValue = getTokenBalanceUsd(
818
- {
819
- balance: tokenBalance.balance,
820
- contractAddress: tokenBalance.contractAddress,
821
- contractInfo: tokenBalance.contractInfo,
822
- } as any,
823
- price,
824
- )
825
- totalUsd += usdValue
750
+
751
+ // Use balanceUsd if already calculated, otherwise calculate
752
+ if (token.balanceUsd !== undefined) {
753
+ totalUsd += token.balanceUsd
754
+ } else {
755
+ // Create a compatible object for getTokenBalanceUsd
756
+ const balanceObj = token.isNativeToken
757
+ ? { balance: token.balance, chainId: token.chainId }
758
+ : {
759
+ balance: token.balance,
760
+ contractInfo: {
761
+ decimals: token.decimals,
762
+ chainId: token.chainId,
763
+ },
764
+ }
765
+ const usdValue = getTokenBalanceUsd(balanceObj as any, price)
766
+ totalUsd += usdValue
767
+ }
826
768
  }
827
769
  }
828
770
  }
@@ -919,9 +861,7 @@ export function useIntentRecover({
919
861
  calculateTotalUsdBalance,
920
862
  ])
921
863
 
922
- const isLoadingBalances =
923
- isLoadingOriginBalances || isLoadingDestinationBalances
924
- const balancesError = originBalanceError || destinationBalanceError
864
+ // isLoadingBalances and balancesError are already defined above from useTokenBalancesForMultipleAccounts
925
865
 
926
866
  // Check if ANY intent address (origin or destination) has any balance (native or ERC20)
927
867
  // This ensures we show the recover button if there are balances in either address
@@ -930,34 +870,17 @@ export function useIntentRecover({
930
870
  const checkBalances = (
931
871
  balancesData:
932
872
  | {
933
- nativeBalances?: Array<{ balance?: string; chainId?: number }>
934
- balances?: Array<{
935
- balance?: string
936
- contractAddress?: string
937
- contractInfo?: { decimals?: number; chainId?: number }
938
- }>
873
+ tokens?: Array<{ balance?: string }>
939
874
  }
940
875
  | undefined,
941
876
  ): boolean => {
942
- if (!balancesData) return false
943
-
944
- // Check native token balances
945
- if (balancesData.nativeBalances) {
946
- for (const nativeBalance of balancesData.nativeBalances) {
947
- const balance = BigInt(nativeBalance.balance || "0")
948
- if (balance > 0n) {
949
- return true
950
- }
951
- }
952
- }
877
+ if (!balancesData?.tokens) return false
953
878
 
954
- // Check ERC20 token balances
955
- if (balancesData.balances) {
956
- for (const tokenBalance of balancesData.balances) {
957
- const balance = BigInt(tokenBalance.balance || "0")
958
- if (balance > 0n) {
959
- return true
960
- }
879
+ // Check all tokens (both native and ERC20)
880
+ for (const token of balancesData.tokens) {
881
+ const balance = BigInt(token.balance || "0")
882
+ if (balance > 0n) {
883
+ return true
961
884
  }
962
885
  }
963
886
 
@@ -971,6 +894,26 @@ export function useIntentRecover({
971
894
  return originHasBalance || destinationHasBalance
972
895
  }, [originTokenBalancesData, destinationTokenBalancesData])
973
896
 
897
+ // Get the token that will be recovered (highest balance from selected intent address)
898
+ // Get the token that will be recovered (highest balance from selected intent address)
899
+ const recoverToken = useMemo<Token | null>(() => {
900
+ if (!selectedIntentAddress?.tokenBalancesData?.tokens) return null
901
+
902
+ // Find the token with the highest balance
903
+ let highestBalanceToken: Token | null = null
904
+ let highestBalance = 0n
905
+
906
+ for (const token of selectedIntentAddress.tokenBalancesData.tokens) {
907
+ const balance = BigInt(token.balance || "0")
908
+ if (balance > 0n && balance > highestBalance) {
909
+ highestBalance = balance
910
+ highestBalanceToken = token
911
+ }
912
+ }
913
+
914
+ return highestBalanceToken
915
+ }, [selectedIntentAddress?.tokenBalancesData?.tokens])
916
+
974
917
  // Get refund address (use walletClient account or provided address)
975
918
  // Don't throw during render - validate when methods are called
976
919
  const effectiveRefundAddress = useMemo<`0x${string}` | null>(() => {
@@ -1411,6 +1354,7 @@ export function useIntentRecover({
1411
1354
  intentError: intentError as Error | null,
1412
1355
  balancesError: balancesError as Error | null,
1413
1356
  hasIntentBalance,
1357
+ recoverToken,
1414
1358
  refetchIntent,
1415
1359
  signPayload,
1416
1360
  getRecoverTx,