@flashnet/sdk 0.5.4 → 0.5.6

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.
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var sha256 = require('fast-sha256');
4
+ var lightBolt11Decoder = require('light-bolt11-decoder');
4
5
  var client = require('../api/client.js');
5
6
  var typedEndpoints = require('../api/typed-endpoints.js');
6
7
  var index$1 = require('../config/index.js');
@@ -11,6 +12,7 @@ var hex = require('../utils/hex.js');
11
12
  var intents = require('../utils/intents.js');
12
13
  var sparkAddress = require('../utils/spark-address.js');
13
14
  var tokenAddress = require('../utils/tokenAddress.js');
15
+ var bigint = require('../utils/bigint.js');
14
16
  var errors = require('../types/errors.js');
15
17
 
16
18
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -245,7 +247,10 @@ class FlashnetClient {
245
247
  const tokenIdentifierHex = hex.getHexFromUint8Array(info.rawTokenIdentifier);
246
248
  const tokenAddress$1 = tokenAddress.encodeSparkHumanReadableTokenIdentifier(info.rawTokenIdentifier, this.sparkNetwork);
247
249
  tokenBalances.set(tokenPubkey, {
248
- balance: BigInt(tokenData.balance),
250
+ balance: bigint.safeBigInt(tokenData.ownedBalance ?? tokenData.balance),
251
+ availableToSendBalance: bigint.safeBigInt(tokenData.availableToSendBalance ??
252
+ tokenData.ownedBalance ??
253
+ tokenData.balance),
249
254
  tokenInfo: {
250
255
  tokenIdentifier: tokenIdentifierHex,
251
256
  tokenAddress: tokenAddress$1,
@@ -258,7 +263,7 @@ class FlashnetClient {
258
263
  }
259
264
  }
260
265
  return {
261
- balance: BigInt(balance.balance),
266
+ balance: bigint.safeBigInt(balance.balance),
262
267
  tokenBalances,
263
268
  };
264
269
  }
@@ -266,7 +271,7 @@ class FlashnetClient {
266
271
  * Check if wallet has sufficient balance for an operation
267
272
  */
268
273
  async checkBalance(params) {
269
- const balance = await this.getBalance();
274
+ const balance = params.walletBalance ?? (await this.getBalance());
270
275
  // Check balance
271
276
  const requirements = {
272
277
  tokens: new Map(),
@@ -290,11 +295,15 @@ class FlashnetClient {
290
295
  // Check token balances
291
296
  if (requirements.tokens) {
292
297
  for (const [tokenPubkey, requiredAmount,] of requirements.tokens.entries()) {
293
- // If direct lookup fails (possible representation mismatch), try the human-readable form
298
+ // Support both hex and Bech32m token identifiers by trying all representations
294
299
  const hrKey = this.toHumanReadableTokenIdentifier(tokenPubkey);
300
+ const hexKey = this.toHexTokenIdentifier(tokenPubkey);
295
301
  const effectiveTokenBalance = balance.tokenBalances.get(tokenPubkey) ??
296
- balance.tokenBalances.get(hrKey);
297
- const available = effectiveTokenBalance?.balance ?? 0n;
302
+ balance.tokenBalances.get(hrKey) ??
303
+ balance.tokenBalances.get(hexKey);
304
+ const available = params.useAvailableBalance
305
+ ? (effectiveTokenBalance?.availableToSendBalance ?? 0n)
306
+ : (effectiveTokenBalance?.balance ?? 0n);
298
307
  if (available < requiredAmount) {
299
308
  throw new Error([
300
309
  params.errorPrefix ?? "",
@@ -356,6 +365,7 @@ class FlashnetClient {
356
365
  },
357
366
  ],
358
367
  errorPrefix: "Insufficient balance for initial liquidity: ",
368
+ useAvailableBalance: params.useAvailableBalance,
359
369
  });
360
370
  }
361
371
  const poolOwnerPublicKey = params.poolOwnerPublicKey ?? this.publicKey;
@@ -485,6 +495,7 @@ class FlashnetClient {
485
495
  },
486
496
  ],
487
497
  errorPrefix: "Insufficient balance for pool creation: ",
498
+ useAvailableBalance: params.useAvailableBalance,
488
499
  });
489
500
  const poolOwnerPublicKey = params.poolOwnerPublicKey ?? this.publicKey;
490
501
  // Generate intent
@@ -605,10 +616,14 @@ class FlashnetClient {
605
616
  });
606
617
  // If using free balance (V3 pools only), skip the Spark transfer
607
618
  if (params.useFreeBalance) {
608
- return this.executeSwapIntent({
619
+ const swapResponse = await this.executeSwapIntent({
609
620
  ...params,
610
621
  // No transferId - triggers free balance mode
611
622
  });
623
+ return {
624
+ ...swapResponse,
625
+ inboundSparkTransferId: swapResponse.requestId,
626
+ };
612
627
  }
613
628
  // Transfer assets to pool using new address encoding
614
629
  const lpSparkAddress = sparkAddress.encodeSparkAddressNew({
@@ -619,12 +634,13 @@ class FlashnetClient {
619
634
  receiverSparkAddress: lpSparkAddress,
620
635
  assetAddress: params.assetInAddress,
621
636
  amount: params.amountIn,
622
- }, "Insufficient balance for swap: ");
637
+ }, "Insufficient balance for swap: ", params.useAvailableBalance);
623
638
  // Execute with auto-clawback on failure
624
- return this.executeWithAutoClawback(() => this.executeSwapIntent({
639
+ const swapResponse = await this.executeWithAutoClawback(() => this.executeSwapIntent({
625
640
  ...params,
626
641
  transferId,
627
642
  }), [transferId], params.poolId);
643
+ return { ...swapResponse, inboundSparkTransferId: transferId };
628
644
  }
629
645
  /**
630
646
  * Execute a swap with a pre-created transfer or using free balance.
@@ -754,7 +770,7 @@ class FlashnetClient {
754
770
  receiverSparkAddress: lpSparkAddress,
755
771
  assetAddress: params.initialAssetAddress,
756
772
  amount: params.inputAmount,
757
- }, "Insufficient balance for route swap: ");
773
+ }, "Insufficient balance for route swap: ", params.useAvailableBalance);
758
774
  // Execute with auto-clawback on failure
759
775
  return this.executeWithAutoClawback(async () => {
760
776
  // Prepare hops for validation
@@ -877,7 +893,7 @@ class FlashnetClient {
877
893
  assetAddress: pool.assetBAddress,
878
894
  amount: params.assetBAmount,
879
895
  },
880
- ], "Insufficient balance for adding liquidity: ");
896
+ ], "Insufficient balance for adding liquidity: ", params.useAvailableBalance);
881
897
  // Execute with auto-clawback on failure
882
898
  return this.executeWithAutoClawback(async () => {
883
899
  // Generate add liquidity intent
@@ -1183,6 +1199,7 @@ class FlashnetClient {
1183
1199
  depositAddress: createResponse.depositAddress,
1184
1200
  assetId: params.assetId,
1185
1201
  assetAmount: params.assetAmount,
1202
+ useAvailableBalance: params.useAvailableBalance,
1186
1203
  });
1187
1204
  }
1188
1205
  /**
@@ -1200,6 +1217,7 @@ class FlashnetClient {
1200
1217
  { assetAddress: params.assetId, amount: params.assetAmount },
1201
1218
  ],
1202
1219
  errorPrefix: "Insufficient balance to fund escrow: ",
1220
+ useAvailableBalance: params.useAvailableBalance,
1203
1221
  });
1204
1222
  // 2. Perform transfer
1205
1223
  const escrowSparkAddress = sparkAddress.encodeSparkAddressNew({
@@ -1714,19 +1732,20 @@ class FlashnetClient {
1714
1732
  /**
1715
1733
  * Performs asset transfer using generalized asset address for both BTC and tokens.
1716
1734
  */
1717
- async transferAsset(recipient, checkBalanceErrorPrefix) {
1718
- const transferIds = await this.transferAssets([recipient], checkBalanceErrorPrefix);
1735
+ async transferAsset(recipient, checkBalanceErrorPrefix, useAvailableBalance) {
1736
+ const transferIds = await this.transferAssets([recipient], checkBalanceErrorPrefix, useAvailableBalance);
1719
1737
  return transferIds[0];
1720
1738
  }
1721
1739
  /**
1722
1740
  * Performs asset transfers using generalized asset addresses for both BTC and tokens.
1723
1741
  * Supports optional generic to hardcode recipients length so output list can be typed with same length.
1724
1742
  */
1725
- async transferAssets(recipients, checkBalanceErrorPrefix) {
1743
+ async transferAssets(recipients, checkBalanceErrorPrefix, useAvailableBalance) {
1726
1744
  if (checkBalanceErrorPrefix) {
1727
1745
  await this.checkBalance({
1728
1746
  balancesToCheck: recipients,
1729
1747
  errorPrefix: checkBalanceErrorPrefix,
1748
+ useAvailableBalance,
1730
1749
  });
1731
1750
  }
1732
1751
  const transferIds = [];
@@ -1875,7 +1894,7 @@ class FlashnetClient {
1875
1894
  const tokenHex = this.toHexTokenIdentifier(tokenAddress).toLowerCase();
1876
1895
  const tokenMinAmount = minAmounts.get(tokenHex);
1877
1896
  if (tokenMinAmount &&
1878
- BigInt(poolQuote.tokenAmountRequired) < tokenMinAmount) {
1897
+ bigint.safeBigInt(poolQuote.tokenAmountRequired) < tokenMinAmount) {
1879
1898
  const msg = `Token amount too small. Minimum input is ${tokenMinAmount} units, but calculated amount is only ${poolQuote.tokenAmountRequired} units.`;
1880
1899
  throw new errors.FlashnetError(msg, {
1881
1900
  response: {
@@ -1892,7 +1911,7 @@ class FlashnetClient {
1892
1911
  }
1893
1912
  // BTC variable fee adjustment: difference between what the pool targets and unmasked base.
1894
1913
  // For V3 pools this is 0 (no masking). For V2 it's the rounding overhead.
1895
- const btcVariableFeeAdjustment = Number(BigInt(poolQuote.btcAmountUsed) - baseBtcNeeded);
1914
+ const btcVariableFeeAdjustment = Number(bigint.safeBigInt(poolQuote.btcAmountUsed) - baseBtcNeeded);
1896
1915
  return {
1897
1916
  poolId: poolQuote.poolId,
1898
1917
  tokenAddress: this.toHexTokenIdentifier(tokenAddress),
@@ -1964,7 +1983,7 @@ class FlashnetClient {
1964
1983
  amountIn: tokenAmount,
1965
1984
  integratorBps: options?.integratorFeeRateBps,
1966
1985
  });
1967
- const btcOut = BigInt(simulation.amountOut);
1986
+ const btcOut = bigint.safeBigInt(simulation.amountOut);
1968
1987
  if (btcOut > bestBtcOut) {
1969
1988
  bestBtcOut = btcOut;
1970
1989
  bestResult = {
@@ -2052,7 +2071,7 @@ class FlashnetClient {
2052
2071
  await this.ensureInitialized();
2053
2072
  const { invoice, tokenAddress, tokenAmount, maxSlippageBps = 500, // 5% default
2054
2073
  maxLightningFeeSats, preferSpark = true, integratorFeeRateBps, integratorPublicKey, transferTimeoutMs = 30000, // 30s default
2055
- rollbackOnFailure = false, useExistingBtcBalance = false, } = options;
2074
+ rollbackOnFailure = false, useExistingBtcBalance = false, useAvailableBalance = false, } = options;
2056
2075
  try {
2057
2076
  // Step 1: Get a quote for the payment
2058
2077
  const quote = await this.getPayLightningWithTokenQuote(invoice, tokenAddress, {
@@ -2069,6 +2088,7 @@ class FlashnetClient {
2069
2088
  },
2070
2089
  ],
2071
2090
  errorPrefix: "Insufficient token balance for Lightning payment: ",
2091
+ useAvailableBalance,
2072
2092
  });
2073
2093
  // Step 3: Get pool details
2074
2094
  const pool = await this.getPool(quote.poolId);
@@ -2091,6 +2111,7 @@ class FlashnetClient {
2091
2111
  minAmountOut: minBtcOut,
2092
2112
  integratorFeeRateBps,
2093
2113
  integratorPublicKey,
2114
+ useAvailableBalance,
2094
2115
  });
2095
2116
  if (!swapResponse.accepted || !swapResponse.outboundTransferId) {
2096
2117
  return {
@@ -2100,6 +2121,7 @@ class FlashnetClient {
2100
2121
  btcAmountReceived: "0",
2101
2122
  swapTransferId: swapResponse.outboundTransferId || "",
2102
2123
  ammFeePaid: quote.estimatedAmmFee,
2124
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2103
2125
  error: swapResponse.error || "Swap was not accepted",
2104
2126
  };
2105
2127
  }
@@ -2110,7 +2132,7 @@ class FlashnetClient {
2110
2132
  const effectiveMaxLightningFee = maxLightningFeeSats ?? quote.estimatedLightningFee;
2111
2133
  const btcNeededForPayment = invoiceAmountSats + effectiveMaxLightningFee;
2112
2134
  const balance = await this.getBalance();
2113
- canPayImmediately = balance.balance >= BigInt(btcNeededForPayment);
2135
+ canPayImmediately = balance.balance >= bigint.safeBigInt(btcNeededForPayment);
2114
2136
  }
2115
2137
  if (!canPayImmediately) {
2116
2138
  const transferComplete = await this.waitForTransferCompletion(swapResponse.outboundTransferId, transferTimeoutMs);
@@ -2122,6 +2144,7 @@ class FlashnetClient {
2122
2144
  btcAmountReceived: swapResponse.amountOut || "0",
2123
2145
  swapTransferId: swapResponse.outboundTransferId,
2124
2146
  ammFeePaid: quote.estimatedAmmFee,
2147
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2125
2148
  error: "Transfer did not complete within timeout",
2126
2149
  };
2127
2150
  }
@@ -2135,8 +2158,8 @@ class FlashnetClient {
2135
2158
  let invoiceAmountPaid;
2136
2159
  if (quote.isZeroAmountInvoice) {
2137
2160
  // Zero-amount invoice: pay whatever BTC we received minus lightning fee
2138
- const actualBtc = BigInt(btcReceived);
2139
- const lnFee = BigInt(effectiveMaxLightningFee);
2161
+ const actualBtc = bigint.safeBigInt(btcReceived);
2162
+ const lnFee = bigint.safeBigInt(effectiveMaxLightningFee);
2140
2163
  const amountToPay = actualBtc - lnFee;
2141
2164
  if (amountToPay <= 0n) {
2142
2165
  return {
@@ -2146,6 +2169,7 @@ class FlashnetClient {
2146
2169
  btcAmountReceived: btcReceived,
2147
2170
  swapTransferId: swapResponse.outboundTransferId,
2148
2171
  ammFeePaid: quote.estimatedAmmFee,
2172
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2149
2173
  error: `BTC received (${btcReceived} sats) is not enough to cover lightning fee (${effectiveMaxLightningFee} sats).`,
2150
2174
  };
2151
2175
  }
@@ -2165,6 +2189,12 @@ class FlashnetClient {
2165
2189
  preferSpark,
2166
2190
  });
2167
2191
  }
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;
2168
2198
  return {
2169
2199
  success: true,
2170
2200
  poolId: quote.poolId,
@@ -2175,6 +2205,8 @@ class FlashnetClient {
2175
2205
  ammFeePaid: quote.estimatedAmmFee,
2176
2206
  lightningFeePaid: effectiveMaxLightningFee,
2177
2207
  invoiceAmountPaid,
2208
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2209
+ sparkLightningTransferId,
2178
2210
  };
2179
2211
  }
2180
2212
  catch (lightningError) {
@@ -2194,6 +2226,7 @@ class FlashnetClient {
2194
2226
  btcAmountReceived: "0",
2195
2227
  swapTransferId: swapResponse.outboundTransferId,
2196
2228
  ammFeePaid: quote.estimatedAmmFee,
2229
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2197
2230
  error: `Lightning payment failed: ${lightningErrorMessage}. Funds rolled back to ${rollbackResult.tokenAmount} tokens.`,
2198
2231
  };
2199
2232
  }
@@ -2209,6 +2242,7 @@ class FlashnetClient {
2209
2242
  btcAmountReceived: btcReceived,
2210
2243
  swapTransferId: swapResponse.outboundTransferId,
2211
2244
  ammFeePaid: quote.estimatedAmmFee,
2245
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2212
2246
  error: `Lightning payment failed: ${lightningErrorMessage}. Rollback also failed: ${rollbackErrorMessage}. BTC remains in wallet.`,
2213
2247
  };
2214
2248
  }
@@ -2220,6 +2254,7 @@ class FlashnetClient {
2220
2254
  btcAmountReceived: btcReceived,
2221
2255
  swapTransferId: swapResponse.outboundTransferId,
2222
2256
  ammFeePaid: quote.estimatedAmmFee,
2257
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2223
2258
  error: `Lightning payment failed: ${lightningErrorMessage}. BTC (${btcReceived} sats) remains in wallet.`,
2224
2259
  };
2225
2260
  }
@@ -2382,7 +2417,7 @@ class FlashnetClient {
2382
2417
  tokenIsAssetA: pool.tokenIsAssetA,
2383
2418
  integratorBps: integratorFeeRateBps,
2384
2419
  });
2385
- tokenAmount = BigInt(v3Result.amountIn);
2420
+ tokenAmount = bigint.safeBigInt(v3Result.amountIn);
2386
2421
  fee = v3Result.totalFee;
2387
2422
  executionPrice = v3Result.simulation.executionPrice || "0";
2388
2423
  priceImpactPct = v3Result.simulation.priceImpactPct || "0";
@@ -2391,7 +2426,7 @@ class FlashnetClient {
2391
2426
  else {
2392
2427
  // V2: constant product math + simulation verification
2393
2428
  const calculation = this.calculateTokenAmountForBtcOutput(btcTarget.toString(), poolDetails.assetAReserve, poolDetails.assetBReserve, poolDetails.lpFeeBps, poolDetails.hostFeeBps, pool.tokenIsAssetA, integratorFeeRateBps);
2394
- tokenAmount = BigInt(calculation.amountIn);
2429
+ tokenAmount = bigint.safeBigInt(calculation.amountIn);
2395
2430
  // Verify with simulation
2396
2431
  const simulation = await this.simulateSwap({
2397
2432
  poolId: pool.lpPublicKey,
@@ -2400,7 +2435,7 @@ class FlashnetClient {
2400
2435
  amountIn: calculation.amountIn,
2401
2436
  integratorBps: integratorFeeRateBps,
2402
2437
  });
2403
- if (BigInt(simulation.amountOut) < btcTarget) {
2438
+ if (bigint.safeBigInt(simulation.amountOut) < btcTarget) {
2404
2439
  const btcReserve = pool.tokenIsAssetA
2405
2440
  ? poolDetails.assetBReserve
2406
2441
  : poolDetails.assetAReserve;
@@ -2488,9 +2523,9 @@ class FlashnetClient {
2488
2523
  * @private
2489
2524
  */
2490
2525
  calculateTokenAmountForBtcOutput(btcAmountOut, reserveA, reserveB, lpFeeBps, hostFeeBps, tokenIsAssetA, integratorFeeBps) {
2491
- const amountOut = BigInt(btcAmountOut);
2492
- const resA = BigInt(reserveA);
2493
- const resB = BigInt(reserveB);
2526
+ const amountOut = bigint.safeBigInt(btcAmountOut);
2527
+ const resA = bigint.safeBigInt(reserveA);
2528
+ const resB = bigint.safeBigInt(reserveB);
2494
2529
  const totalFeeBps = lpFeeBps + hostFeeBps + (integratorFeeBps || 0);
2495
2530
  const feeRate = Number(totalFeeBps) / 10000; // Convert bps to decimal
2496
2531
  // Token is the input asset
@@ -2588,7 +2623,7 @@ class FlashnetClient {
2588
2623
  amountIn: upperBound.toString(),
2589
2624
  integratorBps,
2590
2625
  });
2591
- if (BigInt(sim.amountOut) >= desiredBtcOut) {
2626
+ if (bigint.safeBigInt(sim.amountOut) >= desiredBtcOut) {
2592
2627
  upperSim = sim;
2593
2628
  break;
2594
2629
  }
@@ -2599,7 +2634,7 @@ class FlashnetClient {
2599
2634
  throw new Error(`V3 pool ${poolId} has insufficient liquidity for ${desiredBtcOut} sats`);
2600
2635
  }
2601
2636
  // Step 3: Refine estimate via linear interpolation
2602
- const upperOut = BigInt(upperSim.amountOut);
2637
+ const upperOut = bigint.safeBigInt(upperSim.amountOut);
2603
2638
  // Scale proportionally: if upperBound produced upperOut, we need roughly
2604
2639
  // (upperBound * desiredBtcOut / upperOut). Add +1 to avoid undershoot from truncation.
2605
2640
  let refined = (upperBound * desiredBtcOut) / upperOut + 1n;
@@ -2617,7 +2652,7 @@ class FlashnetClient {
2617
2652
  amountIn: refined.toString(),
2618
2653
  integratorBps,
2619
2654
  });
2620
- if (BigInt(refinedSim.amountOut) >= desiredBtcOut) {
2655
+ if (bigint.safeBigInt(refinedSim.amountOut) >= desiredBtcOut) {
2621
2656
  bestAmountIn = refined;
2622
2657
  bestSim = refinedSim;
2623
2658
  }
@@ -2651,7 +2686,7 @@ class FlashnetClient {
2651
2686
  amountIn: mid.toString(),
2652
2687
  integratorBps,
2653
2688
  });
2654
- if (BigInt(midSim.amountOut) >= desiredBtcOut) {
2689
+ if (bigint.safeBigInt(midSim.amountOut) >= desiredBtcOut) {
2655
2690
  hi = mid;
2656
2691
  bestAmountIn = mid;
2657
2692
  bestSim = midSim;
@@ -2770,64 +2805,24 @@ class FlashnetClient {
2770
2805
  }
2771
2806
  /**
2772
2807
  * Decode the amount from a Lightning invoice (in sats)
2808
+ * Uses light-bolt11-decoder (same library as Spark SDK) for reliable parsing.
2773
2809
  * @private
2774
2810
  */
2775
2811
  async decodeInvoiceAmount(invoice) {
2776
- // Extract amount from BOLT11 invoice
2777
- // Format: ln[network][amount][multiplier]...
2778
- // Amount multipliers: m = milli (0.001), u = micro (0.000001), n = nano, p = pico
2779
- const lowerInvoice = invoice.toLowerCase();
2780
- // Find where the amount starts (after network prefix)
2781
- let amountStart = 0;
2782
- if (lowerInvoice.startsWith("lnbc")) {
2783
- amountStart = 4;
2784
- }
2785
- else if (lowerInvoice.startsWith("lntb")) {
2786
- amountStart = 4;
2787
- }
2788
- else if (lowerInvoice.startsWith("lnbcrt")) {
2789
- amountStart = 6;
2790
- }
2791
- else if (lowerInvoice.startsWith("lntbs")) {
2792
- amountStart = 5;
2812
+ try {
2813
+ const decoded = lightBolt11Decoder.decode(invoice);
2814
+ const amountSection = decoded.sections.find((s) => s.name === "amount");
2815
+ if (!amountSection?.value) {
2816
+ return 0; // Zero-amount invoice
2817
+ }
2818
+ // The library returns amount in millisatoshis as a string
2819
+ const amountMSats = BigInt(amountSection.value);
2820
+ return Number(amountMSats / 1000n);
2793
2821
  }
2794
- else {
2795
- // Unknown format, try to find amount
2796
- const match = lowerInvoice.match(/^ln[a-z]+/);
2797
- if (match) {
2798
- amountStart = match[0].length;
2799
- }
2800
- }
2801
- // Extract amount and multiplier
2802
- const afterPrefix = lowerInvoice.substring(amountStart);
2803
- const amountMatch = afterPrefix.match(/^(\d+)([munp]?)/);
2804
- if (!amountMatch || !amountMatch[1]) {
2805
- return 0; // Zero-amount invoice
2806
- }
2807
- const amount = parseInt(amountMatch[1], 10);
2808
- const multiplier = amountMatch[2] ?? "";
2809
- // Convert to satoshis (1 BTC = 100,000,000 sats)
2810
- // Invoice amounts are in BTC by default
2811
- let btcAmount;
2812
- switch (multiplier) {
2813
- case "m": // milli-BTC (0.001 BTC)
2814
- btcAmount = amount * 0.001;
2815
- break;
2816
- case "u": // micro-BTC (0.000001 BTC)
2817
- btcAmount = amount * 0.000001;
2818
- break;
2819
- case "n": // nano-BTC (0.000000001 BTC)
2820
- btcAmount = amount * 0.000000001;
2821
- break;
2822
- case "p": // pico-BTC (0.000000000001 BTC)
2823
- btcAmount = amount * 0.000000000001;
2824
- break;
2825
- default: // BTC
2826
- btcAmount = amount;
2827
- break;
2822
+ catch {
2823
+ // Fallback: if library fails, return 0 (treated as zero-amount invoice)
2824
+ return 0;
2828
2825
  }
2829
- // Convert BTC to sats
2830
- return Math.round(btcAmount * 100000000);
2831
2826
  }
2832
2827
  /**
2833
2828
  * Clean up wallet connections
@@ -2892,8 +2887,11 @@ class FlashnetClient {
2892
2887
  const map = new Map();
2893
2888
  for (const item of config) {
2894
2889
  if (item.enabled) {
2890
+ if (item.min_amount == null) {
2891
+ continue;
2892
+ }
2895
2893
  const key = item.asset_identifier.toLowerCase();
2896
- const value = BigInt(String(item.min_amount));
2894
+ const value = bigint.safeBigInt(item.min_amount);
2897
2895
  map.set(key, value);
2898
2896
  }
2899
2897
  }
@@ -2915,8 +2913,8 @@ class FlashnetClient {
2915
2913
  const outHex = this.getHexAddress(params.assetOutAddress);
2916
2914
  const minIn = minMap.get(inHex);
2917
2915
  const minOut = minMap.get(outHex);
2918
- const amountIn = BigInt(String(params.amountIn));
2919
- const minAmountOut = BigInt(String(params.minAmountOut));
2916
+ const amountIn = BigInt(params.amountIn);
2917
+ const minAmountOut = BigInt(params.minAmountOut);
2920
2918
  if (minIn && minOut) {
2921
2919
  if (amountIn < minIn) {
2922
2920
  throw new Error(`Minimum amount not met for input asset. Required \
@@ -2950,13 +2948,13 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
2950
2948
  const aMin = minMap.get(aHex);
2951
2949
  const bMin = minMap.get(bHex);
2952
2950
  if (aMin) {
2953
- const aAmt = BigInt(String(params.assetAAmount));
2951
+ const aAmt = BigInt(params.assetAAmount);
2954
2952
  if (aAmt < aMin) {
2955
2953
  throw new Error(`Minimum amount not met for Asset A. Required ${aMin.toString()}, provided ${aAmt.toString()}`);
2956
2954
  }
2957
2955
  }
2958
2956
  if (bMin) {
2959
- const bAmt = BigInt(String(params.assetBAmount));
2957
+ const bAmt = BigInt(params.assetBAmount);
2960
2958
  if (bAmt < bMin) {
2961
2959
  throw new Error(`Minimum amount not met for Asset B. Required ${bMin.toString()}, provided ${bAmt.toString()}`);
2962
2960
  }
@@ -2978,14 +2976,14 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
2978
2976
  const aMin = minMap.get(aHex);
2979
2977
  const bMin = minMap.get(bHex);
2980
2978
  if (aMin) {
2981
- const predictedAOut = BigInt(String(simulation.assetAAmount));
2979
+ const predictedAOut = bigint.safeBigInt(simulation.assetAAmount);
2982
2980
  const relaxedA = aMin / 2n; // apply 50% relaxation for outputs
2983
2981
  if (predictedAOut < relaxedA) {
2984
2982
  throw new Error(`Minimum amount not met for Asset A on withdrawal. Required at least ${relaxedA.toString()} (50% relaxed), predicted ${predictedAOut.toString()}`);
2985
2983
  }
2986
2984
  }
2987
2985
  if (bMin) {
2988
- const predictedBOut = BigInt(String(simulation.assetBAmount));
2986
+ const predictedBOut = bigint.safeBigInt(simulation.assetBAmount);
2989
2987
  const relaxedB = bMin / 2n;
2990
2988
  if (predictedBOut < relaxedB) {
2991
2989
  throw new Error(`Minimum amount not met for Asset B on withdrawal. Required at least ${relaxedB.toString()} (50% relaxed), predicted ${predictedBOut.toString()}`);
@@ -3103,7 +3101,7 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
3103
3101
  receiverSparkAddress: lpSparkAddress,
3104
3102
  assetAddress: pool.assetAAddress,
3105
3103
  amount: params.amountADesired,
3106
- }, "Insufficient balance for adding V3 liquidity (Asset A): ");
3104
+ }, "Insufficient balance for adding V3 liquidity (Asset A): ", params.useAvailableBalance);
3107
3105
  transferIds.push(assetATransferId);
3108
3106
  }
3109
3107
  if (BigInt(params.amountBDesired) > 0n) {
@@ -3111,7 +3109,7 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
3111
3109
  receiverSparkAddress: lpSparkAddress,
3112
3110
  assetAddress: pool.assetBAddress,
3113
3111
  amount: params.amountBDesired,
3114
- }, "Insufficient balance for adding V3 liquidity (Asset B): ");
3112
+ }, "Insufficient balance for adding V3 liquidity (Asset B): ", params.useAvailableBalance);
3115
3113
  transferIds.push(assetBTransferId);
3116
3114
  }
3117
3115
  }
@@ -3304,14 +3302,14 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
3304
3302
  receiverSparkAddress: lpSparkAddress,
3305
3303
  assetAddress: pool.assetAAddress,
3306
3304
  amount: params.additionalAmountA,
3307
- }, "Insufficient balance for rebalance (Asset A): ");
3305
+ }, "Insufficient balance for rebalance (Asset A): ", params.useAvailableBalance);
3308
3306
  }
3309
3307
  if (params.additionalAmountB && BigInt(params.additionalAmountB) > 0n) {
3310
3308
  assetBTransferId = await this.transferAsset({
3311
3309
  receiverSparkAddress: lpSparkAddress,
3312
3310
  assetAddress: pool.assetBAddress,
3313
3311
  amount: params.additionalAmountB,
3314
- }, "Insufficient balance for rebalance (Asset B): ");
3312
+ }, "Insufficient balance for rebalance (Asset B): ", params.useAvailableBalance);
3315
3313
  }
3316
3314
  // Collect transfer IDs for potential clawback
3317
3315
  const transferIds = [];
@@ -3508,7 +3506,7 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
3508
3506
  receiverSparkAddress: lpSparkAddress,
3509
3507
  assetAddress: pool.assetAAddress,
3510
3508
  amount: params.amountA,
3511
- }, "Insufficient balance for depositing to V3 pool (Asset A): ");
3509
+ }, "Insufficient balance for depositing to V3 pool (Asset A): ", params.useAvailableBalance);
3512
3510
  transferIds.push(assetATransferId);
3513
3511
  }
3514
3512
  if (BigInt(params.amountB) > 0n) {
@@ -3516,7 +3514,7 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
3516
3514
  receiverSparkAddress: lpSparkAddress,
3517
3515
  assetAddress: pool.assetBAddress,
3518
3516
  amount: params.amountB,
3519
- }, "Insufficient balance for depositing to V3 pool (Asset B): ");
3517
+ }, "Insufficient balance for depositing to V3 pool (Asset B): ", params.useAvailableBalance);
3520
3518
  transferIds.push(assetBTransferId);
3521
3519
  }
3522
3520
  const executeDeposit = async () => {