0xtrails 0.2.4 → 0.2.5

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 (161) hide show
  1. package/dist/aave.d.ts +8 -0
  2. package/dist/aave.d.ts.map +1 -1
  3. package/dist/{ccip-BlV1Mry3.js → ccip-CXlshvBY.js} +1 -1
  4. package/dist/config.d.ts +1 -1
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/constants.d.ts +1 -0
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/error.d.ts +1 -0
  9. package/dist/error.d.ts.map +1 -1
  10. package/dist/estimate.d.ts +52 -0
  11. package/dist/estimate.d.ts.map +1 -1
  12. package/dist/{index-BNWCIGfQ.js → index-_QuyGrjU.js} +72332 -72246
  13. package/dist/index.js +2 -2
  14. package/dist/intents.d.ts +40 -0
  15. package/dist/intents.d.ts.map +1 -1
  16. package/dist/metaTxnMonitor.d.ts +3 -3
  17. package/dist/metaTxnMonitor.d.ts.map +1 -1
  18. package/dist/metaTxns.d.ts +3 -3
  19. package/dist/metaTxns.d.ts.map +1 -1
  20. package/dist/morpho.d.ts +8 -0
  21. package/dist/morpho.d.ts.map +1 -1
  22. package/dist/prepareSend.d.ts +16 -6
  23. package/dist/prepareSend.d.ts.map +1 -1
  24. package/dist/queryParams.d.ts.map +1 -1
  25. package/dist/relayer.d.ts +6 -6
  26. package/dist/relayer.d.ts.map +1 -1
  27. package/dist/sequenceWallet.d.ts +2 -2
  28. package/dist/sequenceWallet.d.ts.map +1 -1
  29. package/dist/tokens.d.ts.map +1 -1
  30. package/dist/wallets.d.ts.map +1 -1
  31. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  32. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  33. package/dist/widget/components/ClassicSwap.d.ts +2 -0
  34. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  35. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  36. package/dist/widget/components/ConnectedWallets.d.ts +4 -0
  37. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
  38. package/dist/widget/components/Earn.d.ts.map +1 -1
  39. package/dist/widget/components/Fund.d.ts.map +1 -1
  40. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  41. package/dist/widget/components/{FundSendForm.d.ts → FundSwap.d.ts} +11 -5
  42. package/dist/widget/components/FundSwap.d.ts.map +1 -0
  43. package/dist/widget/components/FundingMethodSelectorButton.d.ts +4 -0
  44. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -0
  45. package/dist/widget/components/Modal.d.ts.map +1 -1
  46. package/dist/widget/components/Pay.d.ts.map +1 -1
  47. package/dist/widget/components/PercentageMaxButtons.d.ts +12 -0
  48. package/dist/widget/components/PercentageMaxButtons.d.ts.map +1 -0
  49. package/dist/widget/components/{PaySendForm.d.ts → PoolDeposit.d.ts} +11 -34
  50. package/dist/widget/components/PoolDeposit.d.ts.map +1 -0
  51. package/dist/widget/components/{SimpleSwap.d.ts → PoolWithdraw.d.ts} +16 -8
  52. package/dist/widget/components/PoolWithdraw.d.ts.map +1 -0
  53. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  54. package/dist/widget/components/Receive.d.ts.map +1 -1
  55. package/dist/widget/components/RecipientSelectorButton.d.ts +4 -0
  56. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -0
  57. package/dist/widget/components/Recipients.d.ts.map +1 -1
  58. package/dist/widget/components/RequiredPropsError.d.ts +8 -0
  59. package/dist/widget/components/RequiredPropsError.d.ts.map +1 -0
  60. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  61. package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
  62. package/dist/widget/components/Swap.d.ts +1 -0
  63. package/dist/widget/components/Swap.d.ts.map +1 -1
  64. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  65. package/dist/widget/components/TokenImage.d.ts +1 -0
  66. package/dist/widget/components/TokenImage.d.ts.map +1 -1
  67. package/dist/widget/components/TokenList.d.ts.map +1 -1
  68. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  69. package/dist/widget/components/TokenSelectorButton.d.ts +16 -0
  70. package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -0
  71. package/dist/widget/components/UserPreferences.d.ts.map +1 -1
  72. package/dist/widget/components/WaasFeeOptions.d.ts +8 -0
  73. package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -0
  74. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  75. package/dist/widget/components/WalletList.d.ts.map +1 -1
  76. package/dist/widget/css/compiled.css +2 -0
  77. package/dist/widget/css/index.css +554 -0
  78. package/dist/widget/hooks/useBack.d.ts +1 -0
  79. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  80. package/dist/widget/hooks/useCheckout.d.ts +1 -1
  81. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  82. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  83. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  84. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +3 -3
  85. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
  86. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -1
  87. package/dist/widget/hooks/useSelectedFundMethod.d.ts +12 -0
  88. package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -0
  89. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -1
  90. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  91. package/dist/widget/index.js +1 -1
  92. package/dist/widget/widget.d.ts +4 -4
  93. package/dist/widget/widget.d.ts.map +1 -1
  94. package/package.json +18 -12
  95. package/src/aave.ts +32 -0
  96. package/src/config.ts +12 -4
  97. package/src/constants.ts +2 -0
  98. package/src/error.ts +19 -1
  99. package/src/estimate.ts +416 -5
  100. package/src/intents.ts +161 -11
  101. package/src/metaTxnMonitor.ts +3 -3
  102. package/src/metaTxns.ts +3 -5
  103. package/src/morpho.ts +32 -0
  104. package/src/prepareSend.ts +503 -166
  105. package/src/queryParams.ts +2 -1
  106. package/src/relayer.ts +11 -11
  107. package/src/sequenceWallet.ts +2 -2
  108. package/src/tokens.ts +7 -1
  109. package/src/wallets.ts +8 -0
  110. package/src/widget/compiled.css +2 -2
  111. package/src/widget/components/AccountActionsDropdown.tsx +3 -13
  112. package/src/widget/components/AccountSettings.tsx +6 -24
  113. package/src/widget/components/ClassicSwap.tsx +111 -155
  114. package/src/widget/components/ConnectWallet.tsx +4 -37
  115. package/src/widget/components/ConnectedWallets.tsx +113 -58
  116. package/src/widget/components/Earn.tsx +73 -589
  117. package/src/widget/components/Fund.tsx +31 -82
  118. package/src/widget/components/FundMethods.tsx +82 -159
  119. package/src/widget/components/FundSwap.tsx +52 -0
  120. package/src/widget/components/FundingMethodSelectorButton.tsx +60 -0
  121. package/src/widget/components/Modal.tsx +6 -2
  122. package/src/widget/components/Pay.tsx +183 -208
  123. package/src/widget/components/PercentageMaxButtons.tsx +77 -0
  124. package/src/widget/components/PoolDeposit.tsx +593 -0
  125. package/src/widget/components/PoolWithdraw.tsx +903 -0
  126. package/src/widget/components/QuoteDetails.tsx +22 -8
  127. package/src/widget/components/Receive.tsx +0 -2
  128. package/src/widget/components/RecipientSelectorButton.tsx +42 -0
  129. package/src/widget/components/Recipients.tsx +62 -156
  130. package/src/widget/components/RequiredPropsError.tsx +33 -0
  131. package/src/widget/components/ScreenHeader.tsx +5 -1
  132. package/src/widget/components/SlippageToleranceSettings.tsx +2 -1
  133. package/src/widget/components/Swap.tsx +2 -43
  134. package/src/widget/components/SwapSettings.tsx +2 -14
  135. package/src/widget/components/TokenImage.tsx +21 -4
  136. package/src/widget/components/TokenList.tsx +0 -1
  137. package/src/widget/components/TokenSelector.tsx +1 -0
  138. package/src/widget/components/TokenSelectorButton.tsx +75 -0
  139. package/src/widget/components/UserPreferences.tsx +6 -24
  140. package/src/widget/components/WaasFeeOptions.tsx +331 -0
  141. package/src/widget/components/WalletConfirmation.tsx +55 -3
  142. package/src/widget/components/WalletList.tsx +4 -2
  143. package/src/widget/hooks/useBack.tsx +2 -0
  144. package/src/widget/hooks/useCheckout.ts +36 -20
  145. package/src/widget/hooks/useCurrentScreen.tsx +1 -0
  146. package/src/widget/hooks/useDefaultTokenSelection.tsx +104 -28
  147. package/src/widget/hooks/usePayMessage.tsx +86 -11
  148. package/src/widget/hooks/useSelectedFundMethod.tsx +41 -0
  149. package/src/widget/hooks/useSelectedRecipient.tsx +10 -0
  150. package/src/widget/hooks/useSendForm.ts +24 -2
  151. package/src/widget/index.css +27 -0
  152. package/src/widget/widget.tsx +169 -111
  153. package/dist/widget/components/FundSendForm.d.ts.map +0 -1
  154. package/dist/widget/components/PaySendForm.d.ts.map +0 -1
  155. package/dist/widget/components/SimpleSwap.d.ts.map +0 -1
  156. package/dist/widget/hooks/useSwapSettings.d.ts +0 -16
  157. package/dist/widget/hooks/useSwapSettings.d.ts.map +0 -1
  158. package/src/widget/components/FundSendForm.tsx +0 -903
  159. package/src/widget/components/PaySendForm.tsx +0 -869
  160. package/src/widget/components/SimpleSwap.tsx +0 -983
  161. package/src/widget/hooks/useSwapSettings.tsx +0 -100
