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
@@ -2,6 +2,7 @@ import { useCallback, useRef } from "react"
2
2
  import { getSessionId } from "../../analytics.js"
3
3
  import { logger } from "../../logger.js"
4
4
  import { updatePersistentToast } from "../../toast.js"
5
+ import { getPrettifiedErrorMessage } from "../../error.js"
5
6
 
6
7
  export type CheckoutCallbacks = {
7
8
  onCheckoutStart?: (data: { sessionId: string }) => void
@@ -17,7 +18,7 @@ export type CheckoutCallbacks = {
17
18
  export type CheckoutOnHandlers = {
18
19
  triggerCheckoutStart: () => void
19
20
  triggerCheckoutQuote: (quote: any) => void
20
- triggerCheckoutComplete: () => void
21
+ triggerCheckoutComplete: (txStatus: "success" | "fail") => void
21
22
  triggerCheckoutError: (error: string) => void
22
23
  triggerCheckoutStatusUpdate: (transactionStates: any[]) => void
23
24
  }
@@ -81,26 +82,38 @@ export function useCheckout({
81
82
  [onCheckoutQuote],
82
83
  )
83
84
 
84
- const triggerCheckoutComplete = useCallback(() => {
85
- if (onCheckoutComplete && sessionIdRef.current) {
86
- try {
87
- onCheckoutComplete({
88
- sessionId: sessionIdRef.current,
89
- })
90
- } catch (error) {
91
- logger.console.error(
92
- "[trails-sdk] Error calling onCheckoutComplete:",
93
- error,
94
- )
85
+ const triggerCheckoutComplete = useCallback(
86
+ (txStatus: "success" | "fail") => {
87
+ if (onCheckoutComplete && sessionIdRef.current) {
88
+ try {
89
+ onCheckoutComplete({
90
+ sessionId: sessionIdRef.current,
91
+ })
92
+ } catch (error) {
93
+ logger.console.error(
94
+ "[trails-sdk] Error calling onCheckoutComplete:",
95
+ error,
96
+ )
97
+ }
95
98
  }
96
- }
97
99
 
98
- updatePersistentToast(
99
- "Transaction Complete",
100
- "Your transaction has been successfully processed",
101
- "success",
102
- )
103
- }, [onCheckoutComplete])
100
+ if (txStatus === "success") {
101
+ updatePersistentToast(
102
+ "Transaction Complete",
103
+ "Your transaction has been successfully processed",
104
+ "success",
105
+ )
106
+ } else {
107
+ // Default to error for any non-success status (including 'fail' and any unexpected values)
108
+ updatePersistentToast(
109
+ "Transaction Failed",
110
+ "Your transaction failed or was refunded",
111
+ "error",
112
+ )
113
+ }
114
+ },
115
+ [onCheckoutComplete],
116
+ )
104
117
 
105
118
  const triggerCheckoutError = useCallback(
106
119
  (error: string) => {
@@ -120,7 +133,10 @@ export function useCheckout({
120
133
 
121
134
  updatePersistentToast(
122
135
  "Transaction Failed",
123
- error || "An error occurred while processing your transaction",
136
+ getPrettifiedErrorMessage(
137
+ error,
138
+ "An error occurred while processing your transaction",
139
+ ),
124
140
  "error",
125
141
  )
126
142
  },
@@ -25,6 +25,7 @@ export type Screen =
25
25
  | "user-preferences"
26
26
  | "chain-list"
27
27
  | "disconnect"
28
+ | "home"
28
29
 
29
30
  interface CurrentScreenContextType {
30
31
  currentScreen: Screen
@@ -14,6 +14,7 @@ import { getChainInfo } from "../../chains.js"
14
14
  import { logger } from "../../logger.js"
15
15
 
16
16
  const MINIMUM_24H_VOLUME_USD = 1_000_000 // $1M
17
+ const MINIMUM_BALANCE_USD_FOR_PRIORITIZATION = 1 // $1
17
18
 
18
19
  /**
19
20
  * Hook for intelligent default token selection in 0xtrails widget.
@@ -33,9 +34,9 @@ const MINIMUM_24H_VOLUME_USD = 1_000_000 // $1M
33
34
  * - Fallback to any token with balance > 0
34
35
  *
35
36
  * 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
37
+ * - Default destination chain: Base if origin is not Base, Arbitrum if origin is Base
38
+ * - Default destination token: USDC on destination chain
39
+ * - Fallback: Same symbol on destination chain > ETH on destination chain > first available token on destination chain
39
40
  *
40
41
  * @returns defaultOriginToken - Best origin token that can cover targetAmountUsd (if specified)
41
42
  * @returns defaultDestinationToken - Matching token on different chain (null if toToken/toChainId set)
@@ -231,8 +232,11 @@ function useDefaultTokenSelectionInternal(): UseDefaultTokenSelectionReturn {
231
232
  }
232
233
 
233
234
  // Helper to sort tokens by balance USD and volume
234
- const sortTokensByBalanceAndVolume = (tokens: TokenBalanceExtended[]) => {
235
- return [...tokens].sort((a, b) => {
235
+ const sortTokensByBalanceAndVolume = (
236
+ tokens: TokenBalanceExtended[],
237
+ targetAmount?: number,
238
+ ) => {
239
+ const sortedTokens = [...tokens].sort((a, b) => {
236
240
  const aVolume = getToken24hVolume(a)
237
241
  const bVolume = getToken24hVolume(b)
238
242
  const aHighVolume = aVolume >= MINIMUM_24H_VOLUME_USD
@@ -247,6 +251,56 @@ function useDefaultTokenSelectionInternal(): UseDefaultTokenSelectionReturn {
247
251
  const bBalanceUsd = b.balanceUsd ?? 0
248
252
  return bBalanceUsd - aBalanceUsd
249
253
  })
254
+
255
+ // After initial sorting, prioritize USDC and native tokens at the top
256
+ const effectiveTargetAmount =
257
+ targetAmount ?? MINIMUM_BALANCE_USD_FOR_PRIORITIZATION
258
+
259
+ const prioritizedTokens: TokenBalanceExtended[] = []
260
+ const remainingTokens: TokenBalanceExtended[] = []
261
+
262
+ // Separate tokens into prioritized and remaining
263
+ for (const token of sortedTokens) {
264
+ const balanceUsd = token.balanceUsd ?? 0
265
+ const isNative =
266
+ !("contractAddress" in token) || token.contractAddress === zeroAddress
267
+ const isUSDC =
268
+ !isNative && token.contractInfo?.symbol?.toUpperCase() === "USDC"
269
+
270
+ if ((isUSDC || isNative) && balanceUsd >= effectiveTargetAmount) {
271
+ prioritizedTokens.push(token)
272
+ } else {
273
+ remainingTokens.push(token)
274
+ }
275
+ }
276
+
277
+ // Sort prioritized tokens: USDC first, then native tokens
278
+ prioritizedTokens.sort((a, b) => {
279
+ const aIsNative =
280
+ !("contractAddress" in a) || a.contractAddress === zeroAddress
281
+ const bIsNative =
282
+ !("contractAddress" in b) || b.contractAddress === zeroAddress
283
+ const aIsUSDC =
284
+ !aIsNative && a.contractInfo?.symbol?.toUpperCase() === "USDC"
285
+ const bIsUSDC =
286
+ !bIsNative && b.contractInfo?.symbol?.toUpperCase() === "USDC"
287
+
288
+ // USDC comes first
289
+ if (aIsUSDC && !bIsUSDC) return -1
290
+ if (!aIsUSDC && bIsUSDC) return 1
291
+
292
+ // Then native tokens
293
+ if (aIsNative && !bIsNative) return -1
294
+ if (!aIsNative && bIsNative) return 1
295
+
296
+ // Within same type, sort by balance USD (highest first)
297
+ const aBalanceUsd = a.balanceUsd ?? 0
298
+ const bBalanceUsd = b.balanceUsd ?? 0
299
+ return bBalanceUsd - aBalanceUsd
300
+ })
301
+
302
+ // Return prioritized tokens first, then remaining tokens
303
+ return [...prioritizedTokens, ...remainingTokens]
250
304
  }
251
305
 
252
306
  // Find the best origin token using intelligent selection
@@ -281,7 +335,10 @@ function useDefaultTokenSelectionInternal(): UseDefaultTokenSelectionReturn {
281
335
  const sameChainTokens = tokensWithSufficientBalance.filter(
282
336
  (token) => token.chainId === targetChainId,
283
337
  )
284
- const sortedSameChain = sortTokensByBalanceAndVolume(sameChainTokens)
338
+ const sortedSameChain = sortTokensByBalanceAndVolume(
339
+ sameChainTokens,
340
+ effectiveTargetAmount,
341
+ )
285
342
  bestOriginToken = sortedSameChain[0] ?? null
286
343
  if (bestOriginToken) {
287
344
  logger.console.log(
@@ -296,7 +353,10 @@ function useDefaultTokenSelectionInternal(): UseDefaultTokenSelectionReturn {
296
353
  (token) =>
297
354
  tokenMatches(token, toToken) && token.chainId !== targetChainId,
298
355
  )
299
- const sortedSameToken = sortTokensByBalanceAndVolume(sameTokenDiffChain)
356
+ const sortedSameToken = sortTokensByBalanceAndVolume(
357
+ sameTokenDiffChain,
358
+ effectiveTargetAmount,
359
+ )
300
360
  bestOriginToken = sortedSameToken[0] ?? null
301
361
  if (bestOriginToken) {
302
362
  logger.console.log(
@@ -310,8 +370,10 @@ function useDefaultTokenSelectionInternal(): UseDefaultTokenSelectionReturn {
310
370
  const diffChainDiffToken = tokensWithSufficientBalance.filter(
311
371
  (token) => token.chainId !== targetChainId,
312
372
  )
313
- const sortedDiffChainDiffToken =
314
- sortTokensByBalanceAndVolume(diffChainDiffToken)
373
+ const sortedDiffChainDiffToken = sortTokensByBalanceAndVolume(
374
+ diffChainDiffToken,
375
+ effectiveTargetAmount,
376
+ )
315
377
  bestOriginToken = sortedDiffChainDiffToken[0] ?? null
316
378
  if (bestOriginToken) {
317
379
  logger.console.log(
@@ -322,7 +384,10 @@ function useDefaultTokenSelectionInternal(): UseDefaultTokenSelectionReturn {
322
384
 
323
385
  // Fallback: If no token can cover target amount, use any token (sorted by balance/volume)
324
386
  if (!bestOriginToken) {
325
- const allTokensSorted = sortTokensByBalanceAndVolume(tokensWithBalance)
387
+ const allTokensSorted = sortTokensByBalanceAndVolume(
388
+ tokensWithBalance,
389
+ effectiveTargetAmount,
390
+ )
326
391
  bestOriginToken = allTokensSorted[0] ?? null
327
392
  logger.console.log(
328
393
  "[trails-sdk] Fallback: No token can cover target amount, using highest balance token",
@@ -330,8 +395,10 @@ function useDefaultTokenSelectionInternal(): UseDefaultTokenSelectionReturn {
330
395
  }
331
396
  } else {
332
397
  // When no specific destination is set, select highest value token with good liquidity
333
- const sortedByBalanceAndVolume =
334
- sortTokensByBalanceAndVolume(tokensWithBalance)
398
+ const sortedByBalanceAndVolume = sortTokensByBalanceAndVolume(
399
+ tokensWithBalance,
400
+ MINIMUM_BALANCE_USD_FOR_PRIORITIZATION,
401
+ ) // Use minimum balance for prioritization
335
402
  bestOriginToken = sortedByBalanceAndVolume[0] ?? null
336
403
  logger.console.log(
337
404
  "[trails-sdk] No destination specified, selected highest value token with best liquidity",
@@ -358,30 +425,39 @@ function useDefaultTokenSelectionInternal(): UseDefaultTokenSelectionReturn {
358
425
  let destinationToken: DefaultToken | null = null
359
426
 
360
427
  if (shouldComputeDestination) {
361
- // Determine default destination chain: Base if origin is not Base, Arbitrum if origin is Base
428
+ // Determine destination chain: Base if origin is not Base, Arbitrum if origin is Base
362
429
  const defaultDestChainId =
363
430
  originToken.chainId === base.id ? arbitrum.id : base.id
364
431
 
365
- // Find matching token on destination chain (prefer same symbol)
366
- const destChainTokens = supportedTokens.filter(
367
- (token: any) => token.chainId === defaultDestChainId,
432
+ // Find USDC on destination chain first
433
+ const usdcOnDestChain = supportedTokens.find(
434
+ (token: any) =>
435
+ token.chainId === defaultDestChainId && token.symbol === "USDC",
368
436
  )
369
437
 
370
438
  let destToken: any = null
371
439
 
372
- // Try to find same symbol on dest chain
373
- const sameSymbolToken = destChainTokens.find((token: any) => {
374
- return token.symbol.toUpperCase() === originToken.symbol.toUpperCase()
375
- })
376
-
377
- if (sameSymbolToken) {
378
- destToken = sameSymbolToken
440
+ if (usdcOnDestChain) {
441
+ destToken = usdcOnDestChain
379
442
  } else {
380
- // Fallback: USDC > ETH > first available token
381
- destToken =
382
- destChainTokens.find((token: any) => token.symbol === "USDC") ||
383
- destChainTokens.find((token: any) => token.symbol === "ETH") ||
384
- destChainTokens[0]
443
+ // Fallback: Find matching token on destination chain
444
+ const destChainTokens = supportedTokens.filter(
445
+ (token: any) => token.chainId === defaultDestChainId,
446
+ )
447
+
448
+ // Try to find same symbol on destination chain first
449
+ const sameSymbolOnDestChain = destChainTokens.find((token: any) => {
450
+ return token.symbol.toUpperCase() === originToken.symbol.toUpperCase()
451
+ })
452
+
453
+ if (sameSymbolOnDestChain) {
454
+ destToken = sameSymbolOnDestChain
455
+ } else {
456
+ // Fallback: ETH on destination chain > first available token on destination chain
457
+ destToken =
458
+ destChainTokens.find((token: any) => token.symbol === "ETH") ||
459
+ destChainTokens[0]
460
+ }
385
461
  }
386
462
 
387
463
  const decimals = destToken?.decimals
@@ -8,6 +8,7 @@ import { truncateAddress } from "../../utils.js"
8
8
  import { getExplorerUrlForAddress } from "../../explorer.js"
9
9
  import ChainImage from "../components/ChainImage.js"
10
10
  import TokenImage from "../components/TokenImage.js"
11
+ import { Identicon } from "../components/Identicon.js"
11
12
  import { logger } from "../../logger.js"
12
13
 
13
14
  export interface PayMessagePart {
@@ -85,25 +86,23 @@ export function usePayMessage(): UsePayMessageReturn {
85
86
  })
86
87
 
87
88
  const parsed = useMemo(() => {
88
- // Default message if no payMessage provided
89
- const defaultMessage = appName
90
- ? `Pay {APP_IMAGE} {APP_NAME} {TO_AMOUNT_USD}`
91
- : `Pay {TO_AMOUNT_USD}`
92
-
93
- const template = payMessage || defaultMessage
89
+ // If no payMessage provided, return empty parts (default layout handled separately)
90
+ if (!payMessage) {
91
+ return []
92
+ }
94
93
 
95
94
  // Parse the template into parts
96
95
  const parts: PayMessagePart[] = []
97
96
  const regex = /\{([^}]+)\}/g
98
97
  let lastIndex = 0
99
- let match: RegExpExecArray | null = regex.exec(template)
98
+ let match: RegExpExecArray | null = regex.exec(payMessage)
100
99
 
101
100
  while (match !== null) {
102
101
  // Add text before the placeholder
103
102
  if (match.index > lastIndex) {
104
103
  parts.push({
105
104
  type: "text",
106
- value: template.substring(lastIndex, match.index),
105
+ value: payMessage.substring(lastIndex, match.index),
107
106
  })
108
107
  }
109
108
 
@@ -176,14 +175,14 @@ export function usePayMessage(): UsePayMessageReturn {
176
175
  }
177
176
 
178
177
  lastIndex = regex.lastIndex
179
- match = regex.exec(template)
178
+ match = regex.exec(payMessage)
180
179
  }
181
180
 
182
181
  // Add remaining text
183
- if (lastIndex < template.length) {
182
+ if (lastIndex < payMessage.length) {
184
183
  parts.push({
185
184
  type: "text",
186
- value: template.substring(lastIndex),
185
+ value: payMessage.substring(lastIndex),
187
186
  })
188
187
  }
189
188
 
@@ -206,6 +205,78 @@ export function usePayMessage(): UsePayMessageReturn {
206
205
 
207
206
  // Render the message
208
207
  const message = useMemo(() => {
208
+ // If no payMessage provided, render default layout
209
+ if (!payMessage) {
210
+ return (
211
+ <div className="flex flex-col items-center justify-center px-8 py-4 space-y-4 min-w-full">
212
+ {/* App image above the amount if it exists */}
213
+ {appImageUrl && (
214
+ <img
215
+ src={appImageUrl}
216
+ alt={appName || "App"}
217
+ className="w-12 h-12 rounded-lg"
218
+ onError={(e) => {
219
+ logger.console.error(
220
+ `Error loading app image: ${e}, appImageUrl: ${appImageUrl}`,
221
+ )
222
+ // Hide image on error
223
+ e.currentTarget.style.display = "none"
224
+ }}
225
+ />
226
+ )}
227
+
228
+ {/* Large amount in center */}
229
+ <div className="font-bold text-center">
230
+ {(() => {
231
+ const amountToShow = targetAmountUsdFormatted || toAmount || "0"
232
+ const tokenSymbol = tokenInfo?.symbol || ""
233
+ const displayText = targetAmountUsdFormatted
234
+ ? amountToShow
235
+ : `${amountToShow} ${tokenSymbol}`.trim()
236
+
237
+ // Dynamic font size based on text length
238
+ const textLength = displayText.length
239
+ let fontSize = "text-6xl" // Default large size
240
+
241
+ if (textLength > 12) {
242
+ fontSize = "text-2xl"
243
+ } else if (textLength > 10) {
244
+ fontSize = "text-3xl"
245
+ } else if (textLength > 8) {
246
+ fontSize = "text-4xl"
247
+ } else if (textLength > 6) {
248
+ fontSize = "text-5xl"
249
+ }
250
+
251
+ return <div className={fontSize}>{displayText}</div>
252
+ })()}
253
+ </div>
254
+
255
+ {/* Recipient info below */}
256
+ {toAddress && (
257
+ <div className="flex items-center space-x-2 text-center">
258
+ <span className="text-sm text-gray-600 dark:text-gray-400">
259
+ to
260
+ </span>
261
+ <Identicon value={toAddress} size={20} />
262
+ <a
263
+ href={getExplorerUrlForAddress({
264
+ address: toAddress,
265
+ chainId: toChainId ? Number(toChainId) : 1,
266
+ })}
267
+ target="_blank"
268
+ rel="noopener noreferrer"
269
+ className="text-sm hover:underline cursor-pointer text-blue-600 dark:text-blue-400"
270
+ >
271
+ {truncateAddress(toAddress)}
272
+ </a>
273
+ </div>
274
+ )}
275
+ </div>
276
+ )
277
+ }
278
+
279
+ // Render user-provided message with handlebar interpolation
209
280
  return (
210
281
  <>
211
282
  {parsed.map((part) => {
@@ -354,6 +425,7 @@ export function usePayMessage(): UsePayMessageReturn {
354
425
  </>
355
426
  )
356
427
  }, [
428
+ payMessage,
357
429
  parsed,
358
430
  appName,
359
431
  appUrl,
@@ -361,6 +433,9 @@ export function usePayMessage(): UsePayMessageReturn {
361
433
  appDescription,
362
434
  tokenInfo,
363
435
  toChainId,
436
+ toAddress,
437
+ targetAmountUsdFormatted,
438
+ toAmount,
364
439
  ])
365
440
 
366
441
  return {
@@ -0,0 +1,41 @@
1
+ import { createContext, useContext, useState, type ReactNode } from "react"
2
+
3
+ interface SelectedFundMethodContextType {
4
+ selectedFundMethod: string
5
+ setSelectedFundMethod: (method: string) => void
6
+ }
7
+
8
+ const SelectedFundMethodContext = createContext<
9
+ SelectedFundMethodContextType | undefined
10
+ >(undefined)
11
+
12
+ interface SelectedFundMethodProviderProps {
13
+ children: ReactNode
14
+ }
15
+
16
+ export const SelectedFundMethodProvider = ({
17
+ children,
18
+ }: SelectedFundMethodProviderProps) => {
19
+ const [selectedFundMethod, setSelectedFundMethod] = useState<string>("wallet")
20
+
21
+ const value: SelectedFundMethodContextType = {
22
+ selectedFundMethod,
23
+ setSelectedFundMethod,
24
+ }
25
+
26
+ return (
27
+ <SelectedFundMethodContext.Provider value={value}>
28
+ {children}
29
+ </SelectedFundMethodContext.Provider>
30
+ )
31
+ }
32
+
33
+ export const useSelectedFundMethod = (): SelectedFundMethodContextType => {
34
+ const context = useContext(SelectedFundMethodContext)
35
+ if (context === undefined) {
36
+ throw new Error(
37
+ "useSelectedFundMethod must be used within a SelectedFundMethodProvider",
38
+ )
39
+ }
40
+ return context
41
+ }
@@ -2,8 +2,10 @@ import React, {
2
2
  createContext,
3
3
  useContext,
4
4
  useState,
5
+ useEffect,
5
6
  type ReactNode,
6
7
  } from "react"
8
+ import { useAccount } from "wagmi"
7
9
 
8
10
  interface SelectedRecipientContextType {
9
11
  selectedRecipient: string | null
@@ -21,10 +23,18 @@ interface SelectedRecipientProviderProps {
21
23
  export const SelectedRecipientProvider: React.FC<
22
24
  SelectedRecipientProviderProps
23
25
  > = ({ children }) => {
26
+ const { address } = useAccount()
24
27
  const [selectedRecipient, setSelectedRecipient] = useState<string | null>(
25
28
  null,
26
29
  )
27
30
 
31
+ // Set recipient to address only if address exists and no recipient is selected
32
+ useEffect(() => {
33
+ if (address && !selectedRecipient) {
34
+ setSelectedRecipient(address)
35
+ }
36
+ }, [address, selectedRecipient])
37
+
28
38
  return (
29
39
  <SelectedRecipientContext.Provider
30
40
  value={{
@@ -9,6 +9,7 @@ import {
9
9
  type WalletClient,
10
10
  zeroAddress,
11
11
  } from "viem"
12
+ import { useAccount } from "wagmi"
12
13
  import { useAPIClient } from "../../apiClient.js"
13
14
  import { getChainInfo, useSupportedChains } from "../../chains.js"
14
15
  import { getFullErrorMessage, getPrettifiedErrorMessage } from "../../error.js"
@@ -207,6 +208,10 @@ export function useSendForm({
207
208
  checkoutOnHandlers,
208
209
  refetchTrigger = 0,
209
210
  }: UseSendProps): UseSendReturn {
211
+ // Get the active wallet ID from wagmi
212
+ const { connector } = useAccount()
213
+ const walletId = connector?.id
214
+
210
215
  // Auto-set quoteProvider to "lifi" if either from or to chain is etherlink
211
216
  const effectiveQuoteProvider = useMemo(() => {
212
217
  if (!quoteProvider || quoteProvider === "auto") {
@@ -804,6 +809,9 @@ export function useSendForm({
804
809
  nativeTokenPriceUsd = nativePrice?.price?.value ?? 0
805
810
  }
806
811
 
812
+ // Disable gasless for sequence-waas wallet
813
+ const effectiveGasless = walletId === "sequence-waas" ? false : gasless
814
+
807
815
  const options = {
808
816
  account,
809
817
  originTokenAddress: selectedToken.contractAddress,
@@ -840,13 +848,14 @@ export function useSendForm({
840
848
  paymasterUrls?.find(
841
849
  (p) => p.chainId.toString() === selectedToken.chainId.toString(),
842
850
  )?.url ?? undefined,
843
- gasless,
851
+ gasless: effectiveGasless,
844
852
  originNativeTokenPriceUsd: nativeTokenPriceUsd,
845
853
  quoteProvider: effectiveQuoteProvider,
846
854
  mode,
847
855
  fundMethod,
848
856
  checkoutOnHandlers,
849
857
  selectedFeeToken,
858
+ walletId,
850
859
  }
851
860
 
852
861
  logger.console.log(
@@ -901,6 +910,7 @@ export function useSendForm({
901
910
  checkoutOnHandlers,
902
911
  mode,
903
912
  selectedFeeToken,
913
+ walletId,
904
914
  ])
905
915
 
906
916
  // Auto-fetch quotes when inputs change (debounced)
@@ -1194,7 +1204,19 @@ export function useSendForm({
1194
1204
  }
1195
1205
 
1196
1206
  if (!fundMethod || fundMethod === "wallet") {
1197
- return `Continue on wallet`
1207
+ if (mode === "pay") {
1208
+ return `Pay`
1209
+ } else if (mode === "fund") {
1210
+ return `Fund`
1211
+ } else if (mode === "swap") {
1212
+ return `Swap`
1213
+ } else if (mode === "earn") {
1214
+ return `Deposit`
1215
+ } else if (mode === "receive") {
1216
+ return `Pay`
1217
+ } else {
1218
+ return `Continue on wallet`
1219
+ }
1198
1220
  }
1199
1221
 
1200
1222
  if (mode === "swap") {
@@ -39,6 +39,11 @@
39
39
  --trails-modal-button-hover-bg: var(--trails-primary-hover, rgb(17 75 202)); /* Defaults to primary hover */
40
40
  --trails-modal-button-text: rgb(255 255 255); /* white text */
41
41
  --trails-modal-button-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); /* subtle shadow */
42
+
43
+ /* Percentage Button Colors - Customizable percentage buttons */
44
+ --trails-percentage-button-bg: rgb(228 228 231); /* #E4E4E7 - Light gray background */
45
+ --trails-percentage-button-text: rgb(113 113 123); /* #71717B - Dark gray text */
46
+ --trails-percentage-button-hover-bg: rgb(212 212 216); /* #D4D4D8 - Slightly darker on hover */
42
47
  }
43
48
 
44
49
  /* Utility Classes that use CSS Variables */
@@ -110,6 +115,18 @@
110
115
  color: var(--trails-hover-text);
111
116
  }
112
117
 
118
+ .trails-bg-percentage-button {
119
+ background-color: var(--trails-percentage-button-bg);
120
+ }
121
+
122
+ .trails-text-percentage-button {
123
+ color: var(--trails-percentage-button-text);
124
+ }
125
+
126
+ .trails-hover-percentage-button:hover {
127
+ background-color: var(--trails-percentage-button-hover-bg);
128
+ }
129
+
113
130
 
114
131
 
115
132
  /* Font Family Utility Class */
@@ -304,6 +321,11 @@
304
321
  /* Shadow */
305
322
  --trails-shadow:
306
323
  0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
324
+
325
+ /* Percentage Button Colors - Light Mode */
326
+ --trails-percentage-button-bg: rgb(228 228 231); /* #E4E4E7 - Light gray background */
327
+ --trails-percentage-button-text: rgb(113 113 123); /* #71717B - Dark gray text */
328
+ --trails-percentage-button-hover-bg: rgb(212 212 216); /* #D4D4D8 - Slightly darker on hover */
307
329
  }
308
330
 
309
331
  /* Dark Mode Theme Variables */
@@ -391,6 +413,11 @@
391
413
  /* Shadow */
392
414
  --trails-shadow:
393
415
  0 1px 3px 0 rgb(0 0 0 / 0.3), 0 1px 2px -1px rgb(0 0 0 / 0.3);
416
+
417
+ /* Percentage Button Colors - Dark Mode */
418
+ --trails-percentage-button-bg: rgb(55 65 81); /* gray-700 - Dark gray background */
419
+ --trails-percentage-button-text: rgb(209 213 219); /* gray-300 - Light gray text */
420
+ --trails-percentage-button-hover-bg: rgb(75 85 99); /* gray-600 - Slightly lighter on hover */
394
421
  }
395
422
 
396
423
  /* Custom Scrollbar Styles */