@kaleidorg/wallet-engine 1.0.0-beta.4 → 1.0.0-beta.42

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 (242) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -10
  3. package/dist/adapters/ArkadeAdapter.d.ts +78 -14
  4. package/dist/adapters/ArkadeAdapter.d.ts.map +1 -1
  5. package/dist/adapters/ArkadeAdapter.js +653 -161
  6. package/dist/adapters/ArkadeAdapter.js.map +1 -1
  7. package/dist/adapters/IProtocolAdapter.d.ts +195 -18
  8. package/dist/adapters/IProtocolAdapter.d.ts.map +1 -1
  9. package/dist/adapters/IProtocolAdapter.js +6 -2
  10. package/dist/adapters/IProtocolAdapter.js.map +1 -1
  11. package/dist/adapters/RgbAdapter.d.ts +70 -27
  12. package/dist/adapters/RgbAdapter.d.ts.map +1 -1
  13. package/dist/adapters/RgbAdapter.js +464 -370
  14. package/dist/adapters/RgbAdapter.js.map +1 -1
  15. package/dist/adapters/SparkAdapter.d.ts +93 -15
  16. package/dist/adapters/SparkAdapter.d.ts.map +1 -1
  17. package/dist/adapters/SparkAdapter.js +833 -168
  18. package/dist/adapters/SparkAdapter.js.map +1 -1
  19. package/dist/adapters/arkade.d.ts +15 -0
  20. package/dist/adapters/arkade.d.ts.map +1 -0
  21. package/dist/adapters/arkade.js +15 -0
  22. package/dist/adapters/arkade.js.map +1 -0
  23. package/dist/adapters/flashnet.d.ts +15 -0
  24. package/dist/adapters/flashnet.d.ts.map +1 -0
  25. package/dist/adapters/flashnet.js +17 -0
  26. package/dist/adapters/flashnet.js.map +1 -0
  27. package/dist/adapters/native.d.ts +17 -0
  28. package/dist/adapters/native.d.ts.map +1 -0
  29. package/dist/adapters/native.js +17 -0
  30. package/dist/adapters/native.js.map +1 -0
  31. package/dist/adapters/rgb.d.ts +11 -0
  32. package/dist/adapters/rgb.d.ts.map +1 -0
  33. package/dist/adapters/rgb.js +11 -0
  34. package/dist/adapters/rgb.js.map +1 -0
  35. package/dist/adapters/spark.d.ts +12 -0
  36. package/dist/adapters/spark.d.ts.map +1 -0
  37. package/dist/adapters/spark.js +14 -0
  38. package/dist/adapters/spark.js.map +1 -0
  39. package/dist/adapters/wdk/ArkadeWdkAdapter.d.ts +53 -19
  40. package/dist/adapters/wdk/ArkadeWdkAdapter.d.ts.map +1 -1
  41. package/dist/adapters/wdk/ArkadeWdkAdapter.js +366 -90
  42. package/dist/adapters/wdk/ArkadeWdkAdapter.js.map +1 -1
  43. package/dist/adapters/wdk/BaseWdkAdapter.d.ts +40 -0
  44. package/dist/adapters/wdk/BaseWdkAdapter.d.ts.map +1 -0
  45. package/dist/adapters/wdk/BaseWdkAdapter.js +71 -0
  46. package/dist/adapters/wdk/BaseWdkAdapter.js.map +1 -0
  47. package/dist/adapters/wdk/LiquidWdkAdapter.d.ts +6 -13
  48. package/dist/adapters/wdk/LiquidWdkAdapter.d.ts.map +1 -1
  49. package/dist/adapters/wdk/LiquidWdkAdapter.js +14 -32
  50. package/dist/adapters/wdk/LiquidWdkAdapter.js.map +1 -1
  51. package/dist/adapters/wdk/RgbCore.d.ts +64 -0
  52. package/dist/adapters/wdk/RgbCore.d.ts.map +1 -0
  53. package/dist/adapters/wdk/RgbCore.js +111 -0
  54. package/dist/adapters/wdk/RgbCore.js.map +1 -0
  55. package/dist/adapters/wdk/RgbLibWasmAdapter.d.ts +277 -0
  56. package/dist/adapters/wdk/RgbLibWasmAdapter.d.ts.map +1 -0
  57. package/dist/adapters/wdk/RgbLibWasmAdapter.js +731 -0
  58. package/dist/adapters/wdk/RgbLibWasmAdapter.js.map +1 -0
  59. package/dist/adapters/wdk/RgbLibWdkAdapter.d.ts +104 -0
  60. package/dist/adapters/wdk/RgbLibWdkAdapter.d.ts.map +1 -0
  61. package/dist/adapters/wdk/RgbLibWdkAdapter.js +249 -0
  62. package/dist/adapters/wdk/RgbLibWdkAdapter.js.map +1 -0
  63. package/dist/adapters/wdk/RlnWdkAdapter.d.ts +27 -14
  64. package/dist/adapters/wdk/RlnWdkAdapter.d.ts.map +1 -1
  65. package/dist/adapters/wdk/RlnWdkAdapter.js +124 -89
  66. package/dist/adapters/wdk/RlnWdkAdapter.js.map +1 -1
  67. package/dist/adapters/wdk/SparkWdkAdapter.d.ts +74 -41
  68. package/dist/adapters/wdk/SparkWdkAdapter.d.ts.map +1 -1
  69. package/dist/adapters/wdk/SparkWdkAdapter.js +706 -249
  70. package/dist/adapters/wdk/SparkWdkAdapter.js.map +1 -1
  71. package/dist/adapters/wdk/index.d.ts +17 -0
  72. package/dist/adapters/wdk/index.d.ts.map +1 -0
  73. package/dist/adapters/wdk/index.js +17 -0
  74. package/dist/adapters/wdk/index.js.map +1 -0
  75. package/dist/adapters/wdk/wasm-rgb.d.ts +15 -0
  76. package/dist/adapters/wdk/wasm-rgb.d.ts.map +1 -0
  77. package/dist/adapters/wdk/wasm-rgb.js +15 -0
  78. package/dist/adapters/wdk/wasm-rgb.js.map +1 -0
  79. package/dist/capabilities/index.d.ts +1 -1
  80. package/dist/capabilities/index.d.ts.map +1 -1
  81. package/dist/capabilities/index.js +17 -2
  82. package/dist/capabilities/index.js.map +1 -1
  83. package/dist/capabilities/operations.d.ts +22 -0
  84. package/dist/capabilities/operations.d.ts.map +1 -0
  85. package/dist/capabilities/operations.js +62 -0
  86. package/dist/capabilities/operations.js.map +1 -0
  87. package/dist/constants.d.ts +8 -0
  88. package/dist/constants.d.ts.map +1 -0
  89. package/dist/constants.js +8 -0
  90. package/dist/constants.js.map +1 -0
  91. package/dist/disclosure/index.d.ts +1 -1
  92. package/dist/disclosure/index.js +1 -1
  93. package/dist/disclosure/index.js.map +1 -1
  94. package/dist/format.d.ts +11 -0
  95. package/dist/format.d.ts.map +1 -0
  96. package/dist/format.js +10 -0
  97. package/dist/format.js.map +1 -0
  98. package/dist/index.d.ts +21 -31
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +32 -32
  101. package/dist/index.js.map +1 -1
  102. package/dist/lib/arkade-client-manager.d.ts +64 -24
  103. package/dist/lib/arkade-client-manager.d.ts.map +1 -1
  104. package/dist/lib/arkade-client-manager.js +240 -65
  105. package/dist/lib/arkade-client-manager.js.map +1 -1
  106. package/dist/lib/arkade-converters.d.ts +39 -0
  107. package/dist/lib/arkade-converters.d.ts.map +1 -0
  108. package/dist/lib/arkade-converters.js +148 -0
  109. package/dist/lib/arkade-converters.js.map +1 -0
  110. package/dist/lib/arkade-helpers.d.ts +110 -0
  111. package/dist/lib/arkade-helpers.d.ts.map +1 -0
  112. package/dist/lib/arkade-helpers.js +227 -0
  113. package/dist/lib/arkade-helpers.js.map +1 -0
  114. package/dist/lib/arkade-swaps-client-manager.d.ts +55 -0
  115. package/dist/lib/arkade-swaps-client-manager.d.ts.map +1 -0
  116. package/dist/lib/arkade-swaps-client-manager.js +127 -0
  117. package/dist/lib/arkade-swaps-client-manager.js.map +1 -0
  118. package/dist/lib/arkade-vtxo-lifecycle.d.ts +116 -0
  119. package/dist/lib/arkade-vtxo-lifecycle.d.ts.map +1 -0
  120. package/dist/lib/arkade-vtxo-lifecycle.js +184 -0
  121. package/dist/lib/arkade-vtxo-lifecycle.js.map +1 -0
  122. package/dist/lib/flashnet-client-manager.d.ts +26 -9
  123. package/dist/lib/flashnet-client-manager.d.ts.map +1 -1
  124. package/dist/lib/flashnet-client-manager.js +97 -13
  125. package/dist/lib/flashnet-client-manager.js.map +1 -1
  126. package/dist/lib/kaleido-client-manager.d.ts +38 -3
  127. package/dist/lib/kaleido-client-manager.d.ts.map +1 -1
  128. package/dist/lib/kaleido-client-manager.js +79 -10
  129. package/dist/lib/kaleido-client-manager.js.map +1 -1
  130. package/dist/lib/ln-message-sign.d.ts +20 -0
  131. package/dist/lib/ln-message-sign.d.ts.map +1 -0
  132. package/dist/lib/ln-message-sign.js +90 -0
  133. package/dist/lib/ln-message-sign.js.map +1 -0
  134. package/dist/lib/log.d.ts +15 -0
  135. package/dist/lib/log.d.ts.map +1 -0
  136. package/dist/lib/log.js +16 -0
  137. package/dist/lib/log.js.map +1 -0
  138. package/dist/lib/orchestra-client.d.ts +149 -0
  139. package/dist/lib/orchestra-client.d.ts.map +1 -0
  140. package/dist/lib/orchestra-client.js +178 -0
  141. package/dist/lib/orchestra-client.js.map +1 -0
  142. package/dist/lib/psbt-signer.d.ts +60 -0
  143. package/dist/lib/psbt-signer.d.ts.map +1 -0
  144. package/dist/lib/psbt-signer.js +161 -0
  145. package/dist/lib/psbt-signer.js.map +1 -0
  146. package/dist/lib/rgb-converters.d.ts +62 -0
  147. package/dist/lib/rgb-converters.d.ts.map +1 -0
  148. package/dist/lib/rgb-converters.js +179 -0
  149. package/dist/lib/rgb-converters.js.map +1 -0
  150. package/dist/lib/rgb-fee-policy.d.ts +41 -0
  151. package/dist/lib/rgb-fee-policy.d.ts.map +1 -0
  152. package/dist/lib/rgb-fee-policy.js +52 -0
  153. package/dist/lib/rgb-fee-policy.js.map +1 -0
  154. package/dist/lib/rgb-helpers.d.ts +54 -0
  155. package/dist/lib/rgb-helpers.d.ts.map +1 -0
  156. package/dist/lib/rgb-helpers.js +89 -0
  157. package/dist/lib/rgb-helpers.js.map +1 -0
  158. package/dist/lib/spark-activity.d.ts +5 -0
  159. package/dist/lib/spark-activity.d.ts.map +1 -0
  160. package/dist/lib/spark-activity.js +11 -0
  161. package/dist/lib/spark-activity.js.map +1 -0
  162. package/dist/lib/spark-balance-cache.d.ts +58 -0
  163. package/dist/lib/spark-balance-cache.d.ts.map +1 -0
  164. package/dist/lib/spark-balance-cache.js +86 -0
  165. package/dist/lib/spark-balance-cache.js.map +1 -0
  166. package/dist/lib/spark-client-manager.d.ts +64 -10
  167. package/dist/lib/spark-client-manager.d.ts.map +1 -1
  168. package/dist/lib/spark-client-manager.js +191 -35
  169. package/dist/lib/spark-client-manager.js.map +1 -1
  170. package/dist/lib/spark-converters.d.ts +64 -0
  171. package/dist/lib/spark-converters.d.ts.map +1 -0
  172. package/dist/lib/spark-converters.js +242 -0
  173. package/dist/lib/spark-converters.js.map +1 -0
  174. package/dist/lib/spark-helpers.d.ts +72 -0
  175. package/dist/lib/spark-helpers.d.ts.map +1 -0
  176. package/dist/lib/spark-helpers.js +151 -0
  177. package/dist/lib/spark-helpers.js.map +1 -0
  178. package/dist/lib/spark-sent-token-records.d.ts +43 -0
  179. package/dist/lib/spark-sent-token-records.d.ts.map +1 -0
  180. package/dist/lib/spark-sent-token-records.js +105 -0
  181. package/dist/lib/spark-sent-token-records.js.map +1 -0
  182. package/dist/lib/wallet-seed.d.ts +31 -0
  183. package/dist/lib/wallet-seed.d.ts.map +1 -0
  184. package/dist/lib/wallet-seed.js +58 -0
  185. package/dist/lib/wallet-seed.js.map +1 -0
  186. package/dist/lib/zbase32.d.ts +3 -0
  187. package/dist/lib/zbase32.d.ts.map +1 -0
  188. package/dist/lib/zbase32.js +64 -0
  189. package/dist/lib/zbase32.js.map +1 -0
  190. package/dist/manager/ProtocolManager.d.ts +54 -3
  191. package/dist/manager/ProtocolManager.d.ts.map +1 -1
  192. package/dist/manager/ProtocolManager.js +118 -41
  193. package/dist/manager/ProtocolManager.js.map +1 -1
  194. package/dist/ports/index.d.ts +20 -0
  195. package/dist/ports/index.d.ts.map +1 -1
  196. package/dist/ports/index.js +23 -1
  197. package/dist/ports/index.js.map +1 -1
  198. package/dist/receive/unifiedReceive.d.ts +12 -0
  199. package/dist/receive/unifiedReceive.d.ts.map +1 -1
  200. package/dist/receive/unifiedReceive.js +35 -4
  201. package/dist/receive/unifiedReceive.js.map +1 -1
  202. package/dist/registry/createWdkRegistry.d.ts +10 -2
  203. package/dist/registry/createWdkRegistry.d.ts.map +1 -1
  204. package/dist/registry/createWdkRegistry.js +14 -7
  205. package/dist/registry/createWdkRegistry.js.map +1 -1
  206. package/dist/router/destination.d.ts +2 -2
  207. package/dist/router/destination.d.ts.map +1 -1
  208. package/dist/router/destination.js +34 -11
  209. package/dist/router/destination.js.map +1 -1
  210. package/dist/router/index.d.ts +39 -3
  211. package/dist/router/index.d.ts.map +1 -1
  212. package/dist/router/index.js +113 -4
  213. package/dist/router/index.js.map +1 -1
  214. package/dist/router/preference.d.ts +53 -0
  215. package/dist/router/preference.d.ts.map +1 -0
  216. package/dist/router/preference.js +81 -0
  217. package/dist/router/preference.js.map +1 -0
  218. package/dist/swap/KaleidoswapSwap.d.ts +1 -1
  219. package/dist/swap/KaleidoswapSwap.d.ts.map +1 -1
  220. package/dist/swap/KaleidoswapSwap.js +37 -20
  221. package/dist/swap/KaleidoswapSwap.js.map +1 -1
  222. package/dist/swap/index.d.ts +8 -0
  223. package/dist/swap/index.d.ts.map +1 -0
  224. package/dist/swap/index.js +8 -0
  225. package/dist/swap/index.js.map +1 -0
  226. package/dist/types/arkade.d.ts +1 -1
  227. package/dist/types/base.d.ts +35 -25
  228. package/dist/types/base.d.ts.map +1 -1
  229. package/dist/types/base.js +28 -2
  230. package/dist/types/base.js.map +1 -1
  231. package/dist/types/cross-l2.d.ts +1 -1
  232. package/dist/types/flashnet.d.ts +20 -0
  233. package/dist/types/flashnet.d.ts.map +1 -1
  234. package/dist/types/flashnet.js +34 -6
  235. package/dist/types/flashnet.js.map +1 -1
  236. package/dist/types/rgb.d.ts +18 -4
  237. package/dist/types/rgb.d.ts.map +1 -1
  238. package/dist/types/spark.d.ts +1 -1
  239. package/dist/utils.d.ts +1 -1
  240. package/dist/utils.js +2 -2
  241. package/dist/utils.js.map +1 -1
  242. package/package.json +68 -14
