@flashnet/sdk 0.4.3 → 0.5.0

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 (61) hide show
  1. package/dist/cjs/index.d.ts +2 -1
  2. package/dist/cjs/index.d.ts.map +1 -1
  3. package/dist/cjs/index.js +17 -0
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/src/api/typed-endpoints.d.ts +70 -0
  6. package/dist/cjs/src/api/typed-endpoints.d.ts.map +1 -1
  7. package/dist/cjs/src/api/typed-endpoints.js +106 -11
  8. package/dist/cjs/src/api/typed-endpoints.js.map +1 -1
  9. package/dist/cjs/src/client/FlashnetClient.d.ts +182 -3
  10. package/dist/cjs/src/client/FlashnetClient.d.ts.map +1 -1
  11. package/dist/cjs/src/client/FlashnetClient.js +608 -31
  12. package/dist/cjs/src/client/FlashnetClient.js.map +1 -1
  13. package/dist/cjs/src/config/index.js +1 -1
  14. package/dist/cjs/src/types/errors.js +11 -11
  15. package/dist/cjs/src/types/index.d.ts +414 -0
  16. package/dist/cjs/src/types/index.d.ts.map +1 -1
  17. package/dist/cjs/src/types/index.js +1 -1
  18. package/dist/cjs/src/utils/index.d.ts +1 -0
  19. package/dist/cjs/src/utils/index.d.ts.map +1 -1
  20. package/dist/cjs/src/utils/index.js.map +1 -1
  21. package/dist/cjs/src/utils/intents.d.ts +91 -0
  22. package/dist/cjs/src/utils/intents.d.ts.map +1 -1
  23. package/dist/cjs/src/utils/intents.js +117 -0
  24. package/dist/cjs/src/utils/intents.js.map +1 -1
  25. package/dist/cjs/src/utils/spark-address.js +2 -2
  26. package/dist/cjs/src/utils/tick-math.d.ts +240 -0
  27. package/dist/cjs/src/utils/tick-math.d.ts.map +1 -0
  28. package/dist/cjs/src/utils/tick-math.js +299 -0
  29. package/dist/cjs/src/utils/tick-math.js.map +1 -0
  30. package/dist/cjs/src/utils/tokenAddress.js +1 -1
  31. package/dist/esm/index.d.ts +2 -1
  32. package/dist/esm/index.d.ts.map +1 -1
  33. package/dist/esm/index.js +2 -1
  34. package/dist/esm/index.js.map +1 -1
  35. package/dist/esm/src/api/typed-endpoints.d.ts +70 -0
  36. package/dist/esm/src/api/typed-endpoints.d.ts.map +1 -1
  37. package/dist/esm/src/api/typed-endpoints.js +106 -11
  38. package/dist/esm/src/api/typed-endpoints.js.map +1 -1
  39. package/dist/esm/src/client/FlashnetClient.d.ts +182 -3
  40. package/dist/esm/src/client/FlashnetClient.d.ts.map +1 -1
  41. package/dist/esm/src/client/FlashnetClient.js +609 -32
  42. package/dist/esm/src/client/FlashnetClient.js.map +1 -1
  43. package/dist/esm/src/config/index.js +1 -1
  44. package/dist/esm/src/types/errors.js +11 -11
  45. package/dist/esm/src/types/index.d.ts +414 -0
  46. package/dist/esm/src/types/index.d.ts.map +1 -1
  47. package/dist/esm/src/types/index.js +1 -1
  48. package/dist/esm/src/utils/index.d.ts +1 -0
  49. package/dist/esm/src/utils/index.d.ts.map +1 -1
  50. package/dist/esm/src/utils/index.js.map +1 -1
  51. package/dist/esm/src/utils/intents.d.ts +91 -0
  52. package/dist/esm/src/utils/intents.d.ts.map +1 -1
  53. package/dist/esm/src/utils/intents.js +112 -1
  54. package/dist/esm/src/utils/intents.js.map +1 -1
  55. package/dist/esm/src/utils/spark-address.js +2 -2
  56. package/dist/esm/src/utils/tick-math.d.ts +240 -0
  57. package/dist/esm/src/utils/tick-math.d.ts.map +1 -0
  58. package/dist/esm/src/utils/tick-math.js +287 -0
  59. package/dist/esm/src/utils/tick-math.js.map +1 -0
  60. package/dist/esm/src/utils/tokenAddress.js +1 -1
  61. package/package.json +6 -3
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
+ var sha256 = require('fast-sha256');
3
4
  var client = require('../api/client.js');
4
5
  var typedEndpoints = require('../api/typed-endpoints.js');
5
6
  var index$1 = require('../config/index.js');
6
7
  var index = require('../types/index.js');
7
8
  var index$2 = require('../utils/index.js');
8
9
  var auth = require('../utils/auth.js');
9
- var sha256 = require('fast-sha256');
10
10
  var hex = require('../utils/hex.js');
11
11
  var intents = require('../utils/intents.js');
12
12
  var sparkAddress = require('../utils/spark-address.js');
@@ -220,8 +220,7 @@ class FlashnetClient {
220
220
  }
221
221
  /**
222
222
  * Convert a token identifier into the raw hex string form expected by the Flashnet backend.
223
- * If the identifier is the BTC constant or already a hex string, it is returned unchanged.
224
- * If it is in Bech32m human-readable form, it is decoded to hex.
223
+ * Handles BTC constant, hex strings, and Bech32m human-readable format.
225
224
  */
226
225
  toHexTokenIdentifier(tokenIdentifier) {
227
226
  if (tokenIdentifier === index$1.BTC_ASSET_PUBKEY) {
@@ -306,7 +305,7 @@ class FlashnetClient {
306
305
  }
307
306
  }
308
307
  }