package/src/estimate.ts CHANGED
@@ -1,7 +1,156 @@
1
- import { type PublicClient, formatUnits } from "viem"
1
+ import { type PublicClient, formatUnits, createPublicClient, http } from "viem"
2
+ import { getChainInfo } from "./chains.js"
3
+ import { logger } from "./logger.js"
4
+
5
+ // Default minimum gas limit for deposit transactions
6
+ // Used as fallback when gas estimation is not available
7
+ // Buffer is only added when dynamically estimating gas, not when using this default
8
+ export const DEFAULT_MIN_GASLIMIT = 250_000n
9
+
10
+ // Buffer added to estimated gas (only applied when gas is dynamically estimated)
11
+ export const GAS_BUFFER = 50_000n
12
+
13
+ // Fee multiplier for faster transaction processing (similar to MetaMask's "Aggressive" mode)
14
+ // 2x priority fee increases likelihood of quick inclusion
15
+ export const PRIORITY_FEE_MULTIPLIER = 2.0
16
+
17
+ /**
18
+ * Estimate gas limit for a transaction with consistent logic
19
+ * Buffer is only applied when gas is dynamically estimated (not when using DEFAULT_MIN_GASLIMIT)
20
+ * @param publicClient - Viem public client for the chain
21
+ * @param params - Transaction parameters for gas estimation
22
+ * @param logContext - Context string for logging (e.g., "quote", "deposit")
23
+ * @returns Gas limit as bigint, or undefined if estimation fails
24
+ */
25
+ export async function estimateGasLimit(
26
+ publicClient: PublicClient,
27
+ params: {
28
+ account: string
29
+ to: string | bigint | number
30
+ data: string | bigint | number
31
+ value: bigint | string | number
32
+ },
33
+ logContext: string = "transaction",
34
+ ): Promise<bigint | undefined> {
35
+ try {
36
+ const estimatedGasLimit = await publicClient.estimateGas({
37
+ account: params.account as `0x${string}`,
38
+ to: params.to as `0x${string}`,
39
+ data: params.data as `0x${string}`,
40
+ value:
41
+ typeof params.value === "bigint" ? params.value : BigInt(params.value),
42
+ })
43
+
44
+ // Only add buffer when we actually estimated gas (estimatedGasLimit > DEFAULT_MIN_GASLIMIT)
45
+ // If estimation is below default, just use default without buffer
46
+ let gasLimit: bigint
47
+ if (estimatedGasLimit > DEFAULT_MIN_GASLIMIT) {
48
+ // Dynamic estimation - add buffer for safety
49
+ gasLimit = estimatedGasLimit + GAS_BUFFER
50
+ logger.console.log(
51
+ `[trails-sdk][gas-estimation] estimated gasLimit for ${logContext}:`,
52
+ gasLimit,
53
+ "(estimated:",
54
+ estimatedGasLimit,
55
+ "buffer:",
56
+ GAS_BUFFER,
57
+ ")",
58
+ )
59
+ } else {
60
+ // Use default minimum without buffer
61
+ gasLimit = DEFAULT_MIN_GASLIMIT
62
+ logger.console.log(
63
+ `[trails-sdk][gas-estimation] using default gasLimit for ${logContext}:`,
64
+ gasLimit,
65
+ "(estimated was:",
66
+ estimatedGasLimit,
67
+ "which is below default)",
68
+ )
69
+ }
70
+
71
+ return gasLimit
72
+ } catch (error) {
73
+ logger.console.error(
74
+ `[trails-sdk][gas-estimation] Error estimating gas limit for ${logContext}:`,
75
+ error,
76
+ )
77
+ return undefined
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Get fee parameters (EIP-1559 or legacy) from a public client
83
+ * Applies PRIORITY_FEE_MULTIPLIER for faster transaction processing
84
+ * @param publicClient - Viem public client for the chain
85
+ * @returns Promise<FeeData> - Fee data including maxFeePerGas and maxPriorityFeePerGas if available
86
+ */
87
+ export async function getFeeData(publicClient: PublicClient): Promise<{
88
+ maxFeePerGas?: bigint
89
+ maxPriorityFeePerGas?: bigint
90
+ gasPrice?: bigint
91
+ }> {
92
+ try {
93
+ // Try to get EIP-1559 fee data
94
+ const feeData = await publicClient.estimateFeesPerGas()
95
+
96
+ if (feeData.maxFeePerGas && feeData.maxPriorityFeePerGas) {
97
+ // EIP-1559 chain - apply multiplier for faster processing
98
+ // Increase priority fee by multiplier (e.g., 2x)
99
+ const boostedPriorityFee = BigInt(
100
+ Math.floor(
101
+ Number(feeData.maxPriorityFeePerGas) * PRIORITY_FEE_MULTIPLIER,
102
+ ),
103
+ )
104
+
105
+ // Calculate additional fee to add to maxFeePerGas
106
+ const additionalFee = boostedPriorityFee - feeData.maxPriorityFeePerGas
107
+ const boostedMaxFee = feeData.maxFeePerGas + additionalFee
108
+
109
+ logger.console.log(
110
+ "[trails-sdk][gas-estimation] Boosting fees for faster processing:",
111
+ {
112
+ originalMaxFee: feeData.maxFeePerGas,
113
+ originalPriorityFee: feeData.maxPriorityFeePerGas,
114
+ boostedMaxFee,
115
+ boostedPriorityFee,
116
+ multiplier: PRIORITY_FEE_MULTIPLIER,
117
+ },
118
+ )
119
+
120
+ return {
121
+ maxFeePerGas: boostedMaxFee,
122
+ maxPriorityFeePerGas: boostedPriorityFee,
123
+ }
124
+ }
125
+ } catch (_error) {
126
+ // EIP-1559 not supported, fall through to legacy
127
+ }
128
+
129
+ // Fallback to legacy gas price - also apply multiplier
130
+ try {
131
+ const gasPrice = await publicClient.getGasPrice()
132
+ const boostedGasPrice = BigInt(
133
+ Math.floor(Number(gasPrice) * PRIORITY_FEE_MULTIPLIER),
134
+ )
135
+
136
+ logger.console.log(
137
+ "[trails-sdk][gas-estimation] Boosting legacy gas price for faster processing:",
138
+ {
139
+ originalGasPrice: gasPrice,
140
+ boostedGasPrice,
141
+ multiplier: PRIORITY_FEE_MULTIPLIER,
142
+ },
143
+ )
144
+
145
+ return { gasPrice: boostedGasPrice }
146
+ } catch (error) {
147
+ throw new Error(`Failed to get fee data: ${error}`)
148
+ }
149
+ }
2
150
 
3
151
  /**
4
152
  * Estimates gas cost for a transaction on a given chain
153
+ * Properly handles EIP-1559 fees (uses maxFeePerGas) on supported chains
5
154
  * @param publicClient - Viem public client for the chain
6
155
  * @param gasLimit - Gas limit in wei (defaults to 100,000)
7
156
  * @returns Promise<bigint> - Estimated gas cost in wei
@@ -11,11 +160,17 @@ export async function estimateGasCost(
11
160
  gasLimit: bigint = 100_000n,
12
161
  ): Promise<bigint> {
13
162
  try {
14
- // Get current gas price from the chain
15
- const gasPrice = await publicClient.getGasPrice()
163
+ const feeData = await getFeeData(publicClient)
164
+
165
+ // Use maxFeePerGas for EIP-1559, otherwise use legacy gas price
166
+ const effectiveGasPrice = feeData.maxFeePerGas || feeData.gasPrice
16
167
 
17
- // Calculate gas cost: gas limit * gas price
18
- const gasCost = gasLimit * gasPrice
168
+ if (!effectiveGasPrice) {
169
+ throw new Error("Could not determine gas price")
170
+ }
171
+
172
+ // Calculate gas cost: gas limit * effective gas price
173
+ const gasCost = gasLimit * effectiveGasPrice
19
174
 
20
175
  return gasCost
21
176
  } catch (error) {
@@ -55,3 +210,259 @@ export async function estimateGasCostUsd(
55
210
  const gasCostInEth = parseFloat(formatUnits(gasCost, 18))
56
211
  return gasCostInEth * nativeTokenPriceUsd
57
212
  }
213
+
214
+ /**
215
+ * Calculate maximum amount for native token considering gas costs
216
+ * @param userBalanceFormatted - User's balance as formatted string (e.g., "1.5")
217
+ * @param gasCostFormatted - Gas cost in native token from quote (e.g., "0.001")
218
+ * @param bufferPercentage - Safety buffer % (default 50% for gas price volatility)
219
+ * @returns Maximum usable amount as formatted string (never throws, returns "0" on error)
220
+ */
221
+ export function calculateMaxNativeAmount(
222
+ userBalanceFormatted: string,
223
+ gasCostFormatted?: string,
224
+ bufferPercentage: number = 50,
225
+ ): string {
226
+ logger.console.log(
227
+ "[trails-sdk][gas-estimation] calculateMaxNativeAmount called with:",
228
+ { userBalanceFormatted, gasCostFormatted, bufferPercentage },
229
+ )
230
+ try {
231
+ // Validate inputs
232
+ if (!userBalanceFormatted || typeof userBalanceFormatted !== "string") {
233
+ logger.console.log(
234
+ "[trails-sdk][gas-estimation] Invalid userBalanceFormatted, returning 0",
235
+ )
236
+ return "0"
237
+ }
238
+
239
+ const balance = parseFloat(userBalanceFormatted)
240
+
241
+ // Check for invalid balance
242
+ if (Number.isNaN(balance) || !Number.isFinite(balance) || balance <= 0) {
243
+ return "0"
244
+ }
245
+
246
+ // If no gas estimate, return full balance
247
+ if (!gasCostFormatted || typeof gasCostFormatted !== "string") {
248
+ return userBalanceFormatted
249
+ }
250
+
251
+ const gasCost = parseFloat(gasCostFormatted)
252
+
253
+ // Check for invalid gas cost
254
+ if (Number.isNaN(gasCost) || !Number.isFinite(gasCost) || gasCost < 0) {
255
+ return userBalanceFormatted
256
+ }
257
+
258
+ // If gas cost is zero, return full balance
259
+ if (gasCost === 0) {
260
+ return userBalanceFormatted
261
+ }
262
+
263
+ // Validate buffer percentage
264
+ const safeBufferPercentage =
265
+ typeof bufferPercentage === "number" &&
266
+ Number.isFinite(bufferPercentage) &&
267
+ bufferPercentage >= 0
268
+ ? bufferPercentage
269
+ : 50
270
+
271
+ // Add buffer to gas cost (default 50% to handle gas price spikes)
272
+ const gasWithBuffer = gasCost * (1 + safeBufferPercentage / 100)
273
+
274
+ // Check for overflow
275
+ if (!Number.isFinite(gasWithBuffer)) {
276
+ return "0"
277
+ }
278
+
279
+ // Calculate max amount
280
+ const maxAmount = balance - gasWithBuffer
281
+
282
+ // Check for invalid result
283
+ if (Number.isNaN(maxAmount) || !Number.isFinite(maxAmount)) {
284
+ return "0"
285
+ }
286
+
287
+ // If not enough for gas, return 0
288
+ if (maxAmount <= 0) {
289
+ return "0"
290
+ }
291
+
292
+ // Return with reasonable precision (6 decimals)
293
+ const result = maxAmount.toFixed(6)
294
+
295
+ // Final validation of result
296
+ if (!result || typeof result !== "string") {
297
+ return "0"
298
+ }
299
+
300
+ logger.console.log(
301
+ "[trails-sdk][gas-estimation] calculateMaxNativeAmount result:",
302
+ { balance, gasCost, gasWithBuffer, maxAmount, result },
303
+ )
304
+
305
+ return result
306
+ } catch (error) {
307
+ // Catch any unexpected errors and return safe default
308
+ logger.console.error(
309
+ "[trails-sdk][gas-estimation] Error in calculateMaxNativeAmount:",
310
+ error,
311
+ )
312
+ return "0"
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Calculate a conservative default gas cost using real gas price
318
+ * Buffer is only added when gas is dynamically estimated above DEFAULT_MIN_GASLIMIT
319
+ * Properly handles EIP-1559 fees (maxFeePerGas + maxPriorityFee) on supported chains
320
+ * Falls back to 1% of balance if gas price fetch fails
321
+ *
322
+ * @param balance - User's balance as formatted string
323
+ * @param chainId - Chain ID to fetch gas price from
324
+ * @param account - Account address for gas estimation (optional)
325
+ * @param to - Transaction recipient address (optional)
326
+ * @param data - Transaction data (optional)
327
+ * @param value - Transaction value (optional)
328
+ * @returns Promise<string> - Estimated gas cost in native token units
329
+ */
330
+ export async function getDefaultGasCostEstimate(
331
+ balance: string,
332
+ chainId?: number,
333
+ account?: string,
334
+ to?: string,
335
+ data?: string,
336
+ value?: string,
337
+ ): Promise<string> {
338
+ logger.console.log(
339
+ "[trails-sdk][gas-estimation] getDefaultGasCostEstimate called with:",
340
+ {
341
+ balance,
342
+ chainId,
343
+ hasAccount: !!account,
344
+ hasTo: !!to,
345
+ hasData: !!data,
346
+ hasValue: !!value,
347
+ },
348
+ )
349
+ try {
350
+ const balanceNum = parseFloat(balance)
351
+ if (Number.isNaN(balanceNum) || balanceNum <= 0) {
352
+ logger.console.log(
353
+ "[trails-sdk][gas-estimation] Invalid balance, returning 0",
354
+ )
355
+ return "0"
356
+ }
357
+
358
+ // Try to get real gas price if chainId is provided
359
+ if (chainId) {
360
+ try {
361
+ const chain = getChainInfo(chainId)
362
+ if (chain) {
363
+ const publicClient = createPublicClient({
364
+ chain,
365
+ transport: http(),
366
+ })
367
+
368
+ // Try to estimate gas if we have transaction details
369
+ let gasLimit = DEFAULT_MIN_GASLIMIT
370
+ if (account && to && data !== undefined && value !== undefined) {
371
+ try {
372
+ const estimatedGas = await publicClient.estimateGas({
373
+ account: account as `0x${string}`,
374
+ to: to as `0x${string}`,
375
+ data: data as `0x${string}`,
376
+ value: BigInt(value),
377
+ })
378
+ // Only add buffer when dynamically estimating gas above default
379
+ if (estimatedGas > DEFAULT_MIN_GASLIMIT) {
380
+ gasLimit = estimatedGas + GAS_BUFFER
381
+ logger.console.log(
382
+ "[trails-sdk][gas-estimation] Using estimated gas + buffer for max button:",
383
+ { estimatedGas, buffer: GAS_BUFFER, gasLimit },
384
+ )
385
+ } else {
386
+ gasLimit = DEFAULT_MIN_GASLIMIT
387
+ logger.console.log(
388
+ "[trails-sdk][gas-estimation] Using default gas limit for max button (estimated was below default):",
389
+ { estimatedGas, gasLimit },
390
+ )
391
+ }
392
+ } catch (error) {
393
+ logger.console.error(
394
+ "[trails-sdk][gas-estimation] Error estimating gas for max button, using default:",
395
+ error,
396
+ )
397
+ // Use default if estimation fails (no buffer)
398
+ }
399
+ }
400
+
401
+ // Get gas price - use getFeeData which applies the speed multiplier
402
+ let effectiveGasPrice: bigint
403
+ try {
404
+ const feeData = await getFeeData(publicClient)
405
+ effectiveGasPrice = feeData.maxFeePerGas || feeData.gasPrice || 0n
406
+
407
+ if (effectiveGasPrice === 0n) {
408
+ throw new Error("Could not determine gas price")
409
+ }
410
+
411
+ logger.console.log(
412
+ "[trails-sdk][gas-estimation] Using boosted fee data for max button:",
413
+ {
414
+ maxFeePerGas: feeData.maxFeePerGas,
415
+ maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
416
+ gasPrice: feeData.gasPrice,
417
+ effectiveGasPrice,
418
+ },
419
+ )
420
+ } catch (_error) {
421
+ // If getFeeData fails, fall back to basic gas price with manual multiplier
422
+ logger.console.log(
423
+ "[trails-sdk][gas-estimation] getFeeData failed, using fallback with multiplier",
424
+ )
425
+ const basicGasPrice = await publicClient.getGasPrice()
426
+ effectiveGasPrice = BigInt(
427
+ Math.floor(Number(basicGasPrice) * PRIORITY_FEE_MULTIPLIER),
428
+ )
429
+ }
430
+
431
+ // Calculate gas cost: gasLimit × effectiveGasPrice
432
+ const gasCostWei = gasLimit * effectiveGasPrice
433
+
434
+ // Convert to native token units (always 18 decimals)
435
+ const gasCostFormatted = formatUnits(gasCostWei, 18)
436
+
437
+ logger.console.log(
438
+ "[trails-sdk][gas-estimation] Calculated gas cost for max button:",
439
+ { gasLimit, effectiveGasPrice, gasCostWei, gasCostFormatted },
440
+ )
441
+
442
+ return gasCostFormatted
443
+ }
444
+ } catch (error) {
445
+ logger.console.error(
446
+ "[trails-sdk][gas-estimation] Error fetching gas price for max button:",
447
+ error,
448
+ )
449
+ // Fall through to percentage-based fallback
450
+ }
451
+ }
452
+
453
+ // Fallback: Reserve 1% of balance for gas
454
+ // This works for any native token and any chain
455
+ const defaultGasCost = balanceNum * 0.01
456
+ logger.console.log(
457
+ "[trails-sdk][gas-estimation] Using fallback 1% gas cost estimate:",
458
+ { balance: balanceNum, defaultGasCost: defaultGasCost.toFixed(6) },
459
+ )
460
+ return defaultGasCost.toFixed(6)
461
+ } catch (error) {
462
+ logger.console.error(
463
+ "[trails-sdk][gas-estimation] Error in getDefaultGasCostEstimate:",
464
+ error,
465
+ )
466
+ return "0"
467
+ }
468
+ }
package/src/intents.ts CHANGED
@@ -53,6 +53,9 @@ import {
53
53
  } from "./analytics.js"
54
54
  import { getQueryParam } from "./queryParams.js"
55
55
  import { getFullErrorMessage } from "./error.js"
56
+ import { GAS_BUFFER, getFeeData } from "./estimate.js"
57
+ import { getERC20TransferData } from "./encoders.js"
58
+ import { zeroAddress } from "viem"
56
59
 
57
60
  export type IntentRequestParams = {
58
61
  userAddress: string
@@ -126,6 +129,9 @@ export type SendOriginCallTxArgs = {
126
129
  data: Hex.Hex
127
130
  value: bigint
128
131
  chain: Chain
132
+ gasLimit?: bigint // Optional: if provided, skip gas estimation
133
+ maxFeePerGas?: bigint // Optional: EIP-1559 max fee per gas
134
+ maxPriorityFeePerGas?: bigint // Optional: EIP-1559 max priority fee per gas
129
135
  }
130
136
 
131
137
  export async function getLocalClientIfEnabled(
@@ -486,17 +492,67 @@ export async function sendOriginTransaction(
486
492
  transport: http(),
487
493
  })
488
494
 
489
- const estimatedGasLimit = await publicClient.estimateGas({
490
- account: account,
491
- to: originParams.to as `0x${string}`,
492
- data: originParams.data as `0x${string}`,
493
- value: BigInt(originParams.value),
494
- })
495
+ // Use provided gasLimit if available, otherwise estimate
496
+ let gasLimit: bigint
497
+ if (originParams.gasLimit) {
498
+ gasLimit = originParams.gasLimit
499
+ logger.console.log(
500
+ "[trails-sdk][gas-estimation] using provided gasLimit:",
501
+ gasLimit,
502
+ )
503
+ } else {
504
+ const estimatedGasLimit = await publicClient.estimateGas({
505
+ account: account,
506
+ to: originParams.to as `0x${string}`,
507
+ data: originParams.data as `0x${string}`,
508
+ value: BigInt(originParams.value),
509
+ })
495
510
 
496
- const gsaBuffer = BigInt(50_000)
497
- const gasLimit = estimatedGasLimit + gsaBuffer
511
+ gasLimit = estimatedGasLimit + GAS_BUFFER
512
+ logger.console.log(
513
+ "[trails-sdk][gas-estimation] estimated gasLimit in sendOriginTransaction:",
514
+ gasLimit,
515
+ "(estimated:",
516
+ estimatedGasLimit,
517
+ "buffer:",
518
+ GAS_BUFFER,
519
+ ")",
520
+ )
521
+ }
498
522
 
499
- logger.console.log("[trails-sdk] estimated gasLimit:", gasLimit)
523
+ // Get fee parameters - try EIP-1559 first, then fall back to legacy
524
+ // Use provided fees if available, otherwise fetch and boost them for faster processing
525
+ let maxFeePerGas: bigint | undefined = originParams.maxFeePerGas
526
+ let maxPriorityFeePerGas: bigint | undefined =
527
+ originParams.maxPriorityFeePerGas
528
+ let gasPrice: bigint | undefined
529
+
530
+ if (!maxFeePerGas) {
531
+ try {
532
+ // Use getFeeData which applies the PRIORITY_FEE_MULTIPLIER
533
+ const feeData = await getFeeData(publicClient)
534
+
535
+ if (feeData.maxFeePerGas) {
536
+ maxFeePerGas = feeData.maxFeePerGas
537
+ maxPriorityFeePerGas = feeData.maxPriorityFeePerGas
538
+ logger.console.log(
539
+ "[trails-sdk][gas-estimation] Using boosted EIP-1559 fees in sendOriginTransaction:",
540
+ { maxFeePerGas, maxPriorityFeePerGas },
541
+ )
542
+ } else if (feeData.gasPrice) {
543
+ gasPrice = feeData.gasPrice
544
+ logger.console.log(
545
+ "[trails-sdk][gas-estimation] Using boosted legacy gas price in sendOriginTransaction:",
546
+ { gasPrice },
547
+ )
548
+ }
549
+ } catch (_error) {
550
+ // Fee data fetch failed, will use wallet default
551
+ logger.console.log(
552
+ "[trails-sdk][gas-estimation] Fee data fetch failed in sendOriginTransaction, wallet will use default",
553
+ )
554
+ }
555
+ }
500
556
 
501
557
  logger.console.log(
502
558
  "[trails-sdk] sending origin tx with walletClient.sendTransaction()",
@@ -505,14 +561,27 @@ export async function sendOriginTransaction(
505
561
  logger.console.time(`[trails-sdk] walletClient.sendTransaction-${id}`)
506
562
 
507
563
  try {
508
- const hash = await walletClient.sendTransaction({
564
+ // Build transaction parameters - EIP-1559 and legacy are mutually exclusive
565
+ const txParams: any = {
509
566
  account: account,
510
567
  to: originParams.to as `0x${string}`,
511
568
  data: originParams.data as `0x${string}`,
512
569
  value: BigInt(originParams.value),
513
570
  chain: originParams.chain,
514
571
  gas: gasLimit,
515
- })
572
+ }
573
+
574
+ // Add fee parameters based on transaction type
575
+ if (maxFeePerGas && maxPriorityFeePerGas) {
576
+ // EIP-1559 transaction
577
+ txParams.maxFeePerGas = maxFeePerGas
578
+ txParams.maxPriorityFeePerGas = maxPriorityFeePerGas
579
+ } else if (gasPrice) {
580
+ // Legacy transaction
581
+ txParams.gasPrice = gasPrice
582
+ }
583
+
584
+ const hash = await walletClient.sendTransaction(txParams)
516
585
  logger.console.timeEnd(`[trails-sdk] walletClient.sendTransaction-${id}`)
517
586
  logger.console.log("[trails-sdk] done sending, origin tx hash", hash)
518
587
 
@@ -807,3 +876,84 @@ function buildMerkleTreeFromMembers(
807
876
  }
808
877
  return currentLevel[0]!
809
878
  }
879
+
880
+ /**
881
+ * Build transaction parameters for same-chain same-token transactions
882
+ * Centralizes the logic for determining transaction destination, data, and value
883
+ */
884
+ export function buildSameChainTransactionParams({
885
+ hasCustomCalldata,
886
+ recipient,
887
+ effectiveOriginTokenAddress,
888
+ destinationCalldata,
889
+ swapAmount,
890
+ effectiveOriginChainId,
891
+ effectiveOriginChain,
892
+ }: {
893
+ hasCustomCalldata: boolean
894
+ recipient: string
895
+ effectiveOriginTokenAddress: string
896
+ destinationCalldata?: string
897
+ swapAmount: string
898
+ effectiveOriginChainId: number
899
+ effectiveOriginChain: Chain
900
+ }) {
901
+ return {
902
+ to: hasCustomCalldata
903
+ ? recipient
904
+ : effectiveOriginTokenAddress === zeroAddress
905
+ ? recipient
906
+ : effectiveOriginTokenAddress,
907
+ data: hasCustomCalldata
908
+ ? destinationCalldata || "0x"
909
+ : effectiveOriginTokenAddress === zeroAddress
910
+ ? "0x"
911
+ : getERC20TransferData({
912
+ recipient,
913
+ amount: BigInt(swapAmount),
914
+ }),
915
+ value:
916
+ effectiveOriginTokenAddress === zeroAddress ? BigInt(swapAmount) : "0",
917
+ chainId: effectiveOriginChainId,
918
+ chain: effectiveOriginChain,
919
+ }
920
+ }
921
+
922
+ /**
923
+ * Build transaction parameters for cross-chain deposit transactions
924
+ * Centralizes the logic for determining transaction destination, data, and value for deposits
925
+ */
926
+ export function buildCrossChainDepositParams({
927
+ originTokenAddress,
928
+ originIntentAddress,
929
+ depositAmount,
930
+ fee,
931
+ originChainId,
932
+ chain,
933
+ }: {
934
+ originTokenAddress: string
935
+ originIntentAddress: string
936
+ depositAmount: string
937
+ fee: string
938
+ originChainId: number
939
+ chain: Chain
940
+ }) {
941
+ const totalAmount = BigInt(depositAmount) + BigInt(fee)
942
+
943
+ return {
944
+ to:
945
+ originTokenAddress === zeroAddress
946
+ ? originIntentAddress
947
+ : originTokenAddress,
948
+ data:
949
+ originTokenAddress === zeroAddress
950
+ ? "0x"
951
+ : getERC20TransferData({
952
+ recipient: originIntentAddress,
953
+ amount: totalAmount,
954
+ }),
955
+ value: originTokenAddress === zeroAddress ? totalAmount : "0",
956
+ chainId: originChainId,
957
+ chain,
958
+ }
959
+ }
@@ -5,7 +5,7 @@ import type {
5
5
  RelayerOperationStatus,
6
6
  RelayerOperationUnknownStatus,
7
7
  } from "./relayer.js"
8
- import type { RpcRelayer } from "@0xsequence/relayer"
8
+ import type { Relayer } from "@0xsequence/relayer"
9
9
  import { type Query, useQueries } from "@tanstack/react-query"
10
10
  import { useMemo } from "react"
11
11
  import type { Hex } from "viem"
@@ -26,7 +26,7 @@ export type MetaTxnStatus = {
26
26
  const POLL_INTERVAL = 3_000 // 3 seconds
27
27
 
28
28
  export const getMetaTxStatus = async (
29
- relayer: RpcRelayer.RpcRelayer,
29
+ relayer: Relayer.RpcRelayer,
30
30
  metaTxId: string,
31
31
  chainId: number,
32
32
  ): Promise<RelayerOperationStatus> => {
@@ -35,7 +35,7 @@ export const getMetaTxStatus = async (
35
35
 
36
36
  export const useMetaTxnsMonitor = (
37
37
  metaTxns: MetaTxn[] | undefined,
38
- getRelayer: (chainId: number) => RpcRelayer.RpcRelayer,
38
+ getRelayer: (chainId: number) => Relayer.RpcRelayer,
39
39
  ) => {
40
40
  const results = useQueries({
41
41
  queries: (metaTxns || []).map((metaTxn) => {
package/src/metaTxns.ts CHANGED
@@ -1,13 +1,11 @@
1
+ import type { Relayer } from "@0xsequence/relayer"
1
2
  import type { IntentPrecondition } from "@0xsequence/trails-api"
2
3
  import type { Hex } from "viem"
3
4
  import type { MetaTxn } from "./metaTxnMonitor.js"
4
- import type { RpcRelayer } from "@0xsequence/relayer"
5
-
6
5
  import { logger } from "./logger.js"
7
- import type { Relayer } from "@0xsequence/relayer"
8
6
 
9
7
  export async function relayerSendMetaTx(
10
- relayer: RpcRelayer.RpcRelayer,
8
+ relayer: Relayer.RpcRelayer,
11
9
  metaTx: MetaTxn,
12
10
  preconditions: IntentPrecondition[],
13
11
  feeQuote?: Relayer.FeeQuote,
@@ -25,7 +23,7 @@ export async function relayerSendMetaTx(
25
23
  }
26
24
 
27
25
  export async function getMetaTxnReceipt(
28
- relayer: RpcRelayer.RpcRelayer,
26
+ relayer: Relayer.RpcRelayer,
29
27
  metaTxId: string,
30
28
  chainId: number,
31
29
  ): Promise<{ receipt: { status: "confirmed" | "failed" | "pending" } }> {