@@ -1,17 +1,63 @@
1
1
  /**
2
2
  * Spark Protocol Adapter
3
- * Implements IProtocolAdapter using @buildonspark/spark-sdk.
4
- * Ported from rate-extension for React Native.
3
+ * Implements IProtocolAdapter using @buildonspark/spark-sdk (native Spark SDK).
4
+ *
5
+ * Native SDK API reference:
6
+ * - SparkWallet.initialize({ mnemonicOrSeed, options: { network } })
7
+ * - wallet.getBalance() → { balance: bigint }
8
+ * - wallet.getSparkAddress() → SparkAddressFormat (string)
9
+ * - wallet.getSingleUseDepositAddress() → string (BTC on-chain)
10
+ * - wallet.createLightningInvoice({ amountSats, memo? }) → { invoice: { encodedInvoice } }
11
+ * - wallet.payLightningInvoice({ invoice, maxFeeSats }) → LightningSendRequest | WalletTransfer
12
+ * - wallet.transfer({ receiverSparkAddress, amountSats }) → WalletTransfer
13
+ * - wallet.withdraw({ onchainAddress, amountSats, exitSpeed }) → withdrawal result
14
+ * - wallet.getTransfers(limit?, offset?, createdAfter?, createdBefore?) → { transfers: WalletTransfer[], offset: number }
15
+ * - wallet.getTransfer(id) → WalletTransfer | undefined
16
+ * - wallet.cleanupConnections() → void
17
+ *
18
+ * Amounts in the SDK are in SATS. Balance is returned as bigint.
5
19
  */
