@miradexio/client 0.1.0

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 (405) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +405 -0
  3. package/dist/address/base58.d.ts +9 -0
  4. package/dist/address/base58.d.ts.map +1 -0
  5. package/dist/address/base58.js +33 -0
  6. package/dist/address/base58.js.map +1 -0
  7. package/dist/address/bech32.d.ts +13 -0
  8. package/dist/address/bech32.d.ts.map +1 -0
  9. package/dist/address/bech32.js +41 -0
  10. package/dist/address/bech32.js.map +1 -0
  11. package/dist/address/evm.d.ts +5 -0
  12. package/dist/address/evm.d.ts.map +1 -0
  13. package/dist/address/evm.js +27 -0
  14. package/dist/address/evm.js.map +1 -0
  15. package/dist/address/index.d.ts +15 -0
  16. package/dist/address/index.d.ts.map +1 -0
  17. package/dist/address/index.js +134 -0
  18. package/dist/address/index.js.map +1 -0
  19. package/dist/address/monero.d.ts +15 -0
  20. package/dist/address/monero.d.ts.map +1 -0
  21. package/dist/address/monero.js +30 -0
  22. package/dist/address/monero.js.map +1 -0
  23. package/dist/address/polkadot.d.ts +10 -0
  24. package/dist/address/polkadot.d.ts.map +1 -0
  25. package/dist/address/polkadot.js +36 -0
  26. package/dist/address/polkadot.js.map +1 -0
  27. package/dist/address/solana.d.ts +5 -0
  28. package/dist/address/solana.d.ts.map +1 -0
  29. package/dist/address/solana.js +17 -0
  30. package/dist/address/solana.js.map +1 -0
  31. package/dist/address/ton.d.ts +11 -0
  32. package/dist/address/ton.d.ts.map +1 -0
  33. package/dist/address/ton.js +28 -0
  34. package/dist/address/ton.js.map +1 -0
  35. package/dist/api/index.d.ts +80 -0
  36. package/dist/api/index.d.ts.map +1 -0
  37. package/dist/api/index.js +213 -0
  38. package/dist/api/index.js.map +1 -0
  39. package/dist/atomic-swap/drive.d.ts +22 -0
  40. package/dist/atomic-swap/drive.d.ts.map +1 -0
  41. package/dist/atomic-swap/drive.js +713 -0
  42. package/dist/atomic-swap/drive.js.map +1 -0
  43. package/dist/atomic-swap/extract.d.ts +46 -0
  44. package/dist/atomic-swap/extract.d.ts.map +1 -0
  45. package/dist/atomic-swap/extract.js +55 -0
  46. package/dist/atomic-swap/extract.js.map +1 -0
  47. package/dist/atomic-swap/index.d.ts +15 -0
  48. package/dist/atomic-swap/index.d.ts.map +1 -0
  49. package/dist/atomic-swap/index.js +13 -0
  50. package/dist/atomic-swap/index.js.map +1 -0
  51. package/dist/atomic-swap/monero-sweep/errors.d.ts +2 -0
  52. package/dist/atomic-swap/monero-sweep/errors.d.ts.map +1 -0
  53. package/dist/atomic-swap/monero-sweep/errors.js +43 -0
  54. package/dist/atomic-swap/monero-sweep/errors.js.map +1 -0
  55. package/dist/atomic-swap/monero-sweep/index.d.ts +33 -0
  56. package/dist/atomic-swap/monero-sweep/index.d.ts.map +1 -0
  57. package/dist/atomic-swap/monero-sweep/index.js +415 -0
  58. package/dist/atomic-swap/monero-sweep/index.js.map +1 -0
  59. package/dist/atomic-swap/monero-sweep/ring-select.d.ts +12 -0
  60. package/dist/atomic-swap/monero-sweep/ring-select.d.ts.map +1 -0
  61. package/dist/atomic-swap/monero-sweep/ring-select.js +61 -0
  62. package/dist/atomic-swap/monero-sweep/ring-select.js.map +1 -0
  63. package/dist/atomic-swap/presign.d.ts +101 -0
  64. package/dist/atomic-swap/presign.d.ts.map +1 -0
  65. package/dist/atomic-swap/presign.js +460 -0
  66. package/dist/atomic-swap/presign.js.map +1 -0
  67. package/dist/atomic-swap/refund.d.ts +72 -0
  68. package/dist/atomic-swap/refund.d.ts.map +1 -0
  69. package/dist/atomic-swap/refund.js +224 -0
  70. package/dist/atomic-swap/refund.js.map +1 -0
  71. package/dist/atomic-swap/run.d.ts +27 -0
  72. package/dist/atomic-swap/run.d.ts.map +1 -0
  73. package/dist/atomic-swap/run.js +282 -0
  74. package/dist/atomic-swap/run.js.map +1 -0
  75. package/dist/atomic-swap/snapshot.d.ts +111 -0
  76. package/dist/atomic-swap/snapshot.d.ts.map +1 -0
  77. package/dist/atomic-swap/snapshot.js +69 -0
  78. package/dist/atomic-swap/snapshot.js.map +1 -0
  79. package/dist/atomic-swap/submit-encsig.d.ts +10 -0
  80. package/dist/atomic-swap/submit-encsig.d.ts.map +1 -0
  81. package/dist/atomic-swap/submit-encsig.js +56 -0
  82. package/dist/atomic-swap/submit-encsig.js.map +1 -0
  83. package/dist/atomic-swap/types.d.ts +168 -0
  84. package/dist/atomic-swap/types.d.ts.map +1 -0
  85. package/dist/atomic-swap/types.js +5 -0
  86. package/dist/atomic-swap/types.js.map +1 -0
  87. package/dist/blockchain/quorum-provider.d.ts +25 -0
  88. package/dist/blockchain/quorum-provider.d.ts.map +1 -0
  89. package/dist/blockchain/quorum-provider.js +144 -0
  90. package/dist/blockchain/quorum-provider.js.map +1 -0
  91. package/dist/cooperative-redeem.d.ts +23 -0
  92. package/dist/cooperative-redeem.d.ts.map +1 -0
  93. package/dist/cooperative-redeem.js +40 -0
  94. package/dist/cooperative-redeem.js.map +1 -0
  95. package/dist/engine/blockchain-querier.d.ts +34 -0
  96. package/dist/engine/blockchain-querier.d.ts.map +1 -0
  97. package/dist/engine/blockchain-querier.js +2 -0
  98. package/dist/engine/blockchain-querier.js.map +1 -0
  99. package/dist/engine/engine-state.d.ts +20 -0
  100. package/dist/engine/engine-state.d.ts.map +1 -0
  101. package/dist/engine/engine-state.js +9 -0
  102. package/dist/engine/engine-state.js.map +1 -0
  103. package/dist/engine/flow-context.d.ts +494 -0
  104. package/dist/engine/flow-context.d.ts.map +1 -0
  105. package/dist/engine/flow-context.js +147 -0
  106. package/dist/engine/flow-context.js.map +1 -0
  107. package/dist/engine/flows/atomic-flow-state.d.ts +124 -0
  108. package/dist/engine/flows/atomic-flow-state.d.ts.map +1 -0
  109. package/dist/engine/flows/atomic-flow-state.js +2 -0
  110. package/dist/engine/flows/atomic-flow-state.js.map +1 -0
  111. package/dist/engine/flows/atomic-flow.d.ts +88 -0
  112. package/dist/engine/flows/atomic-flow.d.ts.map +1 -0
  113. package/dist/engine/flows/atomic-flow.js +1192 -0
  114. package/dist/engine/flows/atomic-flow.js.map +1 -0
  115. package/dist/engine/flows/history-flow-state.d.ts +19 -0
  116. package/dist/engine/flows/history-flow-state.d.ts.map +1 -0
  117. package/dist/engine/flows/history-flow-state.js +2 -0
  118. package/dist/engine/flows/history-flow-state.js.map +1 -0
  119. package/dist/engine/flows/providers-flow-state.d.ts +14 -0
  120. package/dist/engine/flows/providers-flow-state.d.ts.map +1 -0
  121. package/dist/engine/flows/providers-flow-state.js +2 -0
  122. package/dist/engine/flows/providers-flow-state.js.map +1 -0
  123. package/dist/engine/flows/quote-flow-state.d.ts +20 -0
  124. package/dist/engine/flows/quote-flow-state.d.ts.map +1 -0
  125. package/dist/engine/flows/quote-flow-state.js +2 -0
  126. package/dist/engine/flows/quote-flow-state.js.map +1 -0
  127. package/dist/engine/flows/swap-flow-state.d.ts +155 -0
  128. package/dist/engine/flows/swap-flow-state.d.ts.map +1 -0
  129. package/dist/engine/flows/swap-flow-state.js +2 -0
  130. package/dist/engine/flows/swap-flow-state.js.map +1 -0
  131. package/dist/engine/flows/swap-flow.d.ts +81 -0
  132. package/dist/engine/flows/swap-flow.d.ts.map +1 -0
  133. package/dist/engine/flows/swap-flow.js +720 -0
  134. package/dist/engine/flows/swap-flow.js.map +1 -0
  135. package/dist/engine/flows/tokens-flow-state.d.ts +16 -0
  136. package/dist/engine/flows/tokens-flow-state.d.ts.map +1 -0
  137. package/dist/engine/flows/tokens-flow-state.js +2 -0
  138. package/dist/engine/flows/tokens-flow-state.js.map +1 -0
  139. package/dist/engine/miradex-engine.d.ts +152 -0
  140. package/dist/engine/miradex-engine.d.ts.map +1 -0
  141. package/dist/engine/miradex-engine.js +278 -0
  142. package/dist/engine/miradex-engine.js.map +1 -0
  143. package/dist/engine/pipeline.d.ts +27 -0
  144. package/dist/engine/pipeline.d.ts.map +1 -0
  145. package/dist/engine/pipeline.js +166 -0
  146. package/dist/engine/pipeline.js.map +1 -0
  147. package/dist/engine/platform.d.ts +220 -0
  148. package/dist/engine/platform.d.ts.map +1 -0
  149. package/dist/engine/platform.js +2 -0
  150. package/dist/engine/platform.js.map +1 -0
  151. package/dist/index.d.ts +85 -0
  152. package/dist/index.d.ts.map +1 -0
  153. package/dist/index.js +62 -0
  154. package/dist/index.js.map +1 -0
  155. package/dist/interfaces/blockchain.d.ts +33 -0
  156. package/dist/interfaces/blockchain.d.ts.map +1 -0
  157. package/dist/interfaces/blockchain.js +2 -0
  158. package/dist/interfaces/blockchain.js.map +1 -0
  159. package/dist/interfaces/logger.d.ts +14 -0
  160. package/dist/interfaces/logger.d.ts.map +1 -0
  161. package/dist/interfaces/logger.js +7 -0
  162. package/dist/interfaces/logger.js.map +1 -0
  163. package/dist/lib/bitcoin/deposit-watcher.d.ts +66 -0
  164. package/dist/lib/bitcoin/deposit-watcher.d.ts.map +1 -0
  165. package/dist/lib/bitcoin/deposit-watcher.js +218 -0
  166. package/dist/lib/bitcoin/deposit-watcher.js.map +1 -0
  167. package/dist/lib/bitcoin/script-hash.d.ts +8 -0
  168. package/dist/lib/bitcoin/script-hash.d.ts.map +1 -0
  169. package/dist/lib/bitcoin/script-hash.js +29 -0
  170. package/dist/lib/bitcoin/script-hash.js.map +1 -0
  171. package/dist/lib/bitcoin/sweep.d.ts +56 -0
  172. package/dist/lib/bitcoin/sweep.d.ts.map +1 -0
  173. package/dist/lib/bitcoin/sweep.js +185 -0
  174. package/dist/lib/bitcoin/sweep.js.map +1 -0
  175. package/dist/lib/bitcoin/tx-verify.d.ts +43 -0
  176. package/dist/lib/bitcoin/tx-verify.d.ts.map +1 -0
  177. package/dist/lib/bitcoin/tx-verify.js +202 -0
  178. package/dist/lib/bitcoin/tx-verify.js.map +1 -0
  179. package/dist/lib/bitcoin/wallet.d.ts +71 -0
  180. package/dist/lib/bitcoin/wallet.d.ts.map +1 -0
  181. package/dist/lib/bitcoin/wallet.js +141 -0
  182. package/dist/lib/bitcoin/wallet.js.map +1 -0
  183. package/dist/lib/crypto/bytes.d.ts +21 -0
  184. package/dist/lib/crypto/bytes.d.ts.map +1 -0
  185. package/dist/lib/crypto/bytes.js +39 -0
  186. package/dist/lib/crypto/bytes.js.map +1 -0
  187. package/dist/lib/crypto/errors.d.ts +12 -0
  188. package/dist/lib/crypto/errors.d.ts.map +1 -0
  189. package/dist/lib/crypto/errors.js +16 -0
  190. package/dist/lib/crypto/errors.js.map +1 -0
  191. package/dist/lib/crypto/keygen.d.ts +19 -0
  192. package/dist/lib/crypto/keygen.d.ts.map +1 -0
  193. package/dist/lib/crypto/keygen.js +24 -0
  194. package/dist/lib/crypto/keygen.js.map +1 -0
  195. package/dist/lib/crypto/libp2p-identity.d.ts +25 -0
  196. package/dist/lib/crypto/libp2p-identity.d.ts.map +1 -0
  197. package/dist/lib/crypto/libp2p-identity.js +80 -0
  198. package/dist/lib/crypto/libp2p-identity.js.map +1 -0
  199. package/dist/lib/crypto/mnemonic.d.ts +28 -0
  200. package/dist/lib/crypto/mnemonic.d.ts.map +1 -0
  201. package/dist/lib/crypto/mnemonic.js +97 -0
  202. package/dist/lib/crypto/mnemonic.js.map +1 -0
  203. package/dist/lib/crypto/platform.d.ts +10 -0
  204. package/dist/lib/crypto/platform.d.ts.map +1 -0
  205. package/dist/lib/crypto/platform.js +38 -0
  206. package/dist/lib/crypto/platform.js.map +1 -0
  207. package/dist/lib/crypto/scalars.d.ts +33 -0
  208. package/dist/lib/crypto/scalars.d.ts.map +1 -0
  209. package/dist/lib/crypto/scalars.js +81 -0
  210. package/dist/lib/crypto/scalars.js.map +1 -0
  211. package/dist/lib/crypto/types.d.ts +24 -0
  212. package/dist/lib/crypto/types.d.ts.map +1 -0
  213. package/dist/lib/crypto/types.js +2 -0
  214. package/dist/lib/crypto/types.js.map +1 -0
  215. package/dist/lib/crypto/wasm.d.ts +51 -0
  216. package/dist/lib/crypto/wasm.d.ts.map +1 -0
  217. package/dist/lib/crypto/wasm.js +192 -0
  218. package/dist/lib/crypto/wasm.js.map +1 -0
  219. package/dist/lib/default-config.d.ts +140 -0
  220. package/dist/lib/default-config.d.ts.map +1 -0
  221. package/dist/lib/default-config.js +239 -0
  222. package/dist/lib/default-config.js.map +1 -0
  223. package/dist/lib/delay.d.ts +6 -0
  224. package/dist/lib/delay.d.ts.map +1 -0
  225. package/dist/lib/delay.js +20 -0
  226. package/dist/lib/delay.js.map +1 -0
  227. package/dist/lib/errors.d.ts +90 -0
  228. package/dist/lib/errors.d.ts.map +1 -0
  229. package/dist/lib/errors.js +129 -0
  230. package/dist/lib/errors.js.map +1 -0
  231. package/dist/lib/format.d.ts +7 -0
  232. package/dist/lib/format.d.ts.map +1 -0
  233. package/dist/lib/format.js +43 -0
  234. package/dist/lib/format.js.map +1 -0
  235. package/dist/lib/keystore.d.ts +85 -0
  236. package/dist/lib/keystore.d.ts.map +1 -0
  237. package/dist/lib/keystore.js +105 -0
  238. package/dist/lib/keystore.js.map +1 -0
  239. package/dist/lib/monero/output-scanner.d.ts +53 -0
  240. package/dist/lib/monero/output-scanner.d.ts.map +1 -0
  241. package/dist/lib/monero/output-scanner.js +180 -0
  242. package/dist/lib/monero/output-scanner.js.map +1 -0
  243. package/dist/lib/monero/rpc.d.ts +131 -0
  244. package/dist/lib/monero/rpc.d.ts.map +1 -0
  245. package/dist/lib/monero/rpc.js +267 -0
  246. package/dist/lib/monero/rpc.js.map +1 -0
  247. package/dist/lib/monero/verify-lock.d.ts +50 -0
  248. package/dist/lib/monero/verify-lock.d.ts.map +1 -0
  249. package/dist/lib/monero/verify-lock.js +161 -0
  250. package/dist/lib/monero/verify-lock.js.map +1 -0
  251. package/dist/lib/monero/verify-sweep.d.ts +59 -0
  252. package/dist/lib/monero/verify-sweep.d.ts.map +1 -0
  253. package/dist/lib/monero/verify-sweep.js +82 -0
  254. package/dist/lib/monero/verify-sweep.js.map +1 -0
  255. package/dist/lib/monero/wasm.d.ts +19 -0
  256. package/dist/lib/monero/wasm.d.ts.map +1 -0
  257. package/dist/lib/monero/wasm.js +24 -0
  258. package/dist/lib/monero/wasm.js.map +1 -0
  259. package/dist/lib/pow-solver.d.ts +4 -0
  260. package/dist/lib/pow-solver.d.ts.map +1 -0
  261. package/dist/lib/pow-solver.js +37 -0
  262. package/dist/lib/pow-solver.js.map +1 -0
  263. package/dist/lib/retry.d.ts +86 -0
  264. package/dist/lib/retry.d.ts.map +1 -0
  265. package/dist/lib/retry.js +104 -0
  266. package/dist/lib/retry.js.map +1 -0
  267. package/dist/portable.d.ts +23 -0
  268. package/dist/portable.d.ts.map +1 -0
  269. package/dist/portable.js +13 -0
  270. package/dist/portable.js.map +1 -0
  271. package/dist/quote-binding.d.ts +31 -0
  272. package/dist/quote-binding.d.ts.map +1 -0
  273. package/dist/quote-binding.js +40 -0
  274. package/dist/quote-binding.js.map +1 -0
  275. package/dist/swap-executor.d.ts +51 -0
  276. package/dist/swap-executor.d.ts.map +1 -0
  277. package/dist/swap-executor.js +138 -0
  278. package/dist/swap-executor.js.map +1 -0
  279. package/dist/types/api.d.ts +34 -0
  280. package/dist/types/api.d.ts.map +1 -0
  281. package/dist/types/api.js +18 -0
  282. package/dist/types/api.js.map +1 -0
  283. package/dist/types/errors.d.ts +94 -0
  284. package/dist/types/errors.d.ts.map +1 -0
  285. package/dist/types/errors.js +93 -0
  286. package/dist/types/errors.js.map +1 -0
  287. package/dist/types/index.d.ts +8 -0
  288. package/dist/types/index.d.ts.map +1 -0
  289. package/dist/types/index.js +10 -0
  290. package/dist/types/index.js.map +1 -0
  291. package/dist/types/keys.d.ts +33 -0
  292. package/dist/types/keys.d.ts.map +1 -0
  293. package/dist/types/keys.js +2 -0
  294. package/dist/types/keys.js.map +1 -0
  295. package/dist/types/protocol.d.ts +93 -0
  296. package/dist/types/protocol.d.ts.map +1 -0
  297. package/dist/types/protocol.js +18 -0
  298. package/dist/types/protocol.js.map +1 -0
  299. package/dist/types/status.d.ts +3 -0
  300. package/dist/types/status.d.ts.map +1 -0
  301. package/dist/types/status.js +23 -0
  302. package/dist/types/status.js.map +1 -0
  303. package/dist/types/verification.d.ts +49 -0
  304. package/dist/types/verification.d.ts.map +1 -0
  305. package/dist/types/verification.js +34 -0
  306. package/dist/types/verification.js.map +1 -0
  307. package/dist/verification/atomic-swap.d.ts +9 -0
  308. package/dist/verification/atomic-swap.d.ts.map +1 -0
  309. package/dist/verification/atomic-swap.js +22 -0
  310. package/dist/verification/atomic-swap.js.map +1 -0
  311. package/dist/verification/chainflip-networks.d.ts +46 -0
  312. package/dist/verification/chainflip-networks.d.ts.map +1 -0
  313. package/dist/verification/chainflip-networks.js +24 -0
  314. package/dist/verification/chainflip-networks.js.map +1 -0
  315. package/dist/verification/chainflip.d.ts +61 -0
  316. package/dist/verification/chainflip.d.ts.map +1 -0
  317. package/dist/verification/chainflip.js +377 -0
  318. package/dist/verification/chainflip.js.map +1 -0
  319. package/dist/verification/constants.d.ts +52 -0
  320. package/dist/verification/constants.d.ts.map +1 -0
  321. package/dist/verification/constants.js +54 -0
  322. package/dist/verification/constants.js.map +1 -0
  323. package/dist/verification/index.d.ts +71 -0
  324. package/dist/verification/index.d.ts.map +1 -0
  325. package/dist/verification/index.js +91 -0
  326. package/dist/verification/index.js.map +1 -0
  327. package/dist/verification/memo.d.ts +27 -0
  328. package/dist/verification/memo.d.ts.map +1 -0
  329. package/dist/verification/memo.js +52 -0
  330. package/dist/verification/memo.js.map +1 -0
  331. package/dist/verification/near-intents.d.ts +91 -0
  332. package/dist/verification/near-intents.d.ts.map +1 -0
  333. package/dist/verification/near-intents.js +213 -0
  334. package/dist/verification/near-intents.js.map +1 -0
  335. package/dist/verification/rate-oracle.d.ts +32 -0
  336. package/dist/verification/rate-oracle.d.ts.map +1 -0
  337. package/dist/verification/rate-oracle.js +43 -0
  338. package/dist/verification/rate-oracle.js.map +1 -0
  339. package/dist/verification/shared.d.ts +20 -0
  340. package/dist/verification/shared.d.ts.map +1 -0
  341. package/dist/verification/shared.js +25 -0
  342. package/dist/verification/shared.js.map +1 -0
  343. package/dist/verification/thorchain-networks.d.ts +35 -0
  344. package/dist/verification/thorchain-networks.d.ts.map +1 -0
  345. package/dist/verification/thorchain-networks.js +35 -0
  346. package/dist/verification/thorchain-networks.js.map +1 -0
  347. package/dist/verification/thorchain.d.ts +55 -0
  348. package/dist/verification/thorchain.d.ts.map +1 -0
  349. package/dist/verification/thorchain.js +232 -0
  350. package/dist/verification/thorchain.js.map +1 -0
  351. package/dist/wasm-pins.d.ts +4 -0
  352. package/dist/wasm-pins.d.ts.map +1 -0
  353. package/dist/wasm-pins.js +6 -0
  354. package/dist/wasm-pins.js.map +1 -0
  355. package/dist/wire/chainflip.zod.d.ts +144 -0
  356. package/dist/wire/chainflip.zod.d.ts.map +1 -0
  357. package/dist/wire/chainflip.zod.js +33 -0
  358. package/dist/wire/chainflip.zod.js.map +1 -0
  359. package/dist/wire/near-intents.zod.d.ts +376 -0
  360. package/dist/wire/near-intents.zod.d.ts.map +1 -0
  361. package/dist/wire/near-intents.zod.js +101 -0
  362. package/dist/wire/near-intents.zod.js.map +1 -0
  363. package/dist/wire/server/action.zod.d.ts +1119 -0
  364. package/dist/wire/server/action.zod.d.ts.map +1 -0
  365. package/dist/wire/server/action.zod.js +173 -0
  366. package/dist/wire/server/action.zod.js.map +1 -0
  367. package/dist/wire/server/common.zod.d.ts +62 -0
  368. package/dist/wire/server/common.zod.d.ts.map +1 -0
  369. package/dist/wire/server/common.zod.js +43 -0
  370. package/dist/wire/server/common.zod.js.map +1 -0
  371. package/dist/wire/server/index.d.ts +8 -0
  372. package/dist/wire/server/index.d.ts.map +1 -0
  373. package/dist/wire/server/index.js +8 -0
  374. package/dist/wire/server/index.js.map +1 -0
  375. package/dist/wire/server/pow.zod.d.ts +45 -0
  376. package/dist/wire/server/pow.zod.d.ts.map +1 -0
  377. package/dist/wire/server/pow.zod.js +18 -0
  378. package/dist/wire/server/pow.zod.js.map +1 -0
  379. package/dist/wire/server/quotes.zod.d.ts +694 -0
  380. package/dist/wire/server/quotes.zod.d.ts.map +1 -0
  381. package/dist/wire/server/quotes.zod.js +103 -0
  382. package/dist/wire/server/quotes.zod.js.map +1 -0
  383. package/dist/wire/server/swap.zod.d.ts +1981 -0
  384. package/dist/wire/server/swap.zod.d.ts.map +1 -0
  385. package/dist/wire/server/swap.zod.js +270 -0
  386. package/dist/wire/server/swap.zod.js.map +1 -0
  387. package/dist/wire/server/tokens.zod.d.ts +93 -0
  388. package/dist/wire/server/tokens.zod.d.ts.map +1 -0
  389. package/dist/wire/server/tokens.zod.js +28 -0
  390. package/dist/wire/server/tokens.zod.js.map +1 -0
  391. package/dist/wire/server/verify.zod.d.ts +30 -0
  392. package/dist/wire/server/verify.zod.d.ts.map +1 -0
  393. package/dist/wire/server/verify.zod.js +12 -0
  394. package/dist/wire/server/verify.zod.js.map +1 -0
  395. package/dist/wire/thorchain.zod.d.ts +224 -0
  396. package/dist/wire/thorchain.zod.d.ts.map +1 -0
  397. package/dist/wire/thorchain.zod.js +51 -0
  398. package/dist/wire/thorchain.zod.js.map +1 -0
  399. package/package.json +128 -0
  400. package/wasm/miradex-rust/README.md +74 -0
  401. package/wasm/miradex-rust/miradex_rust.d.ts +149 -0
  402. package/wasm/miradex-rust/miradex_rust.js +943 -0
  403. package/wasm/miradex-rust/miradex_rust_bg.wasm +0 -0
  404. package/wasm/miradex-rust/miradex_rust_bg.wasm.d.ts +31 -0
  405. package/wasm/miradex-rust/package.json +24 -0
