@lendasat/lendaswap-sdk-pure 0.2.21-1 → 0.2.21

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 (208) hide show
  1. package/dist/api/client.d.ts.map +1 -1
  2. package/dist/api/client.js +4 -1
  3. package/dist/api/client.js.map +1 -1
  4. package/dist/client.d.ts +1 -1
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/client.js.map +1 -1
  7. package/dist/version.d.ts +4 -0
  8. package/dist/version.d.ts.map +1 -0
  9. package/dist/version.js +6 -0
  10. package/dist/version.js.map +1 -0
  11. package/package.json +4 -1
  12. package/dist/price-feed.d.ts +0 -124
  13. package/dist/price-feed.d.ts.map +0 -1
  14. package/dist/price-feed.js +0 -178
  15. package/dist/price-feed.js.map +0 -1
  16. package/dist/src/api/client.d.ts +0 -31
  17. package/dist/src/api/client.d.ts.map +0 -1
  18. package/dist/src/api/client.js +0 -12
  19. package/dist/src/api/client.js.map +0 -1
  20. package/dist/src/arkade.d.ts +0 -36
  21. package/dist/src/arkade.d.ts.map +0 -1
  22. package/dist/src/arkade.js +0 -68
  23. package/dist/src/arkade.js.map +0 -1
  24. package/dist/src/client.d.ts +0 -760
  25. package/dist/src/client.d.ts.map +0 -1
  26. package/dist/src/client.js +0 -2169
  27. package/dist/src/client.js.map +0 -1
  28. package/dist/src/create/arkade.d.ts +0 -34
  29. package/dist/src/create/arkade.d.ts.map +0 -1
  30. package/dist/src/create/arkade.js +0 -76
  31. package/dist/src/create/arkade.js.map +0 -1
  32. package/dist/src/create/bitcoin-to-arkade.d.ts +0 -36
  33. package/dist/src/create/bitcoin-to-arkade.d.ts.map +0 -1
  34. package/dist/src/create/bitcoin-to-arkade.js +0 -69
  35. package/dist/src/create/bitcoin-to-arkade.js.map +0 -1
  36. package/dist/src/create/bitcoin.d.ts +0 -31
  37. package/dist/src/create/bitcoin.d.ts.map +0 -1
  38. package/dist/src/create/bitcoin.js +0 -67
  39. package/dist/src/create/bitcoin.js.map +0 -1
  40. package/dist/src/create/evm-to-arkade.d.ts +0 -34
  41. package/dist/src/create/evm-to-arkade.d.ts.map +0 -1
  42. package/dist/src/create/evm-to-arkade.js +0 -69
  43. package/dist/src/create/evm-to-arkade.js.map +0 -1
  44. package/dist/src/create/evm-to-bitcoin.d.ts +0 -35
  45. package/dist/src/create/evm-to-bitcoin.d.ts.map +0 -1
  46. package/dist/src/create/evm-to-bitcoin.js +0 -71
  47. package/dist/src/create/evm-to-bitcoin.js.map +0 -1
  48. package/dist/src/create/evm-to-lightning.d.ts +0 -34
  49. package/dist/src/create/evm-to-lightning.d.ts.map +0 -1
  50. package/dist/src/create/evm-to-lightning.js +0 -66
  51. package/dist/src/create/evm-to-lightning.js.map +0 -1
  52. package/dist/src/create/index.d.ts +0 -19
  53. package/dist/src/create/index.d.ts.map +0 -1
  54. package/dist/src/create/index.js +0 -18
  55. package/dist/src/create/index.js.map +0 -1
  56. package/dist/src/create/lightning.d.ts +0 -31
  57. package/dist/src/create/lightning.d.ts.map +0 -1
  58. package/dist/src/create/lightning.js +0 -72
  59. package/dist/src/create/lightning.js.map +0 -1
  60. package/dist/src/create/types.d.ts +0 -247
  61. package/dist/src/create/types.d.ts.map +0 -1
  62. package/dist/src/create/types.js +0 -5
  63. package/dist/src/create/types.js.map +0 -1
  64. package/dist/src/delegate.d.ts +0 -62
  65. package/dist/src/delegate.d.ts.map +0 -1
  66. package/dist/src/delegate.js +0 -284
  67. package/dist/src/delegate.js.map +0 -1
  68. package/dist/src/esplora.d.ts +0 -41
  69. package/dist/src/esplora.d.ts.map +0 -1
  70. package/dist/src/esplora.js +0 -47
  71. package/dist/src/esplora.js.map +0 -1
  72. package/dist/src/evm/coordinator.d.ts +0 -247
  73. package/dist/src/evm/coordinator.d.ts.map +0 -1
  74. package/dist/src/evm/coordinator.js +0 -414
  75. package/dist/src/evm/coordinator.js.map +0 -1
  76. package/dist/src/evm/htlc.d.ts +0 -238
  77. package/dist/src/evm/htlc.d.ts.map +0 -1
  78. package/dist/src/evm/htlc.js +0 -278
  79. package/dist/src/evm/htlc.js.map +0 -1
  80. package/dist/src/evm/index.d.ts +0 -9
  81. package/dist/src/evm/index.d.ts.map +0 -1
  82. package/dist/src/evm/index.js +0 -9
  83. package/dist/src/evm/index.js.map +0 -1
  84. package/dist/src/evm/signing.d.ts +0 -30
  85. package/dist/src/evm/signing.d.ts.map +0 -1
  86. package/dist/src/evm/signing.js +0 -91
  87. package/dist/src/evm/signing.js.map +0 -1
  88. package/dist/src/generated/api.d.ts +0 -2736
  89. package/dist/src/generated/api.d.ts.map +0 -1
  90. package/dist/src/generated/api.js +0 -6
  91. package/dist/src/generated/api.js.map +0 -1
  92. package/dist/src/index.d.ts +0 -18
  93. package/dist/src/index.d.ts.map +0 -1
  94. package/dist/src/index.js +0 -24
  95. package/dist/src/index.js.map +0 -1
  96. package/dist/src/node.d.ts +0 -19
  97. package/dist/src/node.d.ts.map +0 -1
  98. package/dist/src/node.js +0 -19
  99. package/dist/src/node.js.map +0 -1
  100. package/dist/src/price-calculations.d.ts +0 -109
  101. package/dist/src/price-calculations.d.ts.map +0 -1
  102. package/dist/src/price-calculations.js +0 -135
  103. package/dist/src/price-calculations.js.map +0 -1
  104. package/dist/src/redeem/arkade.d.ts +0 -65
  105. package/dist/src/redeem/arkade.d.ts.map +0 -1
  106. package/dist/src/redeem/arkade.js +0 -217
  107. package/dist/src/redeem/arkade.js.map +0 -1
  108. package/dist/src/redeem/ethereum.d.ts +0 -52
  109. package/dist/src/redeem/ethereum.d.ts.map +0 -1
  110. package/dist/src/redeem/ethereum.js +0 -206
  111. package/dist/src/redeem/ethereum.js.map +0 -1
  112. package/dist/src/redeem/gasless.d.ts +0 -41
  113. package/dist/src/redeem/gasless.d.ts.map +0 -1
  114. package/dist/src/redeem/gasless.js +0 -71
  115. package/dist/src/redeem/gasless.js.map +0 -1
  116. package/dist/src/redeem/index.d.ts +0 -49
  117. package/dist/src/redeem/index.d.ts.map +0 -1
  118. package/dist/src/redeem/index.js +0 -189
  119. package/dist/src/redeem/index.js.map +0 -1
  120. package/dist/src/redeem/types.d.ts +0 -126
  121. package/dist/src/redeem/types.d.ts.map +0 -1
  122. package/dist/src/redeem/types.js +0 -36
  123. package/dist/src/redeem/types.js.map +0 -1
  124. package/dist/src/refund/arkade.d.ts +0 -62
  125. package/dist/src/refund/arkade.d.ts.map +0 -1
  126. package/dist/src/refund/arkade.js +0 -212
  127. package/dist/src/refund/arkade.js.map +0 -1
  128. package/dist/src/refund/index.d.ts +0 -10
  129. package/dist/src/refund/index.d.ts.map +0 -1
  130. package/dist/src/refund/index.js +0 -10
  131. package/dist/src/refund/index.js.map +0 -1
  132. package/dist/src/refund/onchain.d.ts +0 -137
  133. package/dist/src/refund/onchain.d.ts.map +0 -1
  134. package/dist/src/refund/onchain.js +0 -366
  135. package/dist/src/refund/onchain.js.map +0 -1
  136. package/dist/src/signer/index.d.ts +0 -106
  137. package/dist/src/signer/index.d.ts.map +0 -1
  138. package/dist/src/signer/index.js +0 -179
  139. package/dist/src/signer/index.js.map +0 -1
  140. package/dist/src/storage/idb.d.ts +0 -70
  141. package/dist/src/storage/idb.d.ts.map +0 -1
  142. package/dist/src/storage/idb.js +0 -236
  143. package/dist/src/storage/idb.js.map +0 -1
  144. package/dist/src/storage/index.d.ts +0 -152
  145. package/dist/src/storage/index.d.ts.map +0 -1
  146. package/dist/src/storage/index.js +0 -98
  147. package/dist/src/storage/index.js.map +0 -1
  148. package/dist/src/storage/sqlite.d.ts +0 -95
  149. package/dist/src/storage/sqlite.d.ts.map +0 -1
  150. package/dist/src/storage/sqlite.js +0 -206
  151. package/dist/src/storage/sqlite.js.map +0 -1
  152. package/dist/src/storage/types.d.ts +0 -57
  153. package/dist/src/storage/types.d.ts.map +0 -1
  154. package/dist/src/storage/types.js +0 -9
  155. package/dist/src/storage/types.js.map +0 -1
  156. package/dist/src/tokens.d.ts +0 -29
  157. package/dist/src/tokens.d.ts.map +0 -1
  158. package/dist/src/tokens.js +0 -89
  159. package/dist/src/tokens.js.map +0 -1
  160. package/dist/src/usd-price.d.ts +0 -34
  161. package/dist/src/usd-price.d.ts.map +0 -1
  162. package/dist/src/usd-price.js +0 -83
  163. package/dist/src/usd-price.js.map +0 -1
  164. package/dist/tests/api-client.test.d.ts +0 -2
  165. package/dist/tests/api-client.test.d.ts.map +0 -1
  166. package/dist/tests/api-client.test.js +0 -86
  167. package/dist/tests/api-client.test.js.map +0 -1
  168. package/dist/tests/client.test.d.ts +0 -2
  169. package/dist/tests/client.test.d.ts.map +0 -1
  170. package/dist/tests/client.test.js +0 -150
  171. package/dist/tests/client.test.js.map +0 -1
  172. package/dist/tests/index.test.d.ts +0 -2
  173. package/dist/tests/index.test.d.ts.map +0 -1
  174. package/dist/tests/index.test.js +0 -8
  175. package/dist/tests/index.test.js.map +0 -1
  176. package/dist/tests/onchain-refund.test.d.ts +0 -2
  177. package/dist/tests/onchain-refund.test.d.ts.map +0 -1
  178. package/dist/tests/onchain-refund.test.js +0 -279
  179. package/dist/tests/onchain-refund.test.js.map +0 -1
  180. package/dist/tests/signer.test.d.ts +0 -2
  181. package/dist/tests/signer.test.d.ts.map +0 -1
  182. package/dist/tests/signer.test.js +0 -92
  183. package/dist/tests/signer.test.js.map +0 -1
  184. package/dist/tests/sqlite-storage.test.d.ts +0 -2
  185. package/dist/tests/sqlite-storage.test.d.ts.map +0 -1
  186. package/dist/tests/sqlite-storage.test.js +0 -160
  187. package/dist/tests/sqlite-storage.test.js.map +0 -1
  188. package/dist/tests/storage.test.d.ts +0 -2
  189. package/dist/tests/storage.test.d.ts.map +0 -1
  190. package/dist/tests/storage.test.js +0 -184
  191. package/dist/tests/storage.test.js.map +0 -1
  192. package/dist/tsconfig.tsbuildinfo +0 -1
  193. package/dist/usdt0-bridge/bridge.d.ts +0 -82
  194. package/dist/usdt0-bridge/bridge.d.ts.map +0 -1
  195. package/dist/usdt0-bridge/bridge.js +0 -56
  196. package/dist/usdt0-bridge/bridge.js.map +0 -1
  197. package/dist/usdt0-bridge/chains.d.ts +0 -41
  198. package/dist/usdt0-bridge/chains.d.ts.map +0 -1
  199. package/dist/usdt0-bridge/chains.js +0 -117
  200. package/dist/usdt0-bridge/chains.js.map +0 -1
  201. package/dist/usdt0-bridge/layerzero-tracker.d.ts +0 -34
  202. package/dist/usdt0-bridge/layerzero-tracker.d.ts.map +0 -1
  203. package/dist/usdt0-bridge/layerzero-tracker.js +0 -86
  204. package/dist/usdt0-bridge/layerzero-tracker.js.map +0 -1
  205. package/dist/usdt0-bridge/oft-abi.d.ts +0 -145
  206. package/dist/usdt0-bridge/oft-abi.d.ts.map +0 -1
  207. package/dist/usdt0-bridge/oft-abi.js +0 -117
  208. package/dist/usdt0-bridge/oft-abi.js.map +0 -1