6
- import { sparkClientManager } from '../lib/spark-client-manager';
7
- import { ProtocolError, ConnectionError, } from '../types/base';
20
+ import { ExitSpeed } from "@buildonspark/spark-sdk/types";
21
+ import { isValidSparkAddress, decodeSparkAddress, getNetworkFromSparkAddress, } from "@buildonspark/spark-sdk";
22
+ import { log } from "../lib/log.js";
23
+ import { loadSentTokenRecords, normalizeTxHash, saveSentTokenRecord, } from "../lib/spark-sent-token-records.js";
24
+ import { sparkClientManager } from "../lib/spark-client-manager.js";
25
+ import { PROTOCOL_OPERATIONS } from "../capabilities/operations.js";
26
+ import { ProtocolError, ConnectionError, } from "../types/base.js";
27
+ import { mnemonicToSeedSync } from "@scure/bip39";
28
+ import { HDKey } from "@scure/bip32";
29
+ import { signLnMessage, verifyLnMessage } from "../lib/ln-message-sign.js";
30
+ /** Default maximum fee for Lightning payments (sats). */
8
31
  const DEFAULT_MAX_FEE_SATS = 1000;
32
+ // Pure helpers — timeout wrapper, byte/hex/token utilities, expiry parsing,
33
+ // isEmptyBalance — live in ./helpers.ts. Balance cache state +
34
+ // getSparkBalanceCached / invalidateSparkBalanceCache live in
35
+ // ./balance-cache.ts. Both are re-exported here so existing call sites
36
+ // that import isEmptyBalance / invalidateSparkBalanceCache keep working.
37
+ import { formatAmount, mapTransferStatus, parseSdkExpiryMs, rawTokenIdFromBech32mTokenId, rawTokenIdFromBytes, tokenRefsMatch, txHashFromBytes, withTimeout, } from "../lib/spark-helpers.js";
38
+ import { getSparkBalanceCached, invalidateSparkBalanceCache, SPARK_RPC_TIMEOUT_MS, } from "../lib/spark-balance-cache.js";
39
+ import { buildSentRecordTransaction, convertTokenTransactionToUnified, convertTransferToTransaction, } from "../lib/spark-converters.js";
40
+ export { isEmptyBalance } from "../lib/spark-helpers.js";
41
+ export { invalidateSparkBalanceCache };
42
+ /**
43
+ * Spark Protocol Adapter Implementation
44
+ */
9
45
  export class SparkAdapter {
10
46
  constructor() {
11
- this.protocolName = 'SPARK';
12
- this.supportedLayers = ['SPARK_SPARK', 'BTC_LN'];
13
- this.version = '1.0.0';
47
+ this.protocolName = "SPARK";
48
+ this.supportedLayers = ["SPARK_SPARK", "BTC_LN"];
49
+ this.version = "1.0.0";
50
+ this.capabilities = PROTOCOL_OPERATIONS.SPARK;
14
51
  this.config = null;
52
+ /** Maps Lightning invoice string → LightningReceiveRequest ID for status polling. */
53
+ this.invoiceRequestIds = new Map();
54
+ // ========================================================================
55
+ // Helper Methods
56
+ // ========================================================================
57
+ // Pure helpers (mapTransferStatus, formatAmount, byte/hex/token utilities)
58
+ // live in ./helpers.ts; SDK↔unified converters live in ./converters.ts.
59
+ // Covered by tests/unit/spark-helpers.test.ts +
60
+ // tests/unit/spark-converters.test.ts.
15
61
  }
16
62
  // ========================================================================
17
63
  // Connection Management
@@ -19,41 +65,43 @@ export class SparkAdapter {
19
65
  async connect(config) {
20
66
  const sparkConfig = config;
21
67
  if (!sparkConfig.mnemonic) {
22
- throw new ConnectionError('Mnemonic is required for Spark wallet', 'SPARK');
68
+ throw new ConnectionError("Mnemonic is required for Spark wallet", "SPARK");
23
69
  }
24
70
  try {
25
71
  await sparkClientManager.initialize(sparkConfig);
26
72
  this.config = sparkConfig;
27
- console.log('[SparkAdapter] Connected to Spark successfully');
73
+ log.info("[SparkAdapter] Connected to Spark successfully");
28
74
  }
29
75
  catch (error) {
30
- throw new ConnectionError(`Failed to connect to Spark: ${error.message}`, 'SPARK', error);
76
+ const msg = error instanceof Error ? error.message : String(error);
77
+ throw new ConnectionError(`Failed to connect to Spark: ${msg}`, "SPARK", error);
31
78
  }
32
79
  }
33
80
  async disconnect() {
34
81
  await sparkClientManager.disconnect();
35
82
  this.config = null;
36
- console.log('[SparkAdapter] Disconnected from Spark');
83
+ log.info("[SparkAdapter] Disconnected from Spark");
37
84
  }
38
85
  isConnected() {
39
86
  return sparkClientManager.isInitialized();
40
87
  }
41
88
  async getConnectionInfo() {
42
89
  if (!this.isConnected()) {
43
- throw new ProtocolError('Not connected', 'SPARK', 'NOT_CONNECTED');
90
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
44
91
  }
45
92
  try {
46
93
  const wallet = sparkClientManager.getWallet();
47
- await wallet.getBalance();
94
+ await getSparkBalanceCached(wallet);
48
95
  return {
49
- protocol: 'SPARK',
96
+ protocol: "SPARK",
50
97
  connected: true,
51
- network: this.config?.network || 'regtest',
98
+ network: this.config?.network || "regtest",
52
99
  syncStatus: { synced: true, progress: 100 },
53
100
  };
54
101
  }
55
102
  catch (error) {
56
- throw new ProtocolError(`Failed to get connection info: ${error.message}`, 'SPARK', 'CONNECTION_INFO_ERROR', error);
103
+ const msg = error instanceof Error ? error.message : String(error);
104
+ throw new ProtocolError(`Failed to get connection info: ${msg}`, "SPARK", "CONNECTION_INFO_ERROR", error);
57
105
  }
58
106
  }
59
107
  // ========================================================================
@@ -61,26 +109,26 @@ export class SparkAdapter {
61
109
  // ========================================================================
62
110
  async listAssets() {
63
111
  if (!this.isConnected()) {
64
- throw new ProtocolError('Not connected', 'SPARK', 'NOT_CONNECTED');
112
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
65
113
  }
66
114
  try {
67
115
  const wallet = sparkClientManager.getWallet();
68
- const { balance, tokenBalances } = await wallet.getBalance();
116
+ const { balance, tokenBalances } = await getSparkBalanceCached(wallet);
69
117
  const balanceSats = Number(balance);
70
118
  const btcAsset = {
71
- id: 'BTC',
72
- name: 'Bitcoin',
73
- ticker: 'BTC',
119
+ id: "BTC",
120
+ name: "Bitcoin",
121
+ ticker: "BTC",
74
122
  precision: 8,
75
- protocol: 'SPARK',
76
- layer: 'SPARK_SPARK',
123
+ protocol: "SPARK",
124
+ layer: "SPARK_SPARK",
77
125
  balance: {
78
126
  total: balanceSats,
79
127
  available: balanceSats,
80
128
  pending: 0,
81
129
  locked: 0,
82
- totalDisplay: this.formatAmount(balanceSats, 8),
83
- availableDisplay: this.formatAmount(balanceSats, 8),
130
+ totalDisplay: formatAmount(balanceSats, 8),
131
+ availableDisplay: formatAmount(balanceSats, 8),
84
132
  },
85
133
  capabilities: {
86
134
  canSend: true,
@@ -91,27 +139,28 @@ export class SparkAdapter {
91
139
  },
92
140
  };
93
141
  const assets = [btcAsset];
94
- // Add token assets
142
+ // Add token assets from Spark's BTKN token standard
95
143
  if (tokenBalances && tokenBalances.size > 0) {
96
144
  for (const [tokenId, info] of tokenBalances) {
97
145
  const { tokenMetadata: meta } = info;
98
146
  const owned = Number(info.ownedBalance);
99
147
  const available = Number(info.availableToSendBalance);
100
- const precision = meta.decimals;
148
+ const precision = meta.decimals ?? 8;
101
149
  assets.push({
102
150
  id: tokenId,
103
151
  name: meta.tokenName,
104
152
  ticker: meta.tokenTicker,
153
+ icon: meta.tokenImageUrl,
105
154
  precision,
106
- protocol: 'SPARK',
107
- layer: 'SPARK_SPARK',
155
+ protocol: "SPARK",
156
+ layer: "SPARK_SPARK",
108
157
  balance: {
109
158
  total: owned,
110
159
  available,
111
160
  pending: 0,
112
161
  locked: owned - available,
113
- totalDisplay: this.formatAmount(owned, precision),
114
- availableDisplay: this.formatAmount(available, precision),
162
+ totalDisplay: formatAmount(owned, precision),
163
+ availableDisplay: formatAmount(available, precision),
115
164
  },
116
165
  capabilities: {
117
166
  canSend: true,
@@ -126,14 +175,15 @@ export class SparkAdapter {
126
175
  return assets;
127
176
  }
128
177
  catch (error) {
129
- throw new ProtocolError(`Failed to list assets: ${error.message}`, 'SPARK', 'LIST_ASSETS_ERROR');
178
+ const msg = error instanceof Error ? error.message : String(error);
179
+ throw new ProtocolError(`Failed to list assets: ${msg}`, "SPARK", "LIST_ASSETS_ERROR", error);
130
180
  }
131
181
  }
132
182
  async getAsset(assetId) {
133
183
  const assets = await this.listAssets();
134
- const asset = assets.find(a => a.id === assetId || a.ticker === assetId);
184
+ const asset = assets.find((a) => a.id === assetId || a.ticker === assetId);
135
185
  if (!asset) {
136
- throw new ProtocolError(`Asset not found: ${assetId}`, 'SPARK', 'ASSET_NOT_FOUND');
186
+ throw new ProtocolError(`Asset not found: ${assetId}`, "SPARK", "ASSET_NOT_FOUND");
137
187
  }
138
188
  return asset;
139
189
  }
@@ -142,44 +192,223 @@ export class SparkAdapter {
142
192
  return asset.balance;
143
193
  }
144
194
  async refreshBalances() {
145
- // Balances are fetched live
195
+ // Drop the short-TTL coalescing cache so the next call hits the gateway
196
+ // for a fresh snapshot. The cache only exists to collapse the burst of
197
+ // simultaneous reads from a single dashboard render.
198
+ invalidateSparkBalanceCache();
146
199
  }
147
200
  // ========================================================================
148
201
  // Transaction Operations
149
202
  // ========================================================================
150
203
  async listTransactions(filter) {
151
204
  if (!this.isConnected()) {
152
- throw new ProtocolError('Not connected', 'SPARK', 'NOT_CONNECTED');
205
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
153
206
  }
154
207
  try {
155
208
  const wallet = sparkClientManager.getWallet();
156
- const { transfers } = await wallet.getTransfers(filter?.limit || 50, filter?.offset || 0);
157
- return (transfers || []).map((t) => this.convertTransfer(t)).filter(Boolean);
209
+ const limit = filter?.limit ?? 20;
210
+ const offset = filter?.offset ?? 0;
211
+ const requestedAsset = filter?.asset?.trim();
212
+ const createdAfter = filter?.fromTimestamp ? new Date(filter.fromTimestamp) : undefined;
213
+ const createdBefore = !createdAfter && filter?.toTimestamp ? new Date(filter.toTimestamp) : undefined;
214
+ const shouldFetchBtc = !requestedAsset || requestedAsset === "BTC";
215
+ const shouldFetchTokens = !requestedAsset || requestedAsset !== "BTC";
216
+ const requestedTokenRawId = requestedAsset && requestedAsset !== "BTC"
217
+ ? rawTokenIdFromBech32mTokenId(requestedAsset)
218
+ : "";
219
+ // Fetch BTC transfers — best effort. A gateway/auth failure here must
220
+ // not hide token activity, and especially not the offline send-record
221
+ // fallback below.
222
+ let btcTxs = [];
223
+ if (shouldFetchBtc) {
224
+ try {
225
+ let btcTransfers = [];
226
+ if (createdAfter || createdBefore) {
227
+ const readonlyClient = await sparkClientManager.getReadonlyClient();
228
+ const sparkAddress = (await wallet.getSparkAddress());
229
+ const readonlyResult = await readonlyClient.getTransfers({
230
+ sparkAddress,
231
+ limit,
232
+ offset,
233
+ createdAfter,
234
+ createdBefore,
235
+ });
236
+ const hydratedTransfers = await Promise.all(readonlyResult.transfers.map((transfer) => wallet.getTransfer(transfer.id)));
237
+ btcTransfers = hydratedTransfers.filter((transfer) => !!transfer);
238
+ }
239
+ else {
240
+ btcTransfers = (await wallet.getTransfers(limit, offset)).transfers;
241
+ }
242
+ btcTxs = btcTransfers.map((t) => convertTransferToTransaction(t));
243
+ }
244
+ catch (err) {
245
+ log.warn("[SparkAdapter] Failed to fetch BTC transfers:", err);
246
+ }
247
+ }
248
+ // Fetch token transactions. Every Spark RPC below is best-effort and
249
+ // isolated — a transport/auth failure must never hide locally-recorded
250
+ // sends, which are the only reliable record of an outgoing token
251
+ // transfer (a withdrawal with no change output is invisible to the
252
+ // owner-filtered server query).
253
+ const tokenTxs = [];
254
+ try {
255
+ if (shouldFetchTokens) {
256
+ const sparkAddress = (await wallet.getSparkAddress());
257
+ const identityPubKey = await wallet.getIdentityPublicKey();
258
+ let networkType = "";
259
+ try {
260
+ networkType = getNetworkFromSparkAddress(sparkAddress);
261
+ }
262
+ catch {
263
+ // Non-fatal — networkType only feeds bech32m encoding fallbacks.
264
+ }
265
+ // Token metadata lookup from current balances — best effort. Empty
266
+ // when the balance is 0 or the balance RPC fails; the converter and
267
+ // the stored-record fallback both tolerate missing metadata.
268
+ const tokenMetaMap = new Map();
269
+ const rawTokenMetaMap = new Map();
270
+ try {
271
+ const { tokenBalances } = await wallet.getBalance();
272
+ if (tokenBalances) {
273
+ for (const [tokenId, info] of tokenBalances) {
274
+ const meta = {
275
+ name: info.tokenMetadata.tokenName,
276
+ ticker: info.tokenMetadata.tokenTicker,
277
+ decimals: info.tokenMetadata.decimals,
278
+ };
279
+ tokenMetaMap.set(tokenId, meta);
280
+ const rawTokenIdentifier = info.tokenMetadata.rawTokenIdentifier;
281
+ const rawTokenId = rawTokenIdFromBytes(rawTokenIdentifier);
282
+ if (rawTokenId) {
283
+ rawTokenMetaMap.set(rawTokenId, { id: tokenId, meta });
284
+ }
285
+ }
286
+ }
287
+ }
288
+ catch (err) {
289
+ log.warn("[SparkAdapter] Failed to load token balances for activity:", err);
290
+ }
291
+ // Stored send records — written to chrome.storage at send time, so
292
+ // they are available even when the Spark gateway is unreachable.
293
+ const allSentRecords = await loadSentTokenRecords();
294
+ const walletSentRecords = allSentRecords.filter((record) => record.senderSparkAddress === sparkAddress);
295
+ const sentRecords = requestedAsset && requestedAsset !== "BTC"
296
+ ? walletSentRecords.filter((record) => tokenRefsMatch(record.assetId, requestedAsset))
297
+ : walletSentRecords;
298
+ const sentHashSet = new Set(sentRecords.map((r) => normalizeTxHash(r.hash)));
299
+ const storedRecordMap = new Map(sentRecords.map((r) => [normalizeTxHash(r.hash), r]));
300
+ const storedAmountMap = new Map(sentRecords.map((r) => [normalizeTxHash(r.hash), BigInt(Math.round(r.amount || 0))]));
301
+ // Server-side history — best effort, isolated from the fallback.
302
+ // Uses the owner-keyed `queryTokenTransactions`, which returns
303
+ // complete output owners and amounts. That lets the converter
304
+ // derive direction from output ownership (see
305
+ // convertTokenTransactionToUnified) — the protocol exposes no
306
+ // direction field for token transactions.
307
+ const txsWithStatus = [];
308
+ try {
309
+ const result = await wallet.queryTokenTransactions({
310
+ ownerPublicKeys: [identityPubKey],
311
+ tokenIdentifiers: requestedAsset && requestedAsset !== "BTC" ? [requestedAsset] : undefined,
312
+ pageSize: limit,
313
+ });
314
+ txsWithStatus.push(...(result.tokenTransactionsWithStatus ?? []));
315
+ }
316
+ catch (err) {
317
+ log.warn("[SparkAdapter] Failed to query token transactions:", err);
318
+ }
319
+ // Sends without a change output are invisible to the owner-filtered
320
+ // query above; fetch them explicitly by hash. Also best effort.
321
+ if (sentRecords.length > 0) {
322
+ try {
323
+ const sentResult = await wallet.queryTokenTransactionsByTxHashes(sentRecords.map((r) => normalizeTxHash(r.hash)));
324
+ const existingHashes = new Set(txsWithStatus.map((t) => txHashFromBytes(t.tokenTransactionHash)));
325
+ for (const sentTx of sentResult.tokenTransactionsWithStatus ?? []) {
326
+ const hash = txHashFromBytes(sentTx.tokenTransactionHash);
327
+ if (!existingHashes.has(hash)) {
328
+ txsWithStatus.push(sentTx);
329
+ }
330
+ }
331
+ }
332
+ catch (err) {
333
+ log.warn("[SparkAdapter] Failed to fetch sent token transactions:", err);
334
+ }
335
+ }
336
+ // Convert whatever the gateway returned, tracking which recorded
337
+ // sends were successfully rendered.
338
+ const renderedSendHashes = new Set();
339
+ for (const txWithStatus of txsWithStatus) {
340
+ const converted = convertTokenTransactionToUnified(txWithStatus, identityPubKey, tokenMetaMap, rawTokenMetaMap, sentHashSet, storedRecordMap, storedAmountMap, networkType, requestedAsset && requestedAsset !== "BTC" ? requestedAsset : undefined, requestedTokenRawId);
341
+ if (converted) {
342
+ tokenTxs.push(converted);
343
+ const hash = txHashFromBytes(txWithStatus.tokenTransactionHash);
344
+ if (sentHashSet.has(hash))
345
+ renderedSendHashes.add(hash);
346
+ }
347
+ }
348
+ // Offline / failed-fetch fallback: synthesize a transaction directly
349
+ // from any recorded send the gateway did not return, so a completed
350
+ // withdrawal always shows up in history.
351
+ let synthesizedCount = 0;
352
+ for (const record of sentRecords) {
353
+ const hash = normalizeTxHash(record.hash);
354
+ if (renderedSendHashes.has(hash))
355
+ continue;
356
+ tokenTxs.push(buildSentRecordTransaction(record, requestedAsset && requestedAsset !== "BTC" ? requestedAsset : undefined));
357
+ synthesizedCount += 1;
358
+ }
359
+ // Diagnostic: surfaces whether the send outbox is populated. If a
360
+ // withdrawal is missing from history and these counts are 0, the
361
+ // send was never recorded (e.g. performed on a pre-outbox build).
362
+ log.info(`[SparkAdapter] token activity: ${tokenTxs.length} tx, ` +
363
+ `${allSentRecords.length} stored sends ` +
364
+ `(${sentRecords.length} this wallet, ${renderedSendHashes.size} from gateway, ` +
365
+ `${synthesizedCount} synthesized)`);
366
+ }
367
+ }
368
+ catch (err) {
369
+ log.warn("[SparkAdapter] Failed to fetch token transactions:", err);
370
+ }
371
+ const allTxs = [...btcTxs, ...tokenTxs].sort((a, b) => b.timestamp - a.timestamp);
372
+ return allTxs.filter((tx) => {
373
+ if (!filter)
374
+ return true;
375
+ if (filter.asset &&
376
+ tx.asset?.id !== filter.asset &&
377
+ tx.asset?.ticker !== filter.asset &&
378
+ !tokenRefsMatch(tx.asset?.id, filter.asset))
379
+ return false;
380
+ if (filter.type && tx.type !== filter.type)
381
+ return false;
382
+ if (filter.status && tx.status !== filter.status)
383
+ return false;
384
+ if (filter.fromTimestamp && tx.timestamp < filter.fromTimestamp)
385
+ return false;
386
+ if (filter.toTimestamp && tx.timestamp > filter.toTimestamp)
387
+ return false;
388
+ return true;
389
+ });
158
390
  }
159
391
  catch (error) {
160
- throw new ProtocolError(`Failed to list transactions: ${error.message}`, 'SPARK', 'LIST_TRANSACTIONS_ERROR');
392
+ const msg = error instanceof Error ? error.message : String(error);
393
+ throw new ProtocolError(`Failed to list transactions: ${msg}`, "SPARK", "LIST_TRANSACTIONS_ERROR", error);
161
394
  }
162
395
  }
163
396
  async getTransaction(txId) {
164
397
  if (!this.isConnected()) {
165
- throw new ProtocolError('Not connected', 'SPARK', 'NOT_CONNECTED');
398
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
166
399
  }
167
400
  try {
168
401
  const wallet = sparkClientManager.getWallet();
169
- const transfer = await wallet.getTransfer(txId);
402
+ const transfer = (await wallet.getTransfer(txId));
170
403
  if (!transfer) {
171
- throw new ProtocolError(`Transaction not found: ${txId}`, 'SPARK', 'TX_NOT_FOUND');
404
+ throw new ProtocolError(`Transaction not found: ${txId}`, "SPARK", "TX_NOT_FOUND");
172
405
  }
173
- const converted = this.convertTransfer(transfer);
174
- if (!converted) {
175
- throw new ProtocolError(`Failed to convert transaction: ${txId}`, 'SPARK', 'TX_CONVERT_ERROR');
176
- }
177
- return converted;
406
+ return convertTransferToTransaction(transfer);
178
407
  }
179
408
  catch (error) {
180
409
  if (error instanceof ProtocolError)
181
410
  throw error;
182
- throw new ProtocolError(`Failed to get transaction: ${error.message}`, 'SPARK', 'GET_TX_ERROR');
411
+ throw new ProtocolError(`Transaction not found: ${txId}`, "SPARK", "TX_NOT_FOUND", error);
183
412
  }
184
413
  }
185
414
  // ========================================================================
@@ -187,204 +416,640 @@ export class SparkAdapter {
187
416
  // ========================================================================
188
417
  async createInvoice(request) {
189
418
  if (!this.isConnected()) {
190
- throw new ProtocolError('Not connected', 'SPARK', 'NOT_CONNECTED');
419
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
420
+ }
421
+ if (request.asset && request.asset !== "BTC") {
422
+ throw new ProtocolError("Spark only supports BTC invoices", "SPARK", "UNSUPPORTED_ASSET");
191
423
  }
192
424
  try {
193
425
  const wallet = sparkClientManager.getWallet();
194
426
  const result = await wallet.createLightningInvoice({
195
- amountSats: request.amount || 0,
427
+ amountSats: request.amount ?? 0,
196
428
  memo: request.description,
429
+ expirySeconds: request.expirySeconds,
197
430
  });
198
- const invoice = result.invoice;
431
+ const inv = result.invoice;
432
+ const encodedInvoice = inv.encodedInvoice;
433
+ // Store request ID for invoice status polling
434
+ if (result.id && encodedInvoice) {
435
+ this.invoiceRequestIds.set(encodedInvoice, result.id);
436
+ }
437
+ const expiresAt = parseSdkExpiryMs("expiryTime" in inv
438
+ ? inv.expiryTime
439
+ : "expiresAt" in inv
440
+ ? inv.expiresAt
441
+ : undefined);
199
442
  return {
200
- invoice: invoice.encodedInvoice,
201
- paymentHash: invoice.paymentHash || '',
443
+ invoice: encodedInvoice,
444
+ paymentHash: inv.paymentHash ?? "",
202
445
  amount: request.amount,
203
- expiresAt: invoice.expiryTime ? new Date(invoice.expiryTime).getTime() : Date.now() + 3600000,
446
+ expiresAt: expiresAt ?? Date.now() + (request.expirySeconds ?? 3600) * 1000,
204
447
  description: request.description,
205
448
  };
206
449
  }
207
450
  catch (error) {
208
- throw new ProtocolError(`Failed to create invoice: ${error.message}`, 'SPARK', 'CREATE_INVOICE_ERROR');
451
+ const msg = error instanceof Error ? error.message : String(error);
452
+ throw new ProtocolError(`Failed to create invoice: ${msg}`, "SPARK", "CREATE_INVOICE_ERROR", error);
209
453
  }
210
454
  }
211
- async decodeInvoice(invoice) {
212
- // Spark SDK doesn't have a decode method; return basic parsed info
455
+ async createSparkInvoice(request) {
456
+ if (!this.isConnected()) {
457
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
458
+ }
459
+ try {
460
+ const wallet = sparkClientManager.getWallet();
461
+ const invoice = await wallet.createSatsInvoice({
462
+ amount: request.amount || undefined,
463
+ memo: request.description,
464
+ expiryTime: request.expirySeconds
465
+ ? new Date(Date.now() + request.expirySeconds * 1000)
466
+ : undefined,
467
+ });
468
+ return {
469
+ invoice: invoice,
470
+ paymentHash: "",
471
+ amount: request.amount,
472
+ expiresAt: Date.now() + (request.expirySeconds ?? 3600) * 1000,
473
+ description: request.description,
474
+ };
475
+ }
476
+ catch (error) {
477
+ const msg = error instanceof Error ? error.message : String(error);
478
+ throw new ProtocolError(`Failed to create Spark invoice: ${msg}`, "SPARK", "CREATE_SPARK_INVOICE_ERROR", error);
479
+ }
480
+ }
481
+ async decodeInvoice(input) {
482
+ // Detect payment type without SDK parse() — bolt11 starts with "ln"
483
+ const lower = input.trim().toLowerCase();
484
+ if (lower.startsWith("ln")) {
485
+ // bolt11 invoice: decode fields heuristically
486
+ return {
487
+ paymentHash: "",
488
+ expiresAt: 0,
489
+ destination: input,
490
+ };
491
+ }
492
+ // Spark address or BTC address
213
493
  return {
214
- paymentHash: '',
494
+ paymentHash: "",
215
495
  expiresAt: 0,
216
- destination: invoice,
496
+ destination: input,
217
497
  };
218
498
  }
219
499
  async sendPayment(request) {
220
500
  if (!this.isConnected()) {
221
- throw new ProtocolError('Not connected', 'SPARK', 'NOT_CONNECTED');
501
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
222
502
  }
223
503
  try {
224
504
  const wallet = sparkClientManager.getWallet();
225
- // Detect if this is a Lightning invoice or Spark address
226
- const isLightning = request.invoice.toLowerCase().startsWith('ln');
227
- let result;
228
- if (isLightning) {
229
- result = await wallet.payLightningInvoice({
230
- invoice: request.invoice,
231
- maxFeeSats: DEFAULT_MAX_FEE_SATS,
505
+ const destination = request.invoice.trim();
506
+ const lower = destination.toLowerCase();
507
+ if (lower.startsWith("ln")) {
508
+ // Lightning payment — payLightningInvoice is synchronous: when it
509
+ // returns without throwing the payment has been dispatched. The
510
+ // result is a LightningSendRequest whose ID is *not* queryable via
511
+ // getTransfer, so we treat a successful return as 'confirmed' to
512
+ // avoid the polling path (which would fail with "Payment not found").
513
+ const extReq = request;
514
+ // For amountless ("0-sat") BOLT-11 invoices the Spark SDK requires
515
+ // `amountSatsToSend` to be passed explicitly. We always forward
516
+ // `request.amount` when present so amountless invoices can be paid
517
+ // with the user-entered amount.
518
+ const result = await wallet.payLightningInvoice({
519
+ invoice: destination,
520
+ maxFeeSats: extReq.maxFee ?? DEFAULT_MAX_FEE_SATS,
521
+ ...(request.amount && request.amount > 0 ? { amountSatsToSend: request.amount } : {}),
232
522
  });
523
+ const lnResult = result;
524
+ const id = String(lnResult.id ?? "");
525
+ const amountSats = Number(lnResult.amountSats ?? lnResult.totalValue ?? request.amount ?? 0);
526
+ const feeSats = Number(lnResult.feeSats ?? 0);
527
+ const rawStatus = mapTransferStatus(lnResult.status);
528
+ return {
529
+ paymentHash: id,
530
+ amount: amountSats,
531
+ fee: feeSats,
532
+ // If the call returned without throwing, treat as confirmed
533
+ // (Lightning payments settle atomically).
534
+ status: rawStatus === "failed" ? "failed" : "confirmed",
535
+ timestamp: lnResult.createdTime instanceof Date ? lnResult.createdTime.getTime() : Date.now(),
536
+ };
233
537
  }
234
- else {
235
- // Spark-to-Spark transfer
236
- result = await wallet.transfer({
237
- receiverSparkAddress: request.invoice,
238
- amountSats: request.amount || 0,
239
- });
538
+ // Spark address or Spark invoice
539
+ if (isValidSparkAddress(destination)) {
540
+ // Distinguish a plain Spark address from a Spark invoice by checking for sparkInvoiceFields
541
+ const network = getNetworkFromSparkAddress(destination);
542
+ const decoded = decodeSparkAddress(destination, network);
543
+ if (decoded.sparkInvoiceFields) {
544
+ // Spark invoice — use fulfillSparkInvoice
545
+ const response = await wallet.fulfillSparkInvoice([
546
+ {
547
+ invoice: destination,
548
+ amount: request.amount ? BigInt(request.amount) : undefined,
549
+ },
550
+ ]);
551
+ if (response.satsTransactionErrors.length > 0) {
552
+ throw new Error(response.satsTransactionErrors[0].error.message);
553
+ }
554
+ const success = response.satsTransactionSuccess[0];
555
+ if (!success) {
556
+ throw new Error("Spark invoice payment returned no result");
557
+ }
558
+ const transfer = success.transferResponse;
559
+ return {
560
+ paymentHash: transfer.id,
561
+ amount: transfer.totalValue,
562
+ fee: 0,
563
+ status: mapTransferStatus(transfer.status),
564
+ timestamp: transfer.createdTime?.getTime() ?? Date.now(),
565
+ };
566
+ }
567
+ // Plain Spark address — use transfer
568
+ const transfer = (await wallet.transfer({
569
+ receiverSparkAddress: destination,
570
+ amountSats: request.amount ?? 0,
571
+ }));
572
+ return {
573
+ paymentHash: transfer.id,
574
+ amount: transfer.totalValue,
575
+ fee: 0,
576
+ status: mapTransferStatus(transfer.status),
577
+ timestamp: transfer.createdTime?.getTime() ?? Date.now(),
578
+ };
579
+ }
580
+ // On-chain BTC withdrawal — requires a fee quote first
581
+ const feeQuote = await wallet.getWithdrawalFeeQuote({
582
+ amountSats: request.amount ?? 0,
583
+ withdrawalAddress: destination,
584
+ });
585
+ if (!feeQuote) {
586
+ throw new Error("Failed to get withdrawal fee quote for on-chain exit");
240
587
  }
588
+ const feeAmountSats = (feeQuote.l1BroadcastFeeMedium?.originalValue ?? 0) +
589
+ (feeQuote.userFeeMedium?.originalValue ?? 0);
590
+ const result = await wallet.withdraw({
591
+ onchainAddress: destination,
592
+ amountSats: request.amount ?? 0,
593
+ exitSpeed: ExitSpeed.MEDIUM,
594
+ feeQuoteId: feeQuote.id,
595
+ feeAmountSats,
596
+ });
241
597
  return {
242
- paymentHash: result?.id || '',
243
- amount: request.amount || 0,
244
- fee: 0,
245
- status: 'confirmed',
598
+ paymentHash: result?.id ?? "",
599
+ amount: request.amount ?? 0,
600
+ fee: result?.fee?.originalValue ?? 0,
601
+ status: "pending",
246
602
  timestamp: Date.now(),
247
603
  };
248
604
  }
249
605
  catch (error) {
250
- throw new ProtocolError(`Failed to send payment: ${error.message}`, 'SPARK', 'SEND_PAYMENT_ERROR');
606
+ const msg = error instanceof Error ? error.message : String(error);
607
+ throw new ProtocolError(`Failed to send payment: ${msg}`, "SPARK", "SEND_PAYMENT_ERROR", error);
608
+ }
609
+ finally {
610
+ // Any send attempt (success OR failure) makes the cached balance stale;
611
+ // failures may still have produced a partial state change on the gateway.
612
+ invalidateSparkBalanceCache();
251
613
  }
252
614
  }
253
- async getPaymentStatus(paymentHash) {
254
- return {
255
- paymentHash,
256
- status: 'pending',
257
- };
615
+ async getPaymentStatus(paymentId) {
616
+ if (!this.isConnected()) {
617
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
618
+ }
619
+ try {
620
+ const wallet = sparkClientManager.getWallet();
621
+ // The Spark SDK may return entity IDs like "SparkLightningSendRequest:uuid"
622
+ // but getTransfer expects a plain UUID.
623
+ const transferId = paymentId.includes(":") ? paymentId.split(":").pop() : paymentId;
624
+ const transfer = (await wallet.getTransfer(transferId));
625
+ if (!transfer) {
626
+ throw new ProtocolError(`Payment not found: ${paymentId}`, "SPARK", "PAYMENT_STATUS_ERROR");
627
+ }
628
+ return {
629
+ paymentHash: paymentId,
630
+ status: mapTransferStatus(transfer.status),
631
+ amount: transfer.totalValue,
632
+ timestamp: transfer.createdTime?.getTime() ?? 0,
633
+ };
634
+ }
635
+ catch (error) {
636
+ if (error instanceof ProtocolError)
637
+ throw error;
638
+ const msg = error instanceof Error ? error.message : String(error);
639
+ throw new ProtocolError(`Failed to get payment status: ${msg}`, "SPARK", "PAYMENT_STATUS_ERROR", error);
640
+ }
258
641
  }
259
642
  // ========================================================================
260
643
  // Address Operations
261
644
  // ========================================================================
262
645
  async getReceiveAddress(assetId) {
263
646
  if (!this.isConnected()) {
264
- throw new ProtocolError('Not connected', 'SPARK', 'NOT_CONNECTED');
647
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
265
648
  }
266
649
  try {
267
650
  const wallet = sparkClientManager.getWallet();
268
- if (assetId === 'onchain' || assetId === 'btc_onchain') {
651
+ // Spark-to-Spark native address
652
+ if (assetId === "SPARK") {
653
+ const address = await wallet.getSparkAddress();
654
+ return {
655
+ address: address,
656
+ format: "SPARK_ADDRESS",
657
+ asset: "BTC",
658
+ };
659
+ }
660
+ // BTC on-chain deposit address
661
+ if (!assetId || assetId === "BTC" || assetId.toLowerCase() === "btc") {
269
662
  const address = await wallet.getSingleUseDepositAddress();
270
- return { address, format: 'BTC_ADDRESS', asset: 'BTC' };
663
+ return {
664
+ address,
665
+ format: "BTC_ADDRESS",
666
+ asset: "BTC",
667
+ };
271
668
  }
272
- const address = await wallet.getSparkAddress();
273
- return { address, format: 'SPARK_ADDRESS', asset: 'BTC' };
669
+ throw new ProtocolError("Spark only supports BTC", "SPARK", "UNSUPPORTED_ASSET");
274
670
  }
275
671
  catch (error) {
276
- throw new ProtocolError(`Failed to get address: ${error.message}`, 'SPARK', 'GET_ADDRESS_ERROR');
672
+ if (error instanceof ProtocolError)
673
+ throw error;
674
+ const msg = error instanceof Error ? error.message : String(error);
675
+ throw new ProtocolError(`Failed to get receive address: ${msg}`, "SPARK", "GET_ADDRESS_ERROR", error);
277
676
  }
278
677
  }
678
+ async claimSparkL1Deposit(params) {
679
+ if (!this.isConnected()) {
680
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
681
+ }
682
+ const address = params.address?.trim();
683
+ if (!address) {
684
+ return { status: "error", error: "address is required" };
685
+ }
686
+ const wallet = sparkClientManager.getWallet();
687
+ let utxos;
688
+ try {
689
+ utxos = await wallet.getUtxosForDepositAddress(address, 10, 0, true);
690
+ }
691
+ catch (error) {
692
+ return {
693
+ status: "error",
694
+ error: error instanceof Error ? error.message : "utxo lookup failed",
695
+ };
696
+ }
697
+ if (!utxos || utxos.length === 0)
698
+ return { status: "awaiting" };
699
+ const claimedTxids = [];
700
+ let lastError;
701
+ for (const utxo of utxos) {
702
+ try {
703
+ await wallet.claimDeposit(utxo.txid);
704
+ claimedTxids.push(utxo.txid);
705
+ }
706
+ catch (error) {
707
+ lastError = error instanceof Error ? error.message : String(error);
708
+ }
709
+ }
710
+ if (claimedTxids.length === 0) {
711
+ return { status: "error", error: lastError ?? "no utxos claimed" };
712
+ }
713
+ return { status: "claimed", txids: claimedTxids };
714
+ }
715
+ /**
716
+ * Sweep every previously-generated single-use deposit address that is still
717
+ * unclaimed and credit any confirmed UTXOs paid to them. Each call to
718
+ * `getSingleUseDepositAddress()` returns a *new* address, so a deposit sent
719
+ * to an address from a previous session would otherwise stay stranded:
720
+ * the deposit-screen poller only watches the address currently on screen.
721
+ * Run this on unlock (after SPARK connects) and when the user opens the
722
+ * deposit screen so stuck deposits surface as soon as possible.
723
+ */
724
+ async sweepSparkL1Deposits() {
725
+ if (!this.isConnected()) {
726
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
727
+ }
728
+ const wallet = sparkClientManager.getWallet();
729
+ let unused;
730
+ try {
731
+ unused = await wallet.getUnusedDepositAddresses();
732
+ }
733
+ catch (error) {
734
+ return {
735
+ addressesChecked: 0,
736
+ claimedTxids: [],
737
+ errors: [error instanceof Error ? error.message : "getUnusedDepositAddresses failed"],
738
+ };
739
+ }
740
+ if (!unused || unused.length === 0) {
741
+ return { addressesChecked: 0, claimedTxids: [], errors: [] };
742
+ }
743
+ const claimedTxids = [];
744
+ const errors = [];
745
+ for (const addr of unused) {
746
+ try {
747
+ const utxos = await wallet.getUtxosForDepositAddress(addr, 10, 0, true);
748
+ if (!utxos || utxos.length === 0)
749
+ continue;
750
+ for (const utxo of utxos) {
751
+ try {
752
+ await wallet.claimDeposit(utxo.txid);
753
+ claimedTxids.push(utxo.txid);
754
+ }
755
+ catch (claimErr) {
756
+ errors.push(claimErr instanceof Error ? claimErr.message : String(claimErr));
757
+ }
758
+ }
759
+ }
760
+ catch (lookupErr) {
761
+ errors.push(lookupErr instanceof Error ? lookupErr.message : String(lookupErr));
762
+ }
763
+ }
764
+ return { addressesChecked: unused.length, claimedTxids, errors };
765
+ }
279
766
  // ========================================================================
280
767
  // Node & Balance Operations
281
768
  // ========================================================================
282
769
  async getNodeInfo() {
283
770
  if (!this.isConnected()) {
284
- throw new ProtocolError('Not connected', 'SPARK', 'NOT_CONNECTED');
771
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
772
+ }
773
+ try {
774
+ const wallet = sparkClientManager.getWallet();
775
+ const { balance } = await getSparkBalanceCached(wallet);
776
+ const balanceSats = Number(balance);
777
+ return {
778
+ channelsBalanceMsat: balanceSats * 1000,
779
+ maxPayableMsat: balanceSats * 1000,
780
+ onchainBalanceMsat: 0,
781
+ pendingOnchainBalanceMsat: 0,
782
+ maxReceivableMsat: 0,
783
+ inboundLiquidityMsats: 0,
784
+ connectedPeers: [],
785
+ utxos: 0,
786
+ };
787
+ }
788
+ catch (error) {
789
+ const msg = error instanceof Error ? error.message : String(error);
790
+ throw new ProtocolError(`Failed to get node info: ${msg}`, "SPARK", "NODE_INFO_ERROR", error);
285
791
  }
286
- const wallet = sparkClientManager.getWallet();
287
- const { balance } = await wallet.getBalance();
288
- const balanceSats = Number(balance);
289
- return {
290
- channelsBalanceMsat: balanceSats * 1000,
291
- maxPayableMsat: balanceSats * 1000,
292
- onchainBalanceMsat: 0,
293
- maxReceivableMsat: 0,
294
- inboundLiquidityMsats: 0,
295
- connectedPeers: [],
296
- blockHeight: 0,
297
- pendingOnchainBalanceMsat: 0,
298
- utxos: 0,
299
- };
300
792
  }
301
793
  async getBtcBalance() {
302
794
  if (!this.isConnected()) {
303
- throw new ProtocolError('Not connected', 'SPARK', 'NOT_CONNECTED');
795
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
796
+ }
797
+ try {
798
+ const wallet = sparkClientManager.getWallet();
799
+ const { balance } = await getSparkBalanceCached(wallet);
800
+ const balanceSats = Number(balance);
801
+ return {
802
+ confirmed: balanceSats,
803
+ unconfirmed: 0,
804
+ total: balanceSats,
805
+ };
806
+ }
807
+ catch (error) {
808
+ const msg = error instanceof Error ? error.message : String(error);
809
+ throw new ProtocolError(`Failed to get BTC balance: ${msg}`, "SPARK", "BALANCE_ERROR", error);
304
810
  }
305
- const wallet = sparkClientManager.getWallet();
306
- const { balance } = await wallet.getBalance();
307
- const balanceSats = Number(balance);
308
- return { confirmed: balanceSats, unconfirmed: 0, total: balanceSats };
309
811
  }
310
812
  async listChannels() {
813
+ // Spark doesn't have traditional Lightning channels
311
814
  return [];
312
815
  }
313
816
  async listPayments() {
314
- const txs = await this.listTransactions();
315
- return { payments: txs };
817
+ if (!this.isConnected()) {
818
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
819
+ }
820
+ try {
821
+ const wallet = sparkClientManager.getWallet();
822
+ const { transfers } = (await withTimeout(wallet.getTransfers(), SPARK_RPC_TIMEOUT_MS, "spark.getTransfers"));
823
+ return { transfers: transfers };
824
+ }
825
+ catch (error) {
826
+ const msg = error instanceof Error ? error.message : String(error);
827
+ throw new ProtocolError(`Failed to list payments: ${msg}`, "SPARK", "LIST_PAYMENTS_ERROR", error);
828
+ }
316
829
  }
317
830
  async listTransfers(_options) {
831
+ // Spark doesn't have RGB-style transfers
318
832
  return { transfers: [] };
319
833
  }
834
+ async sendBtcOnchain(params) {
835
+ if (!this.isConnected()) {
836
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
837
+ }
838
+ try {
839
+ const wallet = sparkClientManager.getWallet();
840
+ // Step 1: Get fee quote — required by the SDK for cooperative exit
841
+ const feeQuote = await wallet.getWithdrawalFeeQuote({
842
+ amountSats: params.amount,
843
+ withdrawalAddress: params.address,
844
+ });
845
+ if (!feeQuote) {
846
+ throw new Error("Failed to get withdrawal fee quote");
847
+ }
848
+ // Step 2: Execute withdrawal with the fee quote
849
+ const feeAmountSats = (feeQuote.l1BroadcastFeeMedium?.originalValue ?? 0) +
850
+ (feeQuote.userFeeMedium?.originalValue ?? 0);
851
+ const result = await wallet.withdraw({
852
+ onchainAddress: params.address,
853
+ amountSats: params.amount,
854
+ exitSpeed: ExitSpeed.MEDIUM,
855
+ feeQuoteId: feeQuote.id,
856
+ feeAmountSats,
857
+ });
858
+ return result;
859
+ }
860
+ catch (error) {
861
+ const msg = error instanceof Error ? error.message : String(error);
862
+ throw new ProtocolError(`Failed to send BTC on-chain: ${msg}`, "SPARK", "SEND_BTC_ERROR", error);
863
+ }
864
+ finally {
865
+ invalidateSparkBalanceCache();
866
+ }
867
+ }
320
868
  // ========================================================================
321
- // Unsupported Operations
869
+ // PSBT Signing
322
870
  // ========================================================================
323
- supportsSwaps() {
324
- return false;
871
+ async signPsbt(psbtHex) {
872
+ if (!this.config?.mnemonic) {
873
+ throw new ProtocolError("Wallet mnemonic not available", "SPARK", "NOT_CONNECTED");
874
+ }
875
+ const { signPsbt: doSign } = await import("../lib/psbt-signer.js");
876
+ const result = doSign(psbtHex, this.config.mnemonic);
877
+ return { psbt: result.psbt, unchanged: result.unchanged };
325
878
  }
326
- async sendBtcOnchain(params) {
327
- if (!this.isConnected()) {
328
- throw new ProtocolError('Not connected', 'SPARK', 'NOT_CONNECTED');
879
+ // ========================================================================
880
+ // Message Signing
881
+ // ========================================================================
882
+ async signMessage(message) {
883
+ if (!this.config?.mnemonic) {
884
+ throw new ProtocolError("Wallet mnemonic not available", "SPARK", "NOT_CONNECTED");
329
885
  }
330
- const wallet = sparkClientManager.getWallet();
331
- const result = await wallet.withdraw({
332
- onchainAddress: params.address,
333
- amountSats: params.amount,
334
- exitSpeed: 'FAST',
335
- });
336
- return result;
886
+ const seed = mnemonicToSeedSync(this.config.mnemonic);
887
+ const root = HDKey.fromMasterSeed(seed);
888
+ // m/138'/1 — wallet-identity message-signing key, distinct from the
889
+ // LNURL-auth hashing key at m/138'/0.
890
+ const node = root.derive("m/138'/1");
891
+ if (!node.privateKey) {
892
+ throw new ProtocolError("Failed to derive message-signing key", "SPARK", "KEY_DERIVATION_ERROR");
893
+ }
894
+ return signLnMessage(message, node.privateKey);
895
+ }
896
+ async verifyMessage(message, signature) {
897
+ return verifyLnMessage(message, signature);
337
898
  }
338
899
  // ========================================================================
339
- // Private Helpers
900
+ // RGB-Specific Operations (Not supported by Spark)
340
901
  // ========================================================================
341
- convertTransfer(transfer) {
902
+ async createRgbInvoice(_params) {
903
+ throw new ProtocolError("RGB invoices not supported by Spark", "SPARK", "NOT_SUPPORTED");
904
+ }
905
+ async decodeRgbInvoice(_params) {
906
+ throw new ProtocolError("RGB invoice decoding not supported by Spark", "SPARK", "NOT_SUPPORTED");
907
+ }
908
+ async getInvoiceStatus(params) {
909
+ if (!this.isConnected()) {
910
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
911
+ }
912
+ const requestId = this.invoiceRequestIds.get(params.invoice);
913
+ if (!requestId) {
914
+ // Invoice not tracked — might be from a previous session
915
+ return { status: "Pending" };
916
+ }
342
917
  try {
343
- const isIncoming = transfer.transferDirection === 'INCOMING';
344
- const amountSats = Number(transfer.totalValue || 0);
345
- const timestamp = transfer.createdTime ? new Date(transfer.createdTime).getTime() : Date.now();
346
- const statusMap = {
347
- 'TRANSFER_STATUS_COMPLETED': 'confirmed',
348
- 'TRANSFER_STATUS_RETURNED': 'failed',
349
- 'TRANSFER_STATUS_EXPIRED': 'cancelled',
350
- };
351
- const btcAsset = {
352
- id: 'BTC',
353
- name: 'Bitcoin',
354
- ticker: 'BTC',
355
- precision: 8,
356
- protocol: 'SPARK',
357
- layer: 'SPARK_SPARK',
358
- balance: {
359
- total: amountSats,
360
- available: amountSats,
361
- pending: 0,
362
- totalDisplay: this.formatAmount(amountSats, 8),
363
- availableDisplay: this.formatAmount(amountSats, 8),
364
- },
365
- capabilities: {
366
- canSend: true, canReceive: true, canSwap: false,
367
- supportsLightning: true, supportsOnchain: true,
368
- },
369
- };
370
- return {
371
- id: transfer.id,
372
- type: isIncoming ? 'receive' : 'send',
373
- status: statusMap[transfer.status] || 'pending',
374
- timestamp,
375
- amount: amountSats,
376
- amountDisplay: this.formatAmount(amountSats, 8),
377
- fee: 0,
378
- asset: btcAsset,
379
- protocolData: { sparkTransfer: transfer },
380
- };
918
+ const wallet = sparkClientManager.getWallet();
919
+ const request = await wallet.getLightningReceiveRequest(requestId);
920
+ if (!request) {
921
+ return { status: "Pending" };
922
+ }
923
+ // Map LightningReceiveRequestStatus to simple status
924
+ const s = request.status;
925
+ if (s === "LIGHTNING_PAYMENT_RECEIVED" ||
926
+ s === "TRANSFER_COMPLETED" ||
927
+ s === "PAYMENT_PREIMAGE_RECOVERED") {
928
+ // Clean up tracked invoice on terminal state
929
+ this.invoiceRequestIds.delete(params.invoice);
930
+ return { status: "Succeeded" };
931
+ }
932
+ if (s === "TRANSFER_FAILED" ||
933
+ s === "TRANSFER_CREATION_FAILED" ||
934
+ s === "REFUND_SIGNING_COMMITMENTS_QUERYING_FAILED" ||
935
+ s === "REFUND_SIGNING_FAILED" ||
936
+ s === "PAYMENT_PREIMAGE_RECOVERING_FAILED") {
937
+ this.invoiceRequestIds.delete(params.invoice);
938
+ return { status: "Failed" };
939
+ }
940
+ // INVOICE_CREATED, TRANSFER_CREATED, etc.
941
+ return { status: "Pending" };
942
+ }
943
+ catch (error) {
944
+ const msg = error instanceof Error ? error.message : String(error);
945
+ log.warn("[SparkAdapter] Invoice status check failed:", msg);
946
+ return { status: "Pending" };
381
947
  }
382
- catch {
383
- return null;
948
+ }
949
+ async sendAsset(params) {
950
+ if (!this.isConnected()) {
951
+ throw new ProtocolError("Not connected", "SPARK", "NOT_CONNECTED");
384
952
  }
953
+ try {
954
+ const wallet = sparkClientManager.getWallet();
955
+ const senderSparkAddress = (await wallet.getSparkAddress());
956
+ const assignmentAmount = params.assignment?.value;
957
+ const tokenAmount = typeof assignmentAmount === "number" && assignmentAmount > 0
958
+ ? assignmentAmount
959
+ : params.amount;
960
+ if (!Number.isFinite(tokenAmount) || tokenAmount <= 0) {
961
+ throw new Error("Spark token amount must be greater than 0");
962
+ }
963
+ const destination = params.recipientId.trim();
964
+ // Resolve token metadata for the sent-record (cached balance is warm from the send UI)
965
+ let sentMeta = { ticker: "TOKEN", name: params.assetId, decimals: 0 };
966
+ try {
967
+ const { tokenBalances } = await getSparkBalanceCached(wallet);
968
+ const info = tokenBalances?.get(params.assetId);
969
+ if (info) {
970
+ sentMeta = {
971
+ ticker: info.tokenMetadata.tokenTicker,
972
+ name: info.tokenMetadata.tokenName,
973
+ decimals: info.tokenMetadata.decimals,
974
+ };
975
+ }
976
+ }
977
+ catch {
978
+ // Non-critical — falls back to tokenMetaMap in listTransactions
979
+ }
980
+ // Check if the destination is a Spark invoice (contains sparkInvoiceFields)
981
+ if (isValidSparkAddress(destination)) {
982
+ const network = getNetworkFromSparkAddress(destination);
983
+ const decoded = decodeSparkAddress(destination, network);
984
+ if (decoded.sparkInvoiceFields) {
985
+ // Spark token invoice — use fulfillSparkInvoice
986
+ const response = await wallet.fulfillSparkInvoice([
987
+ {
988
+ invoice: destination,
989
+ amount: BigInt(tokenAmount),
990
+ },
991
+ ]);
992
+ if (response.tokenTransactionErrors.length > 0) {
993
+ throw new Error(response.tokenTransactionErrors[0].error.message);
994
+ }
995
+ if (response.invalidInvoices.length > 0) {
996
+ throw new Error(response.invalidInvoices[0].error.message);
997
+ }
998
+ const success = response.tokenTransactionSuccess[0];
999
+ if (success) {
1000
+ await saveSentTokenRecord({
1001
+ hash: success.txid,
1002
+ senderSparkAddress,
1003
+ amount: tokenAmount,
1004
+ assetId: params.assetId,
1005
+ ...sentMeta,
1006
+ timestamp: Date.now(),
1007
+ });
1008
+ return { txId: success.txid };
1009
+ }
1010
+ // Fallback: maybe it was a sats invoice bundled with token
1011
+ const satsSuccess = response.satsTransactionSuccess[0];
1012
+ if (satsSuccess) {
1013
+ return { txId: satsSuccess.transferResponse.id };
1014
+ }
1015
+ throw new Error("Spark invoice payment returned no result");
1016
+ }
1017
+ }
1018
+ // Plain Spark address — use transferTokens
1019
+ const txId = await wallet.transferTokens({
1020
+ tokenIdentifier: params.assetId,
1021
+ tokenAmount: BigInt(tokenAmount),
1022
+ receiverSparkAddress: destination,
1023
+ });
1024
+ await saveSentTokenRecord({
1025
+ hash: txId,
1026
+ senderSparkAddress,
1027
+ amount: tokenAmount,
1028
+ assetId: params.assetId,
1029
+ ...sentMeta,
1030
+ timestamp: Date.now(),
1031
+ });
1032
+ return { txId };
1033
+ }
1034
+ catch (error) {
1035
+ const msg = error instanceof Error ? error.message : String(error);
1036
+ throw new ProtocolError(`Failed to send Spark token: ${msg}`, "SPARK", "SEND_ASSET_ERROR", error);
1037
+ }
1038
+ }
1039
+ // ========================================================================
1040
+ // Swap Operations (Not supported by Spark)
1041
+ // ========================================================================
1042
+ supportsSwaps() {
1043
+ return false;
1044
+ }
1045
+ async getSwapQuote(_request) {
1046
+ throw new ProtocolError("Swap operations not supported by Spark", "SPARK", "NOT_SUPPORTED");
1047
+ }
1048
+ async executeSwap(_quote) {
1049
+ throw new ProtocolError("Swap operations not supported by Spark", "SPARK", "NOT_SUPPORTED");
385
1050
  }
386
- formatAmount(amount, precision) {
387
- return (amount / Math.pow(10, precision)).toFixed(precision);
1051
+ async getSwapStatus(_swapId) {
1052
+ throw new ProtocolError("Swap operations not supported by Spark", "SPARK", "NOT_SUPPORTED");
388
1053
  }
389
1054
  }
390
1055
  //# sourceMappingURL=SparkAdapter.js.map