309
- // ===== Pool Operations =====
308
+ // Pool Operations
310
309
  /**
311
310
  * List pools with optional filters
312
311
  */
@@ -569,7 +568,7 @@ class FlashnetClient {
569
568
  };
570
569
  return this.typedApi.confirmInitialDeposit(request);
571
570
  }
572
- // ===== Swap Operations =====
571
+ // Swap Operations
573
572
  /**
574
573
  * Simulate a swap without executing it
575
574
  */
@@ -818,7 +817,7 @@ class FlashnetClient {
818
817
  return response;
819
818
  }, [initialTransferId], firstPoolId);
820
819
  }
821
- // ===== Liquidity Operations =====
820
+ // Liquidity Operations
822
821
  /**
823
822
  * Simulate adding liquidity
824
823
  */
@@ -969,7 +968,7 @@ class FlashnetClient {
969
968
  }
970
969
  return response;
971
970
  }
972
- // ===== Host Operations =====
971
+ // Host Operations
973
972
  /**
974
973
  * Register as a host
975
974
  */
@@ -1115,7 +1114,7 @@ class FlashnetClient {
1115
1114
  await this.ensureInitialized();
1116
1115
  return this.typedApi.getIntegratorFees();
1117
1116
  }
1118
- // ===== Escrow Operations =====
1117
+ // Escrow Operations
1119
1118
  /**
1120
1119
  * Creates a new escrow contract.
1121
1120
  * This is the first step in a two-step process: create, then fund.
@@ -1254,7 +1253,7 @@ class FlashnetClient {
1254
1253
  await this.ensureInitialized();
1255
1254
  return this.typedApi.getEscrow(escrowId);
1256
1255
  }
1257
- // ===== Swap History =====
1256
+ // Swap History
1258
1257
  /**
1259
1258
  * Get swaps for a specific pool
1260
1259
  */
@@ -1277,7 +1276,7 @@ class FlashnetClient {
1277
1276
  const user = userPublicKey || this.publicKey;
1278
1277
  return this.typedApi.getUserSwaps(user, query);
1279
1278
  }
1280
- // ===== Clawback =====
1279
+ // Clawback
1281
1280
  /**
1282
1281
  * Request clawback of a stuck inbound transfer to an LP wallet
1283
1282
  */
@@ -1462,7 +1461,7 @@ class FlashnetClient {
1462
1461
  throw flashnetError;
1463
1462
  }
1464
1463
  }
1465
- // ===== Clawback Monitor =====
1464
+ // Clawback Monitor
1466
1465
  /**
1467
1466
  * Start a background job that periodically polls for clawbackable transfers
1468
1467
  * and automatically claws them back.
@@ -1596,7 +1595,7 @@ class FlashnetClient {
1596
1595
  },
1597
1596
  };
1598
1597
  }
1599
- // ===== Token Address Operations =====
1598
+ // Token Address Operations
1600
1599
  /**
1601
1600
  * Encode a token identifier into a human-readable token address using the client's Spark network
1602
1601
  * @param tokenIdentifier - Token identifier as hex string or Uint8Array
@@ -1631,8 +1630,8 @@ class FlashnetClient {
1631
1630
  decodeLegacyTokenAddress(address) {
1632
1631
  return tokenAddress.decodeSparkHumanReadableTokenIdentifier(address, this.sparkNetwork);
1633
1632
  }
1634
- // ===== Status =====
1635
- // ===== Config Inspection =====
1633
+ // Status
1634
+ // Config Inspection
1636
1635
  /**
1637
1636
  * Get raw feature status list (cached briefly)
1638
1637
  */
@@ -1693,7 +1692,7 @@ class FlashnetClient {
1693
1692
  await this.ensureInitialized();
1694
1693
  return this.typedApi.ping();
1695
1694
  }
1696
- // ===== Helper Methods =====
1695
+ // Helper Methods
1697
1696
  /**
1698
1697
  * Performs asset transfer using generalized asset address for both BTC and tokens.
1699
1698
  */
@@ -1793,7 +1792,7 @@ class FlashnetClient {
1793
1792
  throw new Error(errorMessage);
1794
1793
  }
1795
1794
  }
