0xtrails 0.8.2 → 0.8.3

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 (68) hide show
  1. package/dist/aave.d.ts.map +1 -1
  2. package/dist/{ccip-ru_Yzdas.js → ccip-Bs-QcZXm.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/{index-Si7cO9V7.js → index-C_EsqqSn.js} +20320 -20063
  8. package/dist/index.js +425 -847
  9. package/dist/intents.d.ts +1 -2
  10. package/dist/intents.d.ts.map +1 -1
  11. package/dist/prepareSend.d.ts.map +1 -1
  12. package/dist/recover.d.ts +8 -9
  13. package/dist/recover.d.ts.map +1 -1
  14. package/dist/tokenBalances.d.ts +51 -0
  15. package/dist/tokenBalances.d.ts.map +1 -1
  16. package/dist/trailsRouter.d.ts +15 -0
  17. package/dist/trailsRouter.d.ts.map +1 -1
  18. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +1 -3
  19. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
  20. package/dist/transactionIntent/deposits/standardDeposit.d.ts +1 -3
  21. package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
  22. package/dist/transactionIntent/handlers/crossChain.d.ts +2 -4
  23. package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
  24. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +5 -4
  25. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
  26. package/dist/transactionIntent/quote/normalizeQuote.d.ts +1 -1
  27. package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
  28. package/dist/transactionIntent/quote/quoteHelpers.d.ts +1 -1
  29. package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
  30. package/dist/transactionIntent/types.d.ts +11 -18
  31. package/dist/transactionIntent/types.d.ts.map +1 -1
  32. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  33. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  34. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  35. package/dist/widget/components/SlippageToleranceSettings.d.ts +2 -1
  36. package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
  37. package/dist/widget/css/compiled.css +1 -1
  38. package/dist/widget/hooks/useQuote.d.ts +94 -35
  39. package/dist/widget/hooks/useQuote.d.ts.map +1 -1
  40. package/dist/widget/hooks/useSendForm.d.ts +2 -2
  41. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  42. package/dist/widget/hooks/useTrailsSendTransaction.d.ts.map +1 -1
  43. package/dist/widget/index.js +1 -1
  44. package/package.json +2 -2
  45. package/src/aave.ts +4 -0
  46. package/src/constants.ts +4 -0
  47. package/src/fees.ts +47 -72
  48. package/src/intents.ts +1 -3
  49. package/src/morpho.ts +1 -1
  50. package/src/prepareSend.ts +42 -6
  51. package/src/recover.ts +116 -172
  52. package/src/tokenBalances.ts +301 -1
  53. package/src/trailsRouter.ts +77 -0
  54. package/src/transactionIntent/deposits/depositOrchestrator.ts +0 -6
  55. package/src/transactionIntent/deposits/standardDeposit.ts +167 -184
  56. package/src/transactionIntent/handlers/crossChain.ts +8 -11
  57. package/src/transactionIntent/handlers/sameChainSameToken.ts +619 -608
  58. package/src/transactionIntent/quote/normalizeQuote.ts +32 -46
  59. package/src/transactionIntent/quote/quoteHelpers.ts +4 -2
  60. package/src/transactionIntent/types.ts +11 -18
  61. package/src/widget/compiled.css +1 -1
  62. package/src/widget/components/AccountIntentTransactionHistory.tsx +50 -18
  63. package/src/widget/components/ClassicSwap.tsx +25 -30
  64. package/src/widget/components/QuoteDetails.tsx +18 -27
  65. package/src/widget/components/SlippageToleranceSettings.tsx +55 -25
  66. package/src/widget/hooks/useQuote.ts +317 -79
  67. package/src/widget/hooks/useSendForm.ts +123 -764
  68. package/src/widget/hooks/useTrailsSendTransaction.ts +0 -2
@@ -437,6 +437,125 @@ export function useTokenBalances(address: Address.Address | null): {
437
437
  }
438
438
  }
439
439
 