@@ -1,2169 +0,0 @@
1
- import { createApiClient, } from "./api/client.js";
2
- import { getVhtlcAmounts } from "./arkade.js";
3
- import { createArkadeToEvmSwapGeneric, createBitcoinToArkadeSwap, createBitcoinToEvmSwap, createEvmToArkadeSwapGeneric, createEvmToBitcoinSwap, createEvmToLightningSwapGeneric, createLightningToEvmSwapGeneric, } from "./create";
4
- import { delegateClaim, delegateRefund } from "./delegate.js";
5
- import { broadcastTransaction, findOutputByAddress } from "./esplora.js";
6
- import { encodeApproveCallData, encodeHtlcErc20RefundCallData } from "./evm";
7
- import { buildArkadeClaim, claimViaGasless as gaslessClaim, claim as redeemClaim, } from "./redeem/index.js";
8
- import { buildArkadeRefund, buildOnchainClaimTransaction, buildOnchainRefundTransaction, verifyHtlcAddress, } from "./refund";
9
- import { bytesToHex, hexToBytes, Signer, } from "./signer/index.js";
10
- import { SWAP_STORAGE_VERSION, } from "./storage";
11
- import { isArkade, isBtcOnchain, isEvmToken, isLightning } from "./tokens.js";
12
- // Re-export coordinator utilities for Arkade-to-EVM redeemAndExecute flow
13
- export { buildExecuteAndCreateCalls, buildRedeemCalls, buildRedeemDigest, encodeExecuteAndCreate, encodeRedeemAndExecute, encodeRefundAndExecute, encodeRefundTo, } from "./evm/index.js";
14
- const DEFAULT_BASE_URL = "https://apilendaswap.lendasat.com/";
15
- /** Default Esplora URLs by network */
16
- const DEFAULT_ESPLORA_URLS = {
17
- mainnet: "https://mempool.space/api",
18
- signet: "https://mutinynet.com/api",
19
- regtest: "http://localhost:3000",
20
- };
21
- /**
22
- * Builder for creating a Lendaswap client with a fluent API.
23
- *
24
- * The `build()` method is async and returns a fully initialized client.
25
- *
26
- * @example
27
- * ```ts
28
- * // Create client with new wallet (generates mnemonic)
29
- * const client = await Client.builder()
30
- * .withSignerStorage(new IdbWalletStorage())
31
- * .build();
32
- *
33
- * // Create client with existing mnemonic
34
- * const client = await Client.builder()
35
- * .withSignerStorage(new IdbWalletStorage())
36
- * .withMnemonic("abandon abandon abandon ...")
37
- * .build();
38
- *
39
- * // Create client without storage (stateless, generates new mnemonic)
40
- * const client = await Client.builder().build();
41
- * ```
42
- */
43
- export class ClientBuilder {
44
- #baseUrl = DEFAULT_BASE_URL;
45
- #apiKey;
46
- #esploraUrl;
47
- #arkadeServerUrl;
48
- #signerStorage;
49
- #swapStorage;
50
- #mnemonic;
51
- /**
52
- * Sets the base URL for the API.
53
- * @param baseUrl - The base URL of the Lendaswap API.
54
- * @returns The builder instance for chaining.
55
- */
56
- withBaseUrl(baseUrl) {
57
- this.#baseUrl = baseUrl;
58
- return this;
59
- }
60
- /**
61
- * Sets the API key for authenticated requests.
62
- * @param apiKey - The API key to use for authentication.
63
- * @returns The builder instance for chaining.
64
- */
65
- withApiKey(apiKey) {
66
- this.#apiKey = apiKey;
67
- return this;
68
- }
69
- /**
70
- * Sets the Esplora API URL for broadcasting Bitcoin transactions.
71
- *
72
- * If not set, defaults will be used based on the network:
73
- * - mainnet: https://mempool.space/api
74
- * - testnet: https://mempool.space/testnet/api
75
- * - signet: https://mempool.space/signet/api
76
- *
77
- * @param esploraUrl - The Esplora API base URL.
78
- * @returns The builder instance for chaining.
79
- */
80
- withEsploraUrl(esploraUrl) {
81
- this.#esploraUrl = esploraUrl;
82
- return this;
83
- }
84
- /**
85
- * Sets the Arkade server URL for VHTLC operations (claim, refund, amounts).
86
- *
87
- * If not set, defaults are used based on the network:
88
- * - bitcoin: https://arkade.computer
89
- * - signet: wa
90
- *
91
- * @param arkadeServerUrl - The Arkade server base URL.
92
- * @returns The builder instance for chaining.
93
- */
94
- withArkadeServerUrl(arkadeServerUrl) {
95
- this.#arkadeServerUrl = arkadeServerUrl;
96
- return this;
97
- }
98
- /**
99
- * Sets the storage backend for signer data (mnemonic and key index).
100
- * @param storage - The storage implementation to use.
101
- * @returns The builder instance for chaining.
102
- */
103
- withSignerStorage(storage) {
104
- this.#signerStorage = storage;
105
- return this;
106
- }
107
- /**
108
- * Sets the storage backend for swap data.
109
- *
110
- * When configured, swaps will be automatically persisted after creation.
111
- *
112
- * @param storage - The swap storage implementation to use.
113
- * @returns The builder instance for chaining.
114
- */
115
- withSwapStorage(storage) {
116
- this.#swapStorage = storage;
117
- return this;
118
- }
119
- /**
120
- * Sets the mnemonic phrase to use for the signer.
121
- *
122
- * If provided, this mnemonic will be used instead of loading from storage
123
- * or generating a new one. The mnemonic will be persisted to storage if
124
- * storage is configured.
125
- *
126
- * @param mnemonic - The BIP39 mnemonic phrase (12, 15, 18, 21, or 24 words).
127
- * @returns The builder instance for chaining.
128
- */
129
- withMnemonic(mnemonic) {
130
- this.#mnemonic = mnemonic;
131
- return this;
132
- }
133
- /**
134
- * Builds and returns a fully initialized Client instance.
135
- *
136
- * Initialization order:
137
- * 1. If `withMnemonic()` was called, use that mnemonic
138
- * 2. Else if storage is configured and contains a mnemonic, load it
139
- * 3. Else generate a new mnemonic
140
- *
141
- * The mnemonic is persisted to storage if storage is configured.
142
- *
143
- * @returns A promise that resolves to a fully initialized Client.
144
- * @throws Error if the provided mnemonic is invalid.
145
- */
146
- async build() {
147
- let signer;
148
- if (this.#mnemonic) {
149
- // Use provided mnemonic
150
- signer = Signer.fromMnemonic(this.#mnemonic);
151
- if (this.#signerStorage) {
152
- await this.#signerStorage.setMnemonic(signer.mnemonic);
153
- }
154
- }
155
- else if (this.#signerStorage) {
156
- // Try to load from storage
157
- const storedMnemonic = await this.#signerStorage.getMnemonic();
158
- if (storedMnemonic) {
159
- signer = Signer.fromMnemonic(storedMnemonic);
160
- }
161
- else {
162
- // Generate new and persist
163
- signer = Signer.generate();
164
- await this.#signerStorage.setMnemonic(signer.mnemonic);
165
- }
166
- }
167
- else {
168
- // No storage, generate new (stateless mode)
169
- signer = Signer.generate();
170
- }
171
- return new Client({
172
- baseUrl: this.#baseUrl,
173
- apiKey: this.#apiKey,
174
- esploraUrl: this.#esploraUrl,
175
- arkadeServerUrl: this.#arkadeServerUrl,
176
- }, signer, this.#signerStorage, this.#swapStorage);
177
- }
178
- }
179
- /**
180
- * Main client for interacting with the Lendaswap API.
181
- *
182
- * The client manages:
183
- * - API communication
184
- * - Signer (HD wallet) for key derivation
185
- * - Storage for persisting mnemonic and key index
186
- *
187
- * Use `Client.builder()` to create a new instance.
188
- *
189
- * @example
190
- * ```ts
191
- * const client = await Client.builder()
192
- * .withSignerStorage(new IdbWalletStorage())
193
- * .withApiKey("your-api-key")
194
- * .build();
195
- *
196
- * // Get mnemonic (for backup)
197
- * const mnemonic = client.getMnemonic();
198
- *
199
- * // Derive swap parameters
200
- * const params = await client.deriveSwapParams();
201
- * ```
202
- */
203
- export class Client {
204
- #apiClient;
205
- #config;
206
- #signer;
207
- #signerStorage;
208
- #swapStorage;
209
- /**
210
- * Creates a new Client instance.
211
- *
212
- * Use `Client.builder()` instead of calling this constructor directly.
213
- *
214
- * @internal
215
- */
216
- constructor(config, signer, signerStorage, swapStorage) {
217
- this.#config = config;
218
- this.#apiClient = createApiClient({
219
- baseUrl: config.baseUrl,
220
- apiKey: config.apiKey,
221
- });
222
- this.#signer = signer;
223
- this.#signerStorage = signerStorage;
224
- this.#swapStorage = swapStorage;
225
- }
226
- /**
227
- * Creates a new ClientBuilder for fluent configuration.
228
- * @returns A new ClientBuilder instance.
229
- */
230
- static builder() {
231
- return new ClientBuilder();
232
- }
233
- /** The underlying typed API client for direct API access. */
234
- get api() {
235
- return this.#apiClient;
236
- }
237
- /** The base URL of the API. */
238
- get baseUrl() {
239
- return this.#config.baseUrl;
240
- }
241
- /** The swap storage, if configured. */
242
- get swapStorage() {
243
- return this.#swapStorage;
244
- }
245
- // =========================================================================
246
- // Signer Methods
247
- // =========================================================================
248
- /**
249
- * Gets the mnemonic phrase.
250
- *
251
- * Store this securely - it's the only way to recover the wallet.
252
- *
253
- * @returns The BIP39 mnemonic phrase.
254
- */
255
- getMnemonic() {
256
- return this.#signer.mnemonic;
257
- }
258
- /**
259
- * Loads a mnemonic phrase, replacing the current signer.
260
- *
261
- * The new mnemonic is persisted to storage if storage is configured.
262
- *
263
- * @param mnemonic - The BIP39 mnemonic phrase to load.
264
- * @throws Error if the mnemonic is invalid.
265
- */
266
- async loadMnemonic(mnemonic) {
267
- this.#signer = Signer.fromMnemonic(mnemonic);
268
- if (this.#signerStorage) {
269
- await this.#signerStorage.setMnemonic(mnemonic);
270
- }
271
- }
272
- /**
273
- * Gets the user ID extended public key for wallet recovery.
274
- *
275
- * This can be shared with the server for recovering swap history.
276
- *
277
- * @returns The hex-encoded user ID xpub.
278
- */
279
- getUserIdXpub() {
280
- return this.#signer.getUserIdXpubString();
281
- }
282
- /**
283
- * Derives swap parameters at the next available index.
284
- *
285
- * Automatically increments the key index in storage (if configured).
286
- *
287
- * @returns The derived swap parameters.
288
- */
289
- async deriveSwapParams() {
290
- let index = 0;
291
- if (this.#signerStorage) {
292
- index = await this.#signerStorage.incrementKeyIndex();
293
- }
294
- return this.#signer.deriveSwapParams(index);
295
- }
296
- /**
297
- * Derives swap parameters at a specific index.
298
- *
299
- * Does not modify the stored key index. Useful for recovery scenarios.
300
- *
301
- * @param index - The key index to derive.
302
- * @returns The derived swap parameters.
303
- */
304
- deriveSwapParamsAtIndex(index) {
305
- return this.#signer.deriveSwapParams(index);
306
- }
307
- /**
308
- * Gets the current key index from storage.
309
- * @returns The current key index, or 0 if no storage is configured.
310
- */
311
- async getKeyIndex() {
312
- if (this.#signerStorage) {
313
- return this.#signerStorage.getKeyIndex();
314
- }
315
- return 0;
316
- }
317
- /**
318
- * Sets the key index in storage.
319
- *
320
- * Useful for recovery scenarios where you need to set the index
321
- * to a specific value.
322
- *
323
- * @param index - The new key index.
324
- * @throws Error if no storage is configured.
325
- */
326
- async setKeyIndex(index) {
327
- if (!this.#signerStorage) {
328
- throw new Error("No signer storage configured");
329
- }
330
- await this.#signerStorage.setKeyIndex(index);
331
- }
332
- // =========================================================================
333
- // Health & Info
334
- // =========================================================================
335
- /**
336
- * Checks the health status of the API.
337
- * @returns A promise that resolves to "ok" if the API is healthy.
338
- * @throws Error if the health check fails.
339
- */
340
- async healthCheck() {
341
- const { data, error } = await this.#apiClient.GET("/health");
342
- if (error) {
343
- throw new Error(`Health check failed: ${JSON.stringify(error)}`);
344
- }
345
- return data ?? "ok";
346
- }
347
- /**
348
- * Gets the version information of the API.
349
- * @returns A promise that resolves to the version info containing tag and commit hash.
350
- * @throws Error if the request fails.
351
- */
352
- async getVersion() {
353
- const { data, error } = await this.#apiClient.GET("/version");
354
- if (error) {
355
- throw new Error(`Failed to get version: ${JSON.stringify(error)}`);
356
- }
357
- if (!data) {
358
- throw new Error("No version data returned");
359
- }
360
- return data;
361
- }
362
- /**
363
- * Gets the current Median Time Past (MTP) and tip block height.
364
- * @returns A promise that resolves to the MTP timestamp and tip height.
365
- * @throws Error if the request fails or MTP is not yet available.
366
- */
367
- async getMtp() {
368
- const { data, error } = await this.#apiClient.GET("/mtp");
369
- if (error) {
370
- throw new Error(`Failed to get MTP: ${JSON.stringify(error)}`);
371
- }
372
- if (!data) {
373
- throw new Error("MTP not available yet");
374
- }
375
- return data;
376
- }
377
- // =========================================================================
378
- // Tokens & Asset Pairs
379
- // =========================================================================
380
- /**
381
- * Gets the list of supported tokens.
382
- * @returns A promise that resolves to an array of token information.
383
- * @throws Error if the request fails.
384
- */
385
- async getTokens() {
386
- const { data, error } = await this.#apiClient.GET("/tokens");
387
- if (error || !data) {
388
- throw new Error(`Failed to get tokens: ${JSON.stringify(error)}`);
389
- }
390
- return data;
391
- }
392
- // =========================================================================
393
- // Quotes
394
- // =========================================================================
395
- /**
396
- * Gets a quote for swapping between two tokens.
397
- * @param params - Quote parameters.
398
- * @param params.sourceChain - Source blockchain (e.g., "Arkade", "Polygon").
399
- * @param params.sourceToken - Source token: contract address for EVM tokens, or "btc" for BTC.
400
- * @param params.targetChain - Target blockchain (e.g., "Polygon", "Lightning").
401
- * @param params.targetToken - Target token: contract address for EVM tokens, or "btc" for BTC.
402
- * @param params.sourceAmount - Amount in smallest unit of source token (mutually exclusive with targetAmount).
403
- * @param params.targetAmount - Amount in smallest unit of target token (mutually exclusive with sourceAmount).
404
- * @returns A promise that resolves to the quote response with pricing details.
405
- * @throws Error if the request fails.
406
- */
407
- async getQuote(params) {
408
- const { data, error } = await this.#apiClient.GET("/quote", {
409
- params: {
410
- query: {
411
- source_chain: params.sourceChain,
412
- source_token: params.sourceToken,
413
- target_chain: params.targetChain,
414
- target_token: params.targetToken,
415
- source_amount: params.sourceAmount,
416
- target_amount: params.targetAmount,
417
- },
418
- },
419
- });
420
- if (error) {
421
- throw new Error(`Failed to get quote: ${JSON.stringify(error)}`);
422
- }
423
- if (!data) {
424
- throw new Error("No quote data returned");
425
- }
426
- return data;
427
- }
428
- // =========================================================================
429
- // Swap Status
430
- // =========================================================================
431
- /**
432
- * Gets the status and details of a swap by its ID.
433
- * @param id - The UUID of the swap.
434
- * @param options - Optional settings.
435
- * @param options.updateStorage - If true, updates the swap in storage after fetching.
436
- * @returns A promise that resolves to the swap details.
437
- * @throws Error if the request fails or swap is not found.
438
- */
439
- async getSwap(id, options) {
440
- const { data, error } = await this.#apiClient.GET("/swap/{id}", {
441
- params: { path: { id } },
442
- });
443
- if (error) {
444
- throw new Error(`Failed to get swap: ${JSON.stringify(error)}`);
445
- }
446
- if (!data) {
447
- throw new Error("No swap data returned");
448
- }
449
- if (options?.updateStorage && this.#swapStorage) {
450
- await this.#swapStorage.update(id, data);
451
- }
452
- return data;
453
- }
454
- /**
455
- * Gets a swap from local storage without making a server request.
456
- *
457
- * Use this when you need swap data but don't need the latest status
458
- * from the server. The stored swap includes the preimage, keys, and
459
- * the last known swap response.
460
- *
461
- * @param id - The UUID of the swap.
462
- * @returns The stored swap data, or null if not found.
463
- *
464
- * @example
465
- * ```ts
466
- * const stored = await client.getStoredSwap(swapId);
467
- * if (stored) {
468
- * console.log("Target:", stored.response.target_token);
469
- * console.log("Status:", stored.response.status);
470
- * }
471
- * ```
472
- */
473
- async getStoredSwap(id) {
474
- if (!this.#swapStorage) {
475
- return null;
476
- }
477
- return this.#swapStorage.get(id);
478
- }
479
- /**
480
- * Gets all stored swaps from local storage.
481
- *
482
- * @returns Array of all stored swap data, or empty array if no storage is configured.
483
- */
484
- async listAllSwaps() {
485
- if (!this.#swapStorage) {
486
- return [];
487
- }
488
- return this.#swapStorage.getAll();
489
- }
490
- async deleteSwap(id) {
491
- if (!this.#swapStorage) {
492
- return;
493
- }
494
- await this.#swapStorage.delete(id);
495
- }
496
- async clearSwapStorage() {
497
- if (!this.#swapStorage) {
498
- return;
499
- }
500
- await this.#swapStorage.clear();
501
- }
502
- /**
503
- * Recovers all swaps associated with the current wallet from the server.
504
- *
505
- * Sends the user's xpub to the server, which returns all swaps belonging
506
- * to that wallet. For each recovered swap, re-derives the keys using the
507
- * swap's derivation index and stores it locally.
508
- *
509
- * After recovery, the key index is set to `highest_index + 1` so that
510
- * new swaps don't reuse derivation indices.
511
- *
512
- * @returns The recovered swaps stored locally.
513
- */
514
- async recoverSwaps() {
515
- console.log(`Recovering ...`);
516
- const xpub = this.getUserIdXpub();
517
- console.log(`Recovering ${xpub}`);
518
- const { data, error } = await this.#apiClient.POST("/swap/recover", {
519
- body: { xpub },
520
- });
521
- if (error) {
522
- throw new Error(`Failed to recover swaps: ${JSON.stringify(error)}`);
523
- }
524
- if (!data) {
525
- throw new Error("No recovery data returned");
526
- }
527
- const storedSwaps = [];
528
- console.log(`Recovered data ${JSON.stringify(data)}`);
529
- for (const recoveredSwap of data.swaps) {
530
- const { index, ...response } = recoveredSwap;
531
- const swapParams = this.deriveSwapParamsAtIndex(index);
532
- await this.#storeSwap(response.id, swapParams, response);
533
- const stored = await this.getStoredSwap(response.id);
534
- if (stored) {
535
- storedSwaps.push(stored);
536
- }
537
- }
538
- // Update key index so new swaps don't reuse indices
539
- if (data.highest_index >= 0) {
540
- await this.setKeyIndex(data.highest_index + 1);
541
- }
542
- return storedSwaps;
543
- }
544
- /**
545
- * Gets VHTLC amounts for an Arkade swap.
546
- *
547
- * Queries the Arkade indexer for spendable, spent, and recoverable balances
548
- * at the VHTLC address associated with a swap. Works for:
549
- * - BTC → EVM swaps where the source asset is Arkade
550
- * - EVM → BTC swaps where the target asset is Arkade
551
- *
552
- * Reads swap data from local storage (does not contact the server).
553
- *
554
- * @param id - The UUID of the swap.
555
- * @returns The VHTLC amounts in satoshis.
556
- */
557
- async amountsForSwap(id) {
558
- const stored = await this.getStoredSwap(id);
559
- if (!stored) {
560
- throw new Error(`Swap not found in local storage: ${id}`);
561
- }
562
- const swap = stored.response;
563
- if (swap.direction !== "btc_to_arkade" &&
564
- swap.direction !== "arkade_to_evm" &&
565
- swap.direction !== "evm_to_arkade") {
566
- throw new Error(`amountsForSwap only applies to VHTLC-based swaps, got ${swap.direction}`);
567
- }
568
- // Get VHTLC address based on swap direction
569
- let vhtlcAddress;
570
- if (swap.direction === "btc_to_arkade") {
571
- vhtlcAddress = swap.arkade_vhtlc_address;
572
- }
573
- else if (swap.direction === "arkade_to_evm" ||
574
- swap.direction === "evm_to_arkade") {
575
- vhtlcAddress = swap.btc_vhtlc_address;
576
- }
577
- if (!vhtlcAddress) {
578
- throw new Error("Swap does not have an Arkade VHTLC address");
579
- }
580
- return getVhtlcAmounts({
581
- vhtlcAddress,
582
- network: swap.network,
583
- arkadeServerUrl: this.#config.arkadeServerUrl,
584
- });
585
- }
586
- // =========================================================================
587
- // Redeem
588
- // =========================================================================
589
- /**
590
- * Claims a swap by revealing the preimage.
591
- *
592
- * Reads swap data and preimage from local storage. The claim method
593
- * depends on the swap direction and target chain:
594
- * - **Arkade/Lightning-to-EVM**: Gasless claim via server
595
- * - **Other EVM swaps**: Returns call data for manual claiming
596
- * - **Arkade**: Claims via Arkade protocol
597
- *
598
- * @param id - The UUID of the swap.
599
- * @param _options - Deprecated. For Arkade/Lightning-to-EVM, destination is set at swap creation.
600
- * @returns A ClaimResult with the outcome.
601
- *
602
- * @example
603
- * ```ts
604
- * // Arkade-to-EVM (gasless via server, uses stored target address)
605
- * const result = await client.claim(swapId);
606
- *
607
- * // Other swap types
608
- * const result = await client.claim(swapId);
609
- * if (result.success) {
610
- * console.log("Claim TX:", result.txHash);
611
- * }
612
- * ```
613
- */
614
- async claim(id, _options) {
615
- // Check swap storage is configured
616
- if (!this.#swapStorage) {
617
- return {
618
- success: false,
619
- message: "Swap storage is not configured. Cannot retrieve swap data needed for claim.",
620
- };
621
- }
622
- // Get stored swap data (contains preimage, keys, and swap response)
623
- const storedSwap = await this.#swapStorage.get(id);
624
- if (!storedSwap) {
625
- return {
626
- success: false,
627
- message: `Swap ${id} not found in local storage. Cannot claim without stored data.`,
628
- };
629
- }
630
- const swap = storedSwap.response;
631
- const secret = storedSwap.preimage;
632
- // EVM-targeted swaps: use gasless claim via server (SDK signs internally)
633
- // The destination is always the stored target_evm_address (set at swap creation time)
634
- if (swap.direction === "arkade_to_evm" ||
635
- swap.direction === "lightning_to_evm" ||
636
- swap.direction === "bitcoin_to_evm") {
637
- const evmSwap = swap;
638
- // Use the stored target address - this was set when the swap was created
639
- const destination = evmSwap.target_evm_address ?? evmSwap.client_evm_address;
640
- if (!destination) {
641
- return {
642
- success: false,
643
- message: "Gasless claim failed: no target address found. " +
644
- "This swap may have been created before target address storage was implemented.",
645
- };
646
- }
647
- const gaslessResult = await this.claimViaGasless(id, destination);
648
- return {
649
- success: true,
650
- message: gaslessResult.message,
651
- txHash: gaslessResult.txHash,
652
- };
653
- }
654
- // EVM-to-Bitcoin: user claims BTC from on-chain Taproot HTLC with preimage
655
- if (swap.direction === "evm_to_bitcoin") {
656
- return this.#claimOnchainBtc(id, _options);
657
- }
658
- // Check if target is Arkade (handle both string "btc_arkade" and TokenInfo object)
659
- const isArkadeTarget = swap.target_token.chain === "Arkade";
660
- if (isArkadeTarget) {
661
- // Determine destination address based on swap direction
662
- let destinationAddress;
663
- if (swap.direction === "btc_to_arkade") {
664
- const btcToArkadeSwap = swap;
665
- destinationAddress = btcToArkadeSwap.target_arkade_address;
666
- }
667
- else if (swap.direction === "evm_to_arkade") {
668
- // For evm_to_arkade swaps, check if we have target_arkade_address in stored response
669
- // Check if we have target_arkade_address in the stored response.
670
- const storedResponse = swap;
671
- if (storedResponse.target_arkade_address) {
672
- destinationAddress = storedResponse.target_arkade_address;
673
- }
674
- else {
675
- // Fetch from API to get the full response with target_arkade_address
676
- const freshSwap = await this.getSwap(id);
677
- const evmToArkadeSwap = freshSwap;
678
- destinationAddress = evmToArkadeSwap.target_arkade_address;
679
- }
680
- }
681
- if (!destinationAddress) {
682
- return {
683
- success: false,
684
- message: "No Arkade destination address found in swap. Use claimArkade() with explicit destinationAddress.",
685
- };
686
- }
687
- const arkadeResult = await this.claimArkade(id, { destinationAddress });
688
- // Convert to ClaimResult format
689
- return {
690
- success: arkadeResult.success,
691
- message: arkadeResult.message,
692
- chain: "arkade",
693
- txHash: arkadeResult.txId,
694
- };
695
- }
696
- // For EVM chains, use the existing claim logic
697
- return redeemClaim(id, secret, {
698
- apiClient: this.#apiClient,
699
- getSwap: () => Promise.resolve(swap),
700
- });
701
- }
702
- /**
703
- * Claims an Arkade-to-EVM swap gaslessly via the server.
704
- *
705
- * The SDK builds the EIP-712 digest, signs it with the swap's internally
706
- * derived EVM key, and sends the signature + secret to the server. The
707
- * server submits the `coordinator.redeemAndExecute` transaction.
708
- *
709
- * @param id - The UUID of the swap.
710
- * @param destination - The EVM address where tokens should be sent.
711
- * @returns The gasless claim result with transaction hash.
712
- *
713
- * @example
714
- * ```ts
715
- * const result = await client.claimViaGasless(swapId, "0xYourAddress");
716
- * console.log("Claimed! TX:", result.txHash);
717
- * ```
718
- */
719
- async claimViaGasless(id, destination, options) {
720
- if (!this.#swapStorage) {
721
- throw new Error("Swap storage is not configured. Cannot retrieve preimage needed for gasless claim.");
722
- }
723
- // Fetch all data upfront
724
- const stored = await this.#swapStorage.get(id);
725
- if (!stored) {
726
- throw new Error(`Swap ${id} not found in local storage.`);
727
- }
728
- const swap = (await this.getSwap(id, {
729
- updateStorage: true,
730
- }));
731
- if (swap.direction !== "arkade_to_evm" &&
732
- swap.direction !== "lightning_to_evm" &&
733
- swap.direction !== "bitcoin_to_evm") {
734
- throw new Error(`Expected arkade_to_evm or lightning_to_evm swap, got ${swap.direction}. claimViaGasless is for EVM-targeted swaps.`);
735
- }
736
- // Fetch DEX calldata if the target token differs from WBTC
737
- const targetTokenAddress = String(swap.target_token.token_id);
738
- const needsDexSwap = targetTokenAddress.toLowerCase() !== swap.wbtc_address.toLowerCase();
739
- let dexCalldata;
740
- if (needsDexSwap) {
741
- const slippage = options?.slippage ?? 1.0;
742
- const calldataResponse = await this.#apiClient.GET("/swap/{id}/redeem-and-swap-calldata", {
743
- params: {
744
- path: { id },
745
- query: { destination, slippage },
746
- },
747
- });
748
- if (calldataResponse.error) {
749
- throw new Error(`Failed to fetch DEX calldata: ${calldataResponse.error.error}`);
750
- }
751
- if (calldataResponse.data) {
752
- dexCalldata = {
753
- to: calldataResponse.data.dex_calldata.to,
754
- data: calldataResponse.data.dex_calldata.data,
755
- value: calldataResponse.data.dex_calldata.value,
756
- };
757
- }
758
- }
759
- return gaslessClaim({
760
- baseUrl: this.#config.baseUrl,
761
- preimage: stored.preimage,
762
- secretKey: hexToBytes(stored.secretKey),
763
- swap,
764
- destination,
765
- dexCalldata,
766
- });
767
- }
768
- /**
769
- * Claims an Arkade (off-chain) VHTLC swap by revealing the preimage.
770
- *
771
- * Automatically selects the best claim method based on VTXO status:
772
- * - **spendable** VTXOs → offchain spend (submitTx/finalizeTx)
773
- * - **recoverable** or **mixed** VTXOs → delegated settlement via backend
774
- *
775
- * This is used for EVM-to-Arkade and BTC-to-Arkade swaps where the user
776
- * claims BTC on Arkade after the server has funded the VHTLC.
777
- *
778
- * @param id - The UUID of the swap.
779
- * @param options - Claim options including destination address.
780
- * @returns The claim result with transaction ID and amount.
781
- *
782
- * @example
783
- * ```ts
784
- * const result = await client.claimArkade(swapId, {
785
- * destinationAddress: "ark1q...", // Where to receive BTC
786
- * });
787
- * if (result.success) {
788
- * console.log("Claim TX:", result.txId);
789
- * console.log("Amount:", result.claimAmount);
790
- * }
791
- * ```
792
- */
793
- async claimArkade(id, options) {
794
- // Validate options
795
- if (!options?.destinationAddress) {
796
- return {
797
- success: false,
798
- message: "Destination address is required for Arkade claims. " +
799
- 'Provide it via the options parameter: { destinationAddress: "ark1..." }',
800
- };
801
- }
802
- // Check swap storage is configured
803
- if (!this.#swapStorage) {
804
- return {
805
- success: false,
806
- message: "Swap storage is not configured. Cannot retrieve the preimage needed for claim.",
807
- };
808
- }
809
- // Get stored swap data (contains preimage and secret key)
810
- const storedSwap = await this.#swapStorage.get(id);
811
- if (!storedSwap) {
812
- return {
813
- success: false,
814
- message: `Swap ${id} not found in local storage. The preimage is required to claim.`,
815
- };
816
- }
817
- const swap = storedSwap.response;
818
- // Ensure we have an Arkade-target swap
819
- if (swap.direction !== "btc_to_arkade" &&
820
- swap.direction !== "evm_to_arkade") {
821
- return {
822
- success: false,
823
- message: `Expected btc_to_arkade or evm_to_arkade swap, got ${swap.direction}. claimArkade is for swaps targeting Arkade.`,
824
- };
825
- }
826
- // Extract common VHTLC parameters
827
- const claimParams = this.#extractArkadeClaimParams(id, storedSwap);
828
- // Query VTXO status to determine claim method
829
- const amounts = await this.amountsForSwap(id);
830
- const vtxoStatus = amounts.vtxoStatus;
831
- if (vtxoStatus === "not_funded" || vtxoStatus === "spent") {
832
- return {
833
- success: false,
834
- message: vtxoStatus === "not_funded"
835
- ? "No VTXOs found at the VHTLC address. The swap may not have been funded yet."
836
- : "All VTXOs have already been spent.",
837
- };
838
- }
839
- // Route based on VTXO status:
840
- // - spendable: offchain spend (faster, no backend dependency)
841
- // - recoverable/mixed: delegated settlement (handles expired batches)
842
- if (vtxoStatus === "spendable") {
843
- return this.#claimArkadeOffchain(claimParams, options);
844
- }
845
- // recoverable or mixed → delegate
846
- return this.#claimArkadeDelegate(id, claimParams, options);
847
- }
848
- /**
849
- * Extracts VHTLC claim parameters from a stored swap.
850
- * @internal
851
- */
852
- #extractArkadeClaimParams(_id, storedSwap) {
853
- const swap = storedSwap.response;
854
- const fullPubKey = storedSwap.publicKey;
855
- const userPubKey = fullPubKey.length === 66 ? fullPubKey.slice(2) : fullPubKey;
856
- let lendaswapPubKey;
857
- let arkadeServerPubKey;
858
- let vhtlcAddress;
859
- let refundLocktime;
860
- let unilateralClaimDelay;
861
- let unilateralRefundDelay;
862
- let unilateralRefundWithoutReceiverDelay;
863
- let network;
864
- if (swap.direction === "btc_to_arkade") {
865
- const s = swap;
866
- lendaswapPubKey = s.server_vhtlc_pk;
867
- arkadeServerPubKey = s.arkade_server_pk;
868
- vhtlcAddress = s.arkade_vhtlc_address;
869
- refundLocktime = s.vhtlc_refund_locktime;
870
- unilateralClaimDelay = s.unilateral_claim_delay;
871
- unilateralRefundDelay = s.unilateral_refund_delay;
872
- unilateralRefundWithoutReceiverDelay =
873
- s.unilateral_refund_without_receiver_delay;
874
- network = s.network;
875
- }
876
- else if (swap.direction === "evm_to_arkade") {
877
- const s = swap;
878
- lendaswapPubKey = s.sender_pk;
879
- arkadeServerPubKey = s.arkade_server_pk;
880
- vhtlcAddress = s.btc_vhtlc_address;
881
- refundLocktime = s.vhtlc_refund_locktime;
882
- unilateralClaimDelay = s.unilateral_claim_delay;
883
- unilateralRefundDelay = s.unilateral_refund_delay;
884
- unilateralRefundWithoutReceiverDelay =
885
- s.unilateral_refund_without_receiver_delay;
886
- network = s.network;
887
- }
888
- else {
889
- throw Error(`Unsupported direction for Arkade claim: ${swap.direction}`);
890
- }
891
- return {
892
- userSecretKey: storedSwap.secretKey,
893
- userPubKey,
894
- lendaswapPubKey,
895
- arkadeServerPubKey,
896
- vhtlcAddress,
897
- refundLocktime,
898
- unilateralClaimDelay,
899
- unilateralRefundDelay,
900
- unilateralRefundWithoutReceiverDelay,
901
- network,
902
- preimage: storedSwap.preimage,
903
- preimageHash: storedSwap.preimageHash,
904
- };
905
- }
906
- /**
907
- * Claims via the offchain submitTx/finalizeTx path (spendable VTXOs only).
908
- * @internal
909
- */
910
- async #claimArkadeOffchain(params, options) {
911
- try {
912
- const result = await buildArkadeClaim({
913
- ...params,
914
- destinationAddress: options.destinationAddress,
915
- arkadeServerUrl: options.arkadeServerUrl ?? this.#config.arkadeServerUrl,
916
- });
917
- return {
918
- success: true,
919
- message: "Arkade claim executed successfully via offchain spend!",
920
- txId: result.txId,
921
- claimAmount: result.claimAmount,
922
- };
923
- }
924
- catch (error) {
925
- const message = error instanceof Error ? error.message : String(error);
926
- return {
927
- success: false,
928
- message: `Failed to execute offchain Arkade claim: ${message}`,
929
- };
930
- }
931
- }
932
- /**
933
- * Claims via the delegated settlement path (works for all VTXO states).
934
- * @internal
935
- */
936
- async #claimArkadeDelegate(swapId, params, options) {
937
- try {
938
- const result = await delegateClaim({
939
- ...params,
940
- destinationAddress: options.destinationAddress,
941
- lendaswapApiUrl: this.#config.baseUrl,
942
- arkadeServerUrl: options.arkadeServerUrl ?? this.#config.arkadeServerUrl,
943
- swapId,
944
- });
945
- return {
946
- success: true,
947
- message: "Arkade claim executed successfully via delegated settlement!",
948
- txId: result.commitmentTxid,
949
- };
950
- }
951
- catch (error) {
952
- const message = error instanceof Error ? error.message : String(error);
953
- return {
954
- success: false,
955
- message: `Failed to execute delegated Arkade claim: ${message}`,
956
- };
957
- }
958
- }
959
- // =========================================================================
960
- // Refund
961
- // =========================================================================
962
- /**
963
- * Attempts to refund a swap.
964
- *
965
- * Refund behavior depends on the swap type:
966
- * - **Lightning to EVM**: Cannot refund - Lightning swaps auto-expire if not completed.
967
- * The invoice will simply expire and no funds are locked.
968
- * - **Arkade to EVM**: Off-chain refund via Arkade server
969
- * - **Bitcoin (on-chain) to EVM**: Builds a signed refund transaction that the user
970
- * must broadcast to reclaim their funds after the locktime.
971
- *
972
- * @param id - The UUID of the swap to refund.
973
- * @param options - Options for on-chain refunds (required for btc_onchain swaps).
974
- * @returns A RefundResult with the transaction details (for on-chain) or status message.
975
- * @throws Error if the swap cannot be found, storage is not configured, or params are invalid.
976
- *
977
- * @example
978
- * ```ts
979
- * // For on-chain swaps
980
- * const result = await client.refundSwap(swapId, {
981
- * destinationAddress: "bc1q...",
982
- * feeRateSatPerVb: 5,
983
- * });
984
- * if (result.success) {
985
- * console.log("Broadcast this transaction:", result.txHex);
986
- * console.log("Transaction ID:", result.txId);
987
- * }
988
- * ```
989
- */
990
- async refundSwap(id, options) {
991
- // Get the swap to determine its type
992
- const storedSwap = await this.getStoredSwap(id);
993
- if (!storedSwap) {
994
- throw Error("Swap not found");
995
- }
996
- const swap = storedSwap.response;
997
- // Use direction to determine refund method (source_token may be a TokenSummary object)
998
- const direction = swap.direction;
999
- // Arkade swaps require off-chain refund
1000
- if (direction === "arkade_to_evm") {
1001
- return this.#buildArkadeRefund(id, swap, options);
1002
- }
1003
- // Bitcoin on-chain swaps require on-chain refund transaction
1004
- if (direction === "bitcoin_to_evm" || direction === "btc_to_arkade") {
1005
- return this.#buildOnchainRefund(id, swap, options);
1006
- }
1007
- // EVM-sourced swaps return calldata for manual execution
1008
- if (direction === "evm_to_arkade") {
1009
- const evmOptions = options;
1010
- return this.#buildEvmToArkadeRefund(id, swap, evmOptions?.mode);
1011
- }
1012
- // EVM-to-Bitcoin uses coordinator refund (same pattern as EVM-to-Arkade)
1013
- if (direction === "evm_to_bitcoin") {
1014
- const evmOptions = options;
1015
- return this.#buildEvmToBitcoinRefund(id, swap, evmOptions?.mode);
1016
- }
1017
- // EVM-to-Lightning uses coordinator refund (same pattern as EVM-to-Arkade)
1018
- if (direction === "evm_to_lightning") {
1019
- const evmOptions = options;
1020
- return this.#buildEvmToLightningRefund(id, swap, evmOptions?.mode);
1021
- }
1022
- return {
1023
- success: false,
1024
- message: `Refund not supported for direction: ${direction}.`,
1025
- };
1026
- }
1027
- /**
1028
- * Claims BTC from an on-chain Taproot HTLC for an EVM-to-Bitcoin swap.
1029
- *
1030
- * The user reveals the preimage to spend from the hashlock script path.
1031
- * @internal
1032
- */
1033
- async #claimOnchainBtc(id, options) {
1034
- if (!this.#swapStorage) {
1035
- return {
1036
- success: false,
1037
- message: "Swap storage is not configured. Cannot retrieve preimage and keys needed for claim.",
1038
- };
1039
- }
1040
- const storedSwap = await this.#swapStorage.get(id);
1041
- if (!storedSwap) {
1042
- return {
1043
- success: false,
1044
- message: `Swap ${id} not found in local storage.`,
1045
- };
1046
- }
1047
- // Fetch the latest swap state from API
1048
- const swap = (await this.getSwap(id, {
1049
- updateStorage: true,
1050
- }));
1051
- if (swap.direction !== "evm_to_bitcoin") {
1052
- return {
1053
- success: false,
1054
- message: `Expected evm_to_bitcoin swap, got ${swap.direction}`,
1055
- };
1056
- }
1057
- // Extract BTC HTLC parameters
1058
- const btcHtlcAddress = swap.btc_htlc_address;
1059
- const btcHashLock = swap.btc_hash_lock;
1060
- const btcRefundLocktime = swap.btc_refund_locktime;
1061
- const networkStr = swap.network;
1062
- // Get server refund pk (needed to reconstruct the Taproot tree)
1063
- const serverRefundPkRaw = swap
1064
- .btc_server_refund_pk;
1065
- if (!serverRefundPkRaw) {
1066
- return {
1067
- success: false,
1068
- message: "Server refund public key not available. The API response may need to be updated.",
1069
- };
1070
- }
1071
- // Map network string
1072
- const networkMap = {
1073
- mainnet: "mainnet",
1074
- testnet: "testnet",
1075
- signet: "signet",
1076
- regtest: "regtest",
1077
- };
1078
- const network = networkMap[networkStr];
1079
- if (!network) {
1080
- return {
1081
- success: false,
1082
- message: `Unknown Bitcoin network: ${networkStr}`,
1083
- };
1084
- }
1085
- // Get user's x-only public key (32 bytes from 33-byte compressed)
1086
- const fullPubKey = storedSwap.publicKey;
1087
- const userClaimPk = fullPubKey.length === 66 ? fullPubKey.slice(2) : fullPubKey;
1088
- // Strip compressed key prefix if present
1089
- const serverRefundPk = serverRefundPkRaw.length === 66
1090
- ? serverRefundPkRaw.slice(2)
1091
- : serverRefundPkRaw;
1092
- // Verify HTLC address matches our reconstruction
1093
- const addressMatches = verifyHtlcAddress(btcHtlcAddress, btcHashLock, userClaimPk, // claimer = user (goes in hashlock position)
1094
- serverRefundPk, // refunder = server (goes in timelock position)
1095
- btcRefundLocktime, network);
1096
- if (!addressMatches) {
1097
- return {
1098
- success: false,
1099
- message: `HTLC address mismatch. Computed address does not match server's (${btcHtlcAddress}). ` +
1100
- `Parameters: hashLock='${btcHashLock}', userPk='${userClaimPk}', ` +
1101
- `serverPk='${serverRefundPk}', locktime='${btcRefundLocktime}', network='${network}'`,
1102
- };
1103
- }
1104
- // Get the HTLC output info - prefer API data over Esplora lookup
1105
- const esploraUrl = this.#config.esploraUrl ?? DEFAULT_ESPLORA_URLS[network];
1106
- if (!esploraUrl) {
1107
- return {
1108
- success: false,
1109
- message: `No Esplora URL configured for network ${network}.`,
1110
- };
1111
- }
1112
- // Try to use funding info from the API response (faster, works before confirmation)
1113
- const btcFundTxid = swap.btc_fund_txid;
1114
- const btcFundVout = swap.btc_fund_vout;
1115
- let htlcOutput = null;
1116
- if (btcFundTxid && btcFundVout !== undefined) {
1117
- // We have the funding info from the API, but we need to get the amount
1118
- // Query the transaction to get the output amount
1119
- try {
1120
- const txResponse = await fetch(`${esploraUrl}/tx/${btcFundTxid}`);
1121
- if (txResponse.ok) {
1122
- const txData = (await txResponse.json());
1123
- if (txData.vout?.[btcFundVout]) {
1124
- htlcOutput = {
1125
- txid: btcFundTxid,
1126
- vout: btcFundVout,
1127
- amount: BigInt(txData.vout[btcFundVout].value),
1128
- };
1129
- }
1130
- }
1131
- }
1132
- catch {
1133
- // Fall through to Esplora lookup
1134
- }
1135
- }
1136
- // Fallback: query Esplora for UTXOs at the address (requires confirmation)
1137
- if (!htlcOutput) {
1138
- htlcOutput = await findOutputByAddress(esploraUrl, btcHtlcAddress);
1139
- }
1140
- if (!htlcOutput) {
1141
- return {
1142
- success: false,
1143
- message: `Could not find UTXO at HTLC address ${btcHtlcAddress}. The server may not have funded the HTLC yet.`,
1144
- };
1145
- }
1146
- // Determine destination address: prefer explicit option, fall back to stored response
1147
- const destinationAddress = options?.destinationAddress ??
1148
- swap.target_btc_address;
1149
- if (!destinationAddress) {
1150
- return {
1151
- success: false,
1152
- message: "Destination address is required to claim BTC. " +
1153
- 'Provide it via options: { destinationAddress: "bc1p..." }',
1154
- };
1155
- }
1156
- try {
1157
- const result = buildOnchainClaimTransaction({
1158
- fundingTxId: htlcOutput.txid,
1159
- fundingVout: htlcOutput.vout,
1160
- htlcAmount: htlcOutput.amount,
1161
- hashLock: btcHashLock,
1162
- userClaimPubKey: userClaimPk,
1163
- serverRefundPubKey: serverRefundPk,
1164
- userSecretKey: storedSwap.secretKey,
1165
- preimage: storedSwap.preimage,
1166
- refundLocktime: btcRefundLocktime,
1167
- destinationAddress,
1168
- feeRateSatPerVb: options?.feeRateSatPerVb ?? 2,
1169
- network,
1170
- });
1171
- // Broadcast
1172
- try {
1173
- await broadcastTransaction(esploraUrl, result.txHex);
1174
- return {
1175
- success: true,
1176
- message: "BTC claim transaction broadcast successfully!",
1177
- txHash: result.txId,
1178
- // chain: "bitcoin" — not in ClaimChain type
1179
- };
1180
- }
1181
- catch (broadcastError) {
1182
- const msg = broadcastError instanceof Error
1183
- ? broadcastError.message
1184
- : String(broadcastError);
1185
- return {
1186
- success: true,
1187
- message: `Claim transaction built but broadcast failed: ${msg}. TxHex: ${result.txHex}`,
1188
- txHash: result.txId,
1189
- // chain: "bitcoin" — not in ClaimChain type
1190
- };
1191
- }
1192
- }
1193
- catch (error) {
1194
- const msg = error instanceof Error ? error.message : String(error);
1195
- return {
1196
- success: false,
1197
- message: `Failed to build claim transaction: ${msg}`,
1198
- };
1199
- }
1200
- }
1201
- /**
1202
- * Builds an on-chain Bitcoin refund transaction.
1203
- * @internal
1204
- */
1205
- async #buildOnchainRefund(id, swap, options) {
1206
- // Validate options
1207
- if (!options?.destinationAddress) {
1208
- return {
1209
- success: false,
1210
- message: "Destination address is required for on-chain refunds. " +
1211
- 'Provide it via the options parameter: { destinationAddress: "bc1q..." }',
1212
- };
1213
- }
1214
- // Check swap storage is configured
1215
- if (!this.#swapStorage) {
1216
- return {
1217
- success: false,
1218
- message: "Swap storage is not configured. Cannot retrieve the secret key needed for refund.",
1219
- };
1220
- }
1221
- // Get stored swap data (contains secret key)
1222
- const storedSwap = await this.#swapStorage.get(id);
1223
- if (!storedSwap) {
1224
- return {
1225
- success: false,
1226
- message: `Swap ${id} not found in local storage. The secret key is required to sign the refund transaction.`,
1227
- };
1228
- }
1229
- // Ensure we have an on-chain funded swap
1230
- if (swap.direction !== "bitcoin_to_evm" &&
1231
- swap.direction !== "btc_to_arkade") {
1232
- return {
1233
- success: false,
1234
- message: `Expected bitcoin_to_evm or btc_to_arkade swap, got ${swap.direction}`,
1235
- };
1236
- }
1237
- // Extract on-chain HTLC fields based on direction
1238
- // Both directions have the same on-chain HTLC but fields are named differently
1239
- let btcHtlcAddress;
1240
- let btcRefundLocktime;
1241
- let hashLock;
1242
- let serverPubKeyFull;
1243
- let networkStr;
1244
- if (swap.direction === "btc_to_arkade") {
1245
- const arkadeSwap = swap;
1246
- btcHtlcAddress = arkadeSwap.btc_htlc_address;
1247
- btcRefundLocktime = arkadeSwap.btc_refund_locktime;
1248
- hashLock = arkadeSwap.hash_lock;
1249
- serverPubKeyFull = arkadeSwap.server_vhtlc_pk;
1250
- networkStr = arkadeSwap.network;
1251
- }
1252
- else {
1253
- // OnchainToEvmSwapResponse (on-chain Bitcoin to EVM)
1254
- const onchainSwap = swap;
1255
- btcHtlcAddress = onchainSwap.btc_htlc_address;
1256
- btcRefundLocktime = onchainSwap.btc_refund_locktime;
1257
- hashLock = onchainSwap.btc_hash_lock;
1258
- serverPubKeyFull = onchainSwap.btc_server_pk;
1259
- networkStr = onchainSwap.network;
1260
- }
1261
- // Check refund locktime
1262
- const now = Math.floor(Date.now() / 1000);
1263
- if (now < btcRefundLocktime) {
1264
- const remainingSeconds = btcRefundLocktime - now;
1265
- const remainingMinutes = Math.ceil(remainingSeconds / 60);
1266
- return {
1267
- success: false,
1268
- message: `Refund is not yet available. The locktime expires in ${remainingMinutes} minutes ` +
1269
- `(at ${new Date(btcRefundLocktime * 1000).toISOString()}).`,
1270
- };
1271
- }
1272
- // Map network string to BitcoinNetwork type
1273
- const networkMap = {
1274
- mainnet: "mainnet",
1275
- testnet: "testnet",
1276
- signet: "signet",
1277
- regtest: "regtest",
1278
- };
1279
- const network = networkMap[networkStr];
1280
- if (!network) {
1281
- return {
1282
- success: false,
1283
- message: `Unknown Bitcoin network: ${networkStr}`,
1284
- };
1285
- }
1286
- // Get user's x-only public key (32 bytes) from stored swap
1287
- // The stored publicKey is the full compressed pubkey (33 bytes)
1288
- // We need to extract the x-only portion (drop the first byte prefix)
1289
- const fullPubKey = storedSwap.publicKey;
1290
- const userPubKey = fullPubKey.length === 66 ? fullPubKey.slice(2) : fullPubKey;
1291
- // Strip compressed key prefix if present (33-byte → 32-byte x-only)
1292
- const serverXOnlyPubKey = serverPubKeyFull.length === 66
1293
- ? serverPubKeyFull.slice(2)
1294
- : serverPubKeyFull;
1295
- // Verify that our computed HTLC address matches the server's address
1296
- const addressMatches = verifyHtlcAddress(btcHtlcAddress, hashLock, serverXOnlyPubKey, userPubKey, btcRefundLocktime, network);
1297
- if (!addressMatches) {
1298
- return {
1299
- success: false,
1300
- message: `HTLC address mismatch. The computed address does not match the server's address (${btcHtlcAddress}). ` +
1301
- `This could indicate different script construction. ` +
1302
- `Parameters: \nhashLock='${hashLock}', \nserverPk='${serverPubKeyFull}', ` +
1303
- `\nuserPk='${userPubKey}', \nlocktime='${btcRefundLocktime}',` +
1304
- `\nnetwork='${network}'`,
1305
- };
1306
- }
1307
- // Get the HTLC output info - prefer API data over Esplora lookup
1308
- const esploraUrl = this.#config.esploraUrl ?? DEFAULT_ESPLORA_URLS[network];
1309
- if (!esploraUrl) {
1310
- return {
1311
- success: false,
1312
- message: `No Esplora URL configured for network ${network}. Cannot look up funding transaction.`,
1313
- };
1314
- }
1315
- // Try to use funding info from the API response (faster, works before confirmation)
1316
- const btcFundTxid = swap.btc_fund_txid;
1317
- const btcFundVout = swap.btc_fund_vout;
1318
- let htlcOutput = null;
1319
- if (btcFundTxid && btcFundVout !== undefined) {
1320
- // We have the funding info from the API, get the amount from the transaction
1321
- try {
1322
- const txResponse = await fetch(`${esploraUrl}/tx/${btcFundTxid}`);
1323
- if (txResponse.ok) {
1324
- const txData = (await txResponse.json());
1325
- if (txData.vout?.[btcFundVout]) {
1326
- htlcOutput = {
1327
- txid: btcFundTxid,
1328
- vout: btcFundVout,
1329
- amount: BigInt(txData.vout[btcFundVout].value),
1330
- };
1331
- }
1332
- }
1333
- }
1334
- catch {
1335
- // Fall through to Esplora lookup
1336
- }
1337
- }
1338
- // Fallback: query Esplora for UTXOs at the address (requires confirmation)
1339
- if (!htlcOutput) {
1340
- htlcOutput = await findOutputByAddress(esploraUrl, btcHtlcAddress);
1341
- }
1342
- if (!htlcOutput) {
1343
- return {
1344
- success: false,
1345
- message: `Could not find UTXO at HTLC address ${btcHtlcAddress}. ` +
1346
- `The address may not have been funded yet.`,
1347
- };
1348
- }
1349
- try {
1350
- // Build the refund transaction
1351
- const result = buildOnchainRefundTransaction({
1352
- fundingTxId: htlcOutput.txid,
1353
- fundingVout: htlcOutput.vout,
1354
- htlcAmount: htlcOutput.amount,
1355
- hashLock,
1356
- serverPubKey: serverXOnlyPubKey,
1357
- userPubKey,
1358
- userSecretKey: storedSwap.secretKey,
1359
- refundLocktime: btcRefundLocktime,
1360
- destinationAddress: options.destinationAddress,
1361
- feeRateSatPerVb: options.feeRateSatPerVb ?? 2,
1362
- network,
1363
- });
1364
- // If dry run, just return the transaction without broadcasting
1365
- if (options.dryRun) {
1366
- return {
1367
- success: true,
1368
- message: "Refund transaction built successfully (dry run - not broadcast).",
1369
- txHex: result.txHex,
1370
- txId: result.txId,
1371
- refundAmount: result.refundAmount,
1372
- fee: result.fee,
1373
- broadcast: false,
1374
- htlcAddress: result.htlcAddress,
1375
- serverHtlcAddress: btcHtlcAddress,
1376
- };
1377
- }
1378
- // Broadcast the transaction
1379
- const broadcastEsploraUrl = this.#config.esploraUrl ?? DEFAULT_ESPLORA_URLS[network];
1380
- if (!broadcastEsploraUrl) {
1381
- return {
1382
- success: true,
1383
- message: "Refund transaction built successfully. No Esplora URL configured for broadcast. " +
1384
- "Broadcast the txHex manually to the Bitcoin network.",
1385
- txHex: result.txHex,
1386
- txId: result.txId,
1387
- refundAmount: result.refundAmount,
1388
- fee: result.fee,
1389
- broadcast: false,
1390
- htlcAddress: result.htlcAddress,
1391
- serverHtlcAddress: btcHtlcAddress,
1392
- };
1393
- }
1394
- try {
1395
- await broadcastTransaction(broadcastEsploraUrl, result.txHex);
1396
- return {
1397
- success: true,
1398
- message: "Refund transaction broadcast successfully!",
1399
- txHex: result.txHex,
1400
- txId: result.txId,
1401
- refundAmount: result.refundAmount,
1402
- fee: result.fee,
1403
- broadcast: true,
1404
- htlcAddress: result.htlcAddress,
1405
- serverHtlcAddress: btcHtlcAddress,
1406
- };
1407
- }
1408
- catch (broadcastError) {
1409
- const broadcastMessage = broadcastError instanceof Error
1410
- ? broadcastError.message
1411
- : String(broadcastError);
1412
- return {
1413
- success: true,
1414
- message: `Transaction built but broadcast failed: ${broadcastMessage}. ` +
1415
- "You can broadcast the txHex manually.",
1416
- txHex: result.txHex,
1417
- txId: result.txId,
1418
- refundAmount: result.refundAmount,
1419
- fee: result.fee,
1420
- broadcast: false,
1421
- htlcAddress: result.htlcAddress,
1422
- serverHtlcAddress: btcHtlcAddress,
1423
- };
1424
- }
1425
- }
1426
- catch (error) {
1427
- const message = error instanceof Error ? error.message : String(error);
1428
- return {
1429
- success: false,
1430
- message: `Failed to build refund transaction: ${message}`,
1431
- };
1432
- }
1433
- }
1434
- /**
1435
- * Builds and executes an Arkade (off-chain) VHTLC refund.
1436
- *
1437
- * Automatically selects the best refund method based on VTXO status:
1438
- * - **spendable** VTXOs → offchain spend (submitTx/finalizeTx)
1439
- * - **recoverable** or **mixed** VTXOs → delegated settlement via backend
1440
- *
1441
- * @internal
1442
- */
1443
- async #buildArkadeRefund(id, swap, options) {
1444
- // Validate options
1445
- if (!options?.destinationAddress) {
1446
- return {
1447
- success: false,
1448
- message: "Destination address is required for Arkade refunds. " +
1449
- 'Provide it via the options parameter: { destinationAddress: "ark1..." }',
1450
- };
1451
- }
1452
- // Check swap storage is configured
1453
- if (!this.#swapStorage) {
1454
- return {
1455
- success: false,
1456
- message: "Swap storage is not configured. Cannot retrieve the secret key needed for refund.",
1457
- };
1458
- }
1459
- // Get stored swap data (contains secret key)
1460
- const storedSwap = await this.#swapStorage.get(id);
1461
- if (!storedSwap) {
1462
- return {
1463
- success: false,
1464
- message: `Swap ${id} not found in local storage. The secret key is required to sign the refund transaction.`,
1465
- };
1466
- }
1467
- // Ensure we have an arkade_to_evm swap
1468
- if (swap.direction !== "arkade_to_evm") {
1469
- return {
1470
- success: false,
1471
- message: `Expected arkade_to_evm swap, got ${swap.direction}`,
1472
- };
1473
- }
1474
- const s = swap;
1475
- // Check refund locktime
1476
- const now = Math.floor(Date.now() / 1000);
1477
- if (now < s.vhtlc_refund_locktime) {
1478
- const remainingSeconds = s.vhtlc_refund_locktime - now;
1479
- const remainingMinutes = Math.ceil(remainingSeconds / 60);
1480
- return {
1481
- success: false,
1482
- message: `Refund is not yet available. The VHTLC locktime expires in ${remainingMinutes} minutes ` +
1483
- `(at ${new Date(s.vhtlc_refund_locktime * 1000).toISOString()}).`,
1484
- };
1485
- }
1486
- const fullPubKey = storedSwap.publicKey;
1487
- const userPubKey = fullPubKey.length === 66 ? fullPubKey.slice(2) : fullPubKey;
1488
- const hashLock = s.hash_lock.startsWith("0x")
1489
- ? s.hash_lock.slice(2)
1490
- : s.hash_lock;
1491
- // Query VTXO status to determine refund method
1492
- const amounts = await this.amountsForSwap(id);
1493
- const vtxoStatus = amounts.vtxoStatus;
1494
- if (vtxoStatus === "not_funded" || vtxoStatus === "spent") {
1495
- return {
1496
- success: false,
1497
- message: vtxoStatus === "not_funded"
1498
- ? "No VTXOs found at the VHTLC address."
1499
- : "All VTXOs have already been spent.",
1500
- };
1501
- }
1502
- const refundParams = {
1503
- userSecretKey: storedSwap.secretKey,
1504
- userPubKey,
1505
- lendaswapPubKey: s.receiver_pk,
1506
- arkadeServerPubKey: s.arkade_server_pk,
1507
- hashLock,
1508
- vhtlcAddress: s.btc_vhtlc_address,
1509
- refundLocktime: s.vhtlc_refund_locktime,
1510
- unilateralClaimDelay: s.unilateral_claim_delay,
1511
- unilateralRefundDelay: s.unilateral_refund_delay,
1512
- unilateralRefundWithoutReceiverDelay: s.unilateral_refund_without_receiver_delay,
1513
- destinationAddress: options.destinationAddress,
1514
- network: s.network,
1515
- };
1516
- if (vtxoStatus === "spendable") {
1517
- return this.#refundArkadeOffchain(refundParams, options);
1518
- }
1519
- // recoverable or mixed → delegate
1520
- return this.#refundArkadeDelegate(refundParams, options);
1521
- }
1522
- /**
1523
- * Refunds via the offchain submitTx/finalizeTx path (spendable VTXOs only).
1524
- * @internal
1525
- */
1526
- async #refundArkadeOffchain(params, options) {
1527
- try {
1528
- const result = await buildArkadeRefund({
1529
- ...params,
1530
- arkadeServerUrl: options.arkadeServerUrl ?? this.#config.arkadeServerUrl,
1531
- });
1532
- return {
1533
- success: true,
1534
- message: "Arkade refund executed successfully via offchain spend!",
1535
- txId: result.txId,
1536
- refundAmount: result.refundAmount,
1537
- broadcast: true,
1538
- };
1539
- }
1540
- catch (error) {
1541
- const message = error instanceof Error ? error.message : String(error);
1542
- return {
1543
- success: false,
1544
- message: `Failed to execute offchain Arkade refund: ${message}`,
1545
- };
1546
- }
1547
- }
1548
- /**
1549
- * Refunds via the delegated settlement path (works for all VTXO states).
1550
- * @internal
1551
- */
1552
- async #refundArkadeDelegate(params, options) {
1553
- try {
1554
- const result = await delegateRefund({
1555
- ...params,
1556
- lendaswapApiUrl: this.#config.baseUrl,
1557
- arkadeServerUrl: options.arkadeServerUrl ?? this.#config.arkadeServerUrl,
1558
- });
1559
- return {
1560
- success: true,
1561
- message: "Arkade refund executed successfully via delegated settlement!",
1562
- txId: result.commitmentTxid,
1563
- broadcast: true,
1564
- };
1565
- }
1566
- catch (error) {
1567
- const message = error instanceof Error ? error.message : String(error);
1568
- return {
1569
- success: false,
1570
- message: `Failed to execute delegated Arkade refund: ${message}`,
1571
- };
1572
- }
1573
- }
1574
- /**
1575
- * Builds refund data for an EVM-to-Arkade swap via the coordinator.
1576
- *
1577
- * Calls the server's refund-calldata endpoint which builds coordinator
1578
- * calldata for `refundAndExecute` (swap WBTC back to source token) or
1579
- * `refundTo` (return WBTC directly).
1580
- *
1581
- * @internal
1582
- */
1583
- async #buildEvmToArkadeRefund(id, swap, mode = "swap-back") {
1584
- const evmSwap = swap;
1585
- const timelock = evmSwap.evm_refund_locktime;
1586
- const now = Math.floor(Date.now() / 1000);
1587
- const timelockExpired = now >= timelock;
1588
- // Check if source token is WBTC - if so, use direct HTLCErc20 refund
1589
- const sourceSymbol = evmSwap.source_token?.symbol?.toLowerCase();
1590
- const isWbtcSource = sourceSymbol === "wbtc";
1591
- if (isWbtcSource) {
1592
- // Direct HTLCErc20 refund - no DEX swap needed
1593
- const htlcAddress = evmSwap.evm_htlc_address;
1594
- const hashLock = evmSwap.hash_lock;
1595
- const refundData = encodeHtlcErc20RefundCallData(htlcAddress, {
1596
- preimageHash: hashLock,
1597
- amount: BigInt(evmSwap.source_amount),
1598
- token: evmSwap.source_token.token_id,
1599
- claimAddress: evmSwap.server_evm_address, // The server would have been the claimer
1600
- timelock: timelock,
1601
- });
1602
- return {
1603
- success: true,
1604
- message: timelockExpired
1605
- ? "EVM refund calldata ready. Submit this transaction with your EVM wallet."
1606
- : `Timelock has not expired yet. Refund will be available at ${new Date(timelock * 1000).toISOString()}.`,
1607
- evmRefundData: {
1608
- to: refundData.to,
1609
- data: refundData.data,
1610
- timelockExpired,
1611
- timelockExpiry: timelock,
1612
- },
1613
- };
1614
- }
1615
- // Non-WBTC source: fetch coordinator refund calldata from server
1616
- // - "swap-back": swap WBTC back to original token via DEX (default)
1617
- // - "direct": return WBTC directly (useful when DEX calldata is stale)
1618
- const response = await this.#apiClient.GET("/swap/{id}/refund-and-swap-calldata", {
1619
- params: {
1620
- path: { id },
1621
- query: { mode },
1622
- },
1623
- });
1624
- if (response.error) {
1625
- return {
1626
- success: false,
1627
- message: `Failed to fetch refund calldata: ${response.error.error || "Unknown error"}`,
1628
- };
1629
- }
1630
- const { coordinator_address, calldata } = response.data;
1631
- return {
1632
- success: true,
1633
- message: timelockExpired
1634
- ? "EVM refund calldata ready. Submit this transaction with your EVM wallet."
1635
- : `Timelock has not expired yet. Refund will be available at ${new Date(timelock * 1000).toISOString()}.`,
1636
- evmRefundData: {
1637
- to: coordinator_address,
1638
- data: calldata,
1639
- timelockExpired,
1640
- timelockExpiry: timelock,
1641
- },
1642
- };
1643
- }
1644
- /**
1645
- * Builds refund data for an EVM-to-Bitcoin swap via the coordinator.
1646
- * Same pattern as EVM-to-Arkade: uses the coordinator refund-and-swap-calldata endpoint.
1647
- * @internal
1648
- */
1649
- async #buildEvmToBitcoinRefund(id, swap, mode = "swap-back") {
1650
- const evmSwap = swap;
1651
- const timelock = evmSwap.evm_refund_locktime;
1652
- const now = Math.floor(Date.now() / 1000);
1653
- const timelockExpired = now >= timelock;
1654
- // Check if source token is WBTC - if so, use direct HTLCErc20 refund
1655
- const sourceSymbol = evmSwap.source_token?.symbol?.toLowerCase();
1656
- const isWbtcSource = sourceSymbol === "wbtc";
1657
- if (isWbtcSource) {
1658
- // Direct HTLCErc20 refund - no DEX swap needed
1659
- const htlcAddress = evmSwap.evm_htlc_address;
1660
- const hashLock = evmSwap.evm_hash_lock;
1661
- const refundData = encodeHtlcErc20RefundCallData(htlcAddress, {
1662
- preimageHash: hashLock,
1663
- amount: BigInt(evmSwap.source_amount),
1664
- token: evmSwap.source_token.token_id,
1665
- claimAddress: evmSwap.server_evm_address, // The server would have been the claimer
1666
- timelock: timelock,
1667
- });
1668
- return {
1669
- success: true,
1670
- message: timelockExpired
1671
- ? "EVM refund calldata ready. Submit this transaction with your EVM wallet."
1672
- : `Timelock has not expired yet. Refund will be available at ${new Date(timelock * 1000).toISOString()}.`,
1673
- evmRefundData: {
1674
- to: refundData.to,
1675
- data: refundData.data,
1676
- timelockExpired,
1677
- timelockExpiry: timelock,
1678
- },
1679
- };
1680
- }
1681
- // Non-WBTC source: use coordinator refund
1682
- // - "swap-back": swap WBTC back to original token via DEX (default)
1683
- // - "direct": return WBTC directly (useful when DEX calldata is stale)
1684
- const response = await this.#apiClient.GET("/swap/{id}/refund-and-swap-calldata", {
1685
- params: {
1686
- path: { id },
1687
- query: { mode },
1688
- },
1689
- });
1690
- if (response.error) {
1691
- return {
1692
- success: false,
1693
- message: `Failed to fetch refund calldata: ${response.error.error || "Unknown error"}`,
1694
- };
1695
- }
1696
- const { coordinator_address, calldata } = response.data;
1697
- return {
1698
- success: true,
1699
- message: timelockExpired
1700
- ? "EVM refund calldata ready. Submit this transaction with your EVM wallet."
1701
- : `Timelock has not expired yet. Refund will be available at ${new Date(timelock * 1000).toISOString()}.`,
1702
- evmRefundData: {
1703
- to: coordinator_address,
1704
- data: calldata,
1705
- timelockExpired,
1706
- timelockExpiry: timelock,
1707
- },
1708
- };
1709
- }
1710
- /**
1711
- * Builds refund data for an EVM-to-Lightning swap via the coordinator.
1712
- *
1713
- * Like EVM-to-Arkade, the coordinator atomically swapped the source token to WBTC
1714
- * before locking in the HTLC. For refunds:
1715
- * - If source was WBTC: direct HTLCErc20 refund
1716
- * - Otherwise: use coordinator refund endpoint (swap-back or direct mode)
1717
- *
1718
- * @internal
1719
- */
1720
- async #buildEvmToLightningRefund(id, swap, mode = "swap-back") {
1721
- const evmSwap = swap;
1722
- const timelock = evmSwap.evm_refund_locktime;
1723
- const now = Math.floor(Date.now() / 1000);
1724
- const timelockExpired = now >= timelock;
1725
- // Check if source token is WBTC - if so, use direct HTLCErc20 refund
1726
- const sourceSymbol = evmSwap.source_token?.symbol?.toLowerCase();
1727
- const isWbtcSource = sourceSymbol === "wbtc";
1728
- if (isWbtcSource) {
1729
- // Direct HTLCErc20 refund - no DEX swap needed
1730
- const htlcAddress = evmSwap.evm_htlc_address;
1731
- const hashLock = evmSwap.hash_lock;
1732
- const refundData = encodeHtlcErc20RefundCallData(htlcAddress, {
1733
- preimageHash: hashLock,
1734
- amount: BigInt(evmSwap.source_amount),
1735
- token: evmSwap.source_token.token_id,
1736
- claimAddress: evmSwap.server_evm_address,
1737
- timelock: timelock,
1738
- });
1739
- return {
1740
- success: true,
1741
- message: timelockExpired
1742
- ? "EVM refund calldata ready. Submit this transaction with your EVM wallet."
1743
- : `Timelock has not expired yet. Refund will be available at ${new Date(timelock * 1000).toISOString()}.`,
1744
- evmRefundData: {
1745
- to: refundData.to,
1746
- data: refundData.data,
1747
- timelockExpired,
1748
- timelockExpiry: timelock,
1749
- },
1750
- };
1751
- }
1752
- // Non-WBTC source: fetch coordinator refund calldata from server
1753
- // - "swap-back": swap WBTC back to original token via DEX (default)
1754
- // - "direct": return WBTC directly (useful when DEX calldata is stale)
1755
- const response = await this.#apiClient.GET("/swap/{id}/refund-and-swap-calldata", {
1756
- params: {
1757
- path: { id },
1758
- query: { mode },
1759
- },
1760
- });
1761
- if (response.error) {
1762
- return {
1763
- success: false,
1764
- message: `Failed to fetch refund calldata: ${response.error.error || "Unknown error"}`,
1765
- };
1766
- }
1767
- const { coordinator_address, calldata } = response.data;
1768
- return {
1769
- success: true,
1770
- message: timelockExpired
1771
- ? "EVM refund calldata ready. Submit this transaction with your EVM wallet."
1772
- : `Timelock has not expired yet. Refund will be available at ${new Date(timelock * 1000).toISOString()}.`,
1773
- evmRefundData: {
1774
- to: coordinator_address,
1775
- data: calldata,
1776
- timelockExpired,
1777
- timelockExpiry: timelock,
1778
- },
1779
- };
1780
- }
1781
- // =========================================================================
1782
- // Swap Creation - BTC to EVM
1783
- // =========================================================================
1784
- /**
1785
- * Gets the context object for swap creation functions.
1786
- * @internal
1787
- */
1788
- #getCreateContext() {
1789
- return {
1790
- apiClient: this.#apiClient,
1791
- baseUrl: this.#config.baseUrl,
1792
- deriveSwapParams: () => this.deriveSwapParams(),
1793
- storeSwap: (swapId, swapParams, response) => this.#storeSwap(swapId, swapParams, response),
1794
- };
1795
- }
1796
- /**
1797
- * Stores a swap in the configured swap storage.
1798
- * @internal
1799
- */
1800
- async #storeSwap(swapId, swapParams, response, targetAddress) {
1801
- if (!this.#swapStorage)
1802
- return;
1803
- const storedSwap = {
1804
- version: SWAP_STORAGE_VERSION,
1805
- swapId,
1806
- keyIndex: swapParams.keyIndex,
1807
- response: response,
1808
- publicKey: bytesToHex(swapParams.publicKey),
1809
- preimage: bytesToHex(swapParams.preimage),
1810
- preimageHash: bytesToHex(swapParams.preimageHash),
1811
- secretKey: bytesToHex(swapParams.secretKey),
1812
- storedAt: Date.now(),
1813
- updatedAt: Date.now(),
1814
- targetAddress,
1815
- };
1816
- await this.#swapStorage.store(storedSwap);
1817
- }
1818
- /**
1819
- * Creates a swap by routing to the correct direction-specific method
1820
- * based on `sourceAsset.chain` and `targetAsset.chain`.
1821
- *
1822
- * Supported directions:
1823
- * - Arkade → EVM
1824
- * - Lightning → EVM
1825
- * - Bitcoin (on-chain) → EVM
1826
- * - Bitcoin (on-chain) → Arkade
1827
- * - EVM → Arkade
1828
- * - EVM → Bitcoin (on-chain)
1829
- * - EVM → Lightning
1830
- *
1831
- * @param options - The swap options including source/target assets, amounts, and addresses.
1832
- * @returns The swap result (response + swapParams).
1833
- * @throws Error if the swap direction is unsupported or required fields are missing.
1834
- */
1835
- async createSwap(options) {
1836
- const { sourceAsset, targetAsset } = options;
1837
- const sourceChain = sourceAsset.chain;
1838
- const targetChain = targetAsset.chain;
1839
- // Arkade → EVM
1840
- if (isArkade(sourceAsset) && isEvmToken(targetChain)) {
1841
- return this.createArkadeToEvmSwapGeneric({
1842
- targetAddress: options.targetAddress,
1843
- tokenAddress: targetAsset.token_id,
1844
- evmChainId: Number(targetChain),
1845
- sourceAmount: options.sourceAmount
1846
- ? BigInt(options.sourceAmount)
1847
- : undefined,
1848
- targetAmount: options.targetAmount
1849
- ? BigInt(options.targetAmount)
1850
- : undefined,
1851
- referralCode: options.referralCode,
1852
- });
1853
- }
1854
- // Lightning → EVM
1855
- if (isLightning(sourceAsset) && isEvmToken(targetChain)) {
1856
- return this.createLightningToEvmSwapGeneric({
1857
- targetAddress: options.targetAddress,
1858
- tokenAddress: targetAsset.token_id,
1859
- evmChainId: Number(targetChain),
1860
- amountIn: options.sourceAmount,
1861
- amountOut: options.targetAmount,
1862
- referralCode: options.referralCode,
1863
- });
1864
- }
1865
- // Bitcoin (on-chain) → EVM
1866
- if (isBtcOnchain(sourceAsset) && isEvmToken(targetChain)) {
1867
- return this.createBitcoinToEvmSwap({
1868
- targetAddress: options.targetAddress,
1869
- tokenAddress: targetAsset.token_id,
1870
- evmChainId: Number(targetChain),
1871
- sourceAmount: options.sourceAmount,
1872
- targetAmount: options.targetAmount,
1873
- referralCode: options.referralCode,
1874
- });
1875
- }
1876
- // Bitcoin (on-chain) → Arkade
1877
- if (isBtcOnchain(sourceAsset) && isArkade(targetAsset)) {
1878
- if (options.targetAmount == null) {
1879
- throw new Error("targetAmount (sats to receive on Arkade) is required for Bitcoin → Arkade swaps");
1880
- }
1881
- return this.createBitcoinToArkadeSwap({
1882
- satsReceive: options.targetAmount,
1883
- targetAddress: options.targetAddress,
1884
- referralCode: options.referralCode,
1885
- });
1886
- }
1887
- // EVM → Arkade
1888
- if (isEvmToken(sourceChain) && isArkade(targetAsset)) {
1889
- if (!options.userAddress) {
1890
- throw new Error("userAddress is required for EVM → Arkade swaps");
1891
- }
1892
- return this.createEvmToArkadeSwapGeneric({
1893
- targetAddress: options.targetAddress,
1894
- tokenAddress: sourceAsset.token_id,
1895
- evmChainId: Number(sourceChain),
1896
- userAddress: options.userAddress,
1897
- sourceAmount: options.sourceAmount
1898
- ? BigInt(options.sourceAmount)
1899
- : undefined,
1900
- targetAmount: options.targetAmount,
1901
- referralCode: options.referralCode,
1902
- });
1903
- }
1904
- // EVM → Bitcoin (on-chain)
1905
- if (isEvmToken(sourceChain) && isBtcOnchain(targetAsset)) {
1906
- if (!options.userAddress) {
1907
- throw new Error("userAddress is required for EVM → Bitcoin swaps");
1908
- }
1909
- return this.createEvmToBitcoinSwap({
1910
- tokenAddress: sourceAsset.token_id,
1911
- evmChainId: Number(sourceChain),
1912
- userAddress: options.userAddress,
1913
- targetAddress: options.targetAddress,
1914
- sourceAmount: options.sourceAmount
1915
- ? BigInt(options.sourceAmount)
1916
- : undefined,
1917
- targetAmount: options.targetAmount,
1918
- referralCode: options.referralCode,
1919
- });
1920
- }
1921
- // EVM → Lightning
1922
- if (isEvmToken(sourceChain) && isLightning(targetAsset)) {
1923
- if (!options.userAddress) {
1924
- throw new Error("userAddress is required for EVM → Lightning swaps");
1925
- }
1926
- return this.createEvmToLightningSwapGeneric({
1927
- lightningInvoice: options.targetAddress,
1928
- evmChainId: Number(sourceChain),
1929
- tokenAddress: sourceAsset.token_id,
1930
- userAddress: options.userAddress,
1931
- referralCode: options.referralCode,
1932
- });
1933
- }
1934
- throw new Error(`Unsupported swap direction: ${sourceChain} → ${targetChain}`);
1935
- }
1936
- /**
1937
- * Creates a new Arkade-to-EVM swap via the generic chain-agnostic endpoint.
1938
- *
1939
- * Uses the `/swap/arkade/evm` endpoint which supports any ERC-20 token
1940
- * reachable through 1inch aggregation. Returns coordinator address and
1941
- * optional 1inch calldata for the redeem-and-swap flow.
1942
- *
1943
- * @param options - The swap options.
1944
- * @returns The swap response and parameters for storage.
1945
- * @throws Error if the swap creation fails.
1946
- *
1947
- * @example
1948
- * ```ts
1949
- * const result = await client.createArkadeToEvmSwapGeneric({
1950
- * targetAddress: "0x1234...",
1951
- * tokenAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", // USDC on Polygon
1952
- * evmChainId: 137,
1953
- * sourceAmount: 100000, // 100k sats
1954
- * });
1955
- * console.log("Fund:", result.response.btc_vhtlc_address);
1956
- * console.log("Coordinator:", result.response.evm_coordinator_address);
1957
- * ```
1958
- */
1959
- async createArkadeToEvmSwapGeneric(options) {
1960
- return createArkadeToEvmSwapGeneric(options, this.#getCreateContext());
1961
- }
1962
- /**
1963
- * Creates a new Lightning to EVM swap using the generic chain-agnostic endpoint.
1964
- *
1965
- * @param options - The swap options including evmChainId and tokenAddress.
1966
- * @returns The swap response and parameters for storage.
1967
- */
1968
- async createLightningToEvmSwapGeneric(options) {
1969
- return createLightningToEvmSwapGeneric(options, this.#getCreateContext());
1970
- }
1971
- /**
1972
- * Creates a new Bitcoin (on-chain) to EVM swap.
1973
- *
1974
- * Automatically derives swap parameters and increments the key index.
1975
- *
1976
- * @param options - The swap options.
1977
- * @returns The swap response and parameters for storage.
1978
- * @throws Error if the swap creation fails.
1979
- *
1980
- * @example
1981
- * ```ts
1982
- * const result = await client.createBitcoinToEvmSwap({
1983
- * targetAddress: "0x1234...",
1984
- * tokenAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", // USDC on Polygon
1985
- * evmChainId: 137,
1986
- * sourceAmount: 100000, // 100k sats
1987
- * });
1988
- * console.log("Send BTC to:", result.response.btc_htlc_address);
1989
- * ```
1990
- */
1991
- async createBitcoinToEvmSwap(options) {
1992
- return createBitcoinToEvmSwap(options, this.#getCreateContext());
1993
- }
1994
- // =========================================================================
1995
- // Swap Creation - Bitcoin (on-chain) to Arkade
1996
- // =========================================================================
1997
- /**
1998
- * Creates a new Bitcoin (on-chain) to Arkade swap.
1999
- *
2000
- * The user sends on-chain BTC to a Taproot HTLC address and receives
2001
- * Arkade VTXOs after the server funds the Arkade VHTLC.
2002
- *
2003
- * Automatically derives swap parameters and increments the key index.
2004
- *
2005
- * @param options - The swap options.
2006
- * @returns The swap response and parameters for storage.
2007
- * @throws Error if the swap creation fails.
2008
- *
2009
- * @example
2010
- * ```ts
2011
- * const result = await client.createBitcoinToArkadeSwap({
2012
- * satsReceive: 100000, // 100k sats to receive on Arkade
2013
- * targetAddress: "ark1q...", // Arkade address
2014
- * });
2015
- * console.log("Send BTC to:", result.response.btc_htlc_address);
2016
- * console.log("Amount to send:", result.response.source_amount, "sats");
2017
- * ```
2018
- */
2019
- async createBitcoinToArkadeSwap(options) {
2020
- return createBitcoinToArkadeSwap(options, this.#getCreateContext());
2021
- }
2022
- // =========================================================================
2023
- // Swap Creation - EVM to Arkade
2024
- // =========================================================================
2025
- /**
2026
- * Creates a new EVM-to-Arkade swap via the generic endpoint.
2027
- *
2028
- * Uses the chain-agnostic `/swap/evm/arkade` endpoint which supports any
2029
- * ERC-20 token reachable through 1inch aggregation.
2030
- *
2031
- * @param options - The swap options.
2032
- * @returns The swap response and parameters for storage.
2033
- * @throws Error if the swap creation fails.
2034
- *
2035
- * @example
2036
- * ```ts
2037
- * const result = await client.createEvmToArkadeSwapGeneric({
2038
- * targetAddress: "ark1q...",
2039
- * tokenAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", // USDC on Polygon
2040
- * evmChainId: 137,
2041
- * userAddress: "0x1234...",
2042
- * sourceAmount: 100000000, // 100 USDC (6 decimals)
2043
- * });
2044
- * console.log("HTLC:", result.response.evm_htlc_address);
2045
- * ```
2046
- */
2047
- async createEvmToArkadeSwapGeneric(options) {
2048
- return createEvmToArkadeSwapGeneric(options, this.#getCreateContext());
2049
- }
2050
- /**
2051
- * Creates a new EVM-to-Bitcoin (on-chain) swap.
2052
- *
2053
- * Uses the chain-agnostic `/swap/evm/bitcoin` endpoint which supports any
2054
- * ERC-20 token reachable through 1inch aggregation. The user locks tokens
2055
- * in an EVM HTLC and receives BTC to an on-chain Taproot HTLC.
2056
- *
2057
- * @param options - The swap options.
2058
- * @returns The swap response and parameters for storage.
2059
- * @throws Error if the swap creation fails.
2060
- *
2061
- * @example
2062
- * ```ts
2063
- * const result = await client.createEvmToBitcoinSwap({
2064
- * tokenAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", // USDC on Polygon
2065
- * evmChainId: 137,
2066
- * userAddress: "0x1234...",
2067
- * sourceAmount: 100000000n, // 100 USDC (6 decimals)
2068
- * });
2069
- * console.log("EVM HTLC:", result.response.evm_htlc_address);
2070
- * console.log("BTC HTLC:", result.response.btc_htlc_address);
2071
- * ```
2072
- */
2073
- async createEvmToBitcoinSwap(options) {
2074
- return createEvmToBitcoinSwap(options, this.#getCreateContext());
2075
- }
2076
- /**
2077
- * Creates a new EVM to Lightning swap using the chain-agnostic generic endpoint.
2078
- *
2079
- * This allows users to swap any ERC-20 token from any supported EVM chain
2080
- * to pay a Lightning invoice.
2081
- *
2082
- * @param options - The swap options including Lightning invoice, chain ID, and token address.
2083
- * @returns The swap response and parameters for storage.
2084
- * @throws Error if the swap creation fails.
2085
- *
2086
- * @example
2087
- * ```ts
2088
- * const result = await client.createEvmToLightningSwapGeneric({
2089
- * lightningInvoice: "lnbc...",
2090
- * evmChainId: 137, // Polygon
2091
- * tokenAddress: "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", // WBTC
2092
- * userAddress: "0x1234...",
2093
- * });
2094
- * console.log("HTLC contract:", result.response.evm_htlc_address);
2095
- * console.log("Swap ID:", result.response.id);
2096
- * ```
2097
- */
2098
- async createEvmToLightningSwapGeneric(options) {
2099
- return createEvmToLightningSwapGeneric(options, this.#getCreateContext());
2100
- }
2101
- // =========================================================================
2102
- // Coordinator Funding (EVM-to-BTC via DEX + HTLC)
2103
- // =========================================================================
2104
- /**
2105
- * Gets call data to fund an EVM-to-BTC swap via the HTLCCoordinator.
2106
- *
2107
- * The coordinator atomically swaps source tokens (e.g. USDC) to WBTC via DEX
2108
- * and locks the WBTC into an HTLC in a single transaction.
2109
- *
2110
- * Fetches the coordinator calldata from the server, which builds the 1inch
2111
- * swap calldata and computes the refundCallsHash.
2112
- *
2113
- * @param swapId - The UUID of the swap.
2114
- * @param approveMax - If true, approves max uint256. If false, approves exact amount. Default: true.
2115
- * @returns The approve and executeAndCreate call data.
2116
- *
2117
- * @example
2118
- * ```ts
2119
- * const swap = await client.createEvmToArkadeSwap({...});
2120
- * const funding = await client.getCoordinatorFundingCallData(swap.response.id);
2121
- *
2122
- * // Step 1: Approve source token to coordinator
2123
- * await wallet.sendTransaction({ to: funding.approve.to, data: funding.approve.data });
2124
- *
2125
- * // Step 2: Execute swap + create HTLC
2126
- * await wallet.sendTransaction({ to: funding.executeAndCreate.to, data: funding.executeAndCreate.data });
2127
- * ```
2128
- */
2129
- async getCoordinatorFundingCallData(swapId, approveMax = true) {
2130
- const swap = await this.getSwap(swapId);
2131
- if (swap.direction !== "evm_to_arkade" &&
2132
- swap.direction !== "evm_to_bitcoin" &&
2133
- swap.direction !== "evm_to_lightning") {
2134
- throw new Error(`Expected evm_to_arkade/evm_to_bitcoin/evm_to_lightning swap, got ${swap.direction}. Coordinator fund call data method is for EVM-sourced swaps via coordinator.`);
2135
- }
2136
- // Get source amount based on swap direction
2137
- // All EVM-sourced swaps: source_amount is already in smallest units (integer)
2138
- const evmSwap = swap;
2139
- const exactAmount = BigInt(evmSwap.source_amount);
2140
- // Fetch coordinator funding calldata from server
2141
- const baseUrl = this.#config.baseUrl.replace(/\/$/, "");
2142
- const url = `${baseUrl}/swap/${swapId}/swap-and-lock-calldata`;
2143
- const headers = {};
2144
- if (this.#config.apiKey) {
2145
- headers["X-API-Key"] = this.#config.apiKey;
2146
- }
2147
- const resp = await fetch(url, { headers });
2148
- if (!resp.ok) {
2149
- const body = await resp.text();
2150
- throw new Error(`Failed to get coordinator funding calldata: ${resp.status} ${body}`);
2151
- }
2152
- const serverData = (await resp.json());
2153
- // Build approve call data: approve source token to coordinator
2154
- const maxUint256 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
2155
- const approveAmount = approveMax ? maxUint256 : exactAmount;
2156
- const approve = encodeApproveCallData(serverData.source_token_address, serverData.coordinator_address, approveAmount);
2157
- return {
2158
- approve: {
2159
- to: approve.to,
2160
- data: approve.data,
2161
- },
2162
- executeAndCreate: {
2163
- to: serverData.coordinator_address,
2164
- data: serverData.execute_and_create_calldata,
2165
- },
2166
- };
2167
- }
2168
- }
2169
- //# sourceMappingURL=client.js.map