0xtrails 0.1.13 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/dist/aave.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +12 -2
  3. package/dist/analytics.d.ts.map +1 -1
  4. package/dist/apiClient.d.ts +1 -1
  5. package/dist/apiClient.d.ts.map +1 -1
  6. package/dist/{ccip-D3gTQONK.js → ccip-BbfANth7.js} +12 -12
  7. package/dist/cctp.d.ts.map +1 -1
  8. package/dist/cctpqueue.d.ts +3 -3
  9. package/dist/cctpqueue.d.ts.map +1 -1
  10. package/dist/chains.d.ts.map +1 -1
  11. package/dist/config.d.ts +18 -5
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/constants.d.ts +6 -5
  14. package/dist/constants.d.ts.map +1 -1
  15. package/dist/contractUtils.d.ts +2 -0
  16. package/dist/contractUtils.d.ts.map +1 -1
  17. package/dist/customChains.d.ts +24 -0
  18. package/dist/customChains.d.ts.map +1 -0
  19. package/dist/gasless.d.ts +19 -7
  20. package/dist/gasless.d.ts.map +1 -1
  21. package/dist/{index-CnUM7lKf.js → index-WpIVoh3X.js} +36741 -31761
  22. package/dist/index.d.ts +5 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +405 -394
  25. package/dist/indexerClient.d.ts +10 -0
  26. package/dist/indexerClient.d.ts.map +1 -1
  27. package/dist/intentEntrypoint.d.ts +122 -0
  28. package/dist/intentEntrypoint.d.ts.map +1 -0
  29. package/dist/intents.d.ts +5 -3
  30. package/dist/intents.d.ts.map +1 -1
  31. package/dist/metaTxnMonitor.d.ts.map +1 -1
  32. package/dist/morpho.d.ts.map +1 -1
  33. package/dist/pools.d.ts +3 -1
  34. package/dist/pools.d.ts.map +1 -1
  35. package/dist/prepareSend.d.ts +18 -9
  36. package/dist/prepareSend.d.ts.map +1 -1
  37. package/dist/prices.d.ts +1 -1
  38. package/dist/prices.d.ts.map +1 -1
  39. package/dist/relaySdk.d.ts.map +1 -1
  40. package/dist/relayer.d.ts.map +1 -1
  41. package/dist/toast.d.ts +9 -0
  42. package/dist/toast.d.ts.map +1 -0
  43. package/dist/tokenBalances.d.ts +6 -2
  44. package/dist/tokenBalances.d.ts.map +1 -1
  45. package/dist/tokens.d.ts.map +1 -1
  46. package/dist/trails.d.ts +6 -5
  47. package/dist/trails.d.ts.map +1 -1
  48. package/dist/trailsClient.d.ts +12 -0
  49. package/dist/trailsClient.d.ts.map +1 -0
  50. package/dist/trailsRouter.d.ts +22 -0
  51. package/dist/trailsRouter.d.ts.map +1 -0
  52. package/dist/transactions.d.ts +8 -1
  53. package/dist/transactions.d.ts.map +1 -1
  54. package/dist/wallets.d.ts.map +1 -1
  55. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  56. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  57. package/dist/widget/components/AccountSettings.d.ts +7 -0
  58. package/dist/widget/components/AccountSettings.d.ts.map +1 -0
  59. package/dist/widget/components/ChainList.d.ts +0 -1
  60. package/dist/widget/components/ChainList.d.ts.map +1 -1
  61. package/dist/widget/components/ClassicSwap.d.ts +46 -0
  62. package/dist/widget/components/ClassicSwap.d.ts.map +1 -0
  63. package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
  64. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  65. package/dist/widget/components/ConnectedWallets.d.ts +9 -0
  66. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -0
  67. package/dist/widget/components/DebugMenu.d.ts.map +1 -1
  68. package/dist/widget/components/DebugScreensList.d.ts.map +1 -1
  69. package/dist/widget/components/DebugToast.d.ts +3 -0
  70. package/dist/widget/components/DebugToast.d.ts.map +1 -0
  71. package/dist/widget/components/Earn.d.ts.map +1 -1
  72. package/dist/widget/components/EarnPools.d.ts.map +1 -1
  73. package/dist/widget/components/FeeOption.d.ts +22 -0
  74. package/dist/widget/components/FeeOption.d.ts.map +1 -0
  75. package/dist/widget/components/FeeOptions.d.ts +13 -17
  76. package/dist/widget/components/FeeOptions.d.ts.map +1 -1
  77. package/dist/widget/components/Fund.d.ts +44 -0
  78. package/dist/widget/components/Fund.d.ts.map +1 -0
  79. package/dist/widget/components/FundMethods.d.ts +1 -1
  80. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  81. package/dist/widget/components/FundSendForm.d.ts.map +1 -1
  82. package/dist/widget/components/Identicon.d.ts +9 -0
  83. package/dist/widget/components/Identicon.d.ts.map +1 -0
  84. package/dist/widget/components/MeshConnectExchanges.d.ts +5 -2
  85. package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
  86. package/dist/widget/components/MeshConnectFlow.d.ts +2 -0
  87. package/dist/widget/components/MeshConnectFlow.d.ts.map +1 -1
  88. package/dist/widget/components/NativeGasOption.d.ts +12 -0
  89. package/dist/widget/components/NativeGasOption.d.ts.map +1 -0
  90. package/dist/widget/components/Pay.d.ts +46 -0
  91. package/dist/widget/components/Pay.d.ts.map +1 -0
  92. package/dist/widget/components/PaySendForm.d.ts.map +1 -1
  93. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  94. package/dist/widget/components/Receive.d.ts.map +1 -1
  95. package/dist/widget/components/RecentTokens.d.ts.map +1 -1
  96. package/dist/widget/components/Recipients.d.ts +9 -0
  97. package/dist/widget/components/Recipients.d.ts.map +1 -0
  98. package/dist/widget/components/RefundWarning.d.ts +9 -0
  99. package/dist/widget/components/RefundWarning.d.ts.map +1 -0
  100. package/dist/widget/components/SimpleSwap.d.ts.map +1 -1
  101. package/dist/widget/components/Swap.d.ts.map +1 -1
  102. package/dist/widget/components/SwapSettings.d.ts +1 -5
  103. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  104. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  105. package/dist/widget/components/ThemeSyncer.d.ts +6 -0
  106. package/dist/widget/components/ThemeSyncer.d.ts.map +1 -0
  107. package/dist/widget/components/Toast.d.ts +24 -0
  108. package/dist/widget/components/Toast.d.ts.map +1 -0
  109. package/dist/widget/components/TokenList.d.ts.map +1 -1
  110. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  111. package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
  112. package/dist/widget/components/TruncatedAddress.d.ts +2 -0
  113. package/dist/widget/components/TruncatedAddress.d.ts.map +1 -1
  114. package/dist/widget/components/UserPreferences.d.ts +7 -0
  115. package/dist/widget/components/UserPreferences.d.ts.map +1 -0
  116. package/dist/widget/hooks/useBack.d.ts +2 -0
  117. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  118. package/dist/widget/hooks/useBalanceVisible.d.ts +1 -0
  119. package/dist/widget/hooks/useBalanceVisible.d.ts.map +1 -1
  120. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  121. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  122. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  123. package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
  124. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  125. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +54 -0
  126. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -0
  127. package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
  128. package/dist/widget/hooks/usePayMessage.d.ts +34 -0
  129. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -0
  130. package/dist/widget/hooks/useRecipients.d.ts +17 -0
  131. package/dist/widget/hooks/useRecipients.d.ts.map +1 -0
  132. package/dist/widget/hooks/useSelectedFeeToken.d.ts +32 -0
  133. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -0
  134. package/dist/widget/hooks/useSelectedMeshExchange.d.ts +14 -0
  135. package/dist/widget/hooks/useSelectedMeshExchange.d.ts.map +1 -0
  136. package/dist/widget/hooks/useSelectedRecipient.d.ts +12 -0
  137. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -0
  138. package/dist/widget/hooks/useSendForm.d.ts +10 -13
  139. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  140. package/dist/widget/hooks/useSwapAmount.d.ts +13 -0
  141. package/dist/widget/hooks/useSwapAmount.d.ts.map +1 -0
  142. package/dist/widget/hooks/useSwapSettings.d.ts +16 -0
  143. package/dist/widget/hooks/useSwapSettings.d.ts.map +1 -0
  144. package/dist/widget/hooks/useTargetAmount.d.ts +5 -0
  145. package/dist/widget/hooks/useTargetAmount.d.ts.map +1 -0
  146. package/dist/widget/hooks/useTheme.d.ts +14 -0
  147. package/dist/widget/hooks/useTheme.d.ts.map +1 -0
  148. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  149. package/dist/widget/index.js +2 -2
  150. package/dist/widget/widget.d.ts +9 -0
  151. package/dist/widget/widget.d.ts.map +1 -1
  152. package/package.json +38 -36
  153. package/src/aave.ts +6 -1
  154. package/src/analytics.ts +109 -53
  155. package/src/apiClient.ts +1 -1
  156. package/src/cctp.ts +6 -2
  157. package/src/cctpqueue.ts +7 -7
  158. package/src/chains.ts +18 -0
  159. package/src/config.ts +63 -17
  160. package/src/constants.ts +20 -16
  161. package/src/contractUtils.ts +33 -2
  162. package/src/customChains.ts +24 -0
  163. package/src/gasless.ts +162 -109
  164. package/src/index.ts +11 -1
  165. package/src/indexerClient.ts +73 -1
  166. package/src/intentEntrypoint.ts +218 -0
  167. package/src/intents.ts +85 -54
  168. package/src/metaTxnMonitor.ts +1 -0
  169. package/src/morpho.ts +13 -2
  170. package/src/pools.ts +68 -86
  171. package/src/prepareSend.ts +1719 -967
  172. package/src/prices.ts +51 -7
  173. package/src/relaySdk.ts +6 -4
  174. package/src/relayer.ts +6 -3
  175. package/src/toast.ts +110 -0
  176. package/src/tokenBalances.ts +112 -20
  177. package/src/tokens.ts +70 -7
  178. package/src/trails.ts +81 -80
  179. package/src/trailsClient.ts +48 -0
  180. package/src/{proxyCaller.ts → trailsRouter.ts} +25 -20
  181. package/src/transactions.ts +30 -88
  182. package/src/umd.tsx +1 -1
  183. package/src/wallets.ts +2 -1
  184. package/src/widget/assets/sequence-logo.svg +15 -0
  185. package/src/widget/compiled.css +2 -2
  186. package/src/widget/components/AccountActionsDropdown.tsx +18 -159
  187. package/src/widget/components/AccountIntentTransactionHistory.tsx +346 -63
  188. package/src/widget/components/AccountSettings.tsx +102 -0
  189. package/src/widget/components/ChainFilterDropdown.tsx +1 -1
  190. package/src/widget/components/ChainList.tsx +10 -20
  191. package/src/widget/components/ClassicSwap.tsx +921 -0
  192. package/src/widget/components/ConfigDisplay.tsx +41 -5
  193. package/src/widget/components/ConnectWallet.tsx +168 -11
  194. package/src/widget/components/ConnectedWallets.tsx +342 -0
  195. package/src/widget/components/DebugMenu.tsx +2 -0
  196. package/src/widget/components/DebugScreensList.tsx +3 -0
  197. package/src/widget/components/DebugToast.tsx +63 -0
  198. package/src/widget/components/Earn.tsx +112 -143
  199. package/src/widget/components/EarnPools.tsx +2 -4
  200. package/src/widget/components/EarnPoolsFilters.tsx +6 -6
  201. package/src/widget/components/FeeOption.tsx +78 -0
  202. package/src/widget/components/FeeOptions.tsx +192 -127
  203. package/src/widget/components/Fund.tsx +1236 -0
  204. package/src/widget/components/FundMethods.tsx +4 -4
  205. package/src/widget/components/FundSendForm.tsx +1 -34
  206. package/src/widget/components/Identicon.tsx +158 -0
  207. package/src/widget/components/MeshConnectExchanges.tsx +32 -3
  208. package/src/widget/components/MeshConnectFlow.tsx +23 -4
  209. package/src/widget/components/NativeGasOption.tsx +99 -0
  210. package/src/widget/components/Pay.tsx +1092 -0
  211. package/src/widget/components/PaySendForm.tsx +1 -38
  212. package/src/widget/components/QuoteDetails.tsx +1 -30
  213. package/src/widget/components/Receipt.tsx +1 -1
  214. package/src/widget/components/Receive.tsx +4 -2
  215. package/src/widget/components/RecentTokens.tsx +2 -1
  216. package/src/widget/components/Recipients.tsx +448 -0
  217. package/src/widget/components/RefundWarning.tsx +61 -0
  218. package/src/widget/components/ScreenHeader.tsx +1 -1
  219. package/src/widget/components/SimpleSwap.tsx +74 -58
  220. package/src/widget/components/Swap.tsx +35 -853
  221. package/src/widget/components/SwapSettings.tsx +5 -11
  222. package/src/widget/components/ThemeProvider.tsx +32 -0
  223. package/src/widget/components/ThemeSyncer.tsx +47 -0
  224. package/src/widget/components/Toast.tsx +315 -0
  225. package/src/widget/components/TokenList.tsx +2 -34
  226. package/src/widget/components/TokenSelector.tsx +14 -3
  227. package/src/widget/components/TransactionDetails.tsx +153 -13
  228. package/src/widget/components/TransferPendingVertical.tsx +1 -1
  229. package/src/widget/components/TruncatedAddress.tsx +5 -1
  230. package/src/widget/components/UserPreferences.tsx +155 -0
  231. package/src/widget/components/WalletList.tsx +1 -1
  232. package/src/widget/hooks/useBack.tsx +4 -0
  233. package/src/widget/hooks/useBalanceVisible.tsx +40 -2
  234. package/src/widget/hooks/useCheckout.ts +13 -0
  235. package/src/widget/hooks/useCurrentScreen.tsx +4 -0
  236. package/src/widget/hooks/useDebugScreens.ts +12 -2
  237. package/src/widget/hooks/useDefaultTokenSelection.tsx +471 -0
  238. package/src/widget/hooks/useIntentTransactionHistory.ts +212 -0
  239. package/src/widget/hooks/usePayMessage.tsx +370 -0
  240. package/src/widget/hooks/useRecipients.ts +168 -0
  241. package/src/widget/hooks/useSelectedFeeToken.tsx +299 -0
  242. package/src/widget/hooks/useSelectedMeshExchange.tsx +46 -0
  243. package/src/widget/hooks/useSelectedRecipient.tsx +48 -0
  244. package/src/widget/hooks/useSendForm.ts +257 -49
  245. package/src/widget/hooks/useSwapAmount.tsx +50 -0
  246. package/src/widget/hooks/useSwapSettings.tsx +100 -0
  247. package/src/widget/hooks/useTargetAmount.ts +23 -0
  248. package/src/widget/hooks/useTheme.tsx +80 -0
  249. package/src/widget/hooks/useTokenList.ts +20 -11
  250. package/src/widget/index.css +45 -21
  251. package/src/widget/widget.tsx +294 -136
  252. package/dist/address.d.ts +0 -2
  253. package/dist/address.d.ts.map +0 -1
  254. package/dist/proxyCaller.d.ts +0 -21
  255. package/dist/proxyCaller.d.ts.map +0 -1
  256. package/src/address.ts +0 -6
@@ -0,0 +1,370 @@
1
+ import { useMemo, createElement } from "react"
2
+ import type { ReactNode } from "react"
3
+ import { useWidgetProps } from "./useWidgetProps.js"
4
+ import { useTargetAmount } from "./useTargetAmount.js"
5
+ import { useTokenInfo } from "../../tokens.js"
6
+ import { getChainInfo } from "../../chains.js"
7
+ import { truncateAddress } from "../../utils.js"
8
+ import { getExplorerUrlForAddress } from "../../explorer.js"
9
+ import ChainImage from "../components/ChainImage.js"
10
+ import TokenImage from "../components/TokenImage.js"
11
+ import { logger } from "../../logger.js"
12
+
13
+ export interface PayMessagePart {
14
+ type:
15
+ | "text"
16
+ | "appImage"
17
+ | "appName"
18
+ | "appUrl"
19
+ | "amount"
20
+ | "toAddress"
21
+ | "toAddressTruncated"
22
+ | "toChainId"
23
+ | "toAmount"
24
+ | "toTokenSymbol"
25
+ | "toTokenName"
26
+ | "toChainName"
27
+ | "toTokenImage"
28
+ | "toChainImage"
29
+ value: string
30
+ }
31
+
32
+ export interface UsePayMessageReturn {
33
+ message: ReactNode
34
+ parts: PayMessagePart[]
35
+ }
36
+
37
+ /**
38
+ * Hook for parsing and rendering payment messages with handlebar-style interpolation.
39
+ *
40
+ * Supported placeholders:
41
+ * - {APP_NAME} - App name from widget props
42
+ * - {APP_URL} - App URL from widget props
43
+ * - {APP_IMAGE} - Rendered app image component
44
+ * - {APP_DESCRIPTION} - App description from widget props
45
+ * - {TO_AMOUNT_USD} - Target amount in USD
46
+ * - {TO_ADDRESS} - Full destination address (linked to explorer)
47
+ * - {TO_ADDRESS_TRUNCATED} - Truncated destination address (linked to explorer)
48
+ * - {TO_CHAIN_ID} - Destination chain ID
49
+ * - {TO_AMOUNT} - Destination amount
50
+ * - {TO_TOKEN_SYMBOL} - Destination token symbol
51
+ * - {TO_TOKEN_NAME} - Destination token name
52
+ * - {TO_CHAIN_NAME} - Destination chain name
53
+ * - {TO_TOKEN_IMAGE} - Rendered destination token image
54
+ * - {TO_CHAIN_IMAGE} - Rendered destination chain image
55
+ *
56
+ * @example
57
+ * payMessage: "Pay {APP_IMAGE} {APP_NAME} {TO_AMOUNT_USD}"
58
+ * // Renders: Pay [icon] MyApp $5.00
59
+ */
60
+ export function usePayMessage(): UsePayMessageReturn {
61
+ const {
62
+ payMessage,
63
+ appName,
64
+ appUrl,
65
+ appImageUrl,
66
+ appDescription,
67
+ toAddress,
68
+ toChainId,
69
+ toAmount,
70
+ toToken,
71
+ } = useWidgetProps()
72
+
73
+ const { targetAmountUsdFormatted } = useTargetAmount()
74
+
75
+ // Get chain info
76
+ const chainInfo = useMemo(() => {
77
+ if (!toChainId) return null
78
+ return getChainInfo(Number(toChainId))
79
+ }, [toChainId])
80
+
81
+ // Get token info
82
+ const { tokenInfo } = useTokenInfo({
83
+ address: toToken || "",
84
+ chainId: toChainId ? Number(toChainId) : undefined,
85
+ })
86
+
87
+ 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
94
+
95
+ // Parse the template into parts
96
+ const parts: PayMessagePart[] = []
97
+ const regex = /\{([^}]+)\}/g
98
+ let lastIndex = 0
99
+ let match: RegExpExecArray | null = regex.exec(template)
100
+
101
+ while (match !== null) {
102
+ // Add text before the placeholder
103
+ if (match.index > lastIndex) {
104
+ parts.push({
105
+ type: "text",
106
+ value: template.substring(lastIndex, match.index),
107
+ })
108
+ }
109
+
110
+ // Add the placeholder
111
+ const placeholder = match[1]
112
+ switch (placeholder) {
113
+ case "APP_NAME":
114
+ parts.push({ type: "appName", value: appName || "" })
115
+ break
116
+ case "APP_URL":
117
+ parts.push({ type: "appUrl", value: appUrl || "" })
118
+ break
119
+ case "APP_IMAGE":
120
+ parts.push({ type: "appImage", value: appImageUrl || "" })
121
+ break
122
+ case "APP_DESCRIPTION":
123
+ parts.push({ type: "text", value: appDescription || "" })
124
+ break
125
+ case "TO_AMOUNT_USD":
126
+ parts.push({
127
+ type: "amount",
128
+ value: targetAmountUsdFormatted || "$0.00",
129
+ })
130
+ break
131
+ case "TO_ADDRESS":
132
+ parts.push({ type: "toAddress", value: toAddress || "" })
133
+ break
134
+ case "TO_ADDRESS_TRUNCATED":
135
+ parts.push({
136
+ type: "toAddressTruncated",
137
+ value: toAddress || "",
138
+ })
139
+ break
140
+ case "TO_CHAIN_ID":
141
+ parts.push({
142
+ type: "toChainId",
143
+ value: toChainId ? String(toChainId) : "",
144
+ })
145
+ break
146
+ case "TO_AMOUNT":
147
+ parts.push({ type: "toAmount", value: toAmount || "" })
148
+ break
149
+ case "TO_TOKEN_SYMBOL":
150
+ parts.push({
151
+ type: "toTokenSymbol",
152
+ value: tokenInfo?.symbol || "",
153
+ })
154
+ break
155
+ case "TO_TOKEN_NAME":
156
+ parts.push({ type: "toTokenName", value: tokenInfo?.name || "" })
157
+ break
158
+ case "TO_CHAIN_NAME":
159
+ parts.push({ type: "toChainName", value: chainInfo?.name || "" })
160
+ break
161
+ case "TO_TOKEN_IMAGE":
162
+ parts.push({
163
+ type: "toTokenImage",
164
+ value: tokenInfo?.imageUrl || "",
165
+ })
166
+ break
167
+ case "TO_CHAIN_IMAGE":
168
+ parts.push({
169
+ type: "toChainImage",
170
+ value: toChainId ? String(toChainId) : "",
171
+ })
172
+ break
173
+ default:
174
+ // Unknown placeholder, keep as text
175
+ parts.push({ type: "text", value: match[0] })
176
+ }
177
+
178
+ lastIndex = regex.lastIndex
179
+ match = regex.exec(template)
180
+ }
181
+
182
+ // Add remaining text
183
+ if (lastIndex < template.length) {
184
+ parts.push({
185
+ type: "text",
186
+ value: template.substring(lastIndex),
187
+ })
188
+ }
189
+
190
+ return parts
191
+ }, [
192
+ payMessage,
193
+ appName,
194
+ appUrl,
195
+ appImageUrl,
196
+ appDescription,
197
+ targetAmountUsdFormatted,
198
+ toAddress,
199
+ toAmount,
200
+ tokenInfo?.symbol,
201
+ tokenInfo?.name,
202
+ tokenInfo?.imageUrl,
203
+ chainInfo?.name,
204
+ toChainId,
205
+ ])
206
+
207
+ // Render the message
208
+ const message = useMemo(() => {
209
+ return (
210
+ <>
211
+ {parsed.map((part) => {
212
+ switch (part.type) {
213
+ case "appImage":
214
+ return part.value ? (
215
+ <img
216
+ key={`${part.type}`}
217
+ src={part.value}
218
+ alt={appName || "App"}
219
+ className="inline-block w-4 h-4 mx-1 rounded"
220
+ onError={(e) => {
221
+ logger.console.error(
222
+ `Error loading app image: ${e}, appImageUrl: ${appImageUrl}`,
223
+ )
224
+ // Hide image on error
225
+ e.currentTarget.style.display = "none"
226
+ }}
227
+ />
228
+ ) : null
229
+ case "appName":
230
+ return appUrl ? (
231
+ <a
232
+ key={`${part.type}`}
233
+ href={appUrl}
234
+ target="_blank"
235
+ rel="noopener noreferrer"
236
+ className="font-bold hover:underline cursor-pointer mx-1"
237
+ title={appDescription || appName || ""}
238
+ >
239
+ {part.value}
240
+ </a>
241
+ ) : (
242
+ <span
243
+ key={`${part.type}`}
244
+ className="font-bold mx-1"
245
+ title={appDescription || ""}
246
+ >
247
+ {part.value}
248
+ </span>
249
+ )
250
+ case "amount":
251
+ return (
252
+ <span key={`${part.type}`} className="font-bold mx-1">
253
+ {part.value}
254
+ </span>
255
+ )
256
+ case "appUrl":
257
+ return part.value ? (
258
+ <a
259
+ key={`${part.type}`}
260
+ href={part.value}
261
+ target="_blank"
262
+ rel="noopener noreferrer"
263
+ className="hover:underline cursor-pointer mx-1"
264
+ >
265
+ {part.value}
266
+ </a>
267
+ ) : null
268
+ case "toAddress":
269
+ return part.value ? (
270
+ <a
271
+ key={`${part.type}`}
272
+ href={getExplorerUrlForAddress({
273
+ address: part.value,
274
+ chainId: toChainId ? Number(toChainId) : 1,
275
+ })}
276
+ target="_blank"
277
+ rel="noopener noreferrer"
278
+ className="font-mono text-sm hover:underline cursor-pointer mx-1"
279
+ >
280
+ {part.value}
281
+ </a>
282
+ ) : null
283
+ case "toAddressTruncated":
284
+ return part.value ? (
285
+ <a
286
+ key={`${part.type}`}
287
+ href={getExplorerUrlForAddress({
288
+ address: part.value,
289
+ chainId: toChainId ? Number(toChainId) : 1,
290
+ })}
291
+ target="_blank"
292
+ rel="noopener noreferrer"
293
+ className="font-mono text-sm hover:underline cursor-pointer mx-1"
294
+ >
295
+ {truncateAddress(part.value)}
296
+ </a>
297
+ ) : null
298
+ case "toChainId":
299
+ return (
300
+ <span key={`${part.type}`} className="mx-1">
301
+ {part.value}
302
+ </span>
303
+ )
304
+ case "toAmount":
305
+ return (
306
+ <span key={`${part.type}`} className="font-bold mx-1">
307
+ {part.value}
308
+ </span>
309
+ )
310
+ case "toTokenSymbol":
311
+ return (
312
+ <span key={`${part.type}`} className="font-bold mx-1">
313
+ {part.value}
314
+ </span>
315
+ )
316
+ case "toTokenName":
317
+ return (
318
+ <span key={`${part.type}`} className="mx-1">
319
+ {part.value}
320
+ </span>
321
+ )
322
+ case "toChainName":
323
+ return (
324
+ <span key={`${part.type}`} className="font-semibold mx-1">
325
+ {part.value}
326
+ </span>
327
+ )
328
+ case "toTokenImage":
329
+ return part.value && tokenInfo?.symbol ? (
330
+ <span key={`${part.type}`} className="inline-block mx-1">
331
+ {createElement(TokenImage, {
332
+ imageUrl: part.value,
333
+ symbol: tokenInfo.symbol,
334
+ chainId: toChainId ? Number(toChainId) : undefined,
335
+ size: 16,
336
+ })}
337
+ </span>
338
+ ) : null
339
+ case "toChainImage":
340
+ return toChainId ? (
341
+ <span key={`${part.type}`} className="inline-block mx-1">
342
+ {createElement(ChainImage, {
343
+ chainId: Number(toChainId),
344
+ size: 16,
345
+ })}
346
+ </span>
347
+ ) : null
348
+ case "text":
349
+ return <span key={`${part.type}`}>{part.value}</span>
350
+ default:
351
+ return <span key={`${part.type}`}>{part.value}</span>
352
+ }
353
+ })}
354
+ </>
355
+ )
356
+ }, [
357
+ parsed,
358
+ appName,
359
+ appUrl,
360
+ appImageUrl,
361
+ appDescription,
362
+ tokenInfo,
363
+ toChainId,
364
+ ])
365
+
366
+ return {
367
+ message,
368
+ parts: parsed,
369
+ }
370
+ }
@@ -0,0 +1,168 @@
1
+ import { useState, useEffect, useCallback } from "react"
2
+ import { isAddress } from "viem"
3
+ import { logger } from "../../logger.js"
4
+
5
+ export interface RecentRecipient {
6
+ address: string
7
+ ensName?: string
8
+ resolvedAddress?: string // For when ENS name resolves to address
9
+ timestamp: number
10
+ }
11
+
12
+ const STORAGE_KEY = "trails-recent-recipients"
13
+ const MAX_RECENT_RECIPIENTS = 10
14
+
15
+ export const useRecipients = () => {
16
+ const [recentRecipients, setRecentRecipients] = useState<RecentRecipient[]>(
17
+ [],
18
+ )
19
+
20
+ // Load recent recipients from localStorage on mount
21
+ useEffect(() => {
22
+ try {
23
+ const stored = localStorage.getItem(STORAGE_KEY)
24
+ if (stored) {
25
+ const parsed = JSON.parse(stored) as RecentRecipient[]
26
+ // Sort by timestamp (most recent first)
27
+ const sorted = parsed.sort((a, b) => b.timestamp - a.timestamp)
28
+ setRecentRecipients(sorted)
29
+ }
30
+ } catch (error) {
31
+ logger.console.error(
32
+ "[trails-sdk] Failed to load recent recipients:",
33
+ error,
34
+ )
35
+ }
36
+ }, [])
37
+
38
+ // Save recipients to localStorage
39
+ const saveToStorage = useCallback((recipients: RecentRecipient[]) => {
40
+ try {
41
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(recipients))
42
+ } catch (error) {
43
+ logger.console.error(
44
+ "[trails-sdk] Failed to save recent recipients:",
45
+ error,
46
+ )
47
+ }
48
+ }, [])
49
+
50
+ // ENS resolution placeholder - in real implementation, use proper ENS resolver
51
+ const resolveENS = useCallback(
52
+ async (input: string): Promise<{ address?: string; ensName?: string }> => {
53
+ try {
54
+ if (input.endsWith(".eth")) {
55
+ // ENS name to address resolution - return empty for now
56
+ logger.console.log(
57
+ `[trails-sdk] ENS resolution not implemented: ${input}`,
58
+ )
59
+ return { ensName: input }
60
+ } else if (isAddress(input)) {
61
+ // Address to ENS name resolution (reverse lookup) - return empty for now
62
+ logger.console.log(
63
+ `[trails-sdk] Reverse ENS resolution not implemented: ${input}`,
64
+ )
65
+ return { address: input, ensName: "" }
66
+ }
67
+
68
+ return {}
69
+ } catch (error) {
70
+ logger.console.error("[trails-sdk] ENS resolution failed:", error)
71
+ return {}
72
+ }
73
+ },
74
+ [],
75
+ )
76
+
77
+ // Add a new recipient to the recent list
78
+ const addRecentRecipient = useCallback(
79
+ async (input: string, resolvedAddress?: string, ensName?: string) => {
80
+ const trimmedInput = input.trim()
81
+ if (!trimmedInput) return
82
+
83
+ let recipientData: RecentRecipient
84
+
85
+ if (isAddress(trimmedInput)) {
86
+ // It's a valid address
87
+ recipientData = {
88
+ address: trimmedInput,
89
+ ensName: ensName, // Use provided ENS name from reverse resolution
90
+ timestamp: Date.now(),
91
+ }
92
+ } else if (trimmedInput.endsWith(".eth")) {
93
+ // It's an ENS name
94
+ if (resolvedAddress && isAddress(resolvedAddress)) {
95
+ // We have a resolved address for this ENS name
96
+ recipientData = {
97
+ address: resolvedAddress,
98
+ ensName: ensName || trimmedInput, // Use provided ENS name or fallback to input
99
+ resolvedAddress: resolvedAddress,
100
+ timestamp: Date.now(),
101
+ }
102
+ } else {
103
+ return // Invalid ENS name - no resolved address
104
+ }
105
+ } else {
106
+ return // Invalid input
107
+ }
108
+
109
+ setRecentRecipients((prev) => {
110
+ // Remove any existing entry with the same address or ENS name
111
+ const filtered = prev.filter(
112
+ (r) =>
113
+ r.address.toLowerCase() !== recipientData.address.toLowerCase() &&
114
+ (!recipientData.ensName || r.ensName !== recipientData.ensName),
115
+ )
116
+
117
+ // Add new entry at the beginning
118
+ const updated = [recipientData, ...filtered].slice(
119
+ 0,
120
+ MAX_RECENT_RECIPIENTS,
121
+ )
122
+
123
+ // Save to localStorage
124
+ saveToStorage(updated)
125
+
126
+ return updated
127
+ })
128
+
129
+ logger.console.log("[trails-sdk] Added recent recipient:", recipientData)
130
+ },
131
+ [saveToStorage],
132
+ )
133
+
134
+ // Remove a recipient from the recent list
135
+ const removeRecentRecipient = useCallback(
136
+ (address: string) => {
137
+ setRecentRecipients((prev) => {
138
+ const updated = prev.filter(
139
+ (r) => r.address.toLowerCase() !== address.toLowerCase(),
140
+ )
141
+ saveToStorage(updated)
142
+ return updated
143
+ })
144
+ },
145
+ [saveToStorage],
146
+ )
147
+
148
+ // Clear all recent recipients
149
+ const clearRecentRecipients = useCallback(() => {
150
+ setRecentRecipients([])
151
+ try {
152
+ localStorage.removeItem(STORAGE_KEY)
153
+ } catch (error) {
154
+ logger.console.error(
155
+ "[trails-sdk] Failed to clear recent recipients:",
156
+ error,
157
+ )
158
+ }
159
+ }, [])
160
+
161
+ return {
162
+ recentRecipients,
163
+ addRecentRecipient,
164
+ removeRecentRecipient,
165
+ clearRecentRecipients,
166
+ resolveENS,
167
+ }
168
+ }