440
+ export type TokenBalancesForAccount = {
441
+ tokenBalancesData: FlatTokenBalancesData | undefined
442
+ isLoading: boolean
443
+ error: Error | null
444
+ }
445
+
446
+ export type UseTokenBalancesForMultipleAccountsReturn = {
447
+ /** Per-account token balances data, keyed by lowercase address */
448
+ balancesByAccount: { [account: string]: TokenBalancesForAccount }
449
+ /** Combined loading state - true if any account is loading */
450
+ isLoading: boolean
451
+ /** Combined error - first error encountered */
452
+ error: Error | null
453
+ }
454
+
455
+ /**
456
+ * Hook to fetch token balances for multiple accounts in a single API call
457
+ * This is more efficient than calling useTokenBalances multiple times
458
+ */
459
+ export function useTokenBalancesForMultipleAccounts(
460
+ addresses: (Address.Address | null)[],
461
+ ): UseTokenBalancesForMultipleAccountsReturn {
462
+ const indexerClient = useIndexerGatewayClient()
463
+
464
+ // Filter out null addresses and normalize to lowercase
465
+ const validAddresses = addresses
466
+ .filter((addr): addr is Address.Address => addr !== null && addr.length > 0)
467
+ .map((addr) => addr.toLowerCase())
468
+
469
+ // Create stable query key from sorted addresses
470
+ const sortedAddresses = [...validAddresses].sort()
471
+ const addressesKey = sortedAddresses.join(",")
472
+
473
+ const {
474
+ data: multiAccountData,
475
+ isLoading,
476
+ error,
477
+ } = useQuery<GetTokenBalancesForMultipleAccountsReturn>({
478
+ queryKey: ["getTokenBalancesWithPrices", "multipleAccounts", addressesKey],
479
+ queryFn: async () => {
480
+ if (!validAddresses.length) {
481
+ return {}
482
+ }
483
+ try {
484
+ // Data is already flat per-account from getTokenBalancesForMultipleAccounts
485
+ // Each account has { balances: [tokenResults], nativeBalances: [nativeResults] }
486
+ return await getTokenBalancesForMultipleAccounts({
487
+ accounts: validAddresses,
488
+ indexerGatewayClient: indexerClient,
489
+ })
490
+ } catch (err) {
491
+ const errorMessage = getFullErrorMessage(err)
492
+
493
+ const isCorsError =
494
+ errorMessage.includes("Cross-Origin") ||
495
+ errorMessage.includes("CORS") ||
496
+ errorMessage.includes("Same Origin Policy")
497
+ const isNetworkError =
498
+ errorMessage.includes("fetch failed") ||
499
+ errorMessage.includes("network")
500
+
501
+ if (isCorsError || isNetworkError) {
502
+ logger.console.warn(
503
+ "[trails-sdk] Network or CORS error fetching token balances for multiple accounts:",
504
+ { error: errorMessage, addresses: validAddresses },
505
+ )
506
+ // Return empty balances for all accounts
507
+ const emptyResult: GetTokenBalancesForMultipleAccountsReturn = {}
508
+ for (const addr of validAddresses) {
509
+ emptyResult[addr] = {
510
+ tokens: [],
511
+ nativeTokens: [],
512
+ erc20Tokens: [],
513
+ page: defaultPage,
514
+ }
515
+ }
516
+ return emptyResult
517
+ }
518
+
519
+ logger.console.error(
520
+ "[trails-sdk] Failed to fetch token balances for multiple accounts:",
521
+ err,
522
+ )
523
+ throw err
524
+ }
525
+ },
526
+ enabled: validAddresses.length > 0 && !!indexerClient,
527
+ staleTime: 10000,
528
+ gcTime: 10000,
529
+ retry: (failureCount, err) => {
530
+ if (err && "status" in err && err.status === 404) return false
531
+ if (failureCount < 3) return true
532
+ return false
533
+ },
534
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
535
+ refetchOnWindowFocus: true,
536
+ refetchOnReconnect: true,
537
+ refetchInterval: REFRESH_INTERVAL,
538
+ refetchIntervalInBackground: true,
539
+ refetchOnMount: true,
540
+ })
541
+
542
+ // Build the per-account result structure
543
+ const balancesByAccount: { [account: string]: TokenBalancesForAccount } = {}
544
+ for (const addr of validAddresses) {
545
+ balancesByAccount[addr] = {
546
+ tokenBalancesData: multiAccountData?.[addr],
547
+ isLoading,
548
+ error,
549
+ }
550
+ }
551
+
552
+ return {
553
+ balancesByAccount,
554
+ isLoading,
555
+ error,
556
+ }
557
+ }
558
+
440
559
  // Helper to format balance
