@flashnet/sdk 0.5.6 → 0.5.8

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 (44) hide show
  1. package/dist/cjs/index.d.ts +1 -1
  2. package/dist/cjs/index.d.ts.map +1 -1
  3. package/dist/cjs/index.js +1 -0
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/node_modules/@buildonspark/spark-sdk/dist/buffer-C7zVfCZb.d.ts +2 -0
  6. package/dist/cjs/node_modules/@buildonspark/spark-sdk/dist/buffer-C7zVfCZb.d.ts.map +1 -0
  7. package/dist/cjs/src/api/typed-endpoints.d.ts +11 -0
  8. package/dist/cjs/src/api/typed-endpoints.d.ts.map +1 -1
  9. package/dist/cjs/src/api/typed-endpoints.js +15 -0
  10. package/dist/cjs/src/api/typed-endpoints.js.map +1 -1
  11. package/dist/cjs/src/client/FlashnetClient.d.ts +37 -1
  12. package/dist/cjs/src/client/FlashnetClient.d.ts.map +1 -1
  13. package/dist/cjs/src/client/FlashnetClient.js +253 -111
  14. package/dist/cjs/src/client/FlashnetClient.js.map +1 -1
  15. package/dist/cjs/src/types/index.d.ts +35 -0
  16. package/dist/cjs/src/types/index.d.ts.map +1 -1
  17. package/dist/cjs/src/utils/intents.d.ts +13 -0
  18. package/dist/cjs/src/utils/intents.d.ts.map +1 -1
  19. package/dist/cjs/src/utils/intents.js +27 -0
  20. package/dist/cjs/src/utils/intents.js.map +1 -1
  21. package/dist/esm/index.d.ts +1 -1
  22. package/dist/esm/index.d.ts.map +1 -1
  23. package/dist/esm/index.js +1 -1
  24. package/dist/esm/node_modules/@buildonspark/spark-sdk/dist/buffer-C7zVfCZb.d.ts +2 -0
  25. package/dist/esm/node_modules/@buildonspark/spark-sdk/dist/buffer-C7zVfCZb.d.ts.map +1 -0
  26. package/dist/esm/src/api/typed-endpoints.d.ts +11 -0
  27. package/dist/esm/src/api/typed-endpoints.d.ts.map +1 -1
  28. package/dist/esm/src/api/typed-endpoints.js +15 -0
  29. package/dist/esm/src/api/typed-endpoints.js.map +1 -1
  30. package/dist/esm/src/client/FlashnetClient.d.ts +37 -1
  31. package/dist/esm/src/client/FlashnetClient.d.ts.map +1 -1
  32. package/dist/esm/src/client/FlashnetClient.js +254 -112
  33. package/dist/esm/src/client/FlashnetClient.js.map +1 -1
  34. package/dist/esm/src/types/index.d.ts +35 -0
  35. package/dist/esm/src/types/index.d.ts.map +1 -1
  36. package/dist/esm/src/utils/intents.d.ts +13 -0
  37. package/dist/esm/src/utils/intents.d.ts.map +1 -1
  38. package/dist/esm/src/utils/intents.js +27 -1
  39. package/dist/esm/src/utils/intents.js.map +1 -1
  40. package/package.json +11 -7
  41. package/dist/cjs/node_modules/@buildonspark/spark-sdk/dist/buffer-DK3I_h9P.d.ts +0 -2
  42. package/dist/cjs/node_modules/@buildonspark/spark-sdk/dist/buffer-DK3I_h9P.d.ts.map +0 -1
  43. package/dist/esm/node_modules/@buildonspark/spark-sdk/dist/buffer-DK3I_h9P.d.ts +0 -2
  44. package/dist/esm/node_modules/@buildonspark/spark-sdk/dist/buffer-DK3I_h9P.d.ts.map +0 -1
@@ -1002,6 +1002,53 @@ class FlashnetClient {
1002
1002
  }
1003
1003
  return response;
1004
1004
  }