@@ -0,0 +1,1192 @@
1
+ import { ApiError, NetworkError } from '../../api/index.js';
2
+ import { TERMINAL_STATUSES, ProtocolError } from '../../types/index.js';
3
+ import { createFlowContext, mergeFlowContext, validatePopulated, validateVerified, } from '../flow-context.js';
4
+ import { resumeAtomicSwap as coreResumeAtomicSwap, SwapCancelledError, } from '../../atomic-swap/index.js';
5
+ import { generateMnemonicKeys } from '../../lib/crypto/mnemonic.js';
6
+ import { generateClientKeysFromSeed, ensureWasm } from '../../lib/crypto/wasm.js';
7
+ import { walletFromWif } from '../../lib/bitcoin/wallet.js';
8
+ import { createKeystore } from '../../lib/keystore.js';
9
+ import { deriveLibp2pIdentity } from '../../lib/crypto/libp2p-identity.js';
10
+ import { bytesToHex, randomBytes } from '@noble/hashes/utils.js';
11
+ import { sweepMonero } from '../../atomic-swap/monero-sweep/index.js';
12
+ import { delay } from '../../lib/delay.js';
13
+ import { discoverAndVerifyTxCancel } from '../../lib/bitcoin/tx-verify.js';
14
+ import { buildFullRefund, buildPartialRefund, signRefund, } from '../../atomic-swap/refund.js';
15
+ import { buildMultisigWitnessScript } from '../../atomic-swap/presign.js';
16
+ import { extractProtocolData } from '../../atomic-swap/extract.js';
17
+ const DEFAULT_POLL_MS = 5_000;
18
+ const MAX_TRANSIENT_RETRIES = 5;
19
+ function isTransientError(err) {
20
+ if (err instanceof NetworkError)
21
+ return true;
22
+ if (err instanceof ApiError) {
23
+ if (err.statusCode >= 500)
24
+ return true;
25
+ if (err.statusCode === 429)
26
+ return true;
27
+ }
28
+ return false;
29
+ }
30
+ /**
31
+ * True iff the protocol params carry enough material for the client to
32
+ * construct a refund without sidecar cooperation. Two valid shapes:
33
+ * - Legacy/Full: `tx_full_refund_encsig` alone is enough — the client
34
+ * builds and broadcasts TxFullRefund spending TxCancel.
35
+ * - Partial (amnesty): `tx_partial_refund_encsig` plus the amnesty
36
+ * triple (`amnesty_amount_sats`, `tx_partial_refund_fee_sats`) drive
37
+ * the partial-refund branch. The amnesty output stays at multisig
38
+ * until a separate TxReclaim phase (not yet implemented client-side).
39
+ */
40
+ function hasRefundEscapeHatch(params) {
41
+ if (params.tx_full_refund_encsig)
42
+ return true;
43
+ return (!!params.tx_partial_refund_encsig &&
44
+ params.amnesty_amount_sats !== undefined &&
45
+ params.amnesty_amount_sats !== null &&
46
+ params.tx_partial_refund_fee_sats !== undefined &&
47
+ params.tx_partial_refund_fee_sats !== null);
48
+ }
49
+ export class AtomicFlow {
50
+ api;
51
+ platform;
52
+ config;
53
+ emitFn;
54
+ abortController = null;
55
+ userActionResolver = null;
56
+ keystore = null;
57
+ deposit = null;
58
+ keystoreId = '';
59
+ lastEmittedState = { phase: 'idle', snapshot: null };
60
+ flowCtx = null;
61
+ lastProgressKey = null;
62
+ pollMs;
63
+ /**
64
+ * True once the driver has emitted a terminal phase (`completed`,
65
+ * `failed`, `refunded`, etc.). Used to suppress the post-driver
66
+ * `requiredAction` re-check that would otherwise trigger a phantom
67
+ * second sweep — the server has no scanner for the on-chain XMR sweep,
68
+ * so a fresh `getSwapDetail` right after the driver completes still
69
+ * reports `requiredAction.type === 'sweep'`.
70
+ */
71
+ hasReachedTerminal = false;
72
+ /**
73
+ * Optional maker pin forwarded to `coreResumeAtomicSwap` via
74
+ * `params.variantId`. Set by `start()` from `StartAtomicSwapParams`;
75
+ * unused (and irrelevant) on the resume path because the maker is
76
+ * already chosen at that point.
77
+ */
78
+ variantId;
79
+ constructor(api, platform, config, emitFn, options) {
80
+ this.api = api;
81
+ this.platform = platform;
82
+ this.config = config;
83
+ this.emitFn = emitFn;
84
+ this.pollMs = options?.pollMs ?? DEFAULT_POLL_MS;
85
+ }
86
+ get signal() {
87
+ return this.abortController?.signal ?? AbortSignal.abort();
88
+ }
89
+ get logger() {
90
+ return this.platform.logger;
91
+ }
92
+ setFlowContext(partial) {
93
+ this.flowCtx = this.flowCtx
94
+ ? mergeFlowContext(this.flowCtx, partial)
95
+ : createFlowContext(partial);
96
+ }
97
+ /**
98
+ * Validate FlowContext as PopulatedFlowContext.
99
+ * On failure, emits a 'failed' phase with structured error and returns null.
100
+ */
101
+ requirePopulated(phase) {
102
+ if (!this.flowCtx) {
103
+ this.emitError(phase, 'FlowContext not initialized');
104
+ return null;
105
+ }
106
+ const result = validatePopulated(this.flowCtx, phase);
107
+ if (result.ok)
108
+ return result.data;
109
+ this.logger.error({ fields: result.error.fields }, result.error.message);
110
+ this.emitError(phase, result.error.message);
111
+ return null;
112
+ }
113
+ /**
114
+ * Validate FlowContext as VerifiedFlowContext.
115
+ * On failure, emits a 'failed' phase with structured error and returns null.
116
+ */
117
+ requireVerified(phase) {
118
+ if (!this.flowCtx) {
119
+ this.emitError(phase, 'FlowContext not initialized');
120
+ return null;
121
+ }
122
+ const result = validateVerified(this.flowCtx, phase);
123
+ if (result.ok)
124
+ return result.data;
125
+ this.logger.error({ fields: result.error.fields }, result.error.message);
126
+ this.emitError(phase, result.error.message);
127
+ return null;
128
+ }
129
+ emitError(phase, message) {
130
+ this.transition({
131
+ phase: 'failed',
132
+ snapshot: this.flowCtx,
133
+ error: `[${phase}] ${message}`,
134
+ });
135
+ }
136
+ transition(state) {
137
+ const prevPhase = this.lastEmittedState.phase;
138
+ if (state.phase === prevPhase)
139
+ return;
140
+ this.logger.info({ phase: state.phase, prevPhase, swapId: this.flowCtx?.swapId ?? null }, 'Atomic phase transition');
141
+ this.lastEmittedState = state;
142
+ this.emitFn(state);
143
+ }
144
+ cancel() {
145
+ this.abortController?.abort();
146
+ this.userActionResolver?.();
147
+ this.userActionResolver = null;
148
+ }
149
+ async start(params) {
150
+ this.abortController = new AbortController();
151
+ this.hasReachedTerminal = false;
152
+ this.variantId = params.variantId;
153
+ this.logger.info({
154
+ destAddress: params.destAddress,
155
+ amount: params.amount,
156
+ variantId: params.variantId ?? null,
157
+ existingKeystoreId: params.existingKeystoreId ?? null,
158
+ }, 'AtomicFlow.start()');
159
+ try {
160
+ this.setFlowContext({
161
+ fromToken: 'BTC',
162
+ toToken: 'XMR',
163
+ destAddress: params.destAddress,
164
+ refundAddress: params.refundAddress,
165
+ provider: 'atomicswap',
166
+ extra: { text: 'Initializing keygen...', type: 'message' },
167
+ });
168
+ this.transition({ phase: 'keygen', snapshot: this.flowCtx, message: 'Initializing keygen...' });
169
+ await ensureWasm();
170
+ const network = this.config.network;
171
+ // Either reuse an existing keystore (re-quote-after-failure path) or
172
+ // generate a fresh one (default path). In both cases the rest of the
173
+ // flow is identical from this point forward — same deposit polling,
174
+ // same swap creation, same drive loop.
175
+ let wallet;
176
+ if (params.existingKeystoreId !== undefined) {
177
+ this.setFlowContext({ extra: { text: 'Loading keystore...', type: 'message' } });
178
+ this.transition({ phase: 'keygen', snapshot: this.flowCtx, message: 'Loading keystore...' });
179
+ // Trust: the keystore was created here originally and its keys
180
+ // already passed `verifyKeys`. Skipping the API call avoids
181
+ // doubling the call's transient-failure surface for a no-op check.
182
+ this.keystore = await this.platform.loadKeystore(params.existingKeystoreId);
183
+ this.keystoreId = params.existingKeystoreId;
184
+ wallet = walletFromWif(this.keystore.btc.wif, network);
185
+ this.logger.info({ keystoreId: this.keystoreId, btcAddress: wallet.address }, 'Reusing existing keystore — skipping keygen + saveKeystore');
186
+ }
187
+ else {
188
+ this.setFlowContext({ extra: { text: 'Generating swap keys...', type: 'message' } });
189
+ this.transition({ phase: 'keygen', snapshot: this.flowCtx, message: 'Generating swap keys...' });
190
+ const mnemonicKeys = generateMnemonicKeys(network);
191
+ const keys = generateClientKeysFromSeed(mnemonicKeys.s_b_seed, mnemonicKeys.v_b_seed, mnemonicKeys.b_seed);
192
+ wallet = walletFromWif(mnemonicKeys.wif, network);
193
+ this.logger.debug({ operation: 'keygen' }, 'Keys generated');
194
+ const keyCheck = await this.api.verifyKeys({
195
+ s_b_bitcoin: keys.s_b_bitcoin, s_b_monero: keys.s_b_monero,
196
+ dleq_proof: keys.dleq_proof, v_b: keys.v_b,
197
+ });
198
+ this.logger.info({ valid: keyCheck.valid }, 'Key verification result');
199
+ if (!keyCheck.valid) {
200
+ throw new Error(`Key verification failed: ${keyCheck.reason}. DO NOT DEPOSIT.`);
201
+ }
202
+ this.checkAborted();
203
+ const masterSeedHex = bytesToHex(randomBytes(32));
204
+ const libp2pIdentity = await deriveLibp2pIdentity(masterSeedHex);
205
+ this.keystore = createKeystore({
206
+ wif: wallet.wif, btcAddress: wallet.address, network,
207
+ s_b: keys.s_b, v_b: keys.v_b, S_b_bitcoin: keys.s_b_bitcoin,
208
+ S_b_monero: keys.s_b_monero, dleq_proof: keys.dleq_proof,
209
+ b: keys.b, B: keys.B,
210
+ eigenwallet_master_seed: masterSeedHex,
211
+ libp2p_peer_id: libp2pIdentity.libp2pPeerId,
212
+ receiveAddress: params.destAddress, refundAddress: params.refundAddress,
213
+ mnemonic: mnemonicKeys.mnemonic, derivation: mnemonicKeys.derivation,
214
+ });
215
+ const saveResult = await this.platform.saveKeystore(this.keystore, params.amount);
216
+ this.keystoreId = saveResult.id;
217
+ this.logger.info({ keystoreId: this.keystoreId }, 'Keystore saved');
218
+ }
219
+ // Emit `keystoreId` to engine state IMMEDIATELY, before the slow
220
+ // pre-deposit prep work (`estimateFee` → mempool.space, `getQuotes`
221
+ // → swap-engine, `generateQr` → wasm). The web app's
222
+ // EngineRegistry races a 30 s timeout against `state.atomic.
223
+ // snapshot.keystoreId` to decide whether to keep this engine; if
224
+ // the slow chain below blows the budget the engine is destroyed
225
+ // mid-flight and the user has to retry. Emitting a placeholder
226
+ // keystore-saved transition here guarantees the registry binds
227
+ // the engine on first save, regardless of subsequent network
228
+ // latency.
229
+ this.setFlowContext({ keystoreId: this.keystoreId });
230
+ this.transition({
231
+ phase: 'keystore-saved',
232
+ snapshot: this.flowCtx,
233
+ message: 'Keystore saved. Computing deposit details...',
234
+ });
235
+ this.checkAborted();
236
+ const { feeSats } = await this.platform.estimateFee(network);
237
+ const userSats = Math.round(parseFloat(params.amount) * 1e8);
238
+ const requiredBtc = ((userSats + feeSats) / 1e8).toFixed(8);
239
+ let expectedXmr = null;
240
+ try {
241
+ const quotes = await this.api.getQuotes({ from: 'BTC', to: 'XMR', amount: params.amount });
242
+ const q = quotes.quotes?.find((x) => x.provider === 'atomicswap');
243
+ expectedXmr = q?.expectedOutput ?? null;
244
+ }
245
+ catch { /* non-fatal */ }
246
+ const qr = await this.platform.generateQr(wallet.address);
247
+ // Now that the slow chain has finished, populate the full
248
+ // FlowContext with the deposit address, expected XMR amount, and
249
+ // QR. The first `keystore-saved` transition above was the bare-
250
+ // bones state emit needed to keep the registry from destroying
251
+ // this engine; this second `setFlowContext` enriches it before
252
+ // `awaiting-deposit` validates against `PopulatedFlowContext`.
253
+ this.setFlowContext({
254
+ depositAddr: wallet.address,
255
+ depositAmount: requiredBtc,
256
+ expectedOut: expectedXmr,
257
+ qr,
258
+ extra: { text: `Send ${requiredBtc} BTC to the address above.`, type: 'message' },
259
+ });
260
+ this.checkAborted();
261
+ // Validate before emitting awaiting-deposit
262
+ const populated = this.requirePopulated('awaiting-deposit');
263
+ if (!populated)
264
+ return;
265
+ this.transition({
266
+ phase: 'awaiting-deposit',
267
+ snapshot: populated,
268
+ message: `Send ${requiredBtc} BTC to the address above.`,
269
+ });
270
+ this.checkAborted();
271
+ this.deposit = await this.platform.watchDeposit(wallet.address, network, this.signal, (msg) => this.logger.debug({}, msg));
272
+ this.logger.info({ txid: this.deposit.txid, value: this.deposit.value }, 'Deposit detected');
273
+ this.setFlowContext({ extra: { text: 'Deposit detected. Creating swap...', type: 'message' } });
274
+ const populatedAfterDeposit = this.requirePopulated('deposit-detected');
275
+ if (!populatedAfterDeposit)
276
+ return;
277
+ this.transition({
278
+ phase: 'deposit-detected',
279
+ snapshot: populatedAfterDeposit,
280
+ deposit: {
281
+ txid: this.deposit.txid,
282
+ vout: this.deposit.vout,
283
+ value: this.deposit.value,
284
+ utxos: this.deposit.utxos?.map((u) => ({ txid: u.txid, vout: u.vout, value: u.value })),
285
+ },
286
+ message: 'Deposit detected. Creating swap...',
287
+ });
288
+ await this.driveSwapToCompletion(null);
289
+ }
290
+ catch (err) {
291
+ this.handleError(err);
292
+ }
293
+ }
294
+ async resumeFromKeystore(keystoreId, existingSwapId) {
295
+ this.abortController = new AbortController();
296
+ this.hasReachedTerminal = false;
297
+ this.logger.info({ keystoreId, existingSwapId: existingSwapId ?? null }, 'AtomicFlow.resumeFromKeystore()');
298
+ try {
299
+ await ensureWasm();
300
+ this.keystoreId = keystoreId;
301
+ this.keystore = await this.platform.loadKeystore(keystoreId);
302
+ const network = (this.keystore.btc?.network ?? 'mainnet');
303
+ const btcAddress = this.keystore.btc.address;
304
+ this.setFlowContext({
305
+ fromToken: 'BTC',
306
+ toToken: 'XMR',
307
+ keystoreId,
308
+ depositAddr: btcAddress,
309
+ destAddress: this.keystore.swap.receiveAddress,
310
+ refundAddress: this.keystore.swap.refundAddress,
311
+ provider: 'atomicswap',
312
+ extra: { text: 'Loading keystore...', type: 'message' },
313
+ });
314
+ this.transition({
315
+ phase: 'creating-swap',
316
+ snapshot: this.flowCtx,
317
+ message: 'Loading keystore...',
318
+ });
319
+ if (existingSwapId) {
320
+ this.logger.info({ swapId: existingSwapId }, 'Resume Path A: existing swap');
321
+ const detail = await this.fetchDetailWithRetry(existingSwapId);
322
+ this.setFlowContext({
323
+ swapId: existingSwapId,
324
+ swapNumber: detail.swapNumber ?? null,
325
+ destAddress: this.keystore.swap.receiveAddress || detail.destAddress || null,
326
+ refundAddress: this.keystore.swap.refundAddress || detail.refundAddress || null,
327
+ depositAmount: detail.amountIn || null,
328
+ expectedOut: detail.expectedAmountOut || null,
329
+ expectedOutUsd: detail.expectedAmountOutUsd ?? null,
330
+ amountInUsd: detail.amountInUsd ?? null,
331
+ expiresAt: detail.expiresAt || null,
332
+ });
333
+ if (TERMINAL_STATUSES.has(detail.status)) {
334
+ this.emitTerminal(existingSwapId, detail.status, detail);
335
+ return;
336
+ }
337
+ // Funding-address UTXO is only meaningful pre-broadcast — once the
338
+ // swap is `deposited` or beyond, the BTC has already been swept
339
+ // into TxLock at the lock address and the funding address is
340
+ // empty. Re-querying it on every resume costs 2-15 s on
341
+ // testnet/stagenet electrs (cold TLS, multi-server fallback,
342
+ // flaky public endpoints) and the result feeds nothing
343
+ // downstream once the protocol is past funding. Skip it for
344
+ // post-funding statuses so resume settles immediately on the
345
+ // current state instead of stalling on a useless network probe.
346
+ const PRE_FUNDING_STATUSES = new Set([
347
+ 'initializing',
348
+ 'pending',
349
+ 'awaiting_funding',
350
+ ]);
351
+ const deposit = PRE_FUNDING_STATUSES.has(detail.status)
352
+ ? await this.platform.fetchUtxo(btcAddress, network)
353
+ : null;
354
+ if (deposit)
355
+ this.deposit = deposit;
356
+ // Verify the contract for the resumed swap
357
+ let verification = null;
358
+ if (detail.depositAddress && detail.verification) {
359
+ const { verifyDepositAddress } = await import('../../verification/index.js');
360
+ verification = await verifyDepositAddress({
361
+ depositAddress: detail.depositAddress,
362
+ verification: detail.verification,
363
+ destAddress: this.keystore.swap.receiveAddress,
364
+ refundAddress: this.keystore.swap.refundAddress,
365
+ toToken: 'XMR',
366
+ amount: '',
367
+ // Lock address lives at the top level of SwapDetail; timelock
368
+ // blocks come from the typed atomicswap params when present.
369
+ protocol: detail.depositAddress &&
370
+ detail.protocolData?.type === 'atomicswap' &&
371
+ detail.protocolData.params
372
+ ? {
373
+ lock_address: detail.depositAddress,
374
+ timelock_blocks: detail.protocolData.params.cancel_timelock,
375
+ }
376
+ : undefined,
377
+ expectedAmountOut: detail.expectedAmountOut ?? undefined,
378
+ expectedDestAddress: this.keystore.swap.receiveAddress,
379
+ fetchFn: this.config.fetchFn,
380
+ });
381
+ }
382
+ // Fallback: if server has progressed past verification, trust it
383
+ if (!verification) {
384
+ verification = { verified: true, provider: 'atomicswap', checks: [], timestamp: Date.now() };
385
+ }
386
+ const qr = await this.platform.generateQr(btcAddress);
387
+ this.setFlowContext({
388
+ qr,
389
+ verification,
390
+ depositAmount: deposit ? (deposit.value / 1e8).toFixed(8) : this.flowCtx?.depositAmount ?? null,
391
+ extra: { text: `Resuming swap (${detail.status})...`, type: 'message' },
392
+ });
393
+ // For resumed swaps the core will provide verification via progress callbacks.
394
+ // Emit as swapping (base FlowContext is sufficient for resume entry).
395
+ this.transition({
396
+ phase: 'creating-swap',
397
+ snapshot: this.flowCtx,
398
+ message: `Resuming swap (${detail.status})...`,
399
+ });
400
+ await this.driveSwapToCompletion(existingSwapId);
401
+ return;
402
+ }
403
+ this.logger.info({ btcAddress }, 'Resume Path B: local keystore');
404
+ const deposit = await this.platform.fetchUtxo(btcAddress, network);
405
+ // Fetch expected XMR output for both branches (required for PopulatedFlowContext).
406
+ // Pre-funding resume: there's no deposit yet, so we can't derive the
407
+ // amount from on-chain. Read the original amount the swap was started
408
+ // with from the keystore metadata (saved by saveKeystore as `label`).
409
+ let amountForQuote;
410
+ if (deposit) {
411
+ amountForQuote = (deposit.value / 1e8).toFixed(8);
412
+ }
413
+ else {
414
+ const meta = await this.platform.listKeystores();
415
+ const ksMeta = meta.find((m) => m.id === keystoreId);
416
+ amountForQuote = ksMeta?.amount ?? '0';
417
+ }
418
+ let expectedOut = this.flowCtx?.expectedOut ?? null;
419
+ if (!expectedOut && amountForQuote !== '0') {
420
+ try {
421
+ const quotes = await this.api.getQuotes({
422
+ from: 'BTC',
423
+ to: 'XMR',
424
+ amount: amountForQuote,
425
+ });
426
+ const q = quotes.quotes?.find((x) => x.provider === 'atomicswap');
427
+ expectedOut = q?.expectedOutput ?? null;
428
+ }
429
+ catch { /* non-fatal — requirePopulated will catch if still null */ }
430
+ }
431
+ const qr = await this.platform.generateQr(btcAddress);
432
+ if (deposit) {
433
+ this.deposit = deposit;
434
+ this.setFlowContext({
435
+ qr,
436
+ expectedOut,
437
+ depositAmount: amountForQuote,
438
+ extra: { text: 'Deposit found. Creating swap...', type: 'message' },
439
+ });
440
+ const populated = this.requirePopulated('deposit-detected');
441
+ if (!populated)
442
+ return;
443
+ this.transition({
444
+ phase: 'deposit-detected',
445
+ snapshot: populated,
446
+ deposit: { txid: deposit.txid, vout: deposit.vout, value: deposit.value },
447
+ message: 'Deposit found. Creating swap...',
448
+ });
449
+ await this.driveSwapToCompletion(null);
450
+ }
451
+ else {
452
+ this.setFlowContext({
453
+ qr,
454
+ expectedOut,
455
+ depositAmount: amountForQuote === '0' ? null : amountForQuote,
456
+ extra: { text: 'Send BTC to the address above to begin the swap.', type: 'message' },
457
+ });
458
+ const populated = this.requirePopulated('awaiting-deposit');
459
+ if (!populated)
460
+ return;
461
+ this.transition({
462
+ phase: 'awaiting-deposit',
463
+ snapshot: populated,
464
+ message: 'Send BTC to the address above to begin the swap.',
465
+ });
466
+ this.checkAborted();
467
+ this.deposit = await this.platform.watchDeposit(btcAddress, network, this.signal, (msg) => this.logger.debug({}, msg));
468
+ this.setFlowContext({
469
+ depositAmount: (this.deposit.value / 1e8).toFixed(8),
470
+ extra: { text: 'Deposit detected. Creating swap...', type: 'message' },
471
+ });
472
+ const populatedAfterDeposit = this.requirePopulated('deposit-detected');
473
+ if (!populatedAfterDeposit)
474
+ return;
475
+ this.transition({
476
+ phase: 'deposit-detected',
477
+ snapshot: populatedAfterDeposit,
478
+ deposit: {
479
+ txid: this.deposit.txid,
480
+ vout: this.deposit.vout,
481
+ value: this.deposit.value,
482
+ utxos: this.deposit.utxos?.map((u) => ({ txid: u.txid, vout: u.vout, value: u.value })),
483
+ },
484
+ message: 'Deposit detected. Creating swap...',
485
+ });
486
+ await this.driveSwapToCompletion(null);
487
+ }
488
+ }
489
+ catch (err) {
490
+ this.handleError(err);
491
+ }
492
+ }
493
+ async fetchDetailWithRetry(swapId) {
494
+ let lastErr;
495
+ for (let attempt = 0; attempt <= MAX_TRANSIENT_RETRIES; attempt++) {
496
+ try {
497
+ return await this.api.getSwapDetail(swapId);
498
+ }
499
+ catch (err) {
500
+ if (!isTransientError(err))
501
+ throw err;
502
+ lastErr = err;
503
+ const msg = err instanceof Error ? err.message : String(err);
504
+ this.logger.warn({ swapId, attempt: attempt + 1, error: msg }, 'Server error fetching swap, retrying');
505
+ this.setFlowContext({
506
+ extra: { text: `Server error, retrying (${attempt + 1}/${MAX_TRANSIENT_RETRIES})...`, type: 'warning' },
507
+ });
508
+ this.transition({
509
+ phase: 'creating-swap',
510
+ snapshot: this.flowCtx,
511
+ message: `Server error, retrying (${attempt + 1}/${MAX_TRANSIENT_RETRIES})...`,
512
+ });
513
+ await delay(this.pollMs * (attempt + 1), this.signal).catch(() => { });
514
+ if (this.signal.aborted)
515
+ throw new SwapCancelledError();
516
+ }
517
+ }
518
+ throw lastErr ?? new Error(`Failed to fetch swap ${swapId} after retries`);
519
+ }
520
+ async driveSwapToCompletion(existingSwapId) {
521
+ if (!this.keystore) {
522
+ throw new Error('keystore must be set before driving swap');
523
+ }
524
+ const network = (this.keystore.btc?.network ?? 'mainnet');
525
+ // Provide a blockchain provider so the resume path can reconstruct the
526
+ // TxLock from on-chain data when recomputing the redeem digest (Fix 1
527
+ // on resume). The provider is shared across the driver's lifetime.
528
+ const blockchain = await this.platform.createBlockchainProvider(network);
529
+ const result = await coreResumeAtomicSwap({
530
+ api: this.api,
531
+ params: {
532
+ keystore: this.keystore,
533
+ deposit: this.deposit ?? { txid: '', vout: 0, value: 0, confirmations: 0, status: 'mempool', utxos: [] },
534
+ network,
535
+ blockchain,
536
+ existingSwapId: existingSwapId ?? undefined,
537
+ logger: this.logger,
538
+ monerodNodes: this.config.monerodNodes,
539
+ ...(this.variantId !== undefined ? { variantId: this.variantId } : {}),
540
+ },
541
+ onProgress: (p) => this.mapCoreProgress(p),
542
+ signal: this.signal,
543
+ fetchFn: this.config.fetchFn,
544
+ saveProtocolSnapshot: this.platform.saveProtocolSnapshot,
545
+ loadProtocolSnapshot: this.platform.loadProtocolSnapshot,
546
+ });
547
+ // If the driver already reached a terminal phase via mapCoreProgress
548
+ // (sweep complete, refunded, cancelled, failed, punished), the post-driver
549
+ // server re-check is meaningless — the server has no scanner for the
550
+ // on-chain XMR sweep, so it still reports requiredAction=sweep /
551
+ // status=sending right after the client broadcast its sweep tx. Treating
552
+ // that as actionable triggers a phantom second executeSweep that emits a
553
+ // spurious `completed → sweeping` transition.
554
+ if (this.hasReachedTerminal)
555
+ return;
556
+ let finalStatus = 'completed';
557
+ let requiredAction = null;
558
+ try {
559
+ const detail = await this.api.getSwapDetail(result.swapId);
560
+ finalStatus = detail.status;
561
+ requiredAction = detail.requiredAction ?? null;
562
+ this.setFlowContext({ swapId: result.swapId, swapNumber: detail.swapNumber ?? null });
563
+ }
564
+ catch { /* best effort */ }
565
+ if (requiredAction?.type === 'refund') {
566
+ await this.executeRefund(result.swapId);
567
+ return;
568
+ }
569
+ if (requiredAction?.type === 'sweep' || finalStatus === 'sending') {
570
+ await this.executeSweep(result.swapId);
571
+ return;
572
+ }
573
+ if (TERMINAL_STATUSES.has(finalStatus)) {
574
+ this.emitTerminal(result.swapId, finalStatus, undefined);
575
+ return;
576
+ }
577
+ // Non-terminal status with no immediately-actionable requiredAction
578
+ // (e.g., server is still broadcasting TxCancel, or waiting for cancel
579
+ // confirmation). Sidecar handles TxCancel automatically; the client just
580
+ // needs to watch for the requiredAction to flip to 'refund' (or the
581
+ // swap to reach terminal). pollUntilTerminal does both.
582
+ this.setFlowContext({
583
+ extra: { text: requiredAction?.message ?? 'Waiting for server...', type: 'message' },
584
+ });
585
+ await this.pollUntilTerminal(result.swapId);
586
+ }
587
+ mapCoreProgress(p) {
588
+ const progressKey = `${p.stage}|${p.swapId ?? ''}|${p.message}`;
589
+ if (progressKey !== this.lastProgressKey) {
590
+ this.logger.debug({ stage: p.stage, swapId: p.swapId ?? null, message: p.message }, 'Core progress');
591
+ this.lastProgressKey = progressKey;
592
+ }
593
+ if (p.swapId)
594
+ this.setFlowContext({ swapId: p.swapId });
595
+ if (p.swapNumber)
596
+ this.setFlowContext({ swapNumber: p.swapNumber });
597
+ if (p.verification)
598
+ this.setFlowContext({ verification: p.verification });
599
+ switch (p.stage) {
600
+ case 'keygen':
601
+ case 'keystore_saved':
602
+ case 'awaiting_deposit':
603
+ case 'deposit_detected':
604
+ case 'creating_swap':
605
+ case 'initializing':
606
+ case 'pending':
607
+ case 'awaiting_funding':
608
+ this.setFlowContext({ extra: { text: p.message, type: 'message' } });
609
+ this.transition({
610
+ phase: 'creating-swap',
611
+ snapshot: this.flowCtx,
612
+ message: p.message,
613
+ });
614
+ break;
615
+ case 'verifying_xmr': {
616
+ this.setFlowContext({ extra: { text: p.message, type: 'message' } });
617
+ const v = this.requireVerified('confirming');
618
+ if (v)
619
+ this.transition({ phase: 'confirming', snapshot: v, message: p.message });
620
+ break;
621
+ }
622
+ case 'signing_psbt': {
623
+ this.setFlowContext({ extra: { text: p.message, type: 'message' } });
624
+ const v = this.requireVerified('signing');
625
+ if (v)
626
+ this.transition({ phase: 'signing', snapshot: v, message: p.message });
627
+ break;
628
+ }
629
+ case 'funding': {
630
+ this.setFlowContext({ extra: { text: p.message, type: 'message' } });
631
+ const v = this.requireVerified('funding');
632
+ if (v)
633
+ this.transition({ phase: 'funding', snapshot: v, message: p.message });
634
+ break;
635
+ }
636
+ case 'submit_encsig': {
637
+ this.setFlowContext({ extra: { text: p.message, type: 'message' } });
638
+ const v = this.requireVerified('computing-encsig');
639
+ if (v)
640
+ this.transition({ phase: 'computing-encsig', snapshot: v, message: p.message });
641
+ break;
642
+ }
643
+ case 'confirming':
644
+ case 'deposited': {
645
+ this.setFlowContext({ extra: { text: p.message, type: 'message' } });
646
+ const v = this.requireVerified('confirming');
647
+ if (v)
648
+ this.transition({ phase: 'confirming', snapshot: v, message: p.message });
649
+ break;
650
+ }
651
+ case 'swapping':
652
+ case 'sending': {
653
+ this.setFlowContext({ extra: { text: p.message, type: 'message' } });
654
+ const v = this.requireVerified('swapping');
655
+ if (v)
656
+ this.transition({ phase: 'swapping', snapshot: v, message: p.message });
657
+ break;
658
+ }
659
+ case 'sweeping': {
660
+ this.setFlowContext({ extra: { text: p.message, type: 'message' } });
661
+ const v = this.requireVerified('sweeping');
662
+ if (v)
663
+ this.transition({ phase: 'sweeping', snapshot: v, message: p.message, sweepStep: 'get-outputs' });
664
+ break;
665
+ }
666
+ case 'complete':
667
+ case 'completed':
668
+ this.setFlowContext({ extra: { text: 'Swap completed', type: 'message' } });
669
+ this.hasReachedTerminal = true;
670
+ this.transition({
671
+ phase: 'completed',
672
+ snapshot: this.flowCtx,
673
+ outputTxHash: p.txHash ?? null,
674
+ // Atomic swaps have no slippage — the buyer receives exactly the
675
+ // negotiated rate × deposit amount. Default to the snapshot's
676
+ // expectedOut so the receipt shows the actual amount the user got
677
+ // even when the in-flight progress event doesn't carry it.
678
+ actualOut: this.flowCtx?.expectedOut ?? '',
679
+ durationSec: null,
680
+ });
681
+ break;
682
+ case 'failed':
683
+ case 'error':
684
+ this.setFlowContext({ extra: { text: p.message, type: 'error' } });
685
+ this.hasReachedTerminal = true;
686
+ this.transition({
687
+ phase: 'failed',
688
+ snapshot: this.flowCtx,
689
+ error: p.message,
690
+ });
691
+ break;
692
+ case 'refunded':
693
+ this.hasReachedTerminal = true;
694
+ this.transition({
695
+ phase: 'refunded',
696
+ snapshot: this.flowCtx,
697
+ swapId: p.swapId ?? this.flowCtx?.swapId ?? '',
698
+ refundTxid: p.txHash ?? null,
699
+ });
700
+ break;
701
+ case 'cancelled':
702
+ this.hasReachedTerminal = true;
703
+ this.transition({
704
+ phase: 'cancelled',
705
+ snapshot: this.flowCtx,
706
+ swapId: p.swapId ?? this.flowCtx?.swapId ?? null,
707
+ txCancelTxid: null,
708
+ });
709
+ break;
710
+ case 'cancelling':
711
+ // Intermediate: BTC TxCancel is in flight, the refund will follow.
712
+ // Stay in a watching phase so the driver keeps polling until the row
713
+ // settles in `refunded` (or `failed` if the refund never lands).
714
+ this.setFlowContext({ extra: { text: p.message, type: 'warning' } });
715
+ this.transition({
716
+ phase: 'creating-swap',
717
+ snapshot: this.flowCtx,
718
+ message: p.message,
719
+ });
720
+ break;
721
+ case 'withheld':
722
+ case 'expired':
723
+ // Terminal: the deposit window closed (`expired`) or the maker
724
+ // refused to release funds (`withheld`). Both are user-visible
725
+ // terminal statuses in `TerminalStatus`. Emit a `failed` phase so
726
+ // poll consumers (engine driver, TUI exec state) observe a terminal
727
+ // transition and resolve.
728
+ this.transition({
729
+ phase: 'failed',
730
+ snapshot: this.flowCtx,
731
+ error: p.message,
732
+ });
733
+ break;
734
+ case 'punished': {
735
+ // Not terminal. Alice published TxPunish (Bob missed the refund
736
+ // window), but the sidecar autonomously runs
737
+ // `cooperative_xmr_redeem_after_punish` to recover s_a — once Alice
738
+ // cooperates, the server flips `requiredAction.type` to `sweep` and
739
+ // the driver finishes the swap with an XMR sweep that lands the row
740
+ // in `completed`. Stay in a watching phase so `drive.ts`'s existing
741
+ // sweep branch can pick up the action transition.
742
+ const reason = p.message ||
743
+ 'BTC punished — recovering XMR via cooperative_xmr_redeem_after_punish...';
744
+ this.setFlowContext({ extra: { text: reason, type: 'warning' } });
745
+ const verified = this.requireVerified('swapping');
746
+ if (verified) {
747
+ this.transition({ phase: 'swapping', snapshot: verified, message: reason });
748
+ }
749
+ break;
750
+ }
751
+ default:
752
+ this.logger.warn({ stage: p.stage }, `Unhandled atomic progress stage: ${p.stage}`);
753
+ break;
754
+ }
755
+ }
756
+ async userCancel() {
757
+ const state = this.getCurrentAwaitingActionState();
758
+ if (!state)
759
+ return;
760
+ const { requiredAction } = state;
761
+ const swapId = this.flowCtx?.swapId ?? '';
762
+ this.logger.info({ swapId, action: 'cancel', blocksRemaining: requiredAction.blocksRemaining }, 'User cancel initiated');
763
+ if (requiredAction.blocksRemaining && requiredAction.blocksRemaining > 0) {
764
+ this.transition({
765
+ ...state,
766
+ error: `TxCancel not available — ${requiredAction.blocksRemaining} blocks remaining`,
767
+ });
768
+ return;
769
+ }
770
+ this.setFlowContext({ extra: { text: 'Broadcasting TxCancel...', type: 'message' } });
771
+ this.transition({
772
+ phase: 'cancelling',
773
+ snapshot: this.flowCtx,
774
+ message: 'Broadcasting TxCancel...',
775
+ });
776
+ try {
777
+ const result = await this.api.executeAction(swapId, { type: 'cancel' });
778
+ this.transition({
779
+ phase: 'cancelled',
780
+ snapshot: this.flowCtx,
781
+ swapId,
782
+ txCancelTxid: result.protocolData && 'tx_cancel_txid' in result.protocolData
783
+ ? result.protocolData.tx_cancel_txid
784
+ : null,
785
+ });
786
+ }
787
+ catch (err) {
788
+ this.transition({
789
+ ...state,
790
+ error: `Cancel failed: ${err instanceof Error ? err.message : String(err)}`,
791
+ });
792
+ }
793
+ this.resumeActionLoop();
794
+ }
795
+ async userRefund() {
796
+ const state = this.getCurrentAwaitingActionState();
797
+ if (!state || !this.keystore)
798
+ return;
799
+ const swapId = this.flowCtx?.swapId ?? '';
800
+ this.logger.info({ swapId, action: 'refund' }, 'User refund initiated');
801
+ await this.executeRefund(swapId);
802
+ this.resumeActionLoop();
803
+ }
804
+ async userRetrySweep() {
805
+ const swapId = this.flowCtx?.swapId ?? '';
806
+ if (!swapId)
807
+ return;
808
+ this.logger.info({ swapId, action: 'retry-sweep' }, 'User retry sweep');
809
+ await this.executeSweep(swapId);
810
+ }
811
+ async executeSweep(swapId) {
812
+ if (!this.keystore) {
813
+ this.emitError('sweeping', 'Keystore not available for sweep');
814
+ return;
815
+ }
816
+ this.logger.info({ swapId }, 'Starting XMR sweep');
817
+ this.setFlowContext({ extra: { text: 'Starting XMR sweep...', type: 'message' } });
818
+ const verified = this.requireVerified('sweeping');
819
+ if (!verified)
820
+ return;
821
+ this.transition({
822
+ phase: 'sweeping', snapshot: verified,
823
+ message: 'Starting XMR sweep...', sweepStep: 'get-outputs',
824
+ });
825
+ try {
826
+ const s_b_bytes = new Uint8Array((this.keystore.keys.s_b.match(/.{2}/g) ?? []).map((b) => parseInt(b, 16)));
827
+ const freshDetail = await this.api.getSwapDetail(swapId);
828
+ const pp = freshDetail.protocolData?.type === 'atomicswap'
829
+ ? freshDetail.protocolData.params
830
+ : null;
831
+ if (!pp?.S_a_monero) {
832
+ throw new ProtocolError('E_PROTOCOL_PARAMS_MISSING', 'sweep requires S_a_monero in protocol params');
833
+ }
834
+ const result = await sweepMonero(this.api, {
835
+ swapId, s_b: s_b_bytes,
836
+ receiveAddress: this.keystore.swap.receiveAddress,
837
+ expectedSAMonero: pp.S_a_monero,
838
+ monerodNodes: this.config.monerodNodes,
839
+ onProgress: (stage) => {
840
+ const sweepStep = stage.includes('key') ? 'key-images'
841
+ : stage.includes('submit') || stage.includes('broadcast') ? 'broadcasting'
842
+ : stage.includes('sign') ? 'submitting' : 'get-outputs';
843
+ this.setFlowContext({ extra: { text: stage, type: 'message' } });
844
+ const v = this.requireVerified('sweeping');
845
+ if (v)
846
+ this.transition({
847
+ phase: 'sweeping', snapshot: v, message: stage,
848
+ sweepStep: sweepStep,
849
+ });
850
+ },
851
+ });
852
+ const amountXmr = result.amount !== 'unknown'
853
+ ? (Number(result.amount) / 1e12).toFixed(12).replace(/\.?0+$/, '') : '';
854
+ this.logger.info({ swapId, txHash: result.txHash, amountXmr }, 'Sweep completed');
855
+ this.setFlowContext({ extra: { text: 'Sweep complete', type: 'message' } });
856
+ this.transition({
857
+ phase: 'completed', snapshot: this.flowCtx,
858
+ outputTxHash: result.txHash, actualOut: amountXmr, durationSec: null,
859
+ });
860
+ }
861
+ catch (err) {
862
+ const msg = err instanceof Error ? err.message : String(err);
863
+ this.logger.error({ swapId, error: msg }, 'Sweep failed');
864
+ if (msg.includes("'completed'") || msg.includes('"completed"')) {
865
+ try {
866
+ const detail = await this.api.getSwapDetail(swapId);
867
+ this.emitTerminal(swapId, 'completed', detail);
868
+ return;
869
+ }
870
+ catch { /* fall through */ }
871
+ }
872
+ this.setFlowContext({ extra: { text: `Sweep failed: ${msg}. Press w to retry.`, type: 'error' } });
873
+ this.transition({
874
+ phase: 'failed', snapshot: this.flowCtx, error: `Sweep failed: ${msg}. Press w to retry.`,
875
+ });
876
+ }
877
+ }
878
+ async executeRefund(swapId) {
879
+ if (!this.keystore) {
880
+ this.emitError('refunding', 'Keystore not available for refund');
881
+ return;
882
+ }
883
+ this.logger.info({ swapId }, 'Starting client-side refund');
884
+ const detail = await this.api.getSwapDetail(swapId).catch(() => null);
885
+ if (!detail) {
886
+ this.emitError('refunding', 'Could not fetch swap detail for refund');
887
+ return;
888
+ }
889
+ const lockAddress = this.resolveLockAddress(detail);
890
+ if (!lockAddress) {
891
+ this.logger.error({ swapId }, 'Cannot determine lock address for refund');
892
+ this.setFlowContext({
893
+ extra: {
894
+ text: 'Cannot determine lock address — unable to verify TxCancel',
895
+ type: 'error',
896
+ },
897
+ });
898
+ this.transition({
899
+ phase: 'failed',
900
+ snapshot: this.flowCtx,
901
+ error: 'Cannot determine lock address — unable to verify TxCancel',
902
+ });
903
+ return;
904
+ }
905
+ const protocolParams = await this.resolveProtocolParams(swapId, detail);
906
+ if (!protocolParams) {
907
+ this.emitRefundAborted(swapId, 'Server did not return protocol params and no local cache available — cannot build refund');
908
+ return;
909
+ }
910
+ if (!hasRefundEscapeHatch(protocolParams)) {
911
+ this.emitRefundAborted(swapId, 'No refund encsig in protocol params (need either tx_full_refund_encsig, or tx_partial_refund_encsig with the amnesty triple) — cannot construct refund');
912
+ return;
913
+ }
914
+ this.logger.info({ swapId, lockAddress }, 'Verifying TxCancel on-chain');
915
+ this.setFlowContext({ extra: { text: 'Verifying TxCancel on-chain...', type: 'message' } });
916
+ this.transition({
917
+ phase: 'verifying-cancel',
918
+ snapshot: this.flowCtx,
919
+ message: 'Verifying TxCancel on-chain...',
920
+ });
921
+ const blockchain = await this.platform.createBlockchainProvider(this.config.network);
922
+ const verification = await discoverAndVerifyTxCancel(blockchain, lockAddress, detail.depositTxHash ?? '', this.config.network);
923
+ if (!verification.verified || !verification.txCancelHex) {
924
+ this.logger.error({ swapId, reason: verification.reason }, 'TxCancel verification failed — refund aborted');
925
+ const reason = verification.reason || 'TxCancel not verified';
926
+ this.setFlowContext({ extra: { text: `REFUSED: ${reason}. Refund aborted.`, type: 'error' } });
927
+ this.transition({
928
+ phase: 'failed',
929
+ snapshot: this.flowCtx,
930
+ error: `REFUSED: ${reason}. Refund aborted.`,
931
+ });
932
+ return;
933
+ }
934
+ this.logger.info({ swapId, reason: verification.reason }, 'TxCancel verified — assembling refund locally');
935
+ this.setFlowContext({
936
+ extra: { text: 'TxCancel confirmed. Signing refund locally...', type: 'message' },
937
+ });
938
+ this.transition({
939
+ phase: 'refunding',
940
+ snapshot: this.flowCtx,
941
+ message: 'TxCancel confirmed. Signing refund locally...',
942
+ });
943
+ await ensureWasm();
944
+ const useAmnesty = protocolParams.tx_partial_refund_encsig !== undefined &&
945
+ protocolParams.amnesty_amount_sats !== undefined &&
946
+ protocolParams.tx_partial_refund_fee_sats !== undefined;
947
+ const aPubHex = protocolParams.A;
948
+ const bPubHex = this.keystore.keys.B;
949
+ const witnessScript = buildMultisigWitnessScript(aPubHex, bPubHex);
950
+ const refundAddress = this.keystore.swap.refundAddress;
951
+ let txRefundHex;
952
+ let cancelOutputValueSats;
953
+ let encsigRefund;
954
+ if (useAmnesty) {
955
+ const built = buildPartialRefund({
956
+ txCancelHex: verification.txCancelHex,
957
+ refundAddress,
958
+ refundFeeSats: BigInt(protocolParams.tx_partial_refund_fee_sats ?? 0),
959
+ network: this.config.network,
960
+ amnestyAmountSats: BigInt(protocolParams.amnesty_amount_sats ?? 0),
961
+ partialRefundFeeSats: BigInt(protocolParams.tx_partial_refund_fee_sats ?? 0),
962
+ aPubHex,
963
+ bPubHex,
964
+ });
965
+ txRefundHex = built.txRefundHex;
966
+ cancelOutputValueSats = built.cancelOutputValueSats;
967
+ encsigRefund = protocolParams.tx_partial_refund_encsig ?? '';
968
+ }
969
+ else {
970
+ const built = buildFullRefund({
971
+ txCancelHex: verification.txCancelHex,
972
+ refundAddress,
973
+ refundFeeSats: BigInt(protocolParams.tx_refund_fee_sats),
974
+ network: this.config.network,
975
+ });
976
+ txRefundHex = built.txRefundHex;
977
+ cancelOutputValueSats = built.cancelOutputValueSats;
978
+ encsigRefund = protocolParams.tx_full_refund_encsig ?? '';
979
+ }
980
+ let assembled;
981
+ try {
982
+ assembled = signRefund({
983
+ txRefundHex,
984
+ witnessScript,
985
+ txCancelOutputValueSats: cancelOutputValueSats,
986
+ encsigRefund,
987
+ sBHexLE: this.keystore.keys.s_b,
988
+ bHex: this.keystore.keys.b,
989
+ aPubHex,
990
+ bPubHex,
991
+ sBPubHex: this.keystore.keys.S_b_bitcoin,
992
+ logger: this.logger,
993
+ });
994
+ }
995
+ catch (err) {
996
+ const msg = err instanceof Error ? err.message : String(err);
997
+ this.logger.error({ swapId, error: msg }, 'Refund assembly failed');
998
+ this.setFlowContext({ extra: { text: `Refund failed: ${msg}`, type: 'error' } });
999
+ this.transition({ phase: 'failed', snapshot: this.flowCtx, error: `Refund failed: ${msg}` });
1000
+ return;
1001
+ }
1002
+ this.logger.info({ swapId, txid: assembled.txid }, 'Broadcasting client-signed refund');
1003
+ let broadcastTxid;
1004
+ try {
1005
+ broadcastTxid = await blockchain.broadcastTransaction(assembled.hex);
1006
+ }
1007
+ catch (err) {
1008
+ const msg = err instanceof Error ? err.message : String(err);
1009
+ if (/already in mempool|already exists|already known/i.test(msg)) {
1010
+ this.logger.warn({ swapId, txid: assembled.txid }, 'Refund already broadcast; continuing');
1011
+ broadcastTxid = assembled.txid;
1012
+ }
1013
+ else {
1014
+ this.logger.error({ swapId, error: msg }, 'Refund broadcast failed');
1015
+ this.setFlowContext({
1016
+ extra: { text: `Refund broadcast failed: ${msg}`, type: 'error' },
1017
+ });
1018
+ this.transition({
1019
+ phase: 'failed',
1020
+ snapshot: this.flowCtx,
1021
+ error: `Refund broadcast failed: ${msg}`,
1022
+ });
1023
+ return;
1024
+ }
1025
+ }
1026
+ try {
1027
+ await this.api.executeAction(swapId, {
1028
+ type: 'notify-refund',
1029
+ refund_txid: broadcastTxid,
1030
+ });
1031
+ }
1032
+ catch (err) {
1033
+ const msg = err instanceof Error ? err.message : String(err);
1034
+ this.logger.warn({ swapId, txid: broadcastTxid, error: msg }, 'notify-refund server call failed; refund tx is on-chain — continuing');
1035
+ }
1036
+ this.logger.info({ swapId, refundTxid: broadcastTxid }, 'Client-side refund broadcast successful');
1037
+ this.transition({
1038
+ phase: 'refunded',
1039
+ snapshot: this.flowCtx,
1040
+ swapId,
1041
+ refundTxid: broadcastTxid,
1042
+ });
1043
+ }
1044
+ resolveLockAddress(detail) {
1045
+ if (detail.depositAddress)
1046
+ return detail.depositAddress;
1047
+ const v = detail.verification;
1048
+ if (v && typeof v === 'object' && 'lock_address' in v) {
1049
+ const candidate = v.lock_address;
1050
+ if (typeof candidate === 'string' && candidate.length > 0)
1051
+ return candidate;
1052
+ }
1053
+ return '';
1054
+ }
1055
+ /**
1056
+ * Read protocol params from the server response; fall back to the platform
1057
+ * adapter's optional write-through cache when the server didn't return the
1058
+ * refund encsig. On success from the server, populate the cache so later
1059
+ * refunds survive a briefly-unreachable backend.
1060
+ */
1061
+ async resolveProtocolParams(swapId, detail) {
1062
+ const fromServer = extractProtocolData(detail).params;
1063
+ if (fromServer && hasRefundEscapeHatch(fromServer)) {
1064
+ await this.cacheProtocolParams(swapId, fromServer);
1065
+ return fromServer;
1066
+ }
1067
+ const loadFn = this.platform.loadSwapProtocol;
1068
+ if (loadFn) {
1069
+ try {
1070
+ const cached = await loadFn(swapId);
1071
+ if (cached && hasRefundEscapeHatch(cached)) {
1072
+ this.logger.info({ swapId }, 'Server omitted refund encsig — loaded from local protocol cache');
1073
+ return cached;
1074
+ }
1075
+ }
1076
+ catch (err) {
1077
+ const msg = err instanceof Error ? err.message : String(err);
1078
+ this.logger.warn({ swapId, error: msg }, 'Local protocol cache read failed — proceeding with server response');
1079
+ }
1080
+ }
1081
+ return fromServer;
1082
+ }
1083
+ async cacheProtocolParams(swapId, params) {
1084
+ const saveFn = this.platform.saveSwapProtocol;
1085
+ if (!saveFn)
1086
+ return;
1087
+ try {
1088
+ await saveFn(swapId, params);
1089
+ }
1090
+ catch (err) {
1091
+ const msg = err instanceof Error ? err.message : String(err);
1092
+ this.logger.warn({ swapId, error: msg }, 'Failed to cache protocol params — refund still works via server');
1093
+ }
1094
+ }
1095
+ emitRefundAborted(swapId, reason) {
1096
+ this.logger.error({ swapId, reason }, 'Refund aborted');
1097
+ this.setFlowContext({ extra: { text: `REFUSED: ${reason}`, type: 'error' } });
1098
+ this.transition({
1099
+ phase: 'failed',
1100
+ snapshot: this.flowCtx,
1101
+ error: `REFUSED: ${reason}`,
1102
+ });
1103
+ }
1104
+ async pollUntilTerminal(swapId) {
1105
+ while (!this.signal.aborted) {
1106
+ try {
1107
+ const detail = await this.api.getSwapDetail(swapId);
1108
+ if (TERMINAL_STATUSES.has(detail.status)) {
1109
+ this.emitTerminal(swapId, detail.status, detail);
1110
+ return;
1111
+ }
1112
+ // Opportunistically prime the protocol cache while the server is
1113
+ // reachable. The refund path will use this cache if the server is
1114
+ // briefly unreachable later.
1115
+ const polledParams = extractProtocolData(detail).params;
1116
+ if (polledParams?.tx_full_refund_encsig) {
1117
+ await this.cacheProtocolParams(swapId, polledParams);
1118
+ }
1119
+ // Server flipped the required action to 'refund' — TxCancel has
1120
+ // confirmed on-chain and Bob's refund window is open. Auto-trigger
1121
+ // the client-side refund so we don't drift toward the punish
1122
+ // deadline while idly polling.
1123
+ const action = detail.requiredAction ?? null;
1124
+ if (action?.type === 'refund') {
1125
+ this.logger.info({ swapId, blocksRemaining: action.blocksRemaining ?? null }, 'Required action flipped to refund — auto-triggering client-side refund');
1126
+ await this.executeRefund(swapId);
1127
+ return;
1128
+ }
1129
+ if (action?.type === 'sweep') {
1130
+ this.logger.info({ swapId }, 'Required action flipped to sweep — auto-triggering sweep');
1131
+ await this.executeSweep(swapId);
1132
+ return;
1133
+ }
1134
+ }
1135
+ catch { /* retry */ }
1136
+ await delay(this.pollMs, this.signal).catch(() => { });
1137
+ if (this.signal.aborted)
1138
+ return;
1139
+ }
1140
+ }
1141
+ emitTerminal(swapId, status, detail) {
1142
+ this.setFlowContext({ swapId, swapNumber: detail?.swapNumber ?? null });
1143
+ if (status === 'completed') {
1144
+ this.setFlowContext({ extra: { text: 'Swap completed', type: 'message' } });
1145
+ this.transition({
1146
+ phase: 'completed', snapshot: this.flowCtx,
1147
+ outputTxHash: detail?.outputTxHash ?? null,
1148
+ actualOut: detail?.actualAmountOut ?? '', durationSec: detail?.durationSeconds ?? null,
1149
+ });
1150
+ }
1151
+ else if (status === 'refunded') {
1152
+ this.transition({
1153
+ phase: 'refunded', snapshot: this.flowCtx, swapId,
1154
+ refundTxid: detail?.refundTxHash ?? null,
1155
+ });
1156
+ }
1157
+ else {
1158
+ this.setFlowContext({ extra: { text: `Swap ended: ${status}`, type: 'error' } });
1159
+ this.transition({
1160
+ phase: 'failed', snapshot: this.flowCtx, error: `Swap ended: ${status}`,
1161
+ });
1162
+ }
1163
+ }
1164
+ resumeActionLoop() {
1165
+ this.userActionResolver?.();
1166
+ this.userActionResolver = null;
1167
+ }
1168
+ checkAborted() {
1169
+ if (this.signal.aborted)
1170
+ throw new SwapCancelledError();
1171
+ }
1172
+ handleError(err) {
1173
+ if (err instanceof SwapCancelledError || this.signal.aborted) {
1174
+ this.logger.info({ swapId: this.flowCtx?.swapId ?? null }, 'AtomicFlow cancelled');
1175
+ this.transition({ phase: 'cancelled', snapshot: this.flowCtx, swapId: null, txCancelTxid: null });
1176
+ return;
1177
+ }
1178
+ const message = err instanceof Error ? err.message : String(err);
1179
+ this.logger.error({ swapId: this.flowCtx?.swapId ?? null, error: message }, 'AtomicFlow error');
1180
+ this.setFlowContext({ extra: { text: message, type: 'error' } });
1181
+ this.transition({
1182
+ phase: 'failed', snapshot: this.flowCtx,
1183
+ error: message,
1184
+ });
1185
+ }
1186
+ getCurrentAwaitingActionState() {
1187
+ if (this.lastEmittedState.phase === 'awaiting-user-action')
1188
+ return this.lastEmittedState;
1189
+ return null;
1190
+ }
1191
+ }
1192
+ //# sourceMappingURL=atomic-flow.js.map