441
560
  export function formatRawAmount(
442
561
  balance: string | bigint,
@@ -801,7 +920,163 @@ export type GetTokenBalancesParams = {
801
920
  indexerGatewayClient: SequenceIndexerGateway
802
921
  }
803
922
 
804
- // Separate fetch function for token balances summary
923
+ export type GetTokenBalancesForMultipleAccountsParams = {
924
+ accounts: string[]
925
+ indexerGatewayClient: SequenceIndexerGateway
926
+ }
927
+
928
+ /**
929
+ * Flat balance data for a single account using Token type (converted from raw gateway data)
930
+ */
931
+ export type FlatTokenBalancesData = {
932
+ /** All tokens (native + ERC20) converted to Token type */
933
+ tokens: Token[]
934
+ /** Native tokens only (converted to Token type) */
935
+ nativeTokens: Token[]
936
+ /** ERC20 tokens only (converted to Token type) */
937
+ erc20Tokens: Token[]
938
+ page: Page
939
+ }
940
+
941
+ export type GetTokenBalancesForMultipleAccountsReturn = {
942
+ [account: string]: FlatTokenBalancesData
943
+ }
944
+
945
+ /**
946
+ * Fetch token balances summary for multiple accounts in a single API call
947
+ */
948
+ export async function fetchGetTokenBalancesSummaryForMultipleAccounts({
949
+ accounts,
950
+ indexerGatewayClient,
951
+ }: GetTokenBalancesForMultipleAccountsParams): Promise<GetTokenBalancesForMultipleAccountsReturn> {
952
+ if (!accounts.length || !indexerGatewayClient) {
953
+ throw new Error(
954
+ "At least one account address and indexer client are required",
955
+ )
956
+ }
957
+
958
+ // Filter out empty/null accounts
959
+ const validAccounts = accounts.filter((a) => a && a.length > 0)
960
+ if (!validAccounts.length) {
961
+ return {}
962
+ }
963
+
964
+ try {
965
+ const summaryFromGateway =
966
+ await indexerGatewayClient.getTokenBalancesSummary({
967
+ filter: {
968
+ accountAddresses: validAccounts,
969
+ contractStatus: ContractVerificationStatus.VERIFIED,
970
+ contractTypes: ["ERC20"],
971
+ omitNativeBalances: false,
972
+ },
973
+ })
974
+
975
+ // The gateway returns balances grouped by account
976
+ // Each entry in balances/nativeBalances arrays corresponds to an account
977
+ const result: GetTokenBalancesForMultipleAccountsReturn = {}
978
+
979
+ // Initialize result with empty arrays for each account
980
+ for (const account of validAccounts) {
981
+ result[account.toLowerCase()] = {
982
+ tokens: [],
983
+ nativeTokens: [],
984
+ erc20Tokens: [],
985
+ page: defaultPage,
986
+ }
987
+ }
988
+
989
+ // Map balances to their respective accounts and convert to Token type
990
+ // Gateway returns data grouped by chainId: { chainId, results: [...] }
991
+ // Each result has accountAddress, so we need to iterate through results
992
+ const gatewayBalances =
993
+ summaryFromGateway.balances as unknown as GatewayTokenBalance[]
994
+ const gatewayNativeBalances =
995
+ summaryFromGateway.nativeBalances as unknown as GatewayNativeTokenBalances[]
996
+
997
+ // For ERC20 balances: iterate through chain entries, then through results
998
+ for (const chainEntry of gatewayBalances) {
999
+ const results = (chainEntry as any).results || []
1000
+ for (const tokenResult of results) {
1001
+ const accountAddr = tokenResult.accountAddress?.toLowerCase()
1002
+ if (accountAddr && result[accountAddr]) {
1003
+ // Convert to Token type and add to arrays
1004
+ const token = convertTokenBalanceToToken(tokenResult as TokenBalance)
1005
+ result[accountAddr].erc20Tokens.push(token)
1006
+ result[accountAddr].tokens.push(token)
1007
+ }
1008
+ }
1009
+ }
1010
+
1011
+ // For native balances: iterate through chain entries, then through results
1012
+ for (const chainEntry of gatewayNativeBalances) {
1013
+ const results = (chainEntry as any).results || []
1014
+ for (const nativeResult of results) {
1015
+ const accountAddr = nativeResult.accountAddress?.toLowerCase()
1016
+ if (accountAddr && result[accountAddr]) {
1017
+ // Convert to Token type and add to arrays
1018
+ const token = convertNativeTokenBalanceToToken(
1019
+ nativeResult as NativeTokenBalance,
1020
+ )
1021
+ result[accountAddr].nativeTokens.push(token)
1022
+ result[accountAddr].tokens.push(token)
1023
+ }
1024
+ }
1025
+ }
1026
+
1027
+ // Set page info from gateway response
1028
+ for (const account of validAccounts) {
1029
+ const accountEntry = result[account.toLowerCase()]
1030
+ if (accountEntry) {
1031
+ accountEntry.page = summaryFromGateway.page ?? defaultPage
1032
+ }
1033
+ }
1034
+
1035
+ return result
1036
+ } catch (error) {
1037
+ const errorMessage = getFullErrorMessage(error)
1038
+
1039
+ // Check if this is a CORS error or network error
1040
+ const isCorsError =
1041
+ errorMessage.includes("Cross-Origin") ||
1042
+ errorMessage.includes("CORS") ||
1043
+ errorMessage.includes("Same Origin Policy")
1044
+ const isNetworkError =
1045
+ errorMessage.includes("fetch failed") || errorMessage.includes("network")
1046
+
1047
+ if (isCorsError || isNetworkError) {
1048
+ logger.console.warn(
1049
+ "[trails-sdk] Network or CORS error fetching token balances, returning empty result:",
1050
+ {
1051
+ error: errorMessage,
1052
+ accounts,
1053
+ },
1054
+ )
1055
+ // Return empty balances for all accounts
1056
+ const result: GetTokenBalancesForMultipleAccountsReturn = {}
1057
+ for (const account of validAccounts) {
1058
+ result[account.toLowerCase()] = {
1059
+ tokens: [],
1060
+ nativeTokens: [],
1061
+ erc20Tokens: [],
1062
+ page: defaultPage,
1063
+ }
1064
+ }
1065
+ return result
1066
+ }
1067
+
1068
+ logger.console.error(
1069
+ "[trails-sdk] Failed to fetch token balances summary for multiple accounts:",
1070
+ error,
1071
+ )
1072
+ throw error
1073
+ }
1074
+ }
1075
+
1076
+ /**
1077
+ * Fetch token balances summary for a single account
1078
+ * Returns chain-grouped data: { balances: [{ chainId, results: [...] }] }
1079
+ */
805
1080
  export async function fetchGetTokenBalancesSummary({
806
1081
  account,
807
1082
  indexerGatewayClient,
@@ -870,6 +1145,31 @@ export async function getTokenBalances({
870
1145
  })
871
1146
  }
872
1147
 
1148
+ /**
1149
+ * Get token balances for multiple accounts in a single query
1150
+ */
1151
+ export async function getTokenBalancesForMultipleAccounts({
1152
+ accounts,
1153
+ indexerGatewayClient,
1154
+ }: GetTokenBalancesForMultipleAccountsParams): Promise<GetTokenBalancesForMultipleAccountsReturn> {
1155
+ // Sort accounts to ensure consistent cache key
1156
+ const sortedAccounts = [...accounts].sort()
1157
+ return tokenBalancesQueryClient.fetchQuery({
1158
+ queryKey: [
1159
+ "fetchGetTokenBalancesSummary",
1160
+ "multipleAccounts",
1161
+ sortedAccounts.join(","),
1162
+ ],
1163
+ queryFn: () =>
1164
+ fetchGetTokenBalancesSummaryForMultipleAccounts({
1165
+ accounts,
1166
+ indexerGatewayClient,
1167
+ }),
1168
+ staleTime: REFRESH_INTERVAL,
1169
+ gcTime: REFRESH_INTERVAL,
1170
+ })
1171
+ }
1172
+
873
1173
  // Cache invalidation utility function
874
1174
  export function invalidateTokenBalancesCache(account?: string) {
875
1175
  // Invalidate all token balance queries that match the pattern
@@ -13,6 +13,83 @@ function getAmountOffset(calldata: `0x${string}`, placeholder: bigint): number {
13
13
  return byteOffset
14
14
  }
15
15
 
16
+ /**
17
+ * Check if calldata contains the placeholder amount.
18
+ * If no placeholder is found, the calldata has a real amount baked in.
19
+ * @param calldata - The calldata to check
20
+ * @returns true if calldata contains the placeholder, false otherwise
21
+ */
22
+ export function calldataHasPlaceholder(calldata?: string): boolean {
23
+ if (!calldata || calldata === "0x") return false
24
+ return (
25
+ getAmountOffset(
26
+ calldata as `0x${string}`,
27
+ TRAILS_ROUTER_PLACEHOLDER_AMOUNT,
28
+ ) !== -1
29
+ )
30
+ }
31
+
32
+ /**
33
+ * Replace the placeholder amount in calldata with the actual amount.
34
+ * Used for same-chain same-token vault deposits where the calldata was generated with a placeholder.
35
+ * @param calldata - The calldata containing the placeholder
36
+ * @param amount - The actual amount to inject (as string in wei)
37
+ * @returns The calldata with the placeholder replaced, or original calldata if no placeholder found
38
+ */
39
+ export function replacePlaceholderInCalldata(
40
+ calldata: string,
41
+ amount: string,
42
+ ): string {
43
+ if (!calldata || calldata === "0x") return calldata
44
+
45
+ // Validate amount
46
+ if (!amount || amount === "") {
47
+ console.warn(
48
+ "replacePlaceholderInCalldata: Amount is empty, returning original calldata",
49
+ )
50
+ return calldata
51
+ }
52
+
53
+ // Check if amount is a valid number/bigint
54
+ let amountBigInt: bigint
55
+ try {
56
+ amountBigInt = BigInt(amount)
57
+ } catch (error) {
58
+ console.warn(
59
+ `replacePlaceholderInCalldata: Invalid amount "${amount}", returning original calldata`,
60
+ error,
61
+ )
62
+ return calldata
63
+ }
64
+
65
+ // Check for negative values
66
+ if (amountBigInt < 0n) {
67
+ console.warn(
68
+ `replacePlaceholderInCalldata: Amount cannot be negative (${amount}), returning original calldata`,
69
+ )
70
+ return calldata
71
+ }
72
+
73
+ // Optional: check if amount exceeds uint256 max
74
+ const MAX_UINT256 = BigInt(
75
+ "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
76
+ )
77
+ if (amountBigInt > MAX_UINT256) {
78
+ console.warn(
79
+ `replacePlaceholderInCalldata: Amount exceeds uint256 max (${amount}), returning original calldata`,
80
+ )
81
+ return calldata
82
+ }
83
+
84
+ const placeholderHex = TRAILS_ROUTER_PLACEHOLDER_AMOUNT.toString(16).padStart(
85
+ 64,
86
+ "0",
87
+ )
88
+ const amountHex = amountBigInt.toString(16).padStart(64, "0")
89
+
90
+ return calldata.replace(new RegExp(placeholderHex, "gi"), amountHex)
91
+ }
92
+
16
93
  const trailsRouterAbi = [
17
94
  {
18
95
  type: "function",
@@ -31,8 +31,6 @@ export async function attemptUserDepositTx({
31
31
  walletClient,
32
32
  destinationTokenDecimals,
33
33
  sourceTokenDecimals,
34
- fee,
35
- dryMode,
36
34
  sourceTokenPriceUsd,
37
35
  destinationTokenPriceUsd,
38
36
  swapAmount,
@@ -67,10 +65,8 @@ export async function attemptUserDepositTx({
67
65
  destinationTokenDecimals: number
68
66
  sourceTokenDecimals: number
69
67
  swapAmount: string
70
- dryMode: boolean
71
68
  sourceTokenPriceUsd: number | null
72
69
  destinationTokenPriceUsd: number | null
73
- fee: string
74
70
  onTransactionStateChange: (transactionStates: TransactionState[]) => void
75
71
  transactionStates: TransactionState[]
76
72
  fundMethod?: string
@@ -193,8 +189,6 @@ export async function attemptUserDepositTx({
193
189
  originChainId,
194
190
  chain,
195
191
  account,
196
- fee,
197
- dryMode,
198
192
  sourceTokenPriceUsd,
199
193
  destinationTokenPriceUsd,
200
194
  swapAmount,