1005
+ // LP Lock & Transfer Operations
1006
+ /**
1007
+ * Lock an LP position to prevent withdrawal until expiry.
1008
+ * Locks can only be set or extended, never shortened.
1009
+ * @param poolId Pool ID (LP identity public key)
1010
+ * @param lockUntilTimestamp Unix timestamp (seconds). "0" = indefinite lock.
1011
+ */
1012
+ async lockPosition(poolId, lockUntilTimestamp, opts) {
1013
+ await this.ensureInitialized();
1014
+ await this.ensurePingOk();
1015
+ const nonce = index$2.generateNonce();
1016
+ const intentMessage = intents.generateLockPositionIntentMessage({
1017
+ userPublicKey: this.publicKey,
1018
+ lpIdentityPublicKey: poolId,
1019
+ lockUntilTimestamp,
1020
+ tickLower: opts?.tickLower,
1021
+ tickUpper: opts?.tickUpper,
1022
+ nonce,
1023
+ });
1024
+ const messageHash = sha256__default.default(intentMessage);
1025
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
1026
+ const request = {
1027
+ userPublicKey: this.publicKey,
1028
+ poolId,
1029
+ lockUntilTimestamp,
1030
+ tickLower: opts?.tickLower,
1031
+ tickUpper: opts?.tickUpper,
1032
+ nonce,
1033
+ signature: hex.getHexFromUint8Array(signature),
1034
+ };
1035
+ const response = await this.typedApi.lockPosition(request);
1036
+ if (!response.accepted) {
1037
+ const errorMessage = response.error || "Lock position rejected by the AMM";
1038
+ throw new Error(errorMessage);
1039
+ }
1040
+ return response;
1041
+ }
1042
+ /**
1043
+ * List LP position locks for a pool. Read-only, no signature required.
1044
+ * @param poolId Pool ID (LP identity public key)
1045
+ * @param ownerPublicKey Optional filter by owner
1046
+ */
1047
+ async getPositionLocks(poolId, ownerPublicKey) {
1048
+ await this.ensureInitialized();
1049
+ const response = await this.typedApi.getPositionLocks(poolId, ownerPublicKey);
1050
+ return response.locks;
1051
+ }
1005
1052
  // Host Operations