1796
- // ===== Lightning Payment with Token =====
1795
+ // Lightning Payment with Token
1797
1796
  /**
1798
1797
  * Get a quote for paying a Lightning invoice with a token.
1799
1798
  * This calculates the optimal pool and token amount needed.
@@ -1809,7 +1808,18 @@ class FlashnetClient {
1809
1808
  // Decode the invoice to get the amount
1810
1809
  const invoiceAmountSats = await this.decodeInvoiceAmount(invoice);
1811
1810
  if (!invoiceAmountSats || invoiceAmountSats <= 0) {
1812
- throw new Error("Unable to decode invoice amount. Zero-amount invoices are not supported for token payments.");
1811
+ throw new errors.FlashnetError("Unable to decode invoice amount. Zero-amount invoices are not supported for token payments.", {
1812
+ response: {
1813
+ errorCode: "FSAG-1002",
1814
+ errorCategory: "Validation",
1815
+ message: "Unable to decode invoice amount. Zero-amount invoices are not supported for token payments.",
1816
+ requestId: "",
1817
+ timestamp: new Date().toISOString(),
1818
+ service: "sdk",
1819
+ severity: "Error",
1820
+ remediation: "Provide a valid BOLT11 invoice with a non-zero amount.",
1821
+ },
1822
+ });
1813
1823
  }
1814
1824
  // Get Lightning fee estimate
1815
1825
  const lightningFeeEstimate = await this.getLightningFeeEstimate(invoice);
@@ -1828,9 +1838,19 @@ class FlashnetClient {
1828
1838
  // Check BTC minimum (output from swap)
1829
1839
  const btcMinAmount = minAmounts.get(index$1.BTC_ASSET_PUBKEY.toLowerCase());
1830
1840
  if (btcMinAmount && totalBtcNeeded < btcMinAmount) {
1831
- throw new Error(`Invoice amount too small. Flashnet minimum BTC output is ${btcMinAmount} sats, ` +
1832
- `but invoice + lightning fee totals only ${totalBtcNeeded} sats. ` +
1833
- `Please use an invoice of at least ${btcMinAmount} sats.`);
1841
+ const msg = `Invoice amount too small. Minimum BTC output is ${btcMinAmount} sats, but invoice + lightning fee totals only ${totalBtcNeeded} sats.`;
1842
+ throw new errors.FlashnetError(msg, {
1843
+ response: {
1844
+ errorCode: "FSAG-1003",
1845
+ errorCategory: "Validation",
1846
+ message: msg,
1847
+ requestId: "",
1848
+ timestamp: new Date().toISOString(),
1849
+ service: "sdk",
1850
+ severity: "Error",
1851
+ remediation: `Use an invoice of at least ${btcMinAmount} sats.`,
1852
+ },
1853
+ });
1834
1854
  }
1835
1855
  // Find the best pool to swap token -> BTC
1836
1856
  const poolQuote = await this.findBestPoolForTokenToBtc(tokenAddress, totalBtcNeeded.toString(), options?.integratorFeeRateBps);
@@ -1839,9 +1859,19 @@ class FlashnetClient {
1839
1859
  const tokenMinAmount = minAmounts.get(tokenHex);
1840
1860
  if (tokenMinAmount &&
1841
1861
  BigInt(poolQuote.tokenAmountRequired) < tokenMinAmount) {
1842
- throw new Error(`Token amount too small. Flashnet minimum input is ${tokenMinAmount} units, ` +
1843
- `but calculated amount is only ${poolQuote.tokenAmountRequired} units. ` +
1844
- `Please use a larger invoice amount.`);
1862
+ const msg = `Token amount too small. Minimum input is ${tokenMinAmount} units, but calculated amount is only ${poolQuote.tokenAmountRequired} units.`;
1863
+ throw new errors.FlashnetError(msg, {
1864
+ response: {
1865
+ errorCode: "FSAG-1003",
1866
+ errorCategory: "Validation",
1867
+ message: msg,
1868
+ requestId: "",
1869
+ timestamp: new Date().toISOString(),
1870
+ service: "sdk",
1871
+ severity: "Error",
1872
+ remediation: "Use a larger invoice amount.",
1873
+ },
1874
+ });
1845
1875
  }
1846
1876
  // Calculate the BTC variable fee adjustment (how much extra we're requesting)
1847
1877
  const btcVariableFeeAdjustment = Number(totalBtcNeeded - baseBtcNeeded);
@@ -2075,6 +2105,8 @@ class FlashnetClient {
2075
2105
  const tokenHex = this.toHexTokenIdentifier(tokenAddress);
2076
2106
  const btcHex = index$1.BTC_ASSET_PUBKEY;
2077
2107
  // Find all pools that have this token paired with BTC
2108
+ // Note: The API may return the same pool for both filter combinations,
2109
+ // so we need to deduplicate and determine tokenIsAssetA from actual pool data
2078
2110
  const poolsWithTokenAsA = await this.listPools({
2079
2111
  assetAAddress: tokenHex,
2080
2112
  assetBAddress: btcHex,
@@ -2083,17 +2115,57 @@ class FlashnetClient {
2083
2115
  assetAAddress: btcHex,
2084
2116
  assetBAddress: tokenHex,
2085
2117
  });
2086
- const allPools = [
2087
- ...poolsWithTokenAsA.pools.map((p) => ({ ...p, tokenIsAssetA: true })),
2088
- ...poolsWithTokenAsB.pools.map((p) => ({ ...p, tokenIsAssetA: false })),
2089
- ];
2118
+ // Deduplicate pools by poolId and determine tokenIsAssetA from actual pool addresses
2119
+ const poolMap = new Map();
2120
+ for (const p of [...poolsWithTokenAsA.pools, ...poolsWithTokenAsB.pools]) {
2121
+ if (!poolMap.has(p.lpPublicKey)) {
2122
+ // Determine tokenIsAssetA from actual pool asset addresses, not from which query returned it
2123
+ const tokenIsAssetA = p.assetAAddress?.toLowerCase() === tokenHex.toLowerCase();
2124
+ poolMap.set(p.lpPublicKey, { pool: p, tokenIsAssetA });
2125
+ }
2126
+ }
2127
+ const allPools = Array.from(poolMap.values()).map(({ pool, tokenIsAssetA }) => ({
2128
+ ...pool,
2129
+ tokenIsAssetA,
2130
+ }));
2090
2131
  if (allPools.length === 0) {
2091
- throw new Error(`No liquidity pool found for token ${tokenAddress} paired with BTC`);
2132
+ throw new errors.FlashnetError(`No liquidity pool found for token ${tokenAddress} paired with BTC`, {
2133
+ response: {
2134
+ errorCode: "FSAG-4001",
2135
+ errorCategory: "Business",
2136
+ message: `No liquidity pool found for token ${tokenAddress} paired with BTC`,
2137
+ requestId: "",
2138
+ timestamp: new Date().toISOString(),
2139
+ service: "sdk",
2140
+ severity: "Error",
2141
+ },
2142
+ });
2143
+ }
2144
+ // Pre-check: Get minimum amounts to provide clear error if invoice is too small
2145
+ const minAmounts = await this.getMinAmountsMap();
2146
+ const btcMinAmount = minAmounts.get(index$1.BTC_ASSET_PUBKEY.toLowerCase());
2147
+ // Check if the BTC amount needed is below the minimum
2148
+ if (btcMinAmount && BigInt(btcAmountNeeded) < btcMinAmount) {
2149
+ const msg = `Invoice amount too small. Minimum ${btcMinAmount} sats required, but invoice only requires ${btcAmountNeeded} sats.`;
2150
+ throw new errors.FlashnetError(msg, {
2151
+ response: {
2152
+ errorCode: "FSAG-1003",
2153
+ errorCategory: "Validation",
2154
+ message: msg,
2155
+ requestId: "",
2156
+ timestamp: new Date().toISOString(),
2157
+ service: "sdk",
2158
+ severity: "Error",
2159
+ remediation: `Use an invoice with at least ${btcMinAmount} sats.`,
2160
+ },
2161
+ });
2092
2162
  }
2093
2163
  // Find the best pool (lowest token cost for the required BTC)
2094
2164
  let bestPool = null;
2095
2165
  let bestTokenAmount = BigInt(Number.MAX_SAFE_INTEGER);
2096
2166
  let bestSimulation = null;
2167
+ // Track errors for each pool to provide better diagnostics
2168
+ const poolErrors = [];
2097
2169
  for (const pool of allPools) {
2098
2170
  try {
2099
2171
  // Get pool details for reserves
@@ -2127,12 +2199,53 @@ class FlashnetClient {
2127
2199
  warningMessage: simulation.warningMessage,
2128
2200
  };
2129
2201
  }
2202
+ else {
2203
+ // Simulation output was insufficient
2204
+ const btcReserve = pool.tokenIsAssetA
2205
+ ? poolDetails.assetBReserve
2206
+ : poolDetails.assetAReserve;
2207
+ poolErrors.push({
2208
+ poolId: pool.lpPublicKey,
2209
+ error: `Simulation output (${simulation.amountOut} sats) < required (${btcAmountNeeded} sats)`,
2210
+ btcReserve,
2211
+ });
2212
+ }
2130
2213
  }
2131
2214
  }
2132
- catch { }
2215
+ catch (e) {
2216
+ // Capture pool errors for diagnostics
2217
+ const errorMessage = e instanceof Error ? e.message : String(e);
2218
+ poolErrors.push({
2219
+ poolId: pool.lpPublicKey,
2220
+ error: errorMessage,
2221
+ });
2222
+ }
2133
2223
  }
2134
2224
  if (!bestPool || !bestSimulation) {
2135
- throw new Error(`No pool has sufficient liquidity for ${btcAmountNeeded} sats`);
2225
+ let errorMessage = `No pool has sufficient liquidity for ${btcAmountNeeded} sats`;
2226
+ if (poolErrors.length > 0) {
2227
+ const details = poolErrors
2228
+ .map((pe) => {
2229
+ const reserveInfo = pe.btcReserve
2230
+ ? ` (BTC reserve: ${pe.btcReserve})`
2231
+ : "";
2232
+ return ` - Pool ${pe.poolId.slice(0, 12)}...${reserveInfo}: ${pe.error}`;
2233
+ })
2234
+ .join("\n");
2235
+ errorMessage += `\n\nPool evaluation details:\n${details}`;
2236
+ }
2237
+ throw new errors.FlashnetError(errorMessage, {
2238
+ response: {
2239
+ errorCode: "FSAG-4201",
2240
+ errorCategory: "Business",
2241
+ message: errorMessage,
2242
+ requestId: "",
2243
+ timestamp: new Date().toISOString(),
2244
+ service: "sdk",
2245
+ severity: "Error",
2246
+ remediation: "Try a smaller amount or wait for more liquidity.",
2247
+ },
2248
+ });
2136
2249
  }
2137
2250
  const poolDetails = await this.getPool(bestPool.lpPublicKey);
2138
2251
  return {
@@ -2381,7 +2494,7 @@ class FlashnetClient {
2381
2494
  async cleanup() {
2382
2495
  await this._wallet.cleanupConnections();
2383
2496
  }
2384
- // ===== Config and Policy Enforcement Helpers =====
2497
+ // Config and Policy Enforcement Helpers
2385
2498
  async ensureAmmOperationAllowed(requiredFeature) {
2386
2499
  await this.ensurePingOk();
2387
2500
  const featureMap = await this.getFeatureStatusMap();
@@ -2561,6 +2674,470 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
2561
2674
  throw new Error(`Asset B is not allowed for pool creation: ${assetBHex}`);
2562
2675
  }
2563
2676
  }
2677
+ // V3 Concentrated Liquidity Operations
2678
+ /**
2679
+ * Create a V3 concentrated liquidity pool
2680
+ *
2681
+ * Concentrated liquidity pools allow LPs to provide liquidity within specific
2682
+ * price ranges (tick ranges) for higher capital efficiency.
2683
+ *
2684
+ * @param params Pool creation parameters
2685
+ * @param params.assetAAddress - Address of asset A (base asset)
2686
+ * @param params.assetBAddress - Address of asset B (quote asset)
2687
+ * @param params.tickSpacing - Tick spacing (common values: 10, 60, 200)
2688
+ * @param params.initialPrice - Initial price of asset A in terms of asset B
2689
+ * @param params.lpFeeRateBps - LP fee rate in basis points
2690
+ * @param params.hostFeeRateBps - Host fee rate in basis points
2691
+ * @param params.hostNamespace - Optional host namespace
2692
+ * @param params.poolOwnerPublicKey - Optional pool owner (defaults to wallet pubkey)
2693
+ */
2694
+ async createConcentratedPool(params) {
2695
+ await this.ensureInitialized();
2696
+ await this.ensureAmmOperationAllowed("allow_pool_creation");
2697
+ await this.assertAllowedAssetBForPoolCreation(this.toHexTokenIdentifier(params.assetBAddress));
2698
+ const poolOwnerPublicKey = params.poolOwnerPublicKey ?? this.publicKey;
2699
+ // Generate intent
2700
+ const nonce = index$2.generateNonce();
2701
+ const intentMessage = intents.generateCreateConcentratedPoolIntentMessage({
2702
+ poolOwnerPublicKey,
2703
+ assetAAddress: this.toHexTokenIdentifier(params.assetAAddress),
2704
+ assetBAddress: this.toHexTokenIdentifier(params.assetBAddress),
2705
+ tickSpacing: params.tickSpacing,
2706
+ initialPrice: params.initialPrice,
2707
+ lpFeeRateBps: params.lpFeeRateBps.toString(),
2708
+ hostFeeRateBps: params.hostFeeRateBps.toString(),
2709
+ nonce,
2710
+ });
2711
+ // Sign intent
2712
+ const messageHash = sha256__default.default(intentMessage);
2713
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
2714
+ const request = {
2715
+ poolOwnerPublicKey,
2716
+ assetAAddress: this.toHexTokenIdentifier(params.assetAAddress),
2717
+ assetBAddress: this.toHexTokenIdentifier(params.assetBAddress),
2718
+ tickSpacing: params.tickSpacing,
2719
+ initialPrice: params.initialPrice,
2720
+ lpFeeRateBps: params.lpFeeRateBps.toString(),
2721
+ hostFeeRateBps: params.hostFeeRateBps.toString(),
2722
+ hostNamespace: params.hostNamespace,
2723
+ nonce,
2724
+ signature: hex.getHexFromUint8Array(signature),
2725
+ };
2726
+ return this.typedApi.createConcentratedPool(request);
2727
+ }
2728
+ /**
2729
+ * Add liquidity to a V3 concentrated position
2730
+ *
2731
+ * Increases liquidity within a specific tick range. If the position doesn't exist,
2732
+ * a new position is created.
2733
+ *
2734
+ * @param params Position parameters
2735
+ * @param params.poolId - Pool ID (LP identity public key)
2736
+ * @param params.tickLower - Lower tick of the position
2737
+ * @param params.tickUpper - Upper tick of the position
2738
+ * @param params.amountADesired - Desired amount of asset A to add
2739
+ * @param params.amountBDesired - Desired amount of asset B to add
2740
+ * @param params.amountAMin - Minimum amount of asset A (slippage protection)
2741
+ * @param params.amountBMin - Minimum amount of asset B (slippage protection)
2742
+ * @param params.useFreeBalanceA - If true, use free balance from pool for asset A instead of Spark transfer
2743
+ * @param params.useFreeBalanceB - If true, use free balance from pool for asset B instead of Spark transfer
2744
+ * @param params.retainExcessInBalance - If true, retain any excess amounts in pool free balance instead of refunding via Spark
2745
+ */
2746
+ async increaseLiquidity(params) {
2747
+ await this.ensureInitialized();
2748
+ await this.ensureAmmOperationAllowed("allow_add_liquidity");
2749
+ // Get pool details to know asset addresses
2750
+ const pool = await this.getPool(params.poolId);
2751
+ // Transfer assets to pool (unless using free balance)
2752
+ const lpSparkAddress = sparkAddress.encodeSparkAddressNew({
2753
+ identityPublicKey: params.poolId,
2754
+ network: this.sparkNetwork,
2755
+ });
2756
+ let assetATransferId = "";
2757
+ let assetBTransferId = "";
2758
+ const transferIds = [];
2759
+ // Transfer asset A if not using free balance
2760
+ if (!params.useFreeBalanceA && BigInt(params.amountADesired) > 0n) {
2761
+ assetATransferId = await this.transferAsset({
2762
+ receiverSparkAddress: lpSparkAddress,
2763
+ assetAddress: pool.assetAAddress,
2764
+ amount: params.amountADesired,
2765
+ }, "Insufficient balance for adding V3 liquidity (Asset A): ");
2766
+ transferIds.push(assetATransferId);
2767
+ }
2768
+ // Transfer asset B if not using free balance
2769
+ if (!params.useFreeBalanceB && BigInt(params.amountBDesired) > 0n) {
2770
+ assetBTransferId = await this.transferAsset({
2771
+ receiverSparkAddress: lpSparkAddress,
2772
+ assetAddress: pool.assetBAddress,
2773
+ amount: params.amountBDesired,
2774
+ }, "Insufficient balance for adding V3 liquidity (Asset B): ");
2775
+ transferIds.push(assetBTransferId);
2776
+ }
2777
+ const executeIncrease = async () => {
2778
+ // Generate intent
2779
+ const nonce = index$2.generateNonce();
2780
+ const intentMessage = intents.generateIncreaseLiquidityIntentMessage({
2781
+ userPublicKey: this.publicKey,
2782
+ lpIdentityPublicKey: params.poolId,
2783
+ tickLower: params.tickLower,
2784
+ tickUpper: params.tickUpper,
2785
+ assetASparkTransferId: assetATransferId,
2786
+ assetBSparkTransferId: assetBTransferId,
2787
+ amountADesired: params.amountADesired,
2788
+ amountBDesired: params.amountBDesired,
2789
+ amountAMin: params.amountAMin,
2790
+ amountBMin: params.amountBMin,
2791
+ nonce,
2792
+ });
2793
+ // Sign intent
2794
+ const messageHash = sha256__default.default(intentMessage);
2795
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
2796
+ const request = {
2797
+ poolId: params.poolId,
2798
+ tickLower: params.tickLower,
2799
+ tickUpper: params.tickUpper,
2800
+ assetASparkTransferId: assetATransferId,
2801
+ assetBSparkTransferId: assetBTransferId,
2802
+ amountADesired: params.amountADesired,
2803
+ amountBDesired: params.amountBDesired,
2804
+ amountAMin: params.amountAMin,
2805
+ amountBMin: params.amountBMin,
2806
+ useFreeBalanceA: params.useFreeBalanceA,
2807
+ useFreeBalanceB: params.useFreeBalanceB,
2808
+ retainExcessInBalance: params.retainExcessInBalance,
2809
+ nonce,
2810
+ signature: hex.getHexFromUint8Array(signature),
2811
+ };
2812
+ const response = await this.typedApi.increaseLiquidity(request);
2813
+ if (!response.accepted) {
2814
+ const errorMessage = response.error || "Increase liquidity rejected by the AMM";
2815
+ const hasRefund = !!(response.amountARefund || response.amountBRefund);
2816
+ const refundInfo = hasRefund
2817
+ ? ` Refunds: Asset A: ${response.amountARefund || "0"}, Asset B: ${response.amountBRefund || "0"}`
2818
+ : "";
2819
+ throw new errors.FlashnetError(`${errorMessage}.${refundInfo}`, {
2820
+ response: {
2821
+ errorCode: hasRefund ? "FSAG-4203" : "UNKNOWN",
2822
+ errorCategory: hasRefund ? "Business" : "System",
2823
+ message: `${errorMessage}.${refundInfo}`,
2824
+ requestId: response.requestId || "",
2825
+ timestamp: new Date().toISOString(),
2826
+ service: "amm-gateway",
2827
+ severity: "Error",
2828
+ },
2829
+ httpStatus: 400,
2830
+ transferIds: hasRefund ? [] : transferIds,
2831
+ lpIdentityPublicKey: params.poolId,
2832
+ });
2833
+ }
2834
+ return response;
2835
+ };
2836
+ // Execute with auto-clawback if we made transfers
2837
+ if (transferIds.length > 0) {
2838
+ return this.executeWithAutoClawback(executeIncrease, transferIds, params.poolId);
2839
+ }
2840
+ return executeIncrease();
2841
+ }
2842
+ /**
2843
+ * Remove liquidity from a V3 concentrated position
2844
+ *
2845
+ * Decreases liquidity from a specific tick range position.
2846
+ *
2847
+ * @param params Position parameters
2848
+ * @param params.poolId - Pool ID (LP identity public key)
2849
+ * @param params.tickLower - Lower tick of the position
2850
+ * @param params.tickUpper - Upper tick of the position
2851
+ * @param params.liquidityToRemove - Amount of liquidity to remove (use "0" to remove all)
2852
+ * @param params.amountAMin - Minimum amount of asset A to receive (slippage protection)
2853
+ * @param params.amountBMin - Minimum amount of asset B to receive (slippage protection)
2854
+ * @param params.retainInBalance - If true, retain withdrawn assets in pool free balance instead of sending via Spark
2855
+ */
2856
+ async decreaseLiquidity(params) {
2857
+ await this.ensureInitialized();
2858
+ await this.ensureAmmOperationAllowed("allow_withdraw_liquidity");
2859
+ // Generate intent
2860
+ const nonce = index$2.generateNonce();
2861
+ const intentMessage = intents.generateDecreaseLiquidityIntentMessage({
2862
+ userPublicKey: this.publicKey,
2863
+ lpIdentityPublicKey: params.poolId,
2864
+ tickLower: params.tickLower,
2865
+ tickUpper: params.tickUpper,
2866
+ liquidityToRemove: params.liquidityToRemove,
2867
+ amountAMin: params.amountAMin,
2868
+ amountBMin: params.amountBMin,
2869
+ nonce,
2870
+ });
2871
+ // Sign intent
2872
+ const messageHash = sha256__default.default(intentMessage);
2873
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
2874
+ const request = {
2875
+ poolId: params.poolId,
2876
+ tickLower: params.tickLower,
2877
+ tickUpper: params.tickUpper,
2878
+ liquidityToRemove: params.liquidityToRemove,
2879
+ amountAMin: params.amountAMin,
2880
+ amountBMin: params.amountBMin,
2881
+ retainInBalance: params.retainInBalance,
2882
+ nonce,
2883
+ signature: hex.getHexFromUint8Array(signature),
2884
+ };
2885
+ const response = await this.typedApi.decreaseLiquidity(request);
2886
+ if (!response.accepted) {
2887
+ const errorMessage = response.error || "Decrease liquidity rejected by the AMM";
2888
+ throw new Error(errorMessage);
2889
+ }
2890
+ return response;
2891
+ }
2892
+ /**
2893
+ * Collect accumulated fees from a V3 position
2894
+ *
2895
+ * Collects fees earned from trading activity without removing liquidity.
2896
+ *
2897
+ * @param params Position parameters
2898
+ * @param params.poolId - Pool ID (LP identity public key)
2899
+ * @param params.tickLower - Lower tick of the position
2900
+ * @param params.tickUpper - Upper tick of the position
2901
+ * @param params.retainInBalance - If true, retain collected fees in pool free balance instead of sending via Spark
2902
+ */
2903
+ async collectFees(params) {
2904
+ await this.ensureInitialized();
2905
+ await this.ensureAmmOperationAllowed("allow_withdraw_fees");
2906
+ // Generate intent
2907
+ const nonce = index$2.generateNonce();
2908
+ const intentMessage = intents.generateCollectFeesIntentMessage({
2909
+ userPublicKey: this.publicKey,
2910
+ lpIdentityPublicKey: params.poolId,
2911
+ tickLower: params.tickLower,
2912
+ tickUpper: params.tickUpper,
2913
+ nonce,
2914
+ });
2915
+ // Sign intent
2916
+ const messageHash = sha256__default.default(intentMessage);
2917
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
2918
+ const request = {
2919
+ poolId: params.poolId,
2920
+ tickLower: params.tickLower,
2921
+ tickUpper: params.tickUpper,
2922
+ retainInBalance: params.retainInBalance,
2923
+ nonce,
2924
+ signature: hex.getHexFromUint8Array(signature),
2925
+ };
2926
+ const response = await this.typedApi.collectFees(request);
2927
+ if (!response.accepted) {
2928
+ const errorMessage = response.error || "Collect fees rejected by the AMM";
2929
+ throw new Error(errorMessage);
2930
+ }
2931
+ return response;
2932
+ }
2933
+ /**
2934
+ * Rebalance a V3 position to a new tick range
2935
+ *
2936
+ * Atomically moves liquidity from an old position to a new tick range.
2937
+ * Optionally can add additional funds during rebalancing.
2938
+ *
2939
+ * @param params Rebalance parameters
2940
+ * @param params.poolId - Pool ID (LP identity public key)
2941
+ * @param params.oldTickLower - Lower tick of the current position
2942
+ * @param params.oldTickUpper - Upper tick of the current position
2943
+ * @param params.newTickLower - Lower tick for the new position
2944
+ * @param params.newTickUpper - Upper tick for the new position
2945
+ * @param params.liquidityToMove - Amount of liquidity to move (use "0" to move all)
2946
+ * @param params.additionalAmountA - Optional additional asset A to add
2947
+ * @param params.additionalAmountB - Optional additional asset B to add
2948
+ * @param params.retainInBalance - If true, retain any excess amounts in pool free balance instead of sending via Spark
2949
+ */
2950
+ async rebalancePosition(params) {
2951
+ await this.ensureInitialized();
2952
+ await this.ensureAmmOperationAllowed("allow_add_liquidity");
2953
+ // Get pool details
2954
+ const pool = await this.getPool(params.poolId);
2955
+ // Transfer additional assets if provided
2956
+ let assetATransferId;
2957
+ let assetBTransferId;
2958
+ const lpSparkAddress = sparkAddress.encodeSparkAddressNew({
2959
+ identityPublicKey: params.poolId,
2960
+ network: this.sparkNetwork,
2961
+ });
2962
+ if (params.additionalAmountA && BigInt(params.additionalAmountA) > 0n) {
2963
+ assetATransferId = await this.transferAsset({
2964
+ receiverSparkAddress: lpSparkAddress,
2965
+ assetAddress: pool.assetAAddress,
2966
+ amount: params.additionalAmountA,
2967
+ }, "Insufficient balance for rebalance (Asset A): ");
2968
+ }
2969
+ if (params.additionalAmountB && BigInt(params.additionalAmountB) > 0n) {
2970
+ assetBTransferId = await this.transferAsset({
2971
+ receiverSparkAddress: lpSparkAddress,
2972
+ assetAddress: pool.assetBAddress,
2973
+ amount: params.additionalAmountB,
2974
+ }, "Insufficient balance for rebalance (Asset B): ");
2975
+ }
2976
+ // Collect transfer IDs for potential clawback
2977
+ const transferIds = [];
2978
+ if (assetATransferId) {
2979
+ transferIds.push(assetATransferId);
2980
+ }
2981
+ if (assetBTransferId) {
2982
+ transferIds.push(assetBTransferId);
2983
+ }
2984
+ // Execute (with auto-clawback if we have transfers)
2985
+ const executeRebalance = async () => {
2986
+ // Generate intent
2987
+ const nonce = index$2.generateNonce();
2988
+ const intentMessage = intents.generateRebalancePositionIntentMessage({
2989
+ userPublicKey: this.publicKey,
2990
+ lpIdentityPublicKey: params.poolId,
2991
+ oldTickLower: params.oldTickLower,
2992
+ oldTickUpper: params.oldTickUpper,
2993
+ newTickLower: params.newTickLower,
2994
+ newTickUpper: params.newTickUpper,
2995
+ liquidityToMove: params.liquidityToMove,
2996
+ assetASparkTransferId: assetATransferId,
2997
+ assetBSparkTransferId: assetBTransferId,
2998
+ additionalAmountA: params.additionalAmountA,
2999
+ additionalAmountB: params.additionalAmountB,
3000
+ nonce,
3001
+ });
3002
+ // Sign intent
3003
+ const messageHash = sha256__default.default(intentMessage);
3004
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
3005
+ const request = {
3006
+ poolId: params.poolId,
3007
+ oldTickLower: params.oldTickLower,
3008
+ oldTickUpper: params.oldTickUpper,
3009
+ newTickLower: params.newTickLower,
3010
+ newTickUpper: params.newTickUpper,
3011
+ liquidityToMove: params.liquidityToMove,
3012
+ assetASparkTransferId: assetATransferId,
3013
+ assetBSparkTransferId: assetBTransferId,
3014
+ additionalAmountA: params.additionalAmountA,
3015
+ additionalAmountB: params.additionalAmountB,
3016
+ retainInBalance: params.retainInBalance,
3017
+ nonce,
3018
+ signature: hex.getHexFromUint8Array(signature),
3019
+ };
3020
+ const response = await this.typedApi.rebalancePosition(request);
3021
+ if (!response.accepted) {
3022
+ const errorMessage = response.error || "Rebalance position rejected by the AMM";
3023
+ throw new errors.FlashnetError(errorMessage, {
3024
+ response: {
3025
+ errorCode: "UNKNOWN",
3026
+ errorCategory: "System",
3027
+ message: errorMessage,
3028
+ requestId: response.requestId || "",
3029
+ timestamp: new Date().toISOString(),
3030
+ service: "amm-gateway",
3031
+ severity: "Error",
3032
+ },
3033
+ httpStatus: 400,
3034
+ transferIds,
3035
+ lpIdentityPublicKey: params.poolId,
3036
+ });
3037
+ }
3038
+ return response;
3039
+ };
3040
+ // Use auto-clawback if we made transfers
3041
+ if (transferIds.length > 0) {
3042
+ return this.executeWithAutoClawback(executeRebalance, transferIds, params.poolId);
3043
+ }
3044
+ return executeRebalance();
3045
+ }
3046
+ /**
3047
+ * List V3 concentrated liquidity positions
3048
+ *
3049
+ * @param query Optional query parameters
3050
+ * @param query.poolId - Filter by pool ID
3051
+ * @param query.page - Page number (default: 1)
3052
+ * @param query.pageSize - Page size (default: 20, max: 100)
3053
+ */
3054
+ async listConcentratedPositions(query) {
3055
+ await this.ensureInitialized();
3056
+ return this.typedApi.listConcentratedPositions(query);
3057
+ }
3058
+ /**
3059
+ * Get pool liquidity distribution for visualization
3060
+ *
3061
+ * Returns aggregated liquidity ranges for visualizing the liquidity distribution.
3062
+ *
3063
+ * @param poolId - Pool ID (LP identity public key)
3064
+ */
3065
+ async getPoolLiquidity(poolId) {
3066
+ await this.ensureInitialized();
3067
+ return this.typedApi.getPoolLiquidity(poolId);
3068
+ }
3069
+ /**
3070
+ * Get pool ticks for simulation
3071
+ *
3072
+ * Returns all initialized ticks with their liquidity deltas for swap simulation.
3073
+ *
3074
+ * @param poolId - Pool ID (LP identity public key)
3075
+ */
3076
+ async getPoolTicks(poolId) {
3077
+ await this.ensureInitialized();
3078
+ return this.typedApi.getPoolTicks(poolId);
3079
+ }
3080
+ // V3 Free Balance Methods
3081
+ /**
3082
+ * Get user's free balance for a specific V3 pool
3083
+ *
3084
+ * Returns the user's current free balance in the pool, which can be used for
3085
+ * liquidity operations without needing to transfer from the wallet.
3086
+ *
3087
+ * @param poolId - Pool ID (LP identity public key)
3088
+ */
3089
+ async getConcentratedBalance(poolId) {
3090
+ await this.ensureInitialized();
3091
+ return this.typedApi.getConcentratedBalance(poolId);
3092
+ }
3093
+ /**
3094
+ * Get user's free balances across all V3 pools
3095
+ *
3096
+ * Returns all free balances for the authenticated user across all V3 pools.
3097
+ */
3098
+ async getConcentratedBalances() {
3099
+ await this.ensureInitialized();
3100
+ return this.typedApi.getConcentratedBalances();
3101
+ }
3102
+ /**
3103
+ * Withdraw free balance from a V3 pool to user's Spark wallet
3104
+ *
3105
+ * Withdraws accumulated free balance from a pool. Use "0" to skip an asset,
3106
+ * or "max" to withdraw all available balance of that asset.
3107
+ *
3108
+ * @param params Withdrawal parameters
3109
+ * @param params.poolId - Pool ID (LP identity public key)
3110
+ * @param params.amountA - Amount of asset A to withdraw ("0" to skip, "max" to withdraw all)
3111
+ * @param params.amountB - Amount of asset B to withdraw ("0" to skip, "max" to withdraw all)
3112
+ */
3113
+ async withdrawConcentratedBalance(params) {
3114
+ await this.ensureInitialized();
3115
+ // Generate intent
3116
+ const nonce = index$2.generateNonce();
3117
+ const intentMessage = intents.generateWithdrawBalanceIntentMessage({
3118
+ userPublicKey: this.publicKey,
3119
+ lpIdentityPublicKey: params.poolId,
3120
+ amountA: params.amountA,
3121
+ amountB: params.amountB,
3122
+ nonce,
3123
+ });
3124
+ // Sign intent
3125
+ const messageHash = sha256__default.default(intentMessage);
3126
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
3127
+ const request = {
3128
+ poolId: params.poolId,
3129
+ amountA: params.amountA,
3130
+ amountB: params.amountB,
3131
+ nonce,
3132
+ signature: hex.getHexFromUint8Array(signature),
3133
+ };
3134
+ const response = await this.typedApi.withdrawConcentratedBalance(request);
3135
+ if (!response.accepted) {
3136
+ const errorMessage = response.error || "Withdraw balance rejected by the AMM";
3137
+ throw new Error(errorMessage);
3138
+ }
3139
+ return response;
3140
+ }
2564
3141
  }
2565
3142
 
2566
3143
  exports.FlashnetClient = FlashnetClient;