@flashnet/sdk 0.5.4 → 0.5.5

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.
@@ -9,6 +9,7 @@ import { getHexFromUint8Array } from '../utils/hex.js';
9
9
  import { generateConstantProductPoolInitializationIntentMessage, generatePoolInitializationIntentMessage, generatePoolConfirmInitialDepositIntentMessage, generatePoolSwapIntentMessage, generateRouteSwapIntentMessage, generateAddLiquidityIntentMessage, generateRemoveLiquidityIntentMessage, generateRegisterHostIntentMessage, generateWithdrawHostFeesIntentMessage, generateWithdrawIntegratorFeesIntentMessage, generateCreateEscrowIntentMessage, generateFundEscrowIntentMessage, generateClaimEscrowIntentMessage, generateClawbackIntentMessage, generateCreateConcentratedPoolIntentMessage, generateDecreaseLiquidityIntentMessage, generateCollectFeesIntentMessage, generateWithdrawBalanceIntentMessage, generateIncreaseLiquidityIntentMessage, generateRebalancePositionIntentMessage, generateDepositBalanceIntentMessage } from '../utils/intents.js';
10
10
  import { getSparkNetworkFromAddress, encodeSparkAddressNew } from '../utils/spark-address.js';
11
11
  import { encodeSparkHumanReadableTokenIdentifier, decodeSparkHumanReadableTokenIdentifier } from '../utils/tokenAddress.js';
12
+ import { safeBigInt } from '../utils/bigint.js';
12
13
  import { FlashnetError } from '../types/errors.js';
13
14
 
14
15
  /**
@@ -239,7 +240,8 @@ class FlashnetClient {
239
240
  const tokenIdentifierHex = getHexFromUint8Array(info.rawTokenIdentifier);
240
241
  const tokenAddress = encodeSparkHumanReadableTokenIdentifier(info.rawTokenIdentifier, this.sparkNetwork);
241
242
  tokenBalances.set(tokenPubkey, {
242
- balance: BigInt(tokenData.balance),
243
+ balance: FlashnetClient.safeBigInt(tokenData.ownedBalance),
244
+ availableToSendBalance: FlashnetClient.safeBigInt(tokenData.availableToSendBalance),
243
245
  tokenInfo: {
244
246
  tokenIdentifier: tokenIdentifierHex,
245
247
  tokenAddress,
@@ -252,7 +254,7 @@ class FlashnetClient {
252
254
  }
253
255
  }
254
256
  return {
255
- balance: BigInt(balance.balance),
257
+ balance: FlashnetClient.safeBigInt(balance.balance),
256
258
  tokenBalances,
257
259
  };
258
260
  }
@@ -260,17 +262,17 @@ class FlashnetClient {
260
262
  * Check if wallet has sufficient balance for an operation
261
263
  */
262
264
  async checkBalance(params) {
263
- const balance = await this.getBalance();
265
+ const balance = params.walletBalance ?? (await this.getBalance());
264
266
  // Check balance
265
267
  const requirements = {
266
268
  tokens: new Map(),
267
269
  };
268
270
  for (const balance of params.balancesToCheck) {
269
271
  if (balance.assetAddress === BTC_ASSET_PUBKEY) {
270
- requirements.btc = BigInt(balance.amount);
272
+ requirements.btc = FlashnetClient.safeBigInt(balance.amount);
271
273
  }
272
274
  else {
273
- requirements.tokens?.set(balance.assetAddress, BigInt(balance.amount));
275
+ requirements.tokens?.set(balance.assetAddress, FlashnetClient.safeBigInt(balance.amount));
274
276
  }
275
277
  }
276
278
  // Check BTC balance
@@ -288,7 +290,9 @@ class FlashnetClient {
288
290
  const hrKey = this.toHumanReadableTokenIdentifier(tokenPubkey);
289
291
  const effectiveTokenBalance = balance.tokenBalances.get(tokenPubkey) ??
290
292
  balance.tokenBalances.get(hrKey);
291
- const available = effectiveTokenBalance?.balance ?? 0n;
293
+ const available = params.useAvailableBalance
294
+ ? (effectiveTokenBalance?.availableToSendBalance ?? 0n)
295
+ : (effectiveTokenBalance?.balance ?? 0n);
292
296
  if (available < requiredAmount) {
293
297
  throw new Error([
294
298
  params.errorPrefix ?? "",
@@ -350,6 +354,7 @@ class FlashnetClient {
350
354
  },
351
355
  ],
352
356
  errorPrefix: "Insufficient balance for initial liquidity: ",
357
+ useAvailableBalance: params.useAvailableBalance,
353
358
  });
354
359
  }
355
360
  const poolOwnerPublicKey = params.poolOwnerPublicKey ?? this.publicKey;
@@ -409,6 +414,12 @@ class FlashnetClient {
409
414
  throw new Error(`${name} must be positive integer`);
410
415
  }
411
416
  }
417
+ /**
418
+ * Safely convert a value to BigInt. Delegates to the shared `safeBigInt` utility.
419
+ */
420
+ static safeBigInt(value, fallback = 0n) {
421
+ return safeBigInt(value, fallback);
422
+ }
412
423
  /**
413
424
  * Calculates virtual reserves for a bonding curve AMM.
414
425
  *
@@ -429,7 +440,7 @@ class FlashnetClient {
429
440
  }
430
441
  const supply = FlashnetClient.parsePositiveIntegerToBigInt(params.initialTokenSupply, "Initial token supply");
431
442
  const targetB = FlashnetClient.parsePositiveIntegerToBigInt(params.targetRaise, "Target raise");
432
- const graduationThresholdPct = BigInt(params.graduationThresholdPct);
443
+ const graduationThresholdPct = FlashnetClient.safeBigInt(params.graduationThresholdPct);
433
444
  // Align bounds with Rust AMM (20%..95%), then check feasibility for g=1 (requires >50%).
434
445
  const MIN_PCT = 20n;
435
446
  const MAX_PCT = 95n;
@@ -479,6 +490,7 @@ class FlashnetClient {
479
490
  },
480
491
  ],
481
492
  errorPrefix: "Insufficient balance for pool creation: ",
493
+ useAvailableBalance: params.useAvailableBalance,
482
494
  });
483
495
  const poolOwnerPublicKey = params.poolOwnerPublicKey ?? this.publicKey;
484
496
  // Generate intent
@@ -599,10 +611,14 @@ class FlashnetClient {
599
611
  });
600
612
  // If using free balance (V3 pools only), skip the Spark transfer
601
613
  if (params.useFreeBalance) {
602
- return this.executeSwapIntent({
614
+ const swapResponse = await this.executeSwapIntent({
603
615
  ...params,
604
616
  // No transferId - triggers free balance mode
605
617
  });
618
+ return {
619
+ ...swapResponse,
620
+ inboundSparkTransferId: swapResponse.requestId,
621
+ };
606
622
  }
607
623
  // Transfer assets to pool using new address encoding
608
624
  const lpSparkAddress = encodeSparkAddressNew({
@@ -613,12 +629,13 @@ class FlashnetClient {
613
629
  receiverSparkAddress: lpSparkAddress,
614
630
  assetAddress: params.assetInAddress,
615
631
  amount: params.amountIn,
616
- }, "Insufficient balance for swap: ");
632
+ }, "Insufficient balance for swap: ", params.useAvailableBalance);
617
633
  // Execute with auto-clawback on failure
618
- return this.executeWithAutoClawback(() => this.executeSwapIntent({
634
+ const swapResponse = await this.executeWithAutoClawback(() => this.executeSwapIntent({
619
635
  ...params,
620
636
  transferId,
621
637
  }), [transferId], params.poolId);
638
+ return { ...swapResponse, inboundSparkTransferId: transferId };
622
639
  }
623
640
  /**
624
641
  * Execute a swap with a pre-created transfer or using free balance.
@@ -748,7 +765,7 @@ class FlashnetClient {
748
765
  receiverSparkAddress: lpSparkAddress,
749
766
  assetAddress: params.initialAssetAddress,
750
767
  amount: params.inputAmount,
751
- }, "Insufficient balance for route swap: ");
768
+ }, "Insufficient balance for route swap: ", params.useAvailableBalance);
752
769
  // Execute with auto-clawback on failure
753
770
  return this.executeWithAutoClawback(async () => {
754
771
  // Prepare hops for validation
@@ -871,7 +888,7 @@ class FlashnetClient {
871
888
  assetAddress: pool.assetBAddress,
872
889
  amount: params.assetBAmount,
873
890
  },
874
- ], "Insufficient balance for adding liquidity: ");
891
+ ], "Insufficient balance for adding liquidity: ", params.useAvailableBalance);
875
892
  // Execute with auto-clawback on failure
876
893
  return this.executeWithAutoClawback(async () => {
877
894
  // Generate add liquidity intent
@@ -1177,6 +1194,7 @@ class FlashnetClient {
1177
1194
  depositAddress: createResponse.depositAddress,
1178
1195
  assetId: params.assetId,
1179
1196
  assetAmount: params.assetAmount,
1197
+ useAvailableBalance: params.useAvailableBalance,
1180
1198
  });
1181
1199
  }
1182
1200
  /**
@@ -1194,6 +1212,7 @@ class FlashnetClient {
1194
1212
  { assetAddress: params.assetId, amount: params.assetAmount },
1195
1213
  ],
1196
1214
  errorPrefix: "Insufficient balance to fund escrow: ",
1215
+ useAvailableBalance: params.useAvailableBalance,
1197
1216
  });
1198
1217
  // 2. Perform transfer
1199
1218
  const escrowSparkAddress = encodeSparkAddressNew({
@@ -1708,19 +1727,20 @@ class FlashnetClient {
1708
1727
  /**
1709
1728
  * Performs asset transfer using generalized asset address for both BTC and tokens.
1710
1729
  */
1711
- async transferAsset(recipient, checkBalanceErrorPrefix) {
1712
- const transferIds = await this.transferAssets([recipient], checkBalanceErrorPrefix);
1730
+ async transferAsset(recipient, checkBalanceErrorPrefix, useAvailableBalance) {
1731
+ const transferIds = await this.transferAssets([recipient], checkBalanceErrorPrefix, useAvailableBalance);
1713
1732
  return transferIds[0];
1714
1733
  }
1715
1734
  /**
1716
1735
  * Performs asset transfers using generalized asset addresses for both BTC and tokens.
1717
1736
  * Supports optional generic to hardcode recipients length so output list can be typed with same length.
1718
1737
  */
1719
- async transferAssets(recipients, checkBalanceErrorPrefix) {
1738
+ async transferAssets(recipients, checkBalanceErrorPrefix, useAvailableBalance) {
1720
1739
  if (checkBalanceErrorPrefix) {
1721
1740
  await this.checkBalance({
1722
1741
  balancesToCheck: recipients,
1723
1742
  errorPrefix: checkBalanceErrorPrefix,
1743
+ useAvailableBalance,
1724
1744
  });
1725
1745
  }
1726
1746
  const transferIds = [];
@@ -1735,7 +1755,7 @@ class FlashnetClient {
1735
1755
  else {
1736
1756
  const transferId = await this._wallet.transferTokens({
1737
1757
  tokenIdentifier: this.toHumanReadableTokenIdentifier(recipient.assetAddress),
1738
- tokenAmount: BigInt(recipient.amount),
1758
+ tokenAmount: FlashnetClient.safeBigInt(recipient.amount),
1739
1759
  receiverSparkAddress: recipient.receiverSparkAddress,
1740
1760
  });
1741
1761
  transferIds.push(transferId);
@@ -1842,7 +1862,8 @@ class FlashnetClient {
1842
1862
  const lightningFeeEstimate = await this.getLightningFeeEstimate(invoice);
1843
1863
  // Total BTC needed = invoice amount + lightning fee (unmasked).
1844
1864
  // Bitmasking for V2 pools is handled inside findBestPoolForTokenToBtc.
1845
- const baseBtcNeeded = BigInt(invoiceAmountSats) + BigInt(lightningFeeEstimate);
1865
+ const baseBtcNeeded = FlashnetClient.safeBigInt(invoiceAmountSats) +
1866
+ FlashnetClient.safeBigInt(lightningFeeEstimate);
1846
1867
  // Check Flashnet minimum amounts early to provide clear error messages
1847
1868
  const minAmounts = await this.getEnabledMinAmountsMap();
1848
1869
  // Check BTC minimum (output from swap)
@@ -1869,7 +1890,7 @@ class FlashnetClient {
1869
1890
  const tokenHex = this.toHexTokenIdentifier(tokenAddress).toLowerCase();
1870
1891
  const tokenMinAmount = minAmounts.get(tokenHex);
1871
1892
  if (tokenMinAmount &&
1872
- BigInt(poolQuote.tokenAmountRequired) < tokenMinAmount) {
1893
+ FlashnetClient.safeBigInt(poolQuote.tokenAmountRequired) < tokenMinAmount) {
1873
1894
  const msg = `Token amount too small. Minimum input is ${tokenMinAmount} units, but calculated amount is only ${poolQuote.tokenAmountRequired} units.`;
1874
1895
  throw new FlashnetError(msg, {
1875
1896
  response: {
@@ -1886,7 +1907,7 @@ class FlashnetClient {
1886
1907
  }
1887
1908
  // BTC variable fee adjustment: difference between what the pool targets and unmasked base.
1888
1909
  // For V3 pools this is 0 (no masking). For V2 it's the rounding overhead.
1889
- const btcVariableFeeAdjustment = Number(BigInt(poolQuote.btcAmountUsed) - baseBtcNeeded);
1910
+ const btcVariableFeeAdjustment = Number(FlashnetClient.safeBigInt(poolQuote.btcAmountUsed) - baseBtcNeeded);
1890
1911
  return {
1891
1912
  poolId: poolQuote.poolId,
1892
1913
  tokenAddress: this.toHexTokenIdentifier(tokenAddress),
@@ -1958,7 +1979,7 @@ class FlashnetClient {
1958
1979
  amountIn: tokenAmount,
1959
1980
  integratorBps: options?.integratorFeeRateBps,
1960
1981
  });
1961
- const btcOut = BigInt(simulation.amountOut);
1982
+ const btcOut = FlashnetClient.safeBigInt(simulation.amountOut);
1962
1983
  if (btcOut > bestBtcOut) {
1963
1984
  bestBtcOut = btcOut;
1964
1985
  bestResult = {
@@ -2046,7 +2067,7 @@ class FlashnetClient {
2046
2067
  await this.ensureInitialized();
2047
2068
  const { invoice, tokenAddress, tokenAmount, maxSlippageBps = 500, // 5% default
2048
2069
  maxLightningFeeSats, preferSpark = true, integratorFeeRateBps, integratorPublicKey, transferTimeoutMs = 30000, // 30s default
2049
- rollbackOnFailure = false, useExistingBtcBalance = false, } = options;
2070
+ rollbackOnFailure = false, useExistingBtcBalance = false, useAvailableBalance = false, } = options;
2050
2071
  try {
2051
2072
  // Step 1: Get a quote for the payment
2052
2073
  const quote = await this.getPayLightningWithTokenQuote(invoice, tokenAddress, {
@@ -2063,6 +2084,7 @@ class FlashnetClient {
2063
2084
  },
2064
2085
  ],
2065
2086
  errorPrefix: "Insufficient token balance for Lightning payment: ",
2087
+ useAvailableBalance,
2066
2088
  });
2067
2089
  // Step 3: Get pool details
2068
2090
  const pool = await this.getPool(quote.poolId);
@@ -2085,6 +2107,7 @@ class FlashnetClient {
2085
2107
  minAmountOut: minBtcOut,
2086
2108
  integratorFeeRateBps,
2087
2109
  integratorPublicKey,
2110
+ useAvailableBalance,
2088
2111
  });
2089
2112
  if (!swapResponse.accepted || !swapResponse.outboundTransferId) {
2090
2113
  return {
@@ -2094,6 +2117,7 @@ class FlashnetClient {
2094
2117
  btcAmountReceived: "0",
2095
2118
  swapTransferId: swapResponse.outboundTransferId || "",
2096
2119
  ammFeePaid: quote.estimatedAmmFee,
2120
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2097
2121
  error: swapResponse.error || "Swap was not accepted",
2098
2122
  };
2099
2123
  }
@@ -2104,7 +2128,8 @@ class FlashnetClient {
2104
2128
  const effectiveMaxLightningFee = maxLightningFeeSats ?? quote.estimatedLightningFee;
2105
2129
  const btcNeededForPayment = invoiceAmountSats + effectiveMaxLightningFee;
2106
2130
  const balance = await this.getBalance();
2107
- canPayImmediately = balance.balance >= BigInt(btcNeededForPayment);
2131
+ canPayImmediately =
2132
+ balance.balance >= FlashnetClient.safeBigInt(btcNeededForPayment);
2108
2133
  }
2109
2134
  if (!canPayImmediately) {
2110
2135
  const transferComplete = await this.waitForTransferCompletion(swapResponse.outboundTransferId, transferTimeoutMs);
@@ -2116,6 +2141,7 @@ class FlashnetClient {
2116
2141
  btcAmountReceived: swapResponse.amountOut || "0",
2117
2142
  swapTransferId: swapResponse.outboundTransferId,
2118
2143
  ammFeePaid: quote.estimatedAmmFee,
2144
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2119
2145
  error: "Transfer did not complete within timeout",
2120
2146
  };
2121
2147
  }
@@ -2129,8 +2155,8 @@ class FlashnetClient {
2129
2155
  let invoiceAmountPaid;
2130
2156
  if (quote.isZeroAmountInvoice) {
2131
2157
  // Zero-amount invoice: pay whatever BTC we received minus lightning fee
2132
- const actualBtc = BigInt(btcReceived);
2133
- const lnFee = BigInt(effectiveMaxLightningFee);
2158
+ const actualBtc = FlashnetClient.safeBigInt(btcReceived);
2159
+ const lnFee = FlashnetClient.safeBigInt(effectiveMaxLightningFee);
2134
2160
  const amountToPay = actualBtc - lnFee;
2135
2161
  if (amountToPay <= 0n) {
2136
2162
  return {
@@ -2140,6 +2166,7 @@ class FlashnetClient {
2140
2166
  btcAmountReceived: btcReceived,
2141
2167
  swapTransferId: swapResponse.outboundTransferId,
2142
2168
  ammFeePaid: quote.estimatedAmmFee,
2169
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2143
2170
  error: `BTC received (${btcReceived} sats) is not enough to cover lightning fee (${effectiveMaxLightningFee} sats).`,
2144
2171
  };
2145
2172
  }
@@ -2159,6 +2186,12 @@ class FlashnetClient {
2159
2186
  preferSpark,
2160
2187
  });
2161
2188
  }
2189
+ // Extract the Spark transfer ID from the lightning payment result.
2190
+ // payLightningInvoice returns LightningSendRequest | WalletTransfer:
2191
+ // - LightningSendRequest has .transfer?.sparkId (the Sparkscan-visible transfer ID)
2192
+ // - WalletTransfer (Spark-to-Spark) has .id directly as the transfer ID
2193
+ // Note: lightningPayment.id (the SSP request ID) is already returned as lightningPaymentId
2194
+ const sparkLightningTransferId = lightningPayment.transfer?.sparkId;
2162
2195
  return {
2163
2196
  success: true,
2164
2197
  poolId: quote.poolId,
@@ -2169,6 +2202,8 @@ class FlashnetClient {
2169
2202
  ammFeePaid: quote.estimatedAmmFee,
2170
2203
  lightningFeePaid: effectiveMaxLightningFee,
2171
2204
  invoiceAmountPaid,
2205
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2206
+ sparkLightningTransferId,
2172
2207
  };
2173
2208
  }
2174
2209
  catch (lightningError) {
@@ -2188,6 +2223,7 @@ class FlashnetClient {
2188
2223
  btcAmountReceived: "0",
2189
2224
  swapTransferId: swapResponse.outboundTransferId,
2190
2225
  ammFeePaid: quote.estimatedAmmFee,
2226
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2191
2227
  error: `Lightning payment failed: ${lightningErrorMessage}. Funds rolled back to ${rollbackResult.tokenAmount} tokens.`,
2192
2228
  };
2193
2229
  }
@@ -2203,6 +2239,7 @@ class FlashnetClient {
2203
2239
  btcAmountReceived: btcReceived,
2204
2240
  swapTransferId: swapResponse.outboundTransferId,
2205
2241
  ammFeePaid: quote.estimatedAmmFee,
2242
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2206
2243
  error: `Lightning payment failed: ${lightningErrorMessage}. Rollback also failed: ${rollbackErrorMessage}. BTC remains in wallet.`,
2207
2244
  };
2208
2245
  }
@@ -2214,6 +2251,7 @@ class FlashnetClient {
2214
2251
  btcAmountReceived: btcReceived,
2215
2252
  swapTransferId: swapResponse.outboundTransferId,
2216
2253
  ammFeePaid: quote.estimatedAmmFee,
2254
+ sparkTokenTransferId: swapResponse.inboundSparkTransferId,
2217
2255
  error: `Lightning payment failed: ${lightningErrorMessage}. BTC (${btcReceived} sats) remains in wallet.`,
2218
2256
  };
2219
2257
  }
@@ -2376,7 +2414,7 @@ class FlashnetClient {
2376
2414
  tokenIsAssetA: pool.tokenIsAssetA,
2377
2415
  integratorBps: integratorFeeRateBps,
2378
2416
  });
2379
- tokenAmount = BigInt(v3Result.amountIn);
2417
+ tokenAmount = FlashnetClient.safeBigInt(v3Result.amountIn);
2380
2418
  fee = v3Result.totalFee;
2381
2419
  executionPrice = v3Result.simulation.executionPrice || "0";
2382
2420
  priceImpactPct = v3Result.simulation.priceImpactPct || "0";
@@ -2385,7 +2423,7 @@ class FlashnetClient {
2385
2423
  else {
2386
2424
  // V2: constant product math + simulation verification
2387
2425
  const calculation = this.calculateTokenAmountForBtcOutput(btcTarget.toString(), poolDetails.assetAReserve, poolDetails.assetBReserve, poolDetails.lpFeeBps, poolDetails.hostFeeBps, pool.tokenIsAssetA, integratorFeeRateBps);
2388
- tokenAmount = BigInt(calculation.amountIn);
2426
+ tokenAmount = FlashnetClient.safeBigInt(calculation.amountIn);
2389
2427
  // Verify with simulation
2390
2428
  const simulation = await this.simulateSwap({
2391
2429
  poolId: pool.lpPublicKey,
@@ -2394,7 +2432,7 @@ class FlashnetClient {
2394
2432
  amountIn: calculation.amountIn,
2395
2433
  integratorBps: integratorFeeRateBps,
2396
2434
  });
2397
- if (BigInt(simulation.amountOut) < btcTarget) {
2435
+ if (FlashnetClient.safeBigInt(simulation.amountOut) < btcTarget) {
2398
2436
  const btcReserve = pool.tokenIsAssetA
2399
2437
  ? poolDetails.assetBReserve
2400
2438
  : poolDetails.assetAReserve;
@@ -2482,9 +2520,9 @@ class FlashnetClient {
2482
2520
  * @private
2483
2521
  */
2484
2522
  calculateTokenAmountForBtcOutput(btcAmountOut, reserveA, reserveB, lpFeeBps, hostFeeBps, tokenIsAssetA, integratorFeeBps) {
2485
- const amountOut = BigInt(btcAmountOut);
2486
- const resA = BigInt(reserveA);
2487
- const resB = BigInt(reserveB);
2523
+ const amountOut = FlashnetClient.safeBigInt(btcAmountOut);
2524
+ const resA = FlashnetClient.safeBigInt(reserveA);
2525
+ const resB = FlashnetClient.safeBigInt(reserveB);
2488
2526
  const totalFeeBps = lpFeeBps + hostFeeBps + (integratorFeeBps || 0);
2489
2527
  const feeRate = Number(totalFeeBps) / 10000; // Convert bps to decimal
2490
2528
  // Token is the input asset
@@ -2582,7 +2620,7 @@ class FlashnetClient {
2582
2620
  amountIn: upperBound.toString(),
2583
2621
  integratorBps,
2584
2622
  });
2585
- if (BigInt(sim.amountOut) >= desiredBtcOut) {
2623
+ if (FlashnetClient.safeBigInt(sim.amountOut) >= desiredBtcOut) {
2586
2624
  upperSim = sim;
2587
2625
  break;
2588
2626
  }
@@ -2593,7 +2631,7 @@ class FlashnetClient {
2593
2631
  throw new Error(`V3 pool ${poolId} has insufficient liquidity for ${desiredBtcOut} sats`);
2594
2632
  }
2595
2633
  // Step 3: Refine estimate via linear interpolation
2596
- const upperOut = BigInt(upperSim.amountOut);
2634
+ const upperOut = FlashnetClient.safeBigInt(upperSim.amountOut);
2597
2635
  // Scale proportionally: if upperBound produced upperOut, we need roughly
2598
2636
  // (upperBound * desiredBtcOut / upperOut). Add +1 to avoid undershoot from truncation.
2599
2637
  let refined = (upperBound * desiredBtcOut) / upperOut + 1n;
@@ -2611,7 +2649,7 @@ class FlashnetClient {
2611
2649
  amountIn: refined.toString(),
2612
2650
  integratorBps,
2613
2651
  });
2614
- if (BigInt(refinedSim.amountOut) >= desiredBtcOut) {
2652
+ if (FlashnetClient.safeBigInt(refinedSim.amountOut) >= desiredBtcOut) {
2615
2653
  bestAmountIn = refined;
2616
2654
  bestSim = refinedSim;
2617
2655
  }
@@ -2645,7 +2683,7 @@ class FlashnetClient {
2645
2683
  amountIn: mid.toString(),
2646
2684
  integratorBps,
2647
2685
  });
2648
- if (BigInt(midSim.amountOut) >= desiredBtcOut) {
2686
+ if (FlashnetClient.safeBigInt(midSim.amountOut) >= desiredBtcOut) {
2649
2687
  hi = mid;
2650
2688
  bestAmountIn = mid;
2651
2689
  bestSim = midSim;
@@ -2667,7 +2705,7 @@ class FlashnetClient {
2667
2705
  * @private
2668
2706
  */
2669
2707
  calculateMinAmountOut(expectedAmount, slippageBps) {
2670
- const amount = BigInt(expectedAmount);
2708
+ const amount = FlashnetClient.safeBigInt(expectedAmount);
2671
2709
  const slippageFactor = BigInt(10000 - slippageBps);
2672
2710
  const minAmount = (amount * slippageFactor) / 10000n;
2673
2711
  return minAmount.toString();
@@ -2886,8 +2924,11 @@ class FlashnetClient {
2886
2924
  const map = new Map();
2887
2925
  for (const item of config) {
2888
2926
  if (item.enabled) {
2927
+ if (item.min_amount == null) {
2928
+ continue;
2929
+ }
2889
2930
  const key = item.asset_identifier.toLowerCase();
2890
- const value = BigInt(String(item.min_amount));
2931
+ const value = FlashnetClient.safeBigInt(item.min_amount);
2891
2932
  map.set(key, value);
2892
2933
  }
2893
2934
  }
@@ -2909,8 +2950,8 @@ class FlashnetClient {
2909
2950
  const outHex = this.getHexAddress(params.assetOutAddress);
2910
2951
  const minIn = minMap.get(inHex);
2911
2952
  const minOut = minMap.get(outHex);
2912
- const amountIn = BigInt(String(params.amountIn));
2913
- const minAmountOut = BigInt(String(params.minAmountOut));
2953
+ const amountIn = FlashnetClient.safeBigInt(params.amountIn);
2954
+ const minAmountOut = FlashnetClient.safeBigInt(params.minAmountOut);
2914
2955
  if (minIn && minOut) {
2915
2956
  if (amountIn < minIn) {
2916
2957
  throw new Error(`Minimum amount not met for input asset. Required \
@@ -2944,13 +2985,13 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
2944
2985
  const aMin = minMap.get(aHex);
2945
2986
  const bMin = minMap.get(bHex);
2946
2987
  if (aMin) {
2947
- const aAmt = BigInt(String(params.assetAAmount));
2988
+ const aAmt = FlashnetClient.safeBigInt(params.assetAAmount);
2948
2989
  if (aAmt < aMin) {
2949
2990
  throw new Error(`Minimum amount not met for Asset A. Required ${aMin.toString()}, provided ${aAmt.toString()}`);
2950
2991
  }
2951
2992
  }
2952
2993
  if (bMin) {
2953
- const bAmt = BigInt(String(params.assetBAmount));
2994
+ const bAmt = FlashnetClient.safeBigInt(params.assetBAmount);
2954
2995
  if (bAmt < bMin) {
2955
2996
  throw new Error(`Minimum amount not met for Asset B. Required ${bMin.toString()}, provided ${bAmt.toString()}`);
2956
2997
  }
@@ -2972,14 +3013,14 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
2972
3013
  const aMin = minMap.get(aHex);
2973
3014
  const bMin = minMap.get(bHex);
2974
3015
  if (aMin) {
2975
- const predictedAOut = BigInt(String(simulation.assetAAmount));
3016
+ const predictedAOut = FlashnetClient.safeBigInt(simulation.assetAAmount);
2976
3017
  const relaxedA = aMin / 2n; // apply 50% relaxation for outputs
2977
3018
  if (predictedAOut < relaxedA) {
2978
3019
  throw new Error(`Minimum amount not met for Asset A on withdrawal. Required at least ${relaxedA.toString()} (50% relaxed), predicted ${predictedAOut.toString()}`);
2979
3020
  }
2980
3021
  }
2981
3022
  if (bMin) {
2982
- const predictedBOut = BigInt(String(simulation.assetBAmount));
3023
+ const predictedBOut = FlashnetClient.safeBigInt(simulation.assetBAmount);
2983
3024
  const relaxedB = bMin / 2n;
2984
3025
  if (predictedBOut < relaxedB) {
2985
3026
  throw new Error(`Minimum amount not met for Asset B on withdrawal. Required at least ${relaxedB.toString()} (50% relaxed), predicted ${predictedBOut.toString()}`);
@@ -3092,20 +3133,20 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
3092
3133
  const transferIds = [];
3093
3134
  // Transfer assets if not using free balance
3094
3135
  if (!params.useFreeBalance) {
3095
- if (BigInt(params.amountADesired) > 0n) {
3136
+ if (FlashnetClient.safeBigInt(params.amountADesired) > 0n) {
3096
3137
  assetATransferId = await this.transferAsset({
3097
3138
  receiverSparkAddress: lpSparkAddress,
3098
3139
  assetAddress: pool.assetAAddress,
3099
3140
  amount: params.amountADesired,
3100
- }, "Insufficient balance for adding V3 liquidity (Asset A): ");
3141
+ }, "Insufficient balance for adding V3 liquidity (Asset A): ", params.useAvailableBalance);
3101
3142
  transferIds.push(assetATransferId);
3102
3143
  }
3103
- if (BigInt(params.amountBDesired) > 0n) {
3144
+ if (FlashnetClient.safeBigInt(params.amountBDesired) > 0n) {
3104
3145
  assetBTransferId = await this.transferAsset({
3105
3146
  receiverSparkAddress: lpSparkAddress,
3106
3147
  assetAddress: pool.assetBAddress,
3107
3148
  amount: params.amountBDesired,
3108
- }, "Insufficient balance for adding V3 liquidity (Asset B): ");
3149
+ }, "Insufficient balance for adding V3 liquidity (Asset B): ", params.useAvailableBalance);
3109
3150
  transferIds.push(assetBTransferId);
3110
3151
  }
3111
3152
  }
@@ -3298,14 +3339,14 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
3298
3339
  receiverSparkAddress: lpSparkAddress,
3299
3340
  assetAddress: pool.assetAAddress,
3300
3341
  amount: params.additionalAmountA,
3301
- }, "Insufficient balance for rebalance (Asset A): ");
3342
+ }, "Insufficient balance for rebalance (Asset A): ", params.useAvailableBalance);
3302
3343
  }
3303
3344
  if (params.additionalAmountB && BigInt(params.additionalAmountB) > 0n) {
3304
3345
  assetBTransferId = await this.transferAsset({
3305
3346
  receiverSparkAddress: lpSparkAddress,
3306
3347
  assetAddress: pool.assetBAddress,
3307
3348
  amount: params.additionalAmountB,
3308
- }, "Insufficient balance for rebalance (Asset B): ");
3349
+ }, "Insufficient balance for rebalance (Asset B): ", params.useAvailableBalance);
3309
3350
  }
3310
3351
  // Collect transfer IDs for potential clawback
3311
3352
  const transferIds = [];
@@ -3497,20 +3538,20 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
3497
3538
  let assetBTransferId = "";
3498
3539
  const transferIds = [];
3499
3540
  // Transfer assets to pool
3500
- if (BigInt(params.amountA) > 0n) {
3541
+ if (FlashnetClient.safeBigInt(params.amountA) > 0n) {
3501
3542
  assetATransferId = await this.transferAsset({
3502
3543
  receiverSparkAddress: lpSparkAddress,
3503
3544
  assetAddress: pool.assetAAddress,
3504
3545
  amount: params.amountA,
3505
- }, "Insufficient balance for depositing to V3 pool (Asset A): ");
3546
+ }, "Insufficient balance for depositing to V3 pool (Asset A): ", params.useAvailableBalance);
3506
3547
  transferIds.push(assetATransferId);
3507
3548
  }
3508
- if (BigInt(params.amountB) > 0n) {
3549
+ if (FlashnetClient.safeBigInt(params.amountB) > 0n) {
3509
3550
  assetBTransferId = await this.transferAsset({
3510
3551
  receiverSparkAddress: lpSparkAddress,
3511
3552
  assetAddress: pool.assetBAddress,
3512
3553
  amount: params.amountB,
3513
- }, "Insufficient balance for depositing to V3 pool (Asset B): ");
3554
+ }, "Insufficient balance for depositing to V3 pool (Asset B): ", params.useAvailableBalance);
3514
3555
  transferIds.push(assetBTransferId);
3515
3556
  }
3516
3557
  const executeDeposit = async () => {