1006
1053
  /**
1007
1054
  * Register as a host
@@ -2099,8 +2146,16 @@ class FlashnetClient {
2099
2146
  const assetOutAddress = quote.tokenIsAssetA
2100
2147
  ? pool.assetBAddress
2101
2148
  : pool.assetAAddress;
2102
- // Calculate min amount out with slippage protection
2103
- const minBtcOut = this.calculateMinAmountOut(quote.btcAmountRequired, maxSlippageBps);
2149
+ const effectiveMaxLightningFee = maxLightningFeeSats ?? quote.estimatedLightningFee;
2150
+ // Floor minAmountOut at invoiceAmount + fee so the swap never returns
2151
+ // less BTC than the lightning payment requires.
2152
+ const slippageMin = this.calculateMinAmountOut(quote.btcAmountRequired, maxSlippageBps);
2153
+ const baseBtcNeeded = !quote.isZeroAmountInvoice
2154
+ ? BigInt(quote.invoiceAmountSats) + BigInt(effectiveMaxLightningFee)
2155
+ : 0n;
2156
+ const minBtcOut = BigInt(slippageMin) >= baseBtcNeeded
2157
+ ? slippageMin
2158
+ : baseBtcNeeded.toString();
2104
2159
  // Execute the swap
2105
2160
  const swapResponse = await this.executeSwap({
2106
2161
  poolId: quote.poolId,
@@ -2125,138 +2180,158 @@ class FlashnetClient {
2125
2180
  error: swapResponse.error || "Swap was not accepted",
2126
2181
  };
2127
2182
  }
2128
- // Step 5: Wait for transfer (skip useExistingBtcBalance for zero-amount invoices)
2129
- let canPayImmediately = false;
2130
- if (!quote.isZeroAmountInvoice && useExistingBtcBalance) {
2131
- const invoiceAmountSats = await this.decodeInvoiceAmount(invoice);
2132
- const effectiveMaxLightningFee = maxLightningFeeSats ?? quote.estimatedLightningFee;
2133
- const btcNeededForPayment = invoiceAmountSats + effectiveMaxLightningFee;
2134
- const balance = await this.getBalance();
2135
- canPayImmediately = balance.balance >= bigint.safeBigInt(btcNeededForPayment);
2136
- }
2137
- if (!canPayImmediately) {
2138
- const transferComplete = await this.waitForTransferCompletion(swapResponse.outboundTransferId, transferTimeoutMs);
2139
- if (!transferComplete) {
2140
- return {
2141
- success: false,
2142
- poolId: quote.poolId,
2143
- tokenAmountSpent: quote.tokenAmountRequired,
2144
- btcAmountReceived: swapResponse.amountOut || "0",
2145
- swapTransferId: swapResponse.outboundTransferId,
2146
- ammFeePaid: quote.estimatedAmmFee,
2147
- sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2148
- error: "Transfer did not complete within timeout",
2149
- };
2150
- }
2151
- }
2152
- // Step 6: Calculate Lightning fee and payment amount
2153
- const effectiveMaxLightningFee = maxLightningFeeSats ?? quote.estimatedLightningFee;
2154
- const btcReceived = swapResponse.amountOut || quote.btcAmountRequired;
2155
- // Step 7: Pay the Lightning invoice
2183
+ // Step 5: Claim the swap output and refresh wallet state.
2184
+ // Suppress leaf optimization for the entire claim-to-pay window so
2185
+ // the SSP cannot swap away the leaves we need for lightning payment.
2186
+ const restoreOptimization = this.suppressOptimization();
2156
2187
  try {
2157
- let lightningPayment;
2158
- let invoiceAmountPaid;
2159
- if (quote.isZeroAmountInvoice) {
2160
- // Zero-amount invoice: pay whatever BTC we received minus lightning fee
2161
- const actualBtc = bigint.safeBigInt(btcReceived);
2162
- const lnFee = bigint.safeBigInt(effectiveMaxLightningFee);
2163
- const amountToPay = actualBtc - lnFee;
2164
- if (amountToPay <= 0n) {
2188
+ let canPayImmediately = false;
2189
+ if (!quote.isZeroAmountInvoice && useExistingBtcBalance) {
2190
+ const invoiceAmountSats = await this.decodeInvoiceAmount(invoice);
2191
+ const btcNeededForPayment = invoiceAmountSats + effectiveMaxLightningFee;
2192
+ const balance = await this.getBalance();
2193
+ canPayImmediately =
2194
+ balance.balance >= bigint.safeBigInt(btcNeededForPayment);
2195
+ }
2196
+ if (!canPayImmediately) {
2197
+ const claimed = await this.instaClaimTransfer(swapResponse.outboundTransferId, transferTimeoutMs);
2198
+ if (!claimed) {
2165
2199
  return {
2166
2200
  success: false,
2167
2201
  poolId: quote.poolId,
2168
2202
  tokenAmountSpent: quote.tokenAmountRequired,
2169
- btcAmountReceived: btcReceived,
2203
+ btcAmountReceived: swapResponse.amountOut || "0",
2170
2204
  swapTransferId: swapResponse.outboundTransferId,
2171
2205
  ammFeePaid: quote.estimatedAmmFee,
2172
2206
  sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2173
- error: `BTC received (${btcReceived} sats) is not enough to cover lightning fee (${effectiveMaxLightningFee} sats).`,
2207
+ error: "Transfer did not complete within timeout",
2174
2208
  };
2175
2209
  }
2176
- invoiceAmountPaid = Number(amountToPay);
2177
- lightningPayment = await this._wallet.payLightningInvoice({
2178
- invoice,
2179
- amountSats: invoiceAmountPaid,
2180
- maxFeeSats: effectiveMaxLightningFee,
2181
- preferSpark,
2182
- });
2183
2210
  }
2184
- else {
2185
- // Standard invoice: pay the specified amount
2186
- lightningPayment = await this._wallet.payLightningInvoice({
2187
- invoice,
2188
- maxFeeSats: effectiveMaxLightningFee,
2189
- preferSpark,
2190
- });
2211
+ // Step 6: Calculate payment amount
2212
+ const requestedMaxLightningFee = effectiveMaxLightningFee;
2213
+ const btcReceived = swapResponse.amountOut || quote.btcAmountRequired;
2214
+ // Cap the lightning fee budget to what the wallet can actually cover.
2215
+ // The swap output may be slightly less than quoted due to rounding or
2216
+ // price movement between quote and execution. The Spark SDK requires
2217
+ // invoiceAmount + maxFeeSats <= balance, so we adjust maxFeeSats down
2218
+ // when the actual BTC received is less than expected.
2219
+ let cappedMaxLightningFee = requestedMaxLightningFee;
2220
+ if (!quote.isZeroAmountInvoice) {
2221
+ const actualBtc = bigint.safeBigInt(btcReceived);
2222
+ const invoiceAmount = bigint.safeBigInt(quote.invoiceAmountSats);
2223
+ const available = actualBtc - invoiceAmount;
2224
+ if (available > 0n && available < bigint.safeBigInt(cappedMaxLightningFee)) {
2225
+ cappedMaxLightningFee = Number(available);
2226
+ }
2191
2227
  }
2192
- // Extract the Spark transfer ID from the lightning payment result.
2193
- // payLightningInvoice returns LightningSendRequest | WalletTransfer:
2194
- // - LightningSendRequest has .transfer?.sparkId (the Sparkscan-visible transfer ID)
2195
- // - WalletTransfer (Spark-to-Spark) has .id directly as the transfer ID
2196
- // Note: lightningPayment.id (the SSP request ID) is already returned as lightningPaymentId
2197
- const sparkLightningTransferId = lightningPayment.transfer?.sparkId;
2198
- return {
2199
- success: true,
2200
- poolId: quote.poolId,
2201
- tokenAmountSpent: quote.tokenAmountRequired,
2202
- btcAmountReceived: btcReceived,
2203
- swapTransferId: swapResponse.outboundTransferId,
2204
- lightningPaymentId: lightningPayment.id,
2205
- ammFeePaid: quote.estimatedAmmFee,
2206
- lightningFeePaid: effectiveMaxLightningFee,
2207
- invoiceAmountPaid,
2208
- sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2209
- sparkLightningTransferId,
2210
- };
2211
- }
2212
- catch (lightningError) {
2213
- // Lightning payment failed after swap succeeded
2214
- const lightningErrorMessage = lightningError instanceof Error
2215
- ? lightningError.message
2216
- : String(lightningError);
2217
- // Attempt rollback if requested
2218
- if (rollbackOnFailure) {
2219
- try {
2220
- const rollbackResult = await this.rollbackSwap(quote.poolId, btcReceived, tokenAddress, maxSlippageBps);
2221
- if (rollbackResult.success) {
2228
+ // Step 7: Pay the Lightning invoice
2229
+ try {
2230
+ let lightningPayment;
2231
+ let invoiceAmountPaid;
2232
+ if (quote.isZeroAmountInvoice) {
2233
+ const actualBtc = bigint.safeBigInt(btcReceived);
2234
+ const lnFee = bigint.safeBigInt(cappedMaxLightningFee);
2235
+ const amountToPay = actualBtc - lnFee;
2236
+ if (amountToPay <= 0n) {
2222
2237
  return {
2223
2238
  success: false,
2224
2239
  poolId: quote.poolId,
2225
- tokenAmountSpent: "0", // Rolled back
2226
- btcAmountReceived: "0",
2240
+ tokenAmountSpent: quote.tokenAmountRequired,
2241
+ btcAmountReceived: btcReceived,
2227
2242
  swapTransferId: swapResponse.outboundTransferId,
2228
2243
  ammFeePaid: quote.estimatedAmmFee,
2229
2244
  sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2230
- error: `Lightning payment failed: ${lightningErrorMessage}. Funds rolled back to ${rollbackResult.tokenAmount} tokens.`,
2245
+ error: `BTC received (${btcReceived} sats) is not enough to cover lightning fee (${cappedMaxLightningFee} sats).`,
2231
2246
  };
2232
2247
  }
2248
+ invoiceAmountPaid = Number(amountToPay);
2249
+ lightningPayment = await this._wallet.payLightningInvoice({
2250
+ invoice,
2251
+ amountSats: invoiceAmountPaid,
2252
+ maxFeeSats: cappedMaxLightningFee,
2253
+ preferSpark,
2254
+ });
2233
2255
  }
2234
- catch (rollbackError) {
2235
- const rollbackErrorMessage = rollbackError instanceof Error
2236
- ? rollbackError.message
2237
- : String(rollbackError);
2238
- return {
2239
- success: false,
2240
- poolId: quote.poolId,
2241
- tokenAmountSpent: quote.tokenAmountRequired,
2242
- btcAmountReceived: btcReceived,
2243
- swapTransferId: swapResponse.outboundTransferId,
2244
- ammFeePaid: quote.estimatedAmmFee,
2245
- sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2246
- error: `Lightning payment failed: ${lightningErrorMessage}. Rollback also failed: ${rollbackErrorMessage}. BTC remains in wallet.`,
2247
- };
2256
+ else {
2257
+ lightningPayment = await this._wallet.payLightningInvoice({
2258
+ invoice,
2259
+ maxFeeSats: cappedMaxLightningFee,
2260
+ preferSpark,
2261
+ });
2248
2262
  }
2263
+ // Extract the Spark transfer ID from the lightning payment result.
2264
+ // payLightningInvoice returns LightningSendRequest | WalletTransfer:
2265
+ // - LightningSendRequest has .transfer?.sparkId (the Sparkscan-visible transfer ID)
2266
+ // - WalletTransfer (Spark-to-Spark) has .id directly as the transfer ID
2267
+ // Note: lightningPayment.id (the SSP request ID) is already returned as lightningPaymentId
2268
+ const sparkLightningTransferId = lightningPayment.transfer?.sparkId;
2269
+ return {
2270
+ success: true,
2271
+ poolId: quote.poolId,
2272
+ tokenAmountSpent: quote.tokenAmountRequired,
2273
+ btcAmountReceived: btcReceived,
2274
+ swapTransferId: swapResponse.outboundTransferId,
2275
+ lightningPaymentId: lightningPayment.id,
2276
+ ammFeePaid: quote.estimatedAmmFee,
2277
+ lightningFeePaid: cappedMaxLightningFee,
2278
+ invoiceAmountPaid,
2279
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2280
+ sparkLightningTransferId,
2281
+ };
2282
+ }
2283
+ catch (lightningError) {
2284
+ // Lightning payment failed after swap succeeded
2285
+ const lightningErrorMessage = lightningError instanceof Error
2286
+ ? lightningError.message
2287
+ : String(lightningError);
2288
+ // Attempt rollback if requested
2289
+ if (rollbackOnFailure) {
2290
+ try {
2291
+ const rollbackResult = await this.rollbackSwap(quote.poolId, btcReceived, tokenAddress, maxSlippageBps);
2292
+ if (rollbackResult.success) {
2293
+ return {
2294
+ success: false,
2295
+ poolId: quote.poolId,
2296
+ tokenAmountSpent: "0", // Rolled back
2297
+ btcAmountReceived: "0",
2298
+ swapTransferId: swapResponse.outboundTransferId,
2299
+ ammFeePaid: quote.estimatedAmmFee,
2300
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2301
+ error: `Lightning payment failed: ${lightningErrorMessage}. Funds rolled back to ${rollbackResult.tokenAmount} tokens.`,
2302
+ };
2303
+ }
2304
+ }
2305
+ catch (rollbackError) {
2306
+ const rollbackErrorMessage = rollbackError instanceof Error
2307
+ ? rollbackError.message
2308
+ : String(rollbackError);
2309
+ return {
2310
+ success: false,
2311
+ poolId: quote.poolId,
2312
+ tokenAmountSpent: quote.tokenAmountRequired,
2313
+ btcAmountReceived: btcReceived,
2314
+ swapTransferId: swapResponse.outboundTransferId,
2315
+ ammFeePaid: quote.estimatedAmmFee,
2316
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2317
+ error: `Lightning payment failed: ${lightningErrorMessage}. Rollback also failed: ${rollbackErrorMessage}. BTC remains in wallet.`,
2318
+ };
2319
+ }
2320
+ }
2321
+ return {
2322
+ success: false,
2323
+ poolId: quote.poolId,
2324
+ tokenAmountSpent: quote.tokenAmountRequired,
2325
+ btcAmountReceived: btcReceived,
2326
+ swapTransferId: swapResponse.outboundTransferId,
2327
+ ammFeePaid: quote.estimatedAmmFee,
2328
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2329
+ error: `Lightning payment failed: ${lightningErrorMessage}. BTC (${btcReceived} sats) remains in wallet.`,
2330
+ };
2249
2331
  }
2250
- return {
2251
- success: false,
2252
- poolId: quote.poolId,
2253
- tokenAmountSpent: quote.tokenAmountRequired,
2254
- btcAmountReceived: btcReceived,
2255
- swapTransferId: swapResponse.outboundTransferId,
2256
- ammFeePaid: quote.estimatedAmmFee,
2257
- sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2258
- error: `Lightning payment failed: ${lightningErrorMessage}. BTC (${btcReceived} sats) remains in wallet.`,
2259
- };
2332
+ }
2333
+ finally {
2334
+ restoreOptimization();
2260
2335
  }
2261
2336
  }
2262
2337
  catch (error) {
@@ -2777,6 +2852,73 @@ class FlashnetClient {
2777
2852
  }
2778
2853
  return false;
2779
2854
  }
2855
+ /**
2856
+ * Suppress leaf optimization on the wallet. Sets the internal
2857
+ * optimizationInProgress flag so optimizeLeaves() returns immediately.
2858
+ * Returns a restore function that clears the flag.
2859
+ * @private
2860
+ */
2861
+ suppressOptimization() {
2862
+ const w = this._wallet;
2863
+ const was = w.optimizationInProgress;
2864
+ w.optimizationInProgress = true;
2865
+ return () => {
2866
+ w.optimizationInProgress = was;
2867
+ };
2868
+ }
2869
+ /**
2870
+ * Insta-claim: listen for the wallet's stream event that fires when
2871
+ * the coordinator broadcasts the transfer. The stream auto-claims
2872
+ * incoming transfers, so no polling is needed.
2873
+ *
2874
+ * After claim, refreshes the leaf cache from the coordinator to
2875
+ * ensure the balance is current.
2876
+ *
2877
+ * Caller is responsible for suppressing optimization around this call
2878
+ * if the claimed leaves must not be swapped before spending.
2879
+ * @private
2880
+ */
2881
+ async instaClaimTransfer(transferId, timeoutMs) {
2882
+ const w = this._wallet;
2883
+ const claimed = await new Promise((resolve) => {
2884
+ let done = false;
2885
+ const finish = (value) => {
2886
+ if (done) {
2887
+ return;
2888
+ }
2889
+ done = true;
2890
+ clearTimeout(timer);
2891
+ try {
2892
+ w.removeListener?.("transfer:claimed", handler);
2893
+ }
2894
+ catch {
2895
+ // Ignore
2896
+ }
2897
+ resolve(value);
2898
+ };
2899
+ const timer = setTimeout(() => finish(false), timeoutMs);
2900
+ const handler = (claimedId) => {
2901
+ if (claimedId === transferId) {
2902
+ finish(true);
2903
+ }
2904
+ };
2905
+ // The wallet's background gRPC stream auto-claims transfers.
2906
+ // We just listen for the event.
2907
+ if (typeof w.on === "function") {
2908
+ w.on("transfer:claimed", handler);
2909
+ }
2910
+ else {
2911
+ // No event support, fall back to passive polling
2912
+ clearTimeout(timer);
2913
+ this.pollForTransferCompletion(transferId, timeoutMs).then(resolve);
2914
+ }
2915
+ });
2916
+ if (claimed) {
2917
+ const leaves = await this._wallet.getLeaves(true);
2918
+ w.leaves = leaves;
2919
+ }
2920
+ return claimed;
2921
+ }
2780
2922
  /**
2781
2923
  * Get Lightning fee estimate for an invoice
2782
2924
  * @private