0xtrails 0.0.1 → 0.0.2

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 (359) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +42 -0
  3. package/dist/abi.d.ts +37 -0
  4. package/dist/abi.d.ts.map +1 -0
  5. package/dist/abi.js +36 -0
  6. package/dist/apiClient.d.ts +9 -0
  7. package/dist/apiClient.d.ts.map +1 -0
  8. package/dist/apiClient.js +18 -0
  9. package/dist/buffer.d.ts +3 -0
  10. package/dist/buffer.d.ts.map +1 -0
  11. package/dist/buffer.js +8 -0
  12. package/dist/cctp.d.ts +84 -0
  13. package/dist/cctp.d.ts.map +1 -0
  14. package/dist/cctp.js +401 -0
  15. package/dist/chainSwitch.d.ts +7 -0
  16. package/dist/chainSwitch.d.ts.map +1 -0
  17. package/dist/chainSwitch.js +33 -0
  18. package/dist/chains.d.ts +13 -0
  19. package/dist/chains.d.ts.map +1 -0
  20. package/dist/chains.js +95 -0
  21. package/dist/constants.d.ts +11 -0
  22. package/dist/constants.d.ts.map +1 -0
  23. package/dist/constants.js +16 -0
  24. package/dist/encoders.d.ts +7 -0
  25. package/dist/encoders.d.ts.map +1 -0
  26. package/dist/encoders.js +8 -0
  27. package/dist/error.d.ts +2 -0
  28. package/dist/error.d.ts.map +1 -0
  29. package/dist/error.js +12 -0
  30. package/dist/explorer.d.ts +12 -0
  31. package/dist/explorer.d.ts.map +1 -0
  32. package/dist/explorer.js +18 -0
  33. package/dist/gasless.d.ts +116 -0
  34. package/dist/gasless.d.ts.map +1 -0
  35. package/dist/gasless.js +297 -0
  36. package/dist/index.d.ts +13 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +9 -0
  39. package/dist/indexerClient.d.ts +9 -0
  40. package/dist/indexerClient.d.ts.map +1 -0
  41. package/dist/indexerClient.js +18 -0
  42. package/dist/intents.d.ts +83 -0
  43. package/dist/intents.d.ts.map +1 -0
  44. package/dist/intents.js +288 -0
  45. package/dist/metaTxnMonitor.d.ts +14 -0
  46. package/dist/metaTxnMonitor.d.ts.map +1 -0
  47. package/dist/metaTxnMonitor.js +121 -0
  48. package/dist/metaTxns.d.ts +6 -0
  49. package/dist/metaTxns.d.ts.map +1 -0
  50. package/dist/metaTxns.js +4 -0
  51. package/dist/paymasterSend.d.ts +90 -0
  52. package/dist/paymasterSend.d.ts.map +1 -0
  53. package/dist/paymasterSend.js +329 -0
  54. package/dist/preconditions.d.ts +11 -0
  55. package/dist/preconditions.d.ts.map +1 -0
  56. package/dist/preconditions.js +29 -0
  57. package/dist/prepareSend.d.ts +102 -0
  58. package/dist/prepareSend.d.ts.map +1 -0
  59. package/dist/prepareSend.js +1080 -0
  60. package/dist/prices.d.ts +18 -0
  61. package/dist/prices.d.ts.map +1 -0
  62. package/dist/prices.js +142 -0
  63. package/dist/queryParams.d.ts +9 -0
  64. package/dist/queryParams.d.ts.map +1 -0
  65. package/dist/queryParams.js +66 -0
  66. package/dist/relaySdk.d.ts +65 -0
  67. package/dist/relaySdk.d.ts.map +1 -0
  68. package/dist/relaySdk.js +314 -0
  69. package/dist/relayer.d.ts +23 -0
  70. package/dist/relayer.d.ts.map +1 -0
  71. package/dist/relayer.js +230 -0
  72. package/dist/sendUserOp.d.ts +140 -0
  73. package/dist/sendUserOp.d.ts.map +1 -0
  74. package/dist/sendUserOp.js +388 -0
  75. package/dist/sequenceWallet.d.ts +79 -0
  76. package/dist/sequenceWallet.d.ts.map +1 -0
  77. package/dist/sequenceWallet.js +374 -0
  78. package/dist/theme.d.ts +3 -0
  79. package/dist/theme.d.ts.map +1 -0
  80. package/dist/theme.js +1 -0
  81. package/dist/toSimpleSmartAccount.d.ts +95 -0
  82. package/dist/toSimpleSmartAccount.d.ts.map +1 -0
  83. package/dist/toSimpleSmartAccount.js +373 -0
  84. package/dist/tokenBalances.d.ts +118 -0
  85. package/dist/tokenBalances.d.ts.map +1 -0
  86. package/dist/tokenBalances.js +492 -0
  87. package/dist/tokens.d.ts +50 -0
  88. package/dist/tokens.d.ts.map +1 -0
  89. package/dist/tokens.js +356 -0
  90. package/dist/trails.d.ts +128 -0
  91. package/dist/trails.d.ts.map +1 -0
  92. package/dist/trails.js +1031 -0
  93. package/dist/umd/trails.min.js +12610 -0
  94. package/dist/umd/trails.min.js.map +1 -0
  95. package/dist/umd.d.ts +24 -0
  96. package/dist/umd.d.ts.map +1 -0
  97. package/dist/utils.d.ts +5 -0
  98. package/dist/utils.d.ts.map +1 -0
  99. package/dist/utils.js +9 -0
  100. package/dist/widget/ConstantsUtil-B-_-u8aQ.js +6 -0
  101. package/dist/widget/add-hVLs3ldJ.js +20 -0
  102. package/dist/widget/all-wallets-Cwxnx4BT.js +11 -0
  103. package/dist/widget/app-store-CAAVQjW0.js +22 -0
  104. package/dist/widget/apple-C3BSbglw.js +23 -0
  105. package/dist/widget/arrow-bottom-circle-BGU9MmsZ.js +16 -0
  106. package/dist/widget/arrow-bottom-hS_SA8Gp.js +13 -0
  107. package/dist/widget/arrow-left-CJZanWz7.js +13 -0
  108. package/dist/widget/arrow-right-C1qL8EMd.js +13 -0
  109. package/dist/widget/arrow-top-CbuCmbQs.js +13 -0
  110. package/dist/widget/bank-CXBEEGbb.js +19 -0
  111. package/dist/widget/bin-Dqzv3zCZ.js +9 -0
  112. package/dist/widget/bitcoin-4y3sovZp.js +18 -0
  113. package/dist/widget/browser-DyOl4_8m.js +1413 -0
  114. package/dist/widget/browser-t7Fh0sEU.js +19 -0
  115. package/dist/widget/card-Bo4CZkTs.js +19 -0
  116. package/dist/widget/ccip-BynehMIN.js +232 -0
  117. package/dist/widget/checkmark-DV6OKvnY.js +16 -0
  118. package/dist/widget/checkmark-bold-CAp1-IQ2.js +13 -0
  119. package/dist/widget/chevron-bottom-BjzsVzk9.js +13 -0
  120. package/dist/widget/chevron-left-CQZBDCiR.js +13 -0
  121. package/dist/widget/chevron-right-Dhg4zeZM.js +13 -0
  122. package/dist/widget/chevron-top-CDQmfJef.js +13 -0
  123. package/dist/widget/chrome-store-BNaC_b6w.js +66 -0
  124. package/dist/widget/circle-BC_GBj91.js +9 -0
  125. package/dist/widget/clock-BmF8-4a0.js +13 -0
  126. package/dist/widget/close-Bf61nZ8o.js +13 -0
  127. package/dist/widget/coinPlaceholder-7cZW2058.js +13 -0
  128. package/dist/widget/compass-CFC3yhnW.js +13 -0
  129. package/dist/widget/components/ChainImage.d.ts +8 -0
  130. package/dist/widget/components/ChainImage.d.ts.map +1 -0
  131. package/dist/widget/components/ChainImage.js +6 -0
  132. package/dist/widget/components/ConnectWallet.d.ts +18 -0
  133. package/dist/widget/components/ConnectWallet.d.ts.map +1 -0
  134. package/dist/widget/components/ConnectWallet.js +66 -0
  135. package/dist/widget/components/DebugScreensDropdown.d.ts +9 -0
  136. package/dist/widget/components/DebugScreensDropdown.d.ts.map +1 -0
  137. package/dist/widget/components/DebugScreensDropdown.js +40 -0
  138. package/dist/widget/components/FeeOptions.d.ts +17 -0
  139. package/dist/widget/components/FeeOptions.d.ts.map +1 -0
  140. package/dist/widget/components/FeeOptions.js +65 -0
  141. package/dist/widget/components/Footer.d.ts +9 -0
  142. package/dist/widget/components/Footer.d.ts.map +1 -0
  143. package/dist/widget/components/Footer.js +13 -0
  144. package/dist/widget/components/GreenCheckAnimation.d.ts +2 -0
  145. package/dist/widget/components/GreenCheckAnimation.d.ts.map +1 -0
  146. package/dist/widget/components/GreenCheckAnimation.js +74 -0
  147. package/dist/widget/components/Modal.d.ts +11 -0
  148. package/dist/widget/components/Modal.d.ts.map +1 -0
  149. package/dist/widget/components/Modal.js +36 -0
  150. package/dist/widget/components/Receipt.d.ts +13 -0
  151. package/dist/widget/components/Receipt.d.ts.map +1 -0
  152. package/dist/widget/components/Receipt.js +36 -0
  153. package/dist/widget/components/SendForm.d.ts +44 -0
  154. package/dist/widget/components/SendForm.d.ts.map +1 -0
  155. package/dist/widget/components/SendForm.js +177 -0
  156. package/dist/widget/components/TokenImage.d.ts +10 -0
  157. package/dist/widget/components/TokenImage.d.ts.map +1 -0
  158. package/dist/widget/components/TokenImage.js +8 -0
  159. package/dist/widget/components/TokenList.d.ts +16 -0
  160. package/dist/widget/components/TokenList.d.ts.map +1 -0
  161. package/dist/widget/components/TokenList.js +39 -0
  162. package/dist/widget/components/TransferPending.d.ts +11 -0
  163. package/dist/widget/components/TransferPending.d.ts.map +1 -0
  164. package/dist/widget/components/TransferPending.js +77 -0
  165. package/dist/widget/components/TransferPendingVertical.d.ts +18 -0
  166. package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -0
  167. package/dist/widget/components/TransferPendingVertical.js +183 -0
  168. package/dist/widget/components/WalletConfirmation.d.ts +18 -0
  169. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -0
  170. package/dist/widget/components/WalletConfirmation.js +22 -0
  171. package/dist/widget/config.d.ts +4 -0
  172. package/dist/widget/config.d.ts.map +1 -0
  173. package/dist/widget/config.js +3 -0
  174. package/dist/widget/copy-e0xXvKN0.js +20 -0
  175. package/dist/widget/cursor-CqM3v0xJ.js +8 -0
  176. package/dist/widget/cursor-transparent-CUQpdsCG.js +17 -0
  177. package/dist/widget/desktop-DUDGIRpM.js +14 -0
  178. package/dist/widget/disconnect-DUFST9QQ.js +13 -0
  179. package/dist/widget/discord-C1cj365Z.js +22 -0
  180. package/dist/widget/email-BHhmb_lX.js +703 -0
  181. package/dist/widget/embedded-wallet-CuuC4eah.js +467 -0
  182. package/dist/widget/ethereum-CfmBVfeB.js +15 -0
  183. package/dist/widget/etherscan-BSiynDhW.js +11 -0
  184. package/dist/widget/exclamation-triangle-DEiFNpHw.js +9 -0
  185. package/dist/widget/extension-mRmfCDxo.js +13 -0
  186. package/dist/widget/external-link-B4xMIVnW.js +13 -0
  187. package/dist/widget/facebook-CBAZStBR.js +31 -0
  188. package/dist/widget/farcaster-LHDEDf5S.js +17 -0
  189. package/dist/widget/filters-CBijuvFv.js +13 -0
  190. package/dist/widget/github-C3ILD420.js +23 -0
  191. package/dist/widget/google-CSj73POX.js +23 -0
  192. package/dist/widget/help-circle-2hdG5IdB.js +17 -0
  193. package/dist/widget/hooks/useAmountUsd.d.ts +13 -0
  194. package/dist/widget/hooks/useAmountUsd.d.ts.map +1 -0
  195. package/dist/widget/hooks/useAmountUsd.js +35 -0
  196. package/dist/widget/hooks/useSendForm.d.ts +125 -0
  197. package/dist/widget/hooks/useSendForm.d.ts.map +1 -0
  198. package/dist/widget/hooks/useSendForm.js +450 -0
  199. package/dist/widget/hooks/useTokenList.d.ts +52 -0
  200. package/dist/widget/hooks/useTokenList.d.ts.map +1 -0
  201. package/dist/widget/hooks/useTokenList.js +252 -0
  202. package/dist/widget/id-ByYSrwsd.js +17 -0
  203. package/dist/widget/if-defined-DRXJEhv7.js +752 -0
  204. package/dist/widget/image-C90L4Rf6.js +9 -0
  205. package/dist/widget/index-B3SlQ9v3.js +46 -0
  206. package/dist/widget/index-B8LPuLXQ.js +78 -0
  207. package/dist/widget/index-BDbworWA.js +171 -0
  208. package/dist/widget/index-BTlDgFSK.js +98 -0
  209. package/dist/widget/index-BUCcjXbd.js +306 -0
  210. package/dist/widget/index-BZ34edi2.js +1055 -0
  211. package/dist/widget/index-BiCU29wK.js +147 -0
  212. package/dist/widget/index-BlmqIKsY.js +266 -0
  213. package/dist/widget/index-BlviH5nG.js +55 -0
  214. package/dist/widget/index-Bwd5X3fS.js +8306 -0
  215. package/dist/widget/index-C5gmknHK.js +78 -0
  216. package/dist/widget/index-CD6dBcRj.js +76 -0
  217. package/dist/widget/index-CDlhy529.js +63 -0
  218. package/dist/widget/index-CHXa5ke-.js +59 -0
  219. package/dist/widget/index-CK94R-H7.js +22498 -0
  220. package/dist/widget/index-CQzo3m3x.js +182 -0
  221. package/dist/widget/index-CRT8cAwG.js +325 -0
  222. package/dist/widget/index-CY7Lt2Yu.js +310 -0
  223. package/dist/widget/index-CiKfAu1E.js +79 -0
  224. package/dist/widget/index-CkCu6rMi.js +258 -0
  225. package/dist/widget/index-CngLTu_R.js +517 -0
  226. package/dist/widget/index-CsMV8-em.js +2577 -0
  227. package/dist/widget/index-Cu2Wva8v.js +200 -0
  228. package/dist/widget/index-DKBxLTEF.js +240 -0
  229. package/dist/widget/index-DQEVT3dx.js +511 -0
  230. package/dist/widget/index-DU2HcCis.js +200 -0
  231. package/dist/widget/index-DutZGWNW.js +321 -0
  232. package/dist/widget/index-O0glArmc.js +182 -0
  233. package/dist/widget/index-O8FmRjKe.js +63719 -0
  234. package/dist/widget/index-RtKXrB6I.js +576 -0
  235. package/dist/widget/index-TIYtS0gE.js +88 -0
  236. package/dist/widget/index-dQNJvWHs.js +66 -0
  237. package/dist/widget/index-wmEwdsq7.js +909 -0
  238. package/dist/widget/index.d.ts +3 -0
  239. package/dist/widget/index.d.ts.map +1 -0
  240. package/dist/widget/index.js +2 -0
  241. package/dist/widget/info-DMPChDjV.js +8 -0
  242. package/dist/widget/info-circle-DAvS_7nY.js +17 -0
  243. package/dist/widget/lightbulb-DnZ9mNEs.js +8 -0
  244. package/dist/widget/lit-html-BRjl1r6K.js +243 -0
  245. package/dist/widget/mail-DpaVSOP8.js +13 -0
  246. package/dist/widget/mobile-CRvdyu7I.js +14 -0
  247. package/dist/widget/more-C5VqW9PR.js +16 -0
  248. package/dist/widget/network-placeholder-CZ0vApma.js +19 -0
  249. package/dist/widget/nftPlaceholder-7jjIK2bT.js +13 -0
  250. package/dist/widget/off-4mHjJLLX.js +9 -0
  251. package/dist/widget/onramp-Bc0ozVsw.js +929 -0
  252. package/dist/widget/play-store-Uocul8nC.js +37 -0
  253. package/dist/widget/plus-DrYF7siO.js +18 -0
  254. package/dist/widget/prepareSend-BQJmzM5B.js +54987 -0
  255. package/dist/widget/qr-code-DcnGMUB3.js +11 -0
  256. package/dist/widget/receive-fvIVd7R_.js +184 -0
  257. package/dist/widget/recycle-horizontal-DrDwXC4D.js +14 -0
  258. package/dist/widget/ref-CXNmEjML.js +41 -0
  259. package/dist/widget/refresh-OK9lIPLS.js +13 -0
  260. package/dist/widget/reown-logo-C-Qn7mS3.js +17 -0
  261. package/dist/widget/search-DZqv1oKg.js +13 -0
  262. package/dist/widget/send-CJlmI-xe.js +1039 -0
  263. package/dist/widget/send-otoEC8uU.js +20 -0
  264. package/dist/widget/socials-BJciurWF.js +599 -0
  265. package/dist/widget/solana-Bv5Hs_0T.js +18 -0
  266. package/dist/widget/swapHorizontal-BzOPGV37.js +13 -0
  267. package/dist/widget/swapHorizontalBold-axyHnSmj.js +13 -0
  268. package/dist/widget/swapHorizontalMedium-C6YOPfPz.js +21 -0
  269. package/dist/widget/swapHorizontalRoundedBold-yVcLbWNT.js +13 -0
  270. package/dist/widget/swapVertical-BDjxt9pE.js +13 -0
  271. package/dist/widget/swaps-DEWNj4kd.js +1637 -0
  272. package/dist/widget/telegram-BQJD7dlP.js +21 -0
  273. package/dist/widget/three-dots-DW9jmSMG.js +10 -0
  274. package/dist/widget/transactions-uCseGQQt.js +38 -0
  275. package/dist/widget/twitch-XugxDfOE.js +23 -0
  276. package/dist/widget/twitterIcon-DQVObQUL.js +11 -0
  277. package/dist/widget/types.d.ts +51 -0
  278. package/dist/widget/types.d.ts.map +1 -0
  279. package/dist/widget/types.js +1 -0
  280. package/dist/widget/verify-DpMYHxLf.js +13 -0
  281. package/dist/widget/verify-filled-KpEL6ZJ_.js +13 -0
  282. package/dist/widget/w3m-modal-C8e-6Kba.js +1047 -0
  283. package/dist/widget/wallet-D8ssEB0o.js +13 -0
  284. package/dist/widget/wallet-placeholder-HtAy21Wc.js +19 -0
  285. package/dist/widget/walletconnect-Bp_4XfrY.js +37 -0
  286. package/dist/widget/warning-circle-FgYS7P7n.js +17 -0
  287. package/dist/widget/widget/index.js +7 -0
  288. package/dist/widget/widget.d.ts +47 -0
  289. package/dist/widget/widget.d.ts.map +1 -0
  290. package/dist/widget/widget.js +932 -0
  291. package/dist/widget/x-DlZBoP9k.js +17 -0
  292. package/dist/widget/x-mark-Ba9pt-_h.js +8 -0
  293. package/package.json +102 -8
  294. package/src/abi.ts +38 -0
  295. package/src/apiClient.ts +32 -0
  296. package/src/buffer.ts +10 -0
  297. package/src/cctp.ts +579 -0
  298. package/src/chainSwitch.ts +55 -0
  299. package/src/chains.ts +124 -0
  300. package/src/constants.ts +26 -0
  301. package/src/encoders.ts +20 -0
  302. package/src/error.ts +15 -0
  303. package/src/explorer.ts +37 -0
  304. package/src/gasless.ts +545 -0
  305. package/src/index.ts +48 -0
  306. package/src/indexerClient.ts +36 -0
  307. package/src/intents.ts +537 -0
  308. package/src/metaTxnMonitor.ts +163 -0
  309. package/src/metaTxns.ts +21 -0
  310. package/src/paymasterSend.ts +503 -0
  311. package/src/preconditions.ts +52 -0
  312. package/src/prepareSend.ts +1849 -0
  313. package/src/prices.ts +186 -0
  314. package/src/queryParams.ts +80 -0
  315. package/src/relaySdk.ts +481 -0
  316. package/src/relayer.ts +255 -0
  317. package/src/sendUserOp.ts +570 -0
  318. package/src/sequenceWallet.ts +579 -0
  319. package/src/theme.ts +2 -0
  320. package/src/toSimpleSmartAccount.ts +567 -0
  321. package/src/tokenBalances.ts +760 -0
  322. package/src/tokens.ts +471 -0
  323. package/src/trails.ts +1591 -0
  324. package/src/types.d.ts +11 -0
  325. package/src/umd.tsx +49 -0
  326. package/src/utils.ts +16 -0
  327. package/src/vite-env.d.ts +4 -0
  328. package/src/widget/assets/MetaMask-icon-fox-with-margins.svg +31 -0
  329. package/src/widget/assets/MetaMask-icon-fox.svg +26 -0
  330. package/src/widget/assets/MetaMask-logo-black.svg +3 -0
  331. package/src/widget/assets/MetaMask-logo-white.svg +16 -0
  332. package/src/widget/assets/Privy_Brandmark_Black.svg +9 -0
  333. package/src/widget/assets/Privy_Brandmark_White.svg +9 -0
  334. package/src/widget/assets/Trails-logo-black.svg +11 -0
  335. package/src/widget/assets/Trails-logo-white.svg +11 -0
  336. package/src/widget/components/ChainImage.tsx +28 -0
  337. package/src/widget/components/ConnectWallet.tsx +206 -0
  338. package/src/widget/components/DebugScreensDropdown.tsx +88 -0
  339. package/src/widget/components/FeeOptions.tsx +199 -0
  340. package/src/widget/components/Footer.tsx +51 -0
  341. package/src/widget/components/GreenCheckAnimation.tsx +119 -0
  342. package/src/widget/components/Modal.tsx +97 -0
  343. package/src/widget/components/Receipt.tsx +237 -0
  344. package/src/widget/components/SendForm.tsx +695 -0
  345. package/src/widget/components/TokenImage.tsx +37 -0
  346. package/src/widget/components/TokenList.tsx +287 -0
  347. package/src/widget/components/TransferPending.tsx +204 -0
  348. package/src/widget/components/TransferPendingVertical.tsx +412 -0
  349. package/src/widget/components/WalletConfirmation.tsx +172 -0
  350. package/src/widget/config.ts +5 -0
  351. package/src/widget/hooks/useAmountUsd.ts +59 -0
  352. package/src/widget/hooks/useSendForm.ts +715 -0
  353. package/src/widget/hooks/useTokenList.ts +397 -0
  354. package/src/widget/index.css +2 -0
  355. package/src/widget/index.tsx +8 -0
  356. package/src/widget/types/svg.d.ts +8 -0
  357. package/src/widget/types.ts +59 -0
  358. package/src/widget/widget.tsx +1438 -0
  359. package/index.js +0 -1
package/src/trails.ts ADDED
@@ -0,0 +1,1591 @@
1
+ import type {
2
+ AddressOverrides,
3
+ GetIntentCallsPayloadsArgs,
4
+ GetIntentConfigReturn,
5
+ IntentCallsPayload,
6
+ IntentPrecondition,
7
+ SequenceAPIClient,
8
+ } from "@0xsequence/trails-api"
9
+ import type { Relayer } from "@0xsequence/wallet-core"
10
+ import { useMutation, useQuery } from "@tanstack/react-query"
11
+ import { Address } from "ox"
12
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react"
13
+ import type { Hex } from "viem"
14
+ import {
15
+ createPublicClient,
16
+ createWalletClient,
17
+ custom,
18
+ http,
19
+ isAddress,
20
+ isAddressEqual,
21
+ zeroAddress,
22
+ } from "viem"
23
+ import type { Connector } from "wagmi"
24
+ import {
25
+ useEstimateGas,
26
+ useSendTransaction,
27
+ useSwitchChain,
28
+ useWaitForTransactionReceipt,
29
+ } from "wagmi"
30
+ import { useAPIClient } from "./apiClient.js"
31
+ import { attemptSwitchChain } from "./chainSwitch.js"
32
+ import { getChainInfo } from "./chains.js"
33
+ import {
34
+ TRAILS_CCTP_SAPIENT_SIGNER_ADDRESS,
35
+ TRAILS_LIFI_SAPIENT_SIGNER_ADDRESS,
36
+ TRAILS_RELAY_SAPIENT_SIGNER_ADDRESS,
37
+ DEFAULT_USE_V3_RELAYERS,
38
+ } from "./constants.js"
39
+ import { getERC20TransferData } from "./encoders.js"
40
+ import type {
41
+ GetIntentCallsPayloadsReturn,
42
+ OriginCallParams,
43
+ QuoteProvider,
44
+ TrailsFee,
45
+ } from "./intents.js"
46
+ import {
47
+ calculateIntentAddress,
48
+ calculateOriginAndDestinationIntentAddresses,
49
+ getIntentCallsPayloads as getIntentCallsPayloadsFromIntents,
50
+ } from "./intents.js"
51
+ import type { MetaTxn } from "./metaTxnMonitor.js"
52
+ import { useMetaTxnsMonitor } from "./metaTxnMonitor.js"
53
+ import { findPreconditionAddresses } from "./preconditions.js"
54
+ import { getBackupRelayer, useRelayers } from "./relayer.js"
55
+
56
+ export type WagmiAccount = {
57
+ address: `0x${string}`
58
+ isConnected: boolean
59
+ chainId: number
60
+ connector?: Connector
61
+ }
62
+
63
+ export type UseTrailsConfig = {
64
+ account: WagmiAccount
65
+ disableAutoExecute?: boolean
66
+ env: "local" | "cors-anywhere" | "dev" | "prod"
67
+ useV3Relayers?: boolean
68
+ sequenceProjectAccessKey?: string
69
+ }
70
+
71
+ export type UseTrailsReturn = {
72
+ apiClient: SequenceAPIClient
73
+ metaTxns: GetIntentCallsPayloadsReturn["metaTxns"] | null
74
+ intentCallsPayloads: GetIntentCallsPayloadsReturn["calls"] | null
75
+ intentPreconditions: GetIntentCallsPayloadsReturn["preconditions"] | null
76
+ trailsFee: TrailsFee | null
77
+ txnHash: Hex | undefined
78
+ committedOriginIntentAddress: string | null
79
+ committedDestinationIntentAddress: string | null
80
+ verificationStatus: {
81
+ success: boolean
82
+ calculatedOriginAddress?: string
83
+ calculatedDestinationAddress?: string
84
+ receivedOriginAddress?: string
85
+ receivedDestinationAddress?: string
86
+ } | null
87
+ getRelayer: (chainId: number) => any // TODO: Add proper type
88
+ estimatedGas: bigint | undefined
89
+ isEstimateError: boolean
90
+ estimateError: Error | null
91
+ calculateIntentAddress: typeof calculateIntentAddress
92
+ calculateOriginAndDestinationIntentAddresses: typeof calculateOriginAndDestinationIntentAddresses
93
+ committedIntentConfig: GetIntentConfigReturn | undefined
94
+ isLoadingCommittedConfig: boolean
95
+ committedConfigError: Error | null
96
+ commitIntentConfig: (args: {
97
+ mainSignerAddress: string
98
+ calls: IntentCallsPayload[]
99
+ preconditions: IntentPrecondition[]
100
+ quoteProvider: "lifi" | "relay" | "cctp"
101
+ addressOverrides?: AddressOverrides
102
+ }) => void
103
+ commitIntentConfigPending: boolean
104
+ commitIntentConfigSuccess: boolean
105
+ commitIntentConfigError: Error | null
106
+ commitIntentConfigArgs:
107
+ | {
108
+ mainSignerAddress: string
109
+ calls: IntentCallsPayload[]
110
+ preconditions: IntentPrecondition[]
111
+ quoteProvider: "lifi" | "relay" | "cctp"
112
+ addressOverrides?: AddressOverrides
113
+ }
114
+ | undefined
115
+ getIntentCallsPayloads: (
116
+ args: GetIntentCallsPayloadsArgs,
117
+ ) => Promise<GetIntentCallsPayloadsReturn>
118
+ operationHashes: { [key: string]: Hex }
119
+ callIntentCallsPayload: (args: GetIntentCallsPayloadsArgs) => void
120
+ sendOriginTransaction: () => Promise<void>
121
+ switchChain: any // TODO: Add proper type
122
+ isSwitchingChain: boolean
123
+ switchChainError: Error | null
124
+ isTransactionInProgress: boolean
125
+ isChainSwitchRequired: boolean
126
+ sendTransaction: any // TODO: Add proper type
127
+ isSendingTransaction: boolean
128
+ originCallStatus: {
129
+ txnHash?: string
130
+ status?: string
131
+ revertReason?: string | null
132
+ gasUsed?: number
133
+ effectiveGasPrice?: string
134
+ } | null
135
+ updateOriginCallStatus: (
136
+ hash: Hex | undefined,
137
+ status: "success" | "reverted" | "pending" | "sending",
138
+ gasUsed?: bigint,
139
+ effectiveGasPrice?: bigint,
140
+ revertReason?: string | null,
141
+ ) => void
142
+ isEstimatingGas: boolean
143
+ isAutoExecute: boolean
144
+ updateAutoExecute: (enabled: boolean) => void
145
+ receipt: any // TODO: Add proper type
146
+ isWaitingForReceipt: boolean
147
+ receiptIsSuccess: boolean
148
+ receiptIsError: boolean
149
+ receiptError: Error | null
150
+ hasAutoExecuted: boolean
151
+ originCallSuccess: boolean
152
+ sentMetaTxns: { [key: string]: number }
153
+ sendMetaTxn: (selectedId: string | null) => void
154
+ sendMetaTxnPending: boolean
155
+ sendMetaTxnSuccess: boolean
156
+ sendMetaTxnError: Error | null
157
+ sendMetaTxnArgs: { selectedId: string | null } | undefined
158
+ clearIntent: () => void
159
+ metaTxnMonitorStatuses: { [key: string]: Relayer.OperationStatus }
160
+ createIntent: (args: GetIntentCallsPayloadsArgs) => void
161
+ createIntentPending: boolean
162
+ createIntentSuccess: boolean
163
+ createIntentError: Error | null
164
+ createIntentArgs: GetIntentCallsPayloadsArgs | undefined
165
+ originCallParams: OriginCallParams | null
166
+ updateOriginCallParams: (
167
+ args: { originChainId: number; tokenAddress: string } | null,
168
+ ) => void
169
+ originBlockTimestamp: number | null
170
+ metaTxnBlockTimestamps: {
171
+ [key: string]: { timestamp: number | null; error?: string }
172
+ }
173
+ originIntentAddress: string | null
174
+ destinationIntentAddress: string | null
175
+ }
176
+
177
+ const RETRY_WINDOW_MS = 10_000
178
+
179
+ export function useTrails(config: UseTrailsConfig): UseTrailsReturn {
180
+ const {
181
+ account,
182
+ disableAutoExecute = false,
183
+ env,
184
+ useV3Relayers = DEFAULT_USE_V3_RELAYERS,
185
+ sequenceProjectAccessKey,
186
+ } = config
187
+ const apiClient = useAPIClient({ projectAccessKey: sequenceProjectAccessKey })
188
+
189
+ const [isAutoExecute, setIsAutoExecute] = useState(!disableAutoExecute)
190
+ const [hasAutoExecuted, setHasAutoExecuted] = useState(false)
191
+
192
+ // Track timestamps of when each meta-transaction was last sent
193
+ const [sentMetaTxns, setSentMetaTxns] = useState<{ [key: string]: number }>(
194
+ {},
195
+ )
196
+
197
+ // State declarations
198
+ const [metaTxns, setMetaTxns] = useState<
199
+ GetIntentCallsPayloadsReturn["metaTxns"] | null
200
+ >(null)
201
+ const [intentCallsPayloads, setIntentCallsPayloads] = useState<
202
+ GetIntentCallsPayloadsReturn["calls"] | null
203
+ >(null)
204
+ const [intentPreconditions, setIntentPreconditions] = useState<
205
+ GetIntentCallsPayloadsReturn["preconditions"] | null
206
+ >(null)
207
+ const [trailsFee, setTrailsFee] = useState<TrailsFee | null>(null)
208
+ const [txnHash, setTxnHash] = useState<Hex | undefined>()
209
+ const [committedOriginIntentAddress, setCommittedOriginIntentAddress] =
210
+ useState<string | null>(null)
211
+ const [
212
+ committedDestinationIntentAddress,
213
+ setCommittedDestinationIntentAddress,
214
+ ] = useState<string | null>(null)
215
+ const [originIntentAddress, setOriginIntentAddress] = useState<string | null>(
216
+ null,
217
+ )
218
+ const [destinationIntentAddress, setDestinationIntentAddress] = useState<
219
+ string | null
220
+ >(null)
221
+ // const [preconditionStatuses, setPreconditionStatuses] = useState<boolean[]>([])
222
+
223
+ const [originCallParams, setOriginCallParams] =
224
+ useState<OriginCallParams | null>(null)
225
+
226
+ const [operationHashes, setOperationHashes] = useState<{
227
+ [key: string]: Hex
228
+ }>({})
229
+ const [isTransactionInProgress, setIsTransactionInProgress] = useState(false)
230
+ const [isChainSwitchRequired, setIsChainSwitchRequired] = useState(false)
231
+ const {
232
+ switchChain,
233
+ isPending: isSwitchingChain,
234
+ error: switchChainError,
235
+ } = useSwitchChain()
236
+
237
+ const sendOriginTxn = useSendTransaction()
238
+
239
+ const [isEstimatingGas, setIsEstimatingGas] = useState(false)
240
+ const [originCallStatus, setOriginCallStatus] = useState<{
241
+ txnHash?: string
242
+ status?: string
243
+ revertReason?: string | null
244
+ gasUsed?: number
245
+ effectiveGasPrice?: string
246
+ } | null>(null)
247
+
248
+ const [originBlockTimestamp, setOriginBlockTimestamp] = useState<
249
+ number | null
250
+ >(null)
251
+ const [metaTxnBlockTimestamps, setMetaTxnBlockTimestamps] = useState<{
252
+ [key: string]: { timestamp: number | null; error?: string }
253
+ }>({})
254
+
255
+ const [verificationStatus, setVerificationStatus] = useState<{
256
+ success: boolean
257
+ calculatedOriginAddress?: string
258
+ calculatedDestinationAddress?: string
259
+ receivedOriginAddress?: string
260
+ receivedDestinationAddress?: string
261
+ } | null>(null)
262
+
263
+ const { getRelayer } = useRelayers({
264
+ env,
265
+ useV3Relayers,
266
+ })
267
+
268
+ // Add gas estimation hook with proper types
269
+ const {
270
+ data: estimatedGas,
271
+ isError: isEstimateError,
272
+ error: estimateError,
273
+ } = useEstimateGas(
274
+ originCallParams?.to && originCallParams?.chainId && !originCallParams.error
275
+ ? {
276
+ to: originCallParams.to || undefined,
277
+ data: originCallParams.data || undefined,
278
+ value: originCallParams.value || undefined,
279
+ chainId: originCallParams.chainId || undefined,
280
+ }
281
+ : undefined,
282
+ )
283
+
284
+ const commitIntentConfigMutation = useMutation({
285
+ mutationFn: async (args: {
286
+ mainSignerAddress: string
287
+ calls: IntentCallsPayload[]
288
+ preconditions: IntentPrecondition[]
289
+ quoteProvider: QuoteProvider
290
+ addressOverrides?: AddressOverrides
291
+ }) => {
292
+ console.log(
293
+ "[useTrails] commitIntentConfigMutation started with args:",
294
+ args,
295
+ )
296
+ if (!apiClient) {
297
+ console.error("[useTrails] API client not available")
298
+ throw new Error("API client not available")
299
+ }
300
+ if (!args.quoteProvider) {
301
+ console.error("[useTrails] quoteProvider is required")
302
+ throw new Error("quoteProvider is required")
303
+ }
304
+
305
+ try {
306
+ console.log("[useTrails] Calculating intent address...")
307
+ console.log("[useTrails] Main signer:", args.mainSignerAddress)
308
+ console.log("[useTrails] Calls:", args.calls)
309
+
310
+ const originChainId = createIntentMutation.variables?.originChainId
311
+ const destinationChainId =
312
+ createIntentMutation.variables?.destinationChainId
313
+
314
+ if (!originChainId || !destinationChainId) {
315
+ console.error(
316
+ "[useTrails] Could not determine origin/destination chain IDs for verification.",
317
+ )
318
+ throw new Error(
319
+ "Could not determine origin/destination chain IDs for verification.",
320
+ )
321
+ }
322
+
323
+ const { originIntentAddress, destinationIntentAddress } =
324
+ calculateOriginAndDestinationIntentAddresses(
325
+ args.mainSignerAddress,
326
+ args.calls,
327
+ )
328
+
329
+ const {
330
+ originAddress: originPreconditionAddress,
331
+ destinationAddress: destinationPreconditionAddress,
332
+ } = findPreconditionAddresses(
333
+ args.preconditions,
334
+ originChainId,
335
+ destinationChainId,
336
+ )
337
+
338
+ console.log("[useTrails] Verification addresses:", {
339
+ calculatedOrigin: originIntentAddress.toString(),
340
+ calculatedDestination: destinationIntentAddress.toString(),
341
+ preconditionOrigin: originPreconditionAddress,
342
+ preconditionDestination: destinationPreconditionAddress,
343
+ })
344
+
345
+ const isOriginVerified =
346
+ !!originPreconditionAddress &&
347
+ isAddressEqual(
348
+ Address.from(originPreconditionAddress),
349
+ originIntentAddress,
350
+ )
351
+
352
+ // For single chain, destination address may not be in preconditions,
353
+ // but the destination intent address should be the zero address.
354
+ const isDestinationVerified =
355
+ (destinationPreconditionAddress &&
356
+ isAddressEqual(
357
+ Address.from(destinationPreconditionAddress),
358
+ destinationIntentAddress,
359
+ )) ||
360
+ (!destinationPreconditionAddress &&
361
+ originChainId === destinationChainId &&
362
+ isAddressEqual(destinationIntentAddress, zeroAddress))
363
+
364
+ const isVerified = isOriginVerified && isDestinationVerified
365
+
366
+ setVerificationStatus({
367
+ success: isVerified,
368
+ receivedOriginAddress: originPreconditionAddress,
369
+ receivedDestinationAddress: destinationPreconditionAddress,
370
+ calculatedOriginAddress: originIntentAddress.toString(),
371
+ calculatedDestinationAddress: destinationIntentAddress.toString(),
372
+ })
373
+
374
+ if (!isVerified) {
375
+ console.error("[useTrails] Address verification failed.", {
376
+ isOriginVerified,
377
+ isDestinationVerified,
378
+ })
379
+ throw new Error(
380
+ `Address verification failed. Origin verified: ${isOriginVerified}, Destination verified: ${isDestinationVerified}`,
381
+ )
382
+ }
383
+
384
+ // Commit the intent config
385
+ console.log("[useTrails] Committing intent config to API...")
386
+ const response = await apiClient.commitIntentConfig({
387
+ originIntentAddress: originIntentAddress.toString(),
388
+ destinationIntentAddress: destinationIntentAddress.toString(),
389
+ mainSigner: args.mainSignerAddress,
390
+ calls: args.calls,
391
+ preconditions: args.preconditions,
392
+ addressOverrides: {
393
+ trailsLiFiSapientSignerAddress: TRAILS_LIFI_SAPIENT_SIGNER_ADDRESS,
394
+ trailsRelaySapientSignerAddress:
395
+ TRAILS_RELAY_SAPIENT_SIGNER_ADDRESS,
396
+ trailsCCTPV2SapientSignerAddress:
397
+ TRAILS_CCTP_SAPIENT_SIGNER_ADDRESS,
398
+ ...args.addressOverrides,
399
+ },
400
+ })
401
+ console.log("[useTrails] API Commit Response:", response)
402
+ return {
403
+ originIntentAddress: originIntentAddress.toString(),
404
+ destinationIntentAddress: destinationIntentAddress.toString(),
405
+ response,
406
+ }
407
+ } catch (error) {
408
+ console.error("[useTrails] Error during commit intent mutation:", error)
409
+ throw error
410
+ }
411
+ },
412
+ onSuccess: (data) => {
413
+ console.log(
414
+ "[useTrails] Intent config committed successfully. Data:",
415
+ data,
416
+ )
417
+ console.log(
418
+ "[useTrails] Setting committedOriginIntentAddress:",
419
+ data.originIntentAddress,
420
+ )
421
+ setCommittedOriginIntentAddress(data.originIntentAddress)
422
+ setCommittedDestinationIntentAddress(data.destinationIntentAddress)
423
+ },
424
+ onError: (error) => {
425
+ console.error("[useTrails] Failed to commit intent config:", error)
426
+ setCommittedOriginIntentAddress(null)
427
+ setCommittedDestinationIntentAddress(null)
428
+ },
429
+ })
430
+
431
+ // New Query to fetch committed intent config
432
+ const {
433
+ data: committedIntentConfig,
434
+ isLoading: isLoadingCommittedConfig,
435
+ error: committedConfigError,
436
+ } = useQuery<GetIntentConfigReturn, Error>({
437
+ queryKey: ["getIntentConfig", committedOriginIntentAddress],
438
+ queryFn: async () => {
439
+ if (!apiClient || !committedOriginIntentAddress) {
440
+ throw new Error("API client or committed intent address not available")
441
+ }
442
+ console.log(
443
+ "Fetching intent config for address:",
444
+ committedOriginIntentAddress,
445
+ )
446
+ return await apiClient.getIntentConfig({
447
+ intentAddress: committedOriginIntentAddress,
448
+ })
449
+ },
450
+ enabled:
451
+ !!committedOriginIntentAddress &&
452
+ !!apiClient &&
453
+ commitIntentConfigMutation.isSuccess,
454
+ staleTime: 1000 * 60 * 5, // 5 minutes
455
+ retry: 1,
456
+ })
457
+
458
+ async function getIntentCallsPayloads(args: GetIntentCallsPayloadsArgs) {
459
+ return getIntentCallsPayloadsFromIntents(apiClient, {
460
+ ...args,
461
+ addressOverrides: {
462
+ trailsLiFiSapientSignerAddress: TRAILS_LIFI_SAPIENT_SIGNER_ADDRESS,
463
+ trailsRelaySapientSignerAddress: TRAILS_RELAY_SAPIENT_SIGNER_ADDRESS,
464
+ trailsCCTPV2SapientSignerAddress: TRAILS_CCTP_SAPIENT_SIGNER_ADDRESS,
465
+ ...args.addressOverrides,
466
+ },
467
+ })
468
+ }
469
+
470
+ // TODO: Add type for args
471
+ const createIntentMutation = useMutation<
472
+ GetIntentCallsPayloadsReturn,
473
+ Error,
474
+ GetIntentCallsPayloadsArgs
475
+ >({
476
+ mutationFn: async (args: GetIntentCallsPayloadsArgs) => {
477
+ if (
478
+ args.originChainId === args.destinationChainId &&
479
+ isAddressEqual(
480
+ Address.from(args.originTokenAddress),
481
+ Address.from(args.destinationTokenAddress),
482
+ )
483
+ ) {
484
+ throw new Error(
485
+ "The same token cannot be used as both the source and destination token.",
486
+ )
487
+ }
488
+ if (!account.address) {
489
+ throw new Error("Missing selected token or account address")
490
+ }
491
+ // Reset commit state when generating a new intent
492
+ setCommittedOriginIntentAddress(null)
493
+ setCommittedDestinationIntentAddress(null)
494
+ setVerificationStatus(null)
495
+ setTrailsFee(null)
496
+ setMetaTxns(null)
497
+ setIntentCallsPayloads(null)
498
+ setIntentPreconditions(null)
499
+ setOriginIntentAddress(null)
500
+ setDestinationIntentAddress(null)
501
+
502
+ const data = await getIntentCallsPayloads(args)
503
+
504
+ setMetaTxns(data.metaTxns)
505
+ setIntentCallsPayloads(data.calls)
506
+ setIntentPreconditions(data.preconditions)
507
+ setTrailsFee(data.trailsFee!)
508
+ setOriginIntentAddress(data.originIntentAddress)
509
+ setDestinationIntentAddress(data.destinationIntentAddress)
510
+ setCommittedOriginIntentAddress(null)
511
+ setCommittedDestinationIntentAddress(null)
512
+
513
+ setVerificationStatus(null)
514
+ return data
515
+ },
516
+ onSuccess: (data) => {
517
+ console.log("Intent Config Success:", data)
518
+
519
+ setTrailsFee(data.trailsFee || null)
520
+ setOriginIntentAddress(data.originIntentAddress)
521
+ setDestinationIntentAddress(data.destinationIntentAddress)
522
+
523
+ if (
524
+ data?.calls &&
525
+ data.calls.length > 0 &&
526
+ data.preconditions &&
527
+ data.preconditions.length > 0 &&
528
+ data.metaTxns &&
529
+ data.metaTxns.length > 0
530
+ ) {
531
+ setIntentCallsPayloads(data.calls)
532
+ setIntentPreconditions(data.preconditions)
533
+ setMetaTxns(data.metaTxns)
534
+ } else {
535
+ console.warn("API returned success but no operations found.")
536
+ setIntentCallsPayloads(null)
537
+ setIntentPreconditions(null)
538
+ setMetaTxns(null)
539
+ }
540
+ },
541
+ onError: (error) => {
542
+ console.error("Intent Config Error:", error)
543
+ setIntentCallsPayloads(null)
544
+ setIntentPreconditions(null)
545
+ setMetaTxns(null)
546
+ setTrailsFee(null)
547
+ setOriginIntentAddress(null)
548
+ setDestinationIntentAddress(null)
549
+ },
550
+ })
551
+
552
+ function callIntentCallsPayload(args: GetIntentCallsPayloadsArgs) {
553
+ createIntentMutation.mutate(args)
554
+ }
555
+
556
+ const clearIntent = useCallback(() => {
557
+ console.log("[Trails] Clearing intent state")
558
+ setIntentCallsPayloads(null)
559
+ setIntentPreconditions(null)
560
+ setMetaTxns(null)
561
+ setTrailsFee(null)
562
+ setCommittedOriginIntentAddress(null)
563
+ setCommittedDestinationIntentAddress(null)
564
+ setVerificationStatus(null)
565
+ setOriginIntentAddress(null)
566
+ setDestinationIntentAddress(null)
567
+ setOperationHashes({})
568
+ setHasAutoExecuted(false)
569
+ setMetaTxnBlockTimestamps({})
570
+ }, []) // Empty deps array since these setters are stable
571
+
572
+ const updateOriginCallStatus = useCallback(
573
+ (
574
+ hash: Hex | undefined,
575
+ status: "success" | "reverted" | "pending" | "sending",
576
+ gasUsed?: bigint,
577
+ effectiveGasPrice?: bigint,
578
+ revertReason?: string | null,
579
+ ) => {
580
+ setOriginCallStatus({
581
+ txnHash: hash,
582
+ status:
583
+ status === "success"
584
+ ? "Success"
585
+ : status === "reverted"
586
+ ? "Failed"
587
+ : status === "sending"
588
+ ? "Sending..."
589
+ : "Pending",
590
+ revertReason:
591
+ status === "reverted"
592
+ ? revertReason || "Transaction reverted"
593
+ : undefined,
594
+ gasUsed: gasUsed ? Number(gasUsed) : undefined,
595
+ effectiveGasPrice: effectiveGasPrice?.toString(),
596
+ })
597
+ },
598
+ [],
599
+ )
600
+
601
+ const sendOriginTransaction = async () => {
602
+ console.log("Sending origin transaction...")
603
+ console.log(
604
+ isTransactionInProgress,
605
+ originCallParams,
606
+ originCallParams?.error,
607
+ originCallParams?.to,
608
+ originCallParams?.data,
609
+ originCallParams?.value,
610
+ originCallParams?.chainId,
611
+ )
612
+ if (
613
+ isTransactionInProgress || // Prevent duplicate transactions
614
+ !originCallParams ||
615
+ originCallParams.error ||
616
+ !originCallParams.to ||
617
+ originCallParams.data === null ||
618
+ originCallParams.value === null ||
619
+ originCallParams.chainId === null
620
+ ) {
621
+ console.error(
622
+ "Origin call parameters not available or invalid:",
623
+ originCallParams,
624
+ )
625
+ updateOriginCallStatus(
626
+ undefined,
627
+ "reverted",
628
+ undefined,
629
+ undefined,
630
+ "Origin call parameters not ready",
631
+ )
632
+ return
633
+ }
634
+
635
+ // Check if we need to switch chains
636
+ if (account.chainId !== originCallParams.chainId) {
637
+ setIsChainSwitchRequired(true)
638
+ updateOriginCallStatus(
639
+ undefined,
640
+ "pending",
641
+ undefined,
642
+ undefined,
643
+ `Switching to chain ${originCallParams.chainId}...`,
644
+ )
645
+
646
+ const walletClient = createWalletClient({
647
+ chain: getChainInfo(originCallParams.chainId)!,
648
+ transport: custom((await account.connector!.getProvider()) as any), // TODO: Add proper type
649
+ })
650
+
651
+ try {
652
+ await attemptSwitchChain({
653
+ walletClient,
654
+ desiredChainId: originCallParams.chainId,
655
+ })
656
+ setIsChainSwitchRequired(false)
657
+ } catch (error: unknown) {
658
+ console.error("Chain switch failed:", error)
659
+ if (error instanceof Error && error.message.includes("User rejected")) {
660
+ setIsAutoExecute(false)
661
+ }
662
+ updateOriginCallStatus(
663
+ undefined,
664
+ "reverted",
665
+ undefined,
666
+ undefined,
667
+ error instanceof Error
668
+ ? error.message
669
+ : "Unknown error switching chain",
670
+ )
671
+ setIsChainSwitchRequired(false)
672
+ return // Stop execution on switch failure
673
+ }
674
+ }
675
+
676
+ // Ensure only one transaction is sent at a time
677
+ if (!isTransactionInProgress) {
678
+ setIsTransactionInProgress(true) // Mark transaction as in progress
679
+ setTxnHash(undefined)
680
+ updateOriginCallStatus(undefined, "sending")
681
+
682
+ if (!estimatedGas && !isEstimateError) {
683
+ setIsEstimatingGas(true)
684
+ return // Wait for gas estimation
685
+ }
686
+
687
+ if (isEstimateError) {
688
+ console.error("Gas estimation failed:", estimateError)
689
+ updateOriginCallStatus(
690
+ undefined,
691
+ "reverted",
692
+ undefined,
693
+ undefined,
694
+ `Gas estimation failed: ${estimateError?.message}`,
695
+ )
696
+ setIsTransactionInProgress(false)
697
+ return
698
+ }
699
+
700
+ // Add 20% buffer to estimated gas
701
+ const gasLimit = estimatedGas
702
+ ? BigInt(Math.floor(Number(estimatedGas) * 1.2))
703
+ : undefined
704
+
705
+ sendOriginTxn.sendTransaction(
706
+ {
707
+ to: originCallParams.to,
708
+ data: originCallParams.data,
709
+ value: originCallParams.value,
710
+ chainId: originCallParams.chainId,
711
+ gas: gasLimit,
712
+ },
713
+ {
714
+ onSuccess: (hash: Hex) => {
715
+ console.log("Transaction sent, hash:", hash)
716
+ setTxnHash(hash)
717
+ setIsTransactionInProgress(false) // Reset transaction state
718
+ },
719
+ onError: (error: unknown) => {
720
+ console.error("Transaction failed:", error)
721
+ if (
722
+ error instanceof Error &&
723
+ (error.message.includes("User rejected") ||
724
+ error.message.includes("user rejected"))
725
+ ) {
726
+ setIsAutoExecute(false)
727
+ }
728
+ updateOriginCallStatus(
729
+ undefined,
730
+ "reverted",
731
+ undefined,
732
+ undefined,
733
+ error instanceof Error ? error.message : "Unknown error",
734
+ )
735
+ setIsTransactionInProgress(false)
736
+ },
737
+ },
738
+ )
739
+ } else {
740
+ console.warn(
741
+ "Transaction already in progress. Skipping duplicate request.",
742
+ )
743
+ }
744
+ }
745
+
746
+ // Remove the chain change effect that might be resetting state
747
+ useEffect(() => {
748
+ if (switchChainError) {
749
+ console.error("Chain switch error:", switchChainError)
750
+ updateOriginCallStatus(
751
+ undefined,
752
+ "reverted",
753
+ undefined,
754
+ undefined,
755
+ `Chain switch failed: ${switchChainError.message || "Unknown error"}`,
756
+ )
757
+ setIsChainSwitchRequired(false)
758
+ }
759
+ }, [switchChainError, updateOriginCallStatus])
760
+
761
+ // Reset gas estimation state when parameters change
762
+ useEffect(() => {
763
+ setIsEstimatingGas(false)
764
+ }, [])
765
+
766
+ // Only update chain switch required state when needed
767
+ useEffect(() => {
768
+ if (
769
+ originCallParams?.chainId &&
770
+ account.chainId === originCallParams.chainId
771
+ ) {
772
+ console.log("No chain switch required")
773
+ setIsChainSwitchRequired(false)
774
+ }
775
+ }, [account.chainId, originCallParams?.chainId])
776
+
777
+ // Effect to handle chain switching
778
+ useEffect(() => {
779
+ if (
780
+ originCallParams?.chainId &&
781
+ account.chainId !== originCallParams.chainId
782
+ ) {
783
+ async function check() {
784
+ try {
785
+ const chainId = originCallParams!.chainId!
786
+ const walletClient = createWalletClient({
787
+ chain: getChainInfo(chainId)!,
788
+ transport: custom((await account.connector!.getProvider()) as any), // TODO: Add proper type
789
+ })
790
+ await attemptSwitchChain({
791
+ walletClient,
792
+ desiredChainId: chainId,
793
+ })
794
+ } catch (error) {
795
+ console.error("Chain switch failed:", error)
796
+ }
797
+ }
798
+ check().catch(console.error)
799
+ }
800
+ }, [account, originCallParams])
801
+
802
+ // Hook to wait for transaction receipt
803
+ const {
804
+ data: receipt,
805
+ isLoading: isWaitingForReceipt,
806
+ isSuccess: receiptIsSuccess,
807
+ isError: receiptIsError,
808
+ error: receiptError,
809
+ } = useWaitForTransactionReceipt({
810
+ hash: txnHash,
811
+ confirmations: 1,
812
+ query: {
813
+ enabled: !!txnHash,
814
+ },
815
+ })
816
+
817
+ // Modify the effect that watches for transaction status
818
+ // biome-ignore lint/correctness/useExhaustiveDependencies: False positive
819
+ useEffect(() => {
820
+ if (!txnHash) {
821
+ // Only reset these when txnHash is cleared
822
+ if (originCallStatus?.txnHash) {
823
+ setOriginCallStatus(null)
824
+ }
825
+ setOriginBlockTimestamp(null)
826
+ if (Object.keys(sentMetaTxns).length > 0) {
827
+ setSentMetaTxns({})
828
+ }
829
+ return
830
+ }
831
+
832
+ if (
833
+ originCallStatus?.txnHash === txnHash &&
834
+ (originCallStatus?.status === "Success" ||
835
+ originCallStatus?.status === "Failed") &&
836
+ !isWaitingForReceipt
837
+ ) {
838
+ return
839
+ }
840
+
841
+ if (isWaitingForReceipt) {
842
+ setOriginCallStatus((prevStatus) => ({
843
+ ...(prevStatus?.txnHash === txnHash
844
+ ? prevStatus
845
+ : {
846
+ gasUsed: undefined,
847
+ effectiveGasPrice: undefined,
848
+ revertReason: undefined,
849
+ }),
850
+ txnHash,
851
+ status: "Pending",
852
+ }))
853
+ return
854
+ }
855
+
856
+ if (receiptIsSuccess && receipt) {
857
+ const newStatus = receipt.status === "success" ? "Success" : "Failed"
858
+ setOriginCallStatus({
859
+ txnHash: receipt.transactionHash,
860
+ status: newStatus,
861
+ gasUsed: receipt.gasUsed ? Number(receipt.gasUsed) : undefined,
862
+ effectiveGasPrice: receipt.effectiveGasPrice?.toString(),
863
+ revertReason:
864
+ receipt.status === "reverted"
865
+ ? ((receiptError as any)?.message as string | undefined) ||
866
+ "Transaction reverted by receipt"
867
+ : undefined,
868
+ })
869
+
870
+ if (newStatus === "Success" && receipt.blockNumber) {
871
+ const fetchTimestamp = async () => {
872
+ try {
873
+ if (!originCallParams?.chainId) {
874
+ console.error(
875
+ "[Trails] Origin chainId not available for fetching origin block timestamp.",
876
+ )
877
+ setOriginBlockTimestamp(null)
878
+ return
879
+ }
880
+ const chainConfig = getChainInfo(originCallParams.chainId)!
881
+ const client = createPublicClient({
882
+ chain: chainConfig,
883
+ transport: http(),
884
+ })
885
+ const block = await client.getBlock({
886
+ blockNumber: BigInt(receipt.blockNumber),
887
+ })
888
+ setOriginBlockTimestamp(Number(block.timestamp))
889
+ } catch (error) {
890
+ console.error(
891
+ "[Trails] Error fetching origin block timestamp:",
892
+ error,
893
+ )
894
+ setOriginBlockTimestamp(null)
895
+ }
896
+ }
897
+ fetchTimestamp()
898
+ } else if (newStatus !== "Success") {
899
+ setOriginBlockTimestamp(null)
900
+ }
901
+
902
+ if (
903
+ newStatus === "Success" &&
904
+ metaTxns &&
905
+ metaTxns.length > 0 &&
906
+ isAutoExecute &&
907
+ !metaTxns.some((tx: MetaTxn) => sentMetaTxns[`${tx.chainId}-${tx.id}`])
908
+ ) {
909
+ console.log(
910
+ "Origin transaction successful, auto-sending all meta transactions...",
911
+ )
912
+ sendMetaTxnMutation.mutate({ selectedId: null })
913
+ }
914
+ } else if (receiptIsError) {
915
+ setOriginCallStatus({
916
+ txnHash,
917
+ status: "Failed",
918
+ revertReason:
919
+ ((receiptError as any)?.message as string | undefined) ||
920
+ "Failed to get receipt",
921
+ gasUsed: undefined,
922
+ effectiveGasPrice: undefined,
923
+ })
924
+ setOriginBlockTimestamp(null)
925
+ }
926
+ }, [
927
+ txnHash,
928
+ isWaitingForReceipt,
929
+ receiptIsSuccess,
930
+ receiptIsError,
931
+ receipt,
932
+ receiptError,
933
+ metaTxns,
934
+ sentMetaTxns,
935
+ isAutoExecute,
936
+ originCallParams?.chainId,
937
+ originCallStatus?.status,
938
+ originCallStatus?.txnHash,
939
+ ])
940
+
941
+ // Modify the auto-execute effect
942
+ useEffect(() => {
943
+ const shouldAutoSend =
944
+ isAutoExecute &&
945
+ commitIntentConfigMutation.isSuccess &&
946
+ originCallParams?.chainId &&
947
+ account.chainId === originCallParams.chainId &&
948
+ !originCallParams.error &&
949
+ originCallParams.to &&
950
+ originCallParams.data !== null &&
951
+ originCallParams.value !== null &&
952
+ !sendOriginTxn.isPending &&
953
+ !isWaitingForReceipt &&
954
+ !txnHash &&
955
+ !isChainSwitchRequired &&
956
+ !originCallStatus &&
957
+ !hasAutoExecuted
958
+
959
+ if (shouldAutoSend) {
960
+ console.log("Auto-executing transaction: All conditions met.")
961
+ setHasAutoExecuted(true)
962
+
963
+ // Set initial status
964
+ setOriginCallStatus({
965
+ status: "Sending...",
966
+ })
967
+
968
+ sendOriginTxn.sendTransaction(
969
+ {
970
+ to: originCallParams.to!,
971
+ data: originCallParams.data!,
972
+ value: originCallParams.value!,
973
+ chainId: originCallParams.chainId!,
974
+ },
975
+ {
976
+ onSuccess: (hash: Hex) => {
977
+ console.log("Auto-executed transaction sent, hash:", hash)
978
+ setTxnHash(hash)
979
+ },
980
+ onError: (error: unknown) => {
981
+ console.error("Auto-executed transaction failed:", error)
982
+ if (
983
+ error instanceof Error &&
984
+ (error.message.includes("User rejected") ||
985
+ error.message.includes("user rejected"))
986
+ ) {
987
+ setIsAutoExecute(false)
988
+ }
989
+ setOriginCallStatus({
990
+ status: "Failed",
991
+ revertReason:
992
+ error instanceof Error ? error.message : "Unknown error",
993
+ })
994
+ setHasAutoExecuted(false)
995
+ },
996
+ },
997
+ )
998
+ }
999
+ }, [
1000
+ isAutoExecute,
1001
+ commitIntentConfigMutation.isSuccess,
1002
+ originCallParams,
1003
+ account.chainId,
1004
+ sendOriginTxn.isPending,
1005
+ isWaitingForReceipt,
1006
+ txnHash,
1007
+ isChainSwitchRequired,
1008
+ originCallStatus,
1009
+ hasAutoExecuted,
1010
+ sendOriginTxn,
1011
+ ])
1012
+
1013
+ // Effect to auto-commit when intent calls payloads are ready
1014
+ useEffect(() => {
1015
+ if (
1016
+ isAutoExecute &&
1017
+ intentCallsPayloads &&
1018
+ intentPreconditions &&
1019
+ trailsFee &&
1020
+ account.address &&
1021
+ originIntentAddress &&
1022
+ !commitIntentConfigMutation.isPending &&
1023
+ !commitIntentConfigMutation.isSuccess
1024
+ ) {
1025
+ console.log("Auto-committing intent configuration...")
1026
+ commitIntentConfigMutation.mutate({
1027
+ mainSignerAddress: account.address,
1028
+ calls: intentCallsPayloads,
1029
+ preconditions: intentPreconditions,
1030
+ quoteProvider: trailsFee.quoteProvider as QuoteProvider,
1031
+ addressOverrides: createIntentMutation.variables?.addressOverrides,
1032
+ })
1033
+ }
1034
+ }, [
1035
+ isAutoExecute,
1036
+ intentCallsPayloads,
1037
+ intentPreconditions,
1038
+ trailsFee,
1039
+ account.address,
1040
+ originIntentAddress,
1041
+ commitIntentConfigMutation,
1042
+ createIntentMutation.variables,
1043
+ ])
1044
+
1045
+ // Update the sendMetaTxn mutation
1046
+ const sendMetaTxnMutation = useMutation({
1047
+ mutationFn: async ({ selectedId }: { selectedId: string | null }) => {
1048
+ if (
1049
+ !intentCallsPayloads ||
1050
+ !intentPreconditions ||
1051
+ !metaTxns ||
1052
+ !account.address
1053
+ ) {
1054
+ throw new Error("Missing required data for meta-transaction")
1055
+ }
1056
+
1057
+ if (!trailsFee?.quoteProvider) {
1058
+ throw new Error("quoteProvider is required")
1059
+ }
1060
+
1061
+ const intentAddress = calculateIntentAddress(
1062
+ account.address,
1063
+ intentCallsPayloads as any[],
1064
+ )
1065
+
1066
+ // If no specific ID is selected, send all meta transactions
1067
+ const txnsToSend = selectedId
1068
+ ? [metaTxns.find((tx: MetaTxn) => tx.id === selectedId)]
1069
+ : metaTxns
1070
+
1071
+ if (!txnsToSend || (selectedId && !txnsToSend[0])) {
1072
+ throw new Error("Meta transaction not found")
1073
+ }
1074
+
1075
+ const results = []
1076
+
1077
+ for (const metaTxn of txnsToSend) {
1078
+ if (!metaTxn) continue
1079
+
1080
+ const operationKey = `${metaTxn.chainId}-${metaTxn.id}`
1081
+ const lastSentTime = sentMetaTxns[operationKey]
1082
+ const now = Date.now()
1083
+
1084
+ if (lastSentTime && now - lastSentTime < RETRY_WINDOW_MS) {
1085
+ const timeLeft = Math.ceil(
1086
+ (RETRY_WINDOW_MS - (now - lastSentTime)) / 1000,
1087
+ )
1088
+ console.log(
1089
+ `Meta transaction for ${operationKey} was sent recently. Wait ${timeLeft}s before retry`,
1090
+ )
1091
+ continue
1092
+ }
1093
+
1094
+ try {
1095
+ const chainId = parseInt(metaTxn.chainId)
1096
+ if (Number.isNaN(chainId) || chainId <= 0) {
1097
+ throw new Error(`Invalid chainId for meta transaction: ${chainId}`)
1098
+ }
1099
+ const chainRelayer = getRelayer(chainId)
1100
+ if (!chainRelayer) {
1101
+ throw new Error(`No relayer found for chainId: ${chainId}`)
1102
+ }
1103
+
1104
+ const relevantPreconditions = intentPreconditions.filter(
1105
+ (p: IntentPrecondition) =>
1106
+ p.chainId && parseInt(p.chainId) === chainId,
1107
+ )
1108
+
1109
+ console.log(
1110
+ `Relaying meta transaction ${operationKey} to intent ${intentAddress} via relayer:`,
1111
+ chainRelayer,
1112
+ )
1113
+
1114
+ const { opHash } = await chainRelayer.sendMetaTxn(
1115
+ metaTxn.walletAddress as Address.Address,
1116
+ metaTxn.contract as Address.Address,
1117
+ metaTxn.input as Hex,
1118
+ BigInt(metaTxn.chainId),
1119
+ undefined,
1120
+ relevantPreconditions,
1121
+ )
1122
+
1123
+ const useBackupRelayer = false // Disable backup relayer for now
1124
+ if (useBackupRelayer) {
1125
+ try {
1126
+ // Fire and forget send tx to backup relayer
1127
+ const backupRelayer = getBackupRelayer(chainId)
1128
+
1129
+ backupRelayer
1130
+ ?.sendMetaTxn(
1131
+ metaTxn.walletAddress as Address.Address,
1132
+ metaTxn.contract as Address.Address,
1133
+ metaTxn.input as Hex,
1134
+ BigInt(metaTxn.chainId),
1135
+ undefined,
1136
+ relevantPreconditions,
1137
+ )
1138
+ .then(() => {})
1139
+ .catch(() => {})
1140
+ } catch {}
1141
+ }
1142
+
1143
+ results.push({
1144
+ operationKey,
1145
+ opHash,
1146
+ success: true,
1147
+ })
1148
+ } catch (error: unknown) {
1149
+ results.push({
1150
+ operationKey,
1151
+ error: error instanceof Error ? error.message : "Unknown error",
1152
+ success: false,
1153
+ })
1154
+ }
1155
+ }
1156
+
1157
+ return results
1158
+ },
1159
+ onSuccess: (results) => {
1160
+ // Update states based on results
1161
+ results.forEach(({ operationKey, opHash, success }) => {
1162
+ if (success && opHash) {
1163
+ setSentMetaTxns((prev) => ({
1164
+ ...prev,
1165
+ [operationKey]: Date.now(),
1166
+ }))
1167
+
1168
+ setOperationHashes((prev) => ({
1169
+ ...prev,
1170
+ [operationKey]: opHash,
1171
+ }))
1172
+ }
1173
+ })
1174
+ },
1175
+ onError: (error) => {
1176
+ console.error("Error in meta-transaction process:", error)
1177
+ },
1178
+ retry: 5, // Allow up to 2 retries
1179
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), // Exponential backoff
1180
+ })
1181
+
1182
+ const [tokenAddress, setTokenAddress] = useState<string | null>(null)
1183
+ const [originChainId, setOriginChainId] = useState<number | null>(null)
1184
+
1185
+ useEffect(() => {
1186
+ if (
1187
+ !originIntentAddress ||
1188
+ !intentCallsPayloads?.[0]?.chainId ||
1189
+ !tokenAddress ||
1190
+ !originChainId ||
1191
+ !intentPreconditions ||
1192
+ !account.address
1193
+ ) {
1194
+ setOriginCallParams(null)
1195
+ return
1196
+ }
1197
+
1198
+ try {
1199
+ const intentAddressString = originIntentAddress as Address.Address
1200
+
1201
+ if (!intentAddressString || !isAddress(intentAddressString)) {
1202
+ setOriginCallParams(null)
1203
+ return
1204
+ }
1205
+
1206
+ let calcTo: Address.Address
1207
+ let calcData: Hex = "0x"
1208
+ let calcValue: bigint = 0n
1209
+
1210
+ const recipientAddress = intentAddressString
1211
+
1212
+ const isNative = tokenAddress === zeroAddress
1213
+
1214
+ if (isNative) {
1215
+ const nativePrecondition = intentPreconditions.find(
1216
+ (p: IntentPrecondition) =>
1217
+ (p.type === "transfer-native" || p.type === "native-balance") &&
1218
+ p.chainId === originChainId.toString(),
1219
+ )
1220
+ const nativeMinAmount =
1221
+ nativePrecondition?.data?.minAmount?.toString() ??
1222
+ nativePrecondition?.data?.min?.toString()
1223
+ if (nativeMinAmount === undefined) {
1224
+ throw new Error(
1225
+ "Could not find native precondition (transfer-native or native-balance) or min amount",
1226
+ )
1227
+ }
1228
+ calcValue = BigInt(nativeMinAmount)
1229
+ calcTo = recipientAddress
1230
+ } else {
1231
+ const erc20Precondition = intentPreconditions.find(
1232
+ (p: IntentPrecondition) =>
1233
+ p.type === "erc20-balance" &&
1234
+ p.chainId === originChainId.toString() &&
1235
+ p.data?.token &&
1236
+ isAddressEqual(
1237
+ Address.from(p.data.token),
1238
+ Address.from(tokenAddress),
1239
+ ),
1240
+ )
1241
+
1242
+ const erc20MinAmount = erc20Precondition?.data?.min?.toString()
1243
+ if (erc20MinAmount === undefined) {
1244
+ throw new Error(
1245
+ "Could not find ERC20 balance precondition or min amount",
1246
+ )
1247
+ }
1248
+ calcData = getERC20TransferData({
1249
+ recipient: recipientAddress,
1250
+ amount: BigInt(erc20MinAmount),
1251
+ })
1252
+ calcTo = tokenAddress as Address.Address
1253
+ }
1254
+
1255
+ setOriginCallParams({
1256
+ to: calcTo,
1257
+ data: calcData,
1258
+ value: calcValue,
1259
+ chainId: originChainId,
1260
+ error: undefined,
1261
+ })
1262
+ } catch (error: unknown) {
1263
+ console.error(
1264
+ "[trails-sdk] Failed to calculate origin call params for UI:",
1265
+ error,
1266
+ )
1267
+ setOriginCallParams({
1268
+ to: null,
1269
+ data: null,
1270
+ value: null,
1271
+ chainId: null,
1272
+ error: error instanceof Error ? error.message : "Unknown error",
1273
+ })
1274
+ }
1275
+ }, [
1276
+ intentCallsPayloads,
1277
+ tokenAddress,
1278
+ originChainId,
1279
+ intentPreconditions,
1280
+ account.address,
1281
+ originIntentAddress,
1282
+ ])
1283
+
1284
+ // const checkPreconditionStatuses = useCallback(async () => {
1285
+ // if (!intentPreconditions) return
1286
+
1287
+ // const statuses = await Promise.all(
1288
+ // intentPreconditions.map(async (precondition) => {
1289
+ // try {
1290
+ // const chainIdString = precondition.chainId
1291
+ // if (!chainIdString) {
1292
+ // console.warn('[trails-sdk] Precondition missing chainId:', precondition)
1293
+ // return false
1294
+ // }
1295
+ // const chainId = parseInt(chainIdString)
1296
+ // if (isNaN(chainId) || chainId <= 0) {
1297
+ // console.warn('[trails-sdk] Precondition has invalid chainId:', chainIdString, precondition)
1298
+ // return false
1299
+ // }
1300
+
1301
+ // const chainRelayer = getRelayer(chainId)
1302
+ // if (!chainRelayer) {
1303
+ // console.error(`[trails-sdk] No relayer found for chainId: ${chainId}`)
1304
+ // return false
1305
+ // }
1306
+
1307
+ // return await chainRelayer.checkPrecondition(precondition)
1308
+ // } catch (error) {
1309
+ // console.error('[trails-sdk] Error checking precondition:', error, 'Precondition:', precondition)
1310
+ // return false
1311
+ // }
1312
+ // }),
1313
+ // )
1314
+
1315
+ // setPreconditionStatuses(statuses)
1316
+ // }, [intentPreconditions, getRelayer])
1317
+
1318
+ // useEffect(() => {
1319
+ // // TODO: Remove this once we have a way to check precondition statuses
1320
+ // if (false) {
1321
+ // checkPreconditionStatuses()
1322
+ // }
1323
+ // }, [intentPreconditions, checkPreconditionStatuses])
1324
+
1325
+ // Add monitoring for each meta transaction
1326
+ const metaTxnMonitorStatuses = useMetaTxnsMonitor(
1327
+ metaTxns as unknown as MetaTxn[] | undefined,
1328
+ getRelayer,
1329
+ )
1330
+
1331
+ // Create a stable dependency for the meta timestamp effect
1332
+ const _stableMetaTxnStatusesKey = useMemo(() => {
1333
+ if (!metaTxns || Object.keys(metaTxnMonitorStatuses).length === 0) {
1334
+ return "no_statuses"
1335
+ }
1336
+ // Sort by a stable key (e.g., id) to ensure consistent order if metaTxns array order changes
1337
+ // but content is the same, though metaTxns itself is a dependency, so this might be redundant if metaTxns order is stable.
1338
+ const sortedTxnIds = metaTxns
1339
+ .map((tx: MetaTxn) => `${tx.chainId}-${tx.id}`)
1340
+ .sort()
1341
+
1342
+ return sortedTxnIds
1343
+ .map((key: string) => {
1344
+ const statusObj = metaTxnMonitorStatuses[key]
1345
+ return `${key}:${statusObj ? statusObj.status : "loading"}`
1346
+ })
1347
+ .join(",")
1348
+ }, [metaTxns, metaTxnMonitorStatuses])
1349
+
1350
+ const processedTxns = useRef(new Set<string>())
1351
+
1352
+ // Effect to fetch meta-transaction block timestamps
1353
+ useEffect(() => {
1354
+ console.log(
1355
+ "[trails-sdk] Running meta-transaction block timestamp effect:",
1356
+ {
1357
+ metaTxnsLength: metaTxns?.length,
1358
+ monitorStatusesLength: Object.keys(metaTxnMonitorStatuses).length,
1359
+ },
1360
+ )
1361
+
1362
+ if (!metaTxns || metaTxns.length === 0) {
1363
+ console.log("[trails-sdk] No meta transactions, clearing timestamps")
1364
+ processedTxns.current.clear()
1365
+ if (Object.keys(metaTxnBlockTimestamps).length > 0) {
1366
+ setMetaTxnBlockTimestamps({})
1367
+ }
1368
+ return
1369
+ }
1370
+
1371
+ if (!Object.keys(metaTxnMonitorStatuses).length) {
1372
+ console.log("[trails-sdk] No monitor statuses yet, waiting...")
1373
+ return
1374
+ }
1375
+
1376
+ metaTxns.forEach(async (metaTxn: MetaTxn) => {
1377
+ const operationKey = `${metaTxn.chainId}-${metaTxn.id}`
1378
+
1379
+ // Skip if already processed
1380
+ if (processedTxns.current.has(operationKey)) {
1381
+ console.log(
1382
+ `[trails-sdk] MetaTxn ${operationKey}: Already processed, skipping`,
1383
+ )
1384
+ return
1385
+ }
1386
+
1387
+ const monitorStatus = metaTxnMonitorStatuses[operationKey]
1388
+ if (!monitorStatus || monitorStatus.status !== "confirmed") {
1389
+ console.log(
1390
+ `[trails-sdk] MetaTxn ${operationKey}: Status not confirmed, skipping`,
1391
+ )
1392
+ return
1393
+ }
1394
+
1395
+ // Type assertion since we know it exists when status is "confirmed"
1396
+ const transactionHash = monitorStatus.transactionHash as Hex | undefined
1397
+ if (!transactionHash) {
1398
+ console.log(
1399
+ `[trails-sdk] MetaTxn ${operationKey}: No transaction hash, skipping`,
1400
+ )
1401
+ return
1402
+ }
1403
+
1404
+ console.log(
1405
+ `[trails-sdk] MetaTxn ${operationKey}: Processing transaction ${transactionHash}`,
1406
+ )
1407
+ processedTxns.current.add(operationKey)
1408
+
1409
+ try {
1410
+ const chainId = parseInt(metaTxn.chainId)
1411
+ if (Number.isNaN(chainId) || chainId <= 0) {
1412
+ throw new Error(
1413
+ `Invalid chainId for meta transaction: ${metaTxn.chainId}`,
1414
+ )
1415
+ }
1416
+
1417
+ const chainConfig = getChainInfo(chainId)!
1418
+ const client = createPublicClient({
1419
+ chain: chainConfig,
1420
+ transport: http(),
1421
+ })
1422
+
1423
+ const receipt = await client.getTransactionReceipt({
1424
+ hash: transactionHash,
1425
+ })
1426
+
1427
+ if (receipt && typeof receipt.blockNumber === "bigint") {
1428
+ const block = await client.getBlock({
1429
+ blockNumber: receipt.blockNumber,
1430
+ })
1431
+ console.log(
1432
+ `[trails-sdk] MetaTxn ${operationKey}: Got block timestamp ${block.timestamp}`,
1433
+ )
1434
+ setMetaTxnBlockTimestamps((prev) => ({
1435
+ ...prev,
1436
+ [operationKey]: {
1437
+ timestamp: Number(block.timestamp),
1438
+ error: undefined,
1439
+ },
1440
+ }))
1441
+ } else {
1442
+ console.warn(
1443
+ `[trails-sdk] MetaTxn ${operationKey}: No block number in receipt`,
1444
+ )
1445
+ setMetaTxnBlockTimestamps((prev) => ({
1446
+ ...prev,
1447
+ [operationKey]: {
1448
+ timestamp: null,
1449
+ error: "Block number not found in receipt",
1450
+ },
1451
+ }))
1452
+ }
1453
+ } catch (error: any) {
1454
+ console.error(`[trails-sdk] MetaTxn ${operationKey}: Error:`, error)
1455
+ setMetaTxnBlockTimestamps((prev) => ({
1456
+ ...prev,
1457
+ [operationKey]: {
1458
+ timestamp: null,
1459
+ error: error.message || "Failed to fetch receipt/timestamp",
1460
+ },
1461
+ }))
1462
+ }
1463
+ })
1464
+ }, [metaTxns, metaTxnMonitorStatuses, metaTxnBlockTimestamps])
1465
+
1466
+ const updateAutoExecute = (enabled: boolean) => {
1467
+ setIsAutoExecute(enabled)
1468
+ }
1469
+
1470
+ function createIntent(args: GetIntentCallsPayloadsArgs) {
1471
+ createIntentMutation.mutate({
1472
+ ...args,
1473
+ destinationSalt: Date.now().toString(),
1474
+ } as GetIntentCallsPayloadsArgs)
1475
+ }
1476
+
1477
+ function commitIntentConfig(args: {
1478
+ mainSignerAddress: string
1479
+ calls: IntentCallsPayload[]
1480
+ preconditions: IntentPrecondition[]
1481
+ quoteProvider: QuoteProvider
1482
+ addressOverrides?: AddressOverrides
1483
+ }) {
1484
+ console.log("[trails-sdk] commitIntentConfig", args)
1485
+ commitIntentConfigMutation.mutate(args)
1486
+ }
1487
+
1488
+ function updateOriginCallParams(
1489
+ args: { originChainId: number; tokenAddress: string } | null,
1490
+ ) {
1491
+ if (!args) {
1492
+ setOriginCallParams(null)
1493
+ return
1494
+ }
1495
+ const { originChainId, tokenAddress } = args
1496
+ setOriginChainId(originChainId)
1497
+ setTokenAddress(tokenAddress)
1498
+ }
1499
+
1500
+ function sendMetaTxn(selectedId: string | null) {
1501
+ sendMetaTxnMutation.mutate({ selectedId })
1502
+ }
1503
+
1504
+ const { chainId } = account
1505
+
1506
+ const originChainIdFromParams = originCallParams?.chainId
1507
+
1508
+ const createIntentPending = createIntentMutation.isPending
1509
+ const createIntentSuccess = createIntentMutation.isSuccess
1510
+ const createIntentError = createIntentMutation.error
1511
+ const createIntentArgs = createIntentMutation.variables
1512
+
1513
+ const commitIntentConfigPending = commitIntentConfigMutation.isPending
1514
+ const commitIntentConfigSuccess = commitIntentConfigMutation.isSuccess
1515
+ const commitIntentConfigError = commitIntentConfigMutation.error
1516
+ const commitIntentConfigArgs = commitIntentConfigMutation.variables
1517
+
1518
+ const sendMetaTxnPending = sendMetaTxnMutation.isPending
1519
+ const sendMetaTxnSuccess = sendMetaTxnMutation.isSuccess
1520
+ const sendMetaTxnError = sendMetaTxnMutation.error
1521
+ const sendMetaTxnArgs = sendMetaTxnMutation.variables
1522
+
1523
+ return {
1524
+ apiClient,
1525
+ metaTxns,
1526
+ intentCallsPayloads,
1527
+ intentPreconditions,
1528
+ trailsFee,
1529
+ txnHash,
1530
+ committedOriginIntentAddress,
1531
+ committedDestinationIntentAddress,
1532
+ verificationStatus,
1533
+ getRelayer,
1534
+ estimatedGas,
1535
+ isEstimateError,
1536
+ estimateError,
1537
+ calculateIntentAddress,
1538
+ calculateOriginAndDestinationIntentAddresses,
1539
+ committedIntentConfig,
1540
+ isLoadingCommittedConfig,
1541
+ committedConfigError,
1542
+ commitIntentConfig,
1543
+ commitIntentConfigPending,
1544
+ commitIntentConfigSuccess,
1545
+ commitIntentConfigError,
1546
+ commitIntentConfigArgs,
1547
+ getIntentCallsPayloads,
1548
+ operationHashes,
1549
+ callIntentCallsPayload,
1550
+ sendOriginTransaction,
1551
+ switchChain,
1552
+ isSwitchingChain,
1553
+ switchChainError,
1554
+ isTransactionInProgress,
1555
+ isChainSwitchRequired:
1556
+ chainId !== originChainIdFromParams && !!originChainIdFromParams,
1557
+ sendTransaction: sendOriginTxn.sendTransaction,
1558
+ isSendingTransaction: sendOriginTxn.isPending,
1559
+ originCallStatus,
1560
+ updateOriginCallStatus,
1561
+ isEstimatingGas,
1562
+ isAutoExecute,
1563
+ updateAutoExecute,
1564
+ receipt,
1565
+ isWaitingForReceipt,
1566
+ receiptIsSuccess,
1567
+ receiptIsError,
1568
+ receiptError,
1569
+ hasAutoExecuted,
1570
+ originCallSuccess: sendOriginTxn.isSuccess,
1571
+ sentMetaTxns,
1572
+ sendMetaTxn,
1573
+ sendMetaTxnPending,
1574
+ sendMetaTxnSuccess,
1575
+ sendMetaTxnError,
1576
+ sendMetaTxnArgs,
1577
+ clearIntent,
1578
+ metaTxnMonitorStatuses,
1579
+ createIntent,
1580
+ createIntentPending,
1581
+ createIntentSuccess,
1582
+ createIntentError,
1583
+ createIntentArgs,
1584
+ originCallParams,
1585
+ updateOriginCallParams,
1586
+ originBlockTimestamp,
1587
+ metaTxnBlockTimestamps,
1588
+ originIntentAddress,
1589
+ destinationIntentAddress,
1590
+ }
1591
+ }