@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
+ import sha256 from 'fast-sha256';
1
2
  import { ApiClient } from '../api/client.js';
2
3
  import { TypedAmmApi } from '../api/typed-endpoints.js';
3
4
  import { getClientEnvironmentName, resolveClientNetworkConfig, getClientNetworkConfig, BTC_ASSET_PUBKEY } from '../config/index.js';
4
5
  import { getSparkNetworkFromLegacy, getClientEnvironmentFromLegacy, Network } from '../types/index.js';
5
6
  import { generateNonce, compareDecimalStrings } from '../utils/index.js';
6
7
  import { AuthManager } from '../utils/auth.js';
7
- import sha256 from 'fast-sha256';
8
8
  import { getHexFromUint8Array } from '../utils/hex.js';
9
- import { generateConstantProductPoolInitializationIntentMessage, generatePoolInitializationIntentMessage, generatePoolConfirmInitialDepositIntentMessage, generatePoolSwapIntentMessage, generateRouteSwapIntentMessage, generateAddLiquidityIntentMessage, generateRemoveLiquidityIntentMessage, generateRegisterHostIntentMessage, generateWithdrawHostFeesIntentMessage, generateWithdrawIntegratorFeesIntentMessage, generateCreateEscrowIntentMessage, generateFundEscrowIntentMessage, generateClaimEscrowIntentMessage, generateClawbackIntentMessage } from '../utils/intents.js';
9
+ import { generateConstantProductPoolInitializationIntentMessage, generatePoolInitializationIntentMessage, generatePoolConfirmInitialDepositIntentMessage, generatePoolSwapIntentMessage, generateRouteSwapIntentMessage, generateAddLiquidityIntentMessage, generateRemoveLiquidityIntentMessage, generateRegisterHostIntentMessage, generateWithdrawHostFeesIntentMessage, generateWithdrawIntegratorFeesIntentMessage, generateCreateEscrowIntentMessage, generateFundEscrowIntentMessage, generateClaimEscrowIntentMessage, generateClawbackIntentMessage, generateCreateConcentratedPoolIntentMessage, generateDecreaseLiquidityIntentMessage, generateCollectFeesIntentMessage, generateWithdrawBalanceIntentMessage, generateIncreaseLiquidityIntentMessage, generateRebalancePositionIntentMessage } from '../utils/intents.js';
10
10
  import { getSparkNetworkFromAddress, encodeSparkAddressNew } from '../utils/spark-address.js';
11
11
  import { encodeSparkHumanReadableTokenIdentifier, decodeSparkHumanReadableTokenIdentifier } from '../utils/tokenAddress.js';
12
12
  import { FlashnetError } from '../types/errors.js';
@@ -214,8 +214,7 @@ class FlashnetClient {
214
214
  }
215
215
  /**
216
216
  * Convert a token identifier into the raw hex string form expected by the Flashnet backend.
217
- * If the identifier is the BTC constant or already a hex string, it is returned unchanged.
218
- * If it is in Bech32m human-readable form, it is decoded to hex.
217
+ * Handles BTC constant, hex strings, and Bech32m human-readable format.
219
218
  */
220
219
  toHexTokenIdentifier(tokenIdentifier) {
221
220
  if (tokenIdentifier === BTC_ASSET_PUBKEY) {
@@ -300,7 +299,7 @@ class FlashnetClient {
300
299
  }
301
300
  }
302
301
  }
303
- // ===== Pool Operations =====
302
+ // Pool Operations
304
303
  /**
305
304
  * List pools with optional filters
306
305
  */
@@ -563,7 +562,7 @@ class FlashnetClient {
563
562
  };
564
563
  return this.typedApi.confirmInitialDeposit(request);
565
564
  }
566
- // ===== Swap Operations =====
565
+ // Swap Operations
567
566
  /**
568
567
  * Simulate a swap without executing it
569
568
  */
@@ -812,7 +811,7 @@ class FlashnetClient {
812
811
  return response;
813
812
  }, [initialTransferId], firstPoolId);
814
813
  }
815
- // ===== Liquidity Operations =====
814
+ // Liquidity Operations
816
815
  /**
817
816
  * Simulate adding liquidity
818
817
  */
@@ -963,7 +962,7 @@ class FlashnetClient {
963
962
  }
964
963
  return response;
965
964
  }
966
- // ===== Host Operations =====
965
+ // Host Operations
967
966
  /**
968
967
  * Register as a host
969
968
  */
@@ -1109,7 +1108,7 @@ class FlashnetClient {
1109
1108
  await this.ensureInitialized();
1110
1109
  return this.typedApi.getIntegratorFees();
1111
1110
  }
1112
- // ===== Escrow Operations =====
1111
+ // Escrow Operations
1113
1112
  /**
1114
1113
  * Creates a new escrow contract.
1115
1114
  * This is the first step in a two-step process: create, then fund.
@@ -1248,7 +1247,7 @@ class FlashnetClient {
1248
1247
  await this.ensureInitialized();
1249
1248
  return this.typedApi.getEscrow(escrowId);
1250
1249
  }
1251
- // ===== Swap History =====
1250
+ // Swap History
1252
1251
  /**
1253
1252
  * Get swaps for a specific pool
1254
1253
  */
@@ -1271,7 +1270,7 @@ class FlashnetClient {
1271
1270
  const user = userPublicKey || this.publicKey;
1272
1271
  return this.typedApi.getUserSwaps(user, query);
1273
1272
  }
1274
- // ===== Clawback =====
1273
+ // Clawback
1275
1274
  /**
1276
1275
  * Request clawback of a stuck inbound transfer to an LP wallet
1277
1276
  */
@@ -1456,7 +1455,7 @@ class FlashnetClient {
1456
1455
  throw flashnetError;
1457
1456
  }
1458
1457
  }
1459
- // ===== Clawback Monitor =====
1458
+ // Clawback Monitor
1460
1459
  /**
1461
1460
  * Start a background job that periodically polls for clawbackable transfers
1462
1461
  * and automatically claws them back.
@@ -1590,7 +1589,7 @@ class FlashnetClient {
1590
1589
  },
1591
1590
  };
1592
1591
  }
1593
- // ===== Token Address Operations =====
1592
+ // Token Address Operations
1594
1593
  /**
1595
1594
  * Encode a token identifier into a human-readable token address using the client's Spark network
1596
1595
  * @param tokenIdentifier - Token identifier as hex string or Uint8Array
@@ -1625,8 +1624,8 @@ class FlashnetClient {
1625
1624
  decodeLegacyTokenAddress(address) {
1626
1625
  return decodeSparkHumanReadableTokenIdentifier(address, this.sparkNetwork);
1627
1626
  }
1628
- // ===== Status =====
1629
- // ===== Config Inspection =====
1627
+ // Status
1628
+ // Config Inspection
1630
1629
  /**
1631
1630
  * Get raw feature status list (cached briefly)
1632
1631
  */
@@ -1687,7 +1686,7 @@ class FlashnetClient {
1687
1686
  await this.ensureInitialized();
1688
1687
  return this.typedApi.ping();
1689
1688
  }
1690
- // ===== Helper Methods =====
1689
+ // Helper Methods
1691
1690
  /**
1692
1691
  * Performs asset transfer using generalized asset address for both BTC and tokens.
1693
1692
  */
@@ -1787,7 +1786,7 @@ class FlashnetClient {
1787
1786
  throw new Error(errorMessage);
1788
1787
  }
1789
1788
  }
1790
- // ===== Lightning Payment with Token =====
1789
+ // Lightning Payment with Token
1791
1790
  /**
1792
1791
  * Get a quote for paying a Lightning invoice with a token.
1793
1792
  * This calculates the optimal pool and token amount needed.
@@ -1803,7 +1802,18 @@ class FlashnetClient {
1803
1802
  // Decode the invoice to get the amount
1804
1803
  const invoiceAmountSats = await this.decodeInvoiceAmount(invoice);
1805
1804
  if (!invoiceAmountSats || invoiceAmountSats <= 0) {
1806
- throw new Error("Unable to decode invoice amount. Zero-amount invoices are not supported for token payments.");
1805
+ throw new FlashnetError("Unable to decode invoice amount. Zero-amount invoices are not supported for token payments.", {
1806
+ response: {
1807
+ errorCode: "FSAG-1002",
1808
+ errorCategory: "Validation",
1809
+ message: "Unable to decode invoice amount. Zero-amount invoices are not supported for token payments.",
1810
+ requestId: "",
1811
+ timestamp: new Date().toISOString(),
1812
+ service: "sdk",
1813
+ severity: "Error",
1814
+ remediation: "Provide a valid BOLT11 invoice with a non-zero amount.",
1815
+ },
1816
+ });
1807
1817
  }
1808
1818
  // Get Lightning fee estimate
1809
1819
  const lightningFeeEstimate = await this.getLightningFeeEstimate(invoice);
@@ -1822,9 +1832,19 @@ class FlashnetClient {
1822
1832
  // Check BTC minimum (output from swap)
1823
1833
  const btcMinAmount = minAmounts.get(BTC_ASSET_PUBKEY.toLowerCase());
1824
1834
  if (btcMinAmount && totalBtcNeeded < btcMinAmount) {
1825
- throw new Error(`Invoice amount too small. Flashnet minimum BTC output is ${btcMinAmount} sats, ` +
1826
- `but invoice + lightning fee totals only ${totalBtcNeeded} sats. ` +
1827
- `Please use an invoice of at least ${btcMinAmount} sats.`);
1835
+ const msg = `Invoice amount too small. Minimum BTC output is ${btcMinAmount} sats, but invoice + lightning fee totals only ${totalBtcNeeded} sats.`;
1836
+ throw new FlashnetError(msg, {
1837
+ response: {
1838
+ errorCode: "FSAG-1003",
1839
+ errorCategory: "Validation",
1840
+ message: msg,
1841
+ requestId: "",
1842
+ timestamp: new Date().toISOString(),
1843
+ service: "sdk",
1844
+ severity: "Error",
1845
+ remediation: `Use an invoice of at least ${btcMinAmount} sats.`,
1846
+ },
1847
+ });
1828
1848
  }
1829
1849
  // Find the best pool to swap token -> BTC
1830
1850
  const poolQuote = await this.findBestPoolForTokenToBtc(tokenAddress, totalBtcNeeded.toString(), options?.integratorFeeRateBps);
@@ -1833,9 +1853,19 @@ class FlashnetClient {
1833
1853
  const tokenMinAmount = minAmounts.get(tokenHex);
1834
1854
  if (tokenMinAmount &&
1835
1855
  BigInt(poolQuote.tokenAmountRequired) < tokenMinAmount) {
1836
- throw new Error(`Token amount too small. Flashnet minimum input is ${tokenMinAmount} units, ` +
1837
- `but calculated amount is only ${poolQuote.tokenAmountRequired} units. ` +
1838
- `Please use a larger invoice amount.`);
1856
+ const msg = `Token amount too small. Minimum input is ${tokenMinAmount} units, but calculated amount is only ${poolQuote.tokenAmountRequired} units.`;
1857
+ throw new FlashnetError(msg, {
1858
+ response: {
1859
+ errorCode: "FSAG-1003",
1860
+ errorCategory: "Validation",
1861
+ message: msg,
1862
+ requestId: "",
1863
+ timestamp: new Date().toISOString(),
1864
+ service: "sdk",
1865
+ severity: "Error",
1866
+ remediation: "Use a larger invoice amount.",
1867
+ },
1868
+ });
1839
1869
  }
1840
1870
  // Calculate the BTC variable fee adjustment (how much extra we're requesting)
1841
1871
  const btcVariableFeeAdjustment = Number(totalBtcNeeded - baseBtcNeeded);
@@ -2069,6 +2099,8 @@ class FlashnetClient {
2069
2099
  const tokenHex = this.toHexTokenIdentifier(tokenAddress);
2070
2100
  const btcHex = BTC_ASSET_PUBKEY;
2071
2101
  // Find all pools that have this token paired with BTC
2102
+ // Note: The API may return the same pool for both filter combinations,
2103
+ // so we need to deduplicate and determine tokenIsAssetA from actual pool data
2072
2104
  const poolsWithTokenAsA = await this.listPools({
2073
2105
  assetAAddress: tokenHex,
2074
2106
  assetBAddress: btcHex,
@@ -2077,17 +2109,57 @@ class FlashnetClient {
2077
2109
  assetAAddress: btcHex,
2078
2110
  assetBAddress: tokenHex,
2079
2111
  });
2080
- const allPools = [
2081
- ...poolsWithTokenAsA.pools.map((p) => ({ ...p, tokenIsAssetA: true })),
2082
- ...poolsWithTokenAsB.pools.map((p) => ({ ...p, tokenIsAssetA: false })),
2083
- ];
2112
+ // Deduplicate pools by poolId and determine tokenIsAssetA from actual pool addresses
2113
+ const poolMap = new Map();
2114
+ for (const p of [...poolsWithTokenAsA.pools, ...poolsWithTokenAsB.pools]) {
2115
+ if (!poolMap.has(p.lpPublicKey)) {
2116
+ // Determine tokenIsAssetA from actual pool asset addresses, not from which query returned it
2117
+ const tokenIsAssetA = p.assetAAddress?.toLowerCase() === tokenHex.toLowerCase();
2118
+ poolMap.set(p.lpPublicKey, { pool: p, tokenIsAssetA });
2119
+ }
2120
+ }
2121
+ const allPools = Array.from(poolMap.values()).map(({ pool, tokenIsAssetA }) => ({
2122
+ ...pool,
2123
+ tokenIsAssetA,
2124
+ }));
2084
2125
  if (allPools.length === 0) {
2085
- throw new Error(`No liquidity pool found for token ${tokenAddress} paired with BTC`);
2126
+ throw new FlashnetError(`No liquidity pool found for token ${tokenAddress} paired with BTC`, {
2127
+ response: {
2128
+ errorCode: "FSAG-4001",
2129
+ errorCategory: "Business",
2130
+ message: `No liquidity pool found for token ${tokenAddress} paired with BTC`,
2131
+ requestId: "",
2132
+ timestamp: new Date().toISOString(),
2133
+ service: "sdk",
2134
+ severity: "Error",
2135
+ },
2136
+ });
2137
+ }
2138
+ // Pre-check: Get minimum amounts to provide clear error if invoice is too small
2139
+ const minAmounts = await this.getMinAmountsMap();
2140
+ const btcMinAmount = minAmounts.get(BTC_ASSET_PUBKEY.toLowerCase());
2141
+ // Check if the BTC amount needed is below the minimum
2142
+ if (btcMinAmount && BigInt(btcAmountNeeded) < btcMinAmount) {
2143
+ const msg = `Invoice amount too small. Minimum ${btcMinAmount} sats required, but invoice only requires ${btcAmountNeeded} sats.`;
2144
+ throw new FlashnetError(msg, {
2145
+ response: {
2146
+ errorCode: "FSAG-1003",
2147
+ errorCategory: "Validation",
2148
+ message: msg,
2149
+ requestId: "",
2150
+ timestamp: new Date().toISOString(),
2151
+ service: "sdk",
2152
+ severity: "Error",
2153
+ remediation: `Use an invoice with at least ${btcMinAmount} sats.`,
2154
+ },
2155
+ });
2086
2156
  }
2087
2157
  // Find the best pool (lowest token cost for the required BTC)
2088
2158
  let bestPool = null;
2089
2159
  let bestTokenAmount = BigInt(Number.MAX_SAFE_INTEGER);
2090
2160
  let bestSimulation = null;
2161
+ // Track errors for each pool to provide better diagnostics
2162
+ const poolErrors = [];
2091
2163
  for (const pool of allPools) {
2092
2164
  try {
2093
2165
  // Get pool details for reserves
@@ -2121,12 +2193,53 @@ class FlashnetClient {
2121
2193
  warningMessage: simulation.warningMessage,
2122
2194
  };
2123
2195
  }
2196
+ else {
2197
+ // Simulation output was insufficient
2198
+ const btcReserve = pool.tokenIsAssetA
2199
+ ? poolDetails.assetBReserve
2200
+ : poolDetails.assetAReserve;
2201
+ poolErrors.push({
2202
+ poolId: pool.lpPublicKey,
2203
+ error: `Simulation output (${simulation.amountOut} sats) < required (${btcAmountNeeded} sats)`,
2204
+ btcReserve,
2205
+ });
2206
+ }
2124
2207
  }
2125
2208
  }
2126
- catch { }
2209
+ catch (e) {
2210
+ // Capture pool errors for diagnostics
2211
+ const errorMessage = e instanceof Error ? e.message : String(e);
2212
+ poolErrors.push({
2213
+ poolId: pool.lpPublicKey,
2214
+ error: errorMessage,
2215
+ });
2216
+ }
2127
2217
  }
2128
2218
  if (!bestPool || !bestSimulation) {
2129
- throw new Error(`No pool has sufficient liquidity for ${btcAmountNeeded} sats`);
2219
+ let errorMessage = `No pool has sufficient liquidity for ${btcAmountNeeded} sats`;
2220
+ if (poolErrors.length > 0) {
2221
+ const details = poolErrors
2222
+ .map((pe) => {
2223
+ const reserveInfo = pe.btcReserve
2224
+ ? ` (BTC reserve: ${pe.btcReserve})`
2225
+ : "";
2226
+ return ` - Pool ${pe.poolId.slice(0, 12)}...${reserveInfo}: ${pe.error}`;
2227
+ })
2228
+ .join("\n");
2229
+ errorMessage += `\n\nPool evaluation details:\n${details}`;
2230
+ }
2231
+ throw new FlashnetError(errorMessage, {
2232
+ response: {
2233
+ errorCode: "FSAG-4201",
2234
+ errorCategory: "Business",
2235
+ message: errorMessage,
2236
+ requestId: "",
2237
+ timestamp: new Date().toISOString(),
2238
+ service: "sdk",
2239
+ severity: "Error",
2240
+ remediation: "Try a smaller amount or wait for more liquidity.",
2241
+ },
2242
+ });
2130
2243
  }
2131
2244
  const poolDetails = await this.getPool(bestPool.lpPublicKey);
2132
2245
  return {
@@ -2375,7 +2488,7 @@ class FlashnetClient {
2375
2488
  async cleanup() {
2376
2489
  await this._wallet.cleanupConnections();
2377
2490
  }
2378
- // ===== Config and Policy Enforcement Helpers =====
2491
+ // Config and Policy Enforcement Helpers
2379
2492
  async ensureAmmOperationAllowed(requiredFeature) {
2380
2493
  await this.ensurePingOk();
2381
2494
  const featureMap = await this.getFeatureStatusMap();
@@ -2555,6 +2668,470 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
2555
2668
  throw new Error(`Asset B is not allowed for pool creation: ${assetBHex}`);
2556
2669
  }
2557
2670
  }
2671
+ // V3 Concentrated Liquidity Operations
2672
+ /**
2673
+ * Create a V3 concentrated liquidity pool
2674
+ *
2675
+ * Concentrated liquidity pools allow LPs to provide liquidity within specific
2676
+ * price ranges (tick ranges) for higher capital efficiency.
2677
+ *
2678
+ * @param params Pool creation parameters
2679
+ * @param params.assetAAddress - Address of asset A (base asset)
2680
+ * @param params.assetBAddress - Address of asset B (quote asset)
2681
+ * @param params.tickSpacing - Tick spacing (common values: 10, 60, 200)
2682
+ * @param params.initialPrice - Initial price of asset A in terms of asset B
2683
+ * @param params.lpFeeRateBps - LP fee rate in basis points
2684
+ * @param params.hostFeeRateBps - Host fee rate in basis points
2685
+ * @param params.hostNamespace - Optional host namespace
2686
+ * @param params.poolOwnerPublicKey - Optional pool owner (defaults to wallet pubkey)
2687
+ */
2688
+ async createConcentratedPool(params) {
2689
+ await this.ensureInitialized();
2690
+ await this.ensureAmmOperationAllowed("allow_pool_creation");
2691
+ await this.assertAllowedAssetBForPoolCreation(this.toHexTokenIdentifier(params.assetBAddress));
2692
+ const poolOwnerPublicKey = params.poolOwnerPublicKey ?? this.publicKey;
2693
+ // Generate intent
2694
+ const nonce = generateNonce();
2695
+ const intentMessage = generateCreateConcentratedPoolIntentMessage({
2696
+ poolOwnerPublicKey,
2697
+ assetAAddress: this.toHexTokenIdentifier(params.assetAAddress),
2698
+ assetBAddress: this.toHexTokenIdentifier(params.assetBAddress),
2699
+ tickSpacing: params.tickSpacing,
2700
+ initialPrice: params.initialPrice,
2701
+ lpFeeRateBps: params.lpFeeRateBps.toString(),
2702
+ hostFeeRateBps: params.hostFeeRateBps.toString(),
2703
+ nonce,
2704
+ });
2705
+ // Sign intent
2706
+ const messageHash = sha256(intentMessage);
2707
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
2708
+ const request = {
2709
+ poolOwnerPublicKey,
2710
+ assetAAddress: this.toHexTokenIdentifier(params.assetAAddress),
2711
+ assetBAddress: this.toHexTokenIdentifier(params.assetBAddress),
2712
+ tickSpacing: params.tickSpacing,
2713
+ initialPrice: params.initialPrice,
2714
+ lpFeeRateBps: params.lpFeeRateBps.toString(),
2715
+ hostFeeRateBps: params.hostFeeRateBps.toString(),
2716
+ hostNamespace: params.hostNamespace,
2717
+ nonce,
2718
+ signature: getHexFromUint8Array(signature),
2719
+ };
2720
+ return this.typedApi.createConcentratedPool(request);
2721
+ }
2722
+ /**
2723
+ * Add liquidity to a V3 concentrated position
2724
+ *
2725
+ * Increases liquidity within a specific tick range. If the position doesn't exist,
2726
+ * a new position is created.
2727
+ *
2728
+ * @param params Position parameters
2729
+ * @param params.poolId - Pool ID (LP identity public key)
2730
+ * @param params.tickLower - Lower tick of the position
2731
+ * @param params.tickUpper - Upper tick of the position
2732
+ * @param params.amountADesired - Desired amount of asset A to add
2733
+ * @param params.amountBDesired - Desired amount of asset B to add
2734
+ * @param params.amountAMin - Minimum amount of asset A (slippage protection)
2735
+ * @param params.amountBMin - Minimum amount of asset B (slippage protection)
2736
+ * @param params.useFreeBalanceA - If true, use free balance from pool for asset A instead of Spark transfer
2737
+ * @param params.useFreeBalanceB - If true, use free balance from pool for asset B instead of Spark transfer
2738
+ * @param params.retainExcessInBalance - If true, retain any excess amounts in pool free balance instead of refunding via Spark
2739
+ */
2740
+ async increaseLiquidity(params) {
2741
+ await this.ensureInitialized();
2742
+ await this.ensureAmmOperationAllowed("allow_add_liquidity");
2743
+ // Get pool details to know asset addresses
2744
+ const pool = await this.getPool(params.poolId);
2745
+ // Transfer assets to pool (unless using free balance)
2746
+ const lpSparkAddress = encodeSparkAddressNew({
2747
+ identityPublicKey: params.poolId,
2748
+ network: this.sparkNetwork,
2749
+ });
2750
+ let assetATransferId = "";
2751
+ let assetBTransferId = "";
2752
+ const transferIds = [];
2753
+ // Transfer asset A if not using free balance
2754
+ if (!params.useFreeBalanceA && BigInt(params.amountADesired) > 0n) {
2755
+ assetATransferId = await this.transferAsset({
2756
+ receiverSparkAddress: lpSparkAddress,
2757
+ assetAddress: pool.assetAAddress,
2758
+ amount: params.amountADesired,
2759
+ }, "Insufficient balance for adding V3 liquidity (Asset A): ");
2760
+ transferIds.push(assetATransferId);
2761
+ }
2762
+ // Transfer asset B if not using free balance
2763
+ if (!params.useFreeBalanceB && BigInt(params.amountBDesired) > 0n) {
2764
+ assetBTransferId = await this.transferAsset({
2765
+ receiverSparkAddress: lpSparkAddress,
2766
+ assetAddress: pool.assetBAddress,
2767
+ amount: params.amountBDesired,
2768
+ }, "Insufficient balance for adding V3 liquidity (Asset B): ");
2769
+ transferIds.push(assetBTransferId);
2770
+ }
2771
+ const executeIncrease = async () => {
2772
+ // Generate intent
2773
+ const nonce = generateNonce();
2774
+ const intentMessage = generateIncreaseLiquidityIntentMessage({
2775
+ userPublicKey: this.publicKey,
2776
+ lpIdentityPublicKey: params.poolId,
2777
+ tickLower: params.tickLower,
2778
+ tickUpper: params.tickUpper,
2779
+ assetASparkTransferId: assetATransferId,
2780
+ assetBSparkTransferId: assetBTransferId,
2781
+ amountADesired: params.amountADesired,
2782
+ amountBDesired: params.amountBDesired,
2783
+ amountAMin: params.amountAMin,
2784
+ amountBMin: params.amountBMin,
2785
+ nonce,
2786
+ });
2787
+ // Sign intent
2788
+ const messageHash = sha256(intentMessage);
2789
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
2790
+ const request = {
2791
+ poolId: params.poolId,
2792
+ tickLower: params.tickLower,
2793
+ tickUpper: params.tickUpper,
2794
+ assetASparkTransferId: assetATransferId,
2795
+ assetBSparkTransferId: assetBTransferId,
2796
+ amountADesired: params.amountADesired,
2797
+ amountBDesired: params.amountBDesired,
2798
+ amountAMin: params.amountAMin,
2799
+ amountBMin: params.amountBMin,
2800
+ useFreeBalanceA: params.useFreeBalanceA,
2801
+ useFreeBalanceB: params.useFreeBalanceB,
2802
+ retainExcessInBalance: params.retainExcessInBalance,
2803
+ nonce,
2804
+ signature: getHexFromUint8Array(signature),
2805
+ };
2806
+ const response = await this.typedApi.increaseLiquidity(request);
2807
+ if (!response.accepted) {
2808
+ const errorMessage = response.error || "Increase liquidity rejected by the AMM";
2809
+ const hasRefund = !!(response.amountARefund || response.amountBRefund);
2810
+ const refundInfo = hasRefund
2811
+ ? ` Refunds: Asset A: ${response.amountARefund || "0"}, Asset B: ${response.amountBRefund || "0"}`
2812
+ : "";
2813
+ throw new FlashnetError(`${errorMessage}.${refundInfo}`, {
2814
+ response: {
2815
+ errorCode: hasRefund ? "FSAG-4203" : "UNKNOWN",
2816
+ errorCategory: hasRefund ? "Business" : "System",
2817
+ message: `${errorMessage}.${refundInfo}`,
2818
+ requestId: response.requestId || "",
2819
+ timestamp: new Date().toISOString(),
2820
+ service: "amm-gateway",
2821
+ severity: "Error",
2822
+ },
2823
+ httpStatus: 400,
2824
+ transferIds: hasRefund ? [] : transferIds,
2825
+ lpIdentityPublicKey: params.poolId,
2826
+ });
2827
+ }
2828
+ return response;
2829
+ };
2830
+ // Execute with auto-clawback if we made transfers
2831
+ if (transferIds.length > 0) {
2832
+ return this.executeWithAutoClawback(executeIncrease, transferIds, params.poolId);
2833
+ }
2834
+ return executeIncrease();
2835
+ }
2836
+ /**
2837
+ * Remove liquidity from a V3 concentrated position
2838
+ *
2839
+ * Decreases liquidity from a specific tick range position.
2840
+ *
2841
+ * @param params Position parameters
2842
+ * @param params.poolId - Pool ID (LP identity public key)
2843
+ * @param params.tickLower - Lower tick of the position
2844
+ * @param params.tickUpper - Upper tick of the position
2845
+ * @param params.liquidityToRemove - Amount of liquidity to remove (use "0" to remove all)
2846
+ * @param params.amountAMin - Minimum amount of asset A to receive (slippage protection)
2847
+ * @param params.amountBMin - Minimum amount of asset B to receive (slippage protection)
2848
+ * @param params.retainInBalance - If true, retain withdrawn assets in pool free balance instead of sending via Spark
2849
+ */
2850
+ async decreaseLiquidity(params) {
2851
+ await this.ensureInitialized();
2852
+ await this.ensureAmmOperationAllowed("allow_withdraw_liquidity");
2853
+ // Generate intent
2854
+ const nonce = generateNonce();
2855
+ const intentMessage = generateDecreaseLiquidityIntentMessage({
2856
+ userPublicKey: this.publicKey,
2857
+ lpIdentityPublicKey: params.poolId,
2858
+ tickLower: params.tickLower,
2859
+ tickUpper: params.tickUpper,
2860
+ liquidityToRemove: params.liquidityToRemove,
2861
+ amountAMin: params.amountAMin,
2862
+ amountBMin: params.amountBMin,
2863
+ nonce,
2864
+ });
2865
+ // Sign intent
2866
+ const messageHash = sha256(intentMessage);
2867
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
2868
+ const request = {
2869
+ poolId: params.poolId,
2870
+ tickLower: params.tickLower,
2871
+ tickUpper: params.tickUpper,
2872
+ liquidityToRemove: params.liquidityToRemove,
2873
+ amountAMin: params.amountAMin,
2874
+ amountBMin: params.amountBMin,
2875
+ retainInBalance: params.retainInBalance,
2876
+ nonce,
2877
+ signature: getHexFromUint8Array(signature),
2878
+ };
2879
+ const response = await this.typedApi.decreaseLiquidity(request);
2880
+ if (!response.accepted) {
2881
+ const errorMessage = response.error || "Decrease liquidity rejected by the AMM";
2882
+ throw new Error(errorMessage);
2883
+ }
2884
+ return response;
2885
+ }
2886
+ /**
2887
+ * Collect accumulated fees from a V3 position
2888
+ *
2889
+ * Collects fees earned from trading activity without removing liquidity.
2890
+ *
2891
+ * @param params Position parameters
2892
+ * @param params.poolId - Pool ID (LP identity public key)
2893
+ * @param params.tickLower - Lower tick of the position
2894
+ * @param params.tickUpper - Upper tick of the position
2895
+ * @param params.retainInBalance - If true, retain collected fees in pool free balance instead of sending via Spark
2896
+ */
2897
+ async collectFees(params) {
2898
+ await this.ensureInitialized();
2899
+ await this.ensureAmmOperationAllowed("allow_withdraw_fees");
2900
+ // Generate intent
2901
+ const nonce = generateNonce();
2902
+ const intentMessage = generateCollectFeesIntentMessage({
2903
+ userPublicKey: this.publicKey,
2904
+ lpIdentityPublicKey: params.poolId,
2905
+ tickLower: params.tickLower,
2906
+ tickUpper: params.tickUpper,
2907
+ nonce,
2908
+ });
2909
+ // Sign intent
2910
+ const messageHash = sha256(intentMessage);
2911
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
2912
+ const request = {
2913
+ poolId: params.poolId,
2914
+ tickLower: params.tickLower,
2915
+ tickUpper: params.tickUpper,
2916
+ retainInBalance: params.retainInBalance,
2917
+ nonce,
2918
+ signature: getHexFromUint8Array(signature),
2919
+ };
2920
+ const response = await this.typedApi.collectFees(request);
2921
+ if (!response.accepted) {
2922
+ const errorMessage = response.error || "Collect fees rejected by the AMM";
2923
+ throw new Error(errorMessage);
2924
+ }
2925
+ return response;
2926
+ }
2927
+ /**
2928
+ * Rebalance a V3 position to a new tick range
2929
+ *
2930
+ * Atomically moves liquidity from an old position to a new tick range.
2931
+ * Optionally can add additional funds during rebalancing.
2932
+ *
2933
+ * @param params Rebalance parameters
2934
+ * @param params.poolId - Pool ID (LP identity public key)
2935
+ * @param params.oldTickLower - Lower tick of the current position
2936
+ * @param params.oldTickUpper - Upper tick of the current position
2937
+ * @param params.newTickLower - Lower tick for the new position
2938
+ * @param params.newTickUpper - Upper tick for the new position
2939
+ * @param params.liquidityToMove - Amount of liquidity to move (use "0" to move all)
2940
+ * @param params.additionalAmountA - Optional additional asset A to add
2941
+ * @param params.additionalAmountB - Optional additional asset B to add
2942
+ * @param params.retainInBalance - If true, retain any excess amounts in pool free balance instead of sending via Spark
2943
+ */
2944
+ async rebalancePosition(params) {
2945
+ await this.ensureInitialized();
2946
+ await this.ensureAmmOperationAllowed("allow_add_liquidity");
2947
+ // Get pool details
2948
+ const pool = await this.getPool(params.poolId);
2949
+ // Transfer additional assets if provided
2950
+ let assetATransferId;
2951
+ let assetBTransferId;
2952
+ const lpSparkAddress = encodeSparkAddressNew({
2953
+ identityPublicKey: params.poolId,
2954
+ network: this.sparkNetwork,
2955
+ });
2956
+ if (params.additionalAmountA && BigInt(params.additionalAmountA) > 0n) {
2957
+ assetATransferId = await this.transferAsset({
2958
+ receiverSparkAddress: lpSparkAddress,
2959
+ assetAddress: pool.assetAAddress,
2960
+ amount: params.additionalAmountA,
2961
+ }, "Insufficient balance for rebalance (Asset A): ");
2962
+ }
2963
+ if (params.additionalAmountB && BigInt(params.additionalAmountB) > 0n) {
2964
+ assetBTransferId = await this.transferAsset({
2965
+ receiverSparkAddress: lpSparkAddress,
2966
+ assetAddress: pool.assetBAddress,
2967
+ amount: params.additionalAmountB,
2968
+ }, "Insufficient balance for rebalance (Asset B): ");
2969
+ }
2970
+ // Collect transfer IDs for potential clawback
2971
+ const transferIds = [];
2972
+ if (assetATransferId) {
2973
+ transferIds.push(assetATransferId);
2974
+ }
2975
+ if (assetBTransferId) {
2976
+ transferIds.push(assetBTransferId);
2977
+ }
2978
+ // Execute (with auto-clawback if we have transfers)
2979
+ const executeRebalance = async () => {
2980
+ // Generate intent
2981
+ const nonce = generateNonce();
2982
+ const intentMessage = generateRebalancePositionIntentMessage({
2983
+ userPublicKey: this.publicKey,
2984
+ lpIdentityPublicKey: params.poolId,
2985
+ oldTickLower: params.oldTickLower,
2986
+ oldTickUpper: params.oldTickUpper,
2987
+ newTickLower: params.newTickLower,
2988
+ newTickUpper: params.newTickUpper,
2989
+ liquidityToMove: params.liquidityToMove,
2990
+ assetASparkTransferId: assetATransferId,
2991
+ assetBSparkTransferId: assetBTransferId,
2992
+ additionalAmountA: params.additionalAmountA,
2993
+ additionalAmountB: params.additionalAmountB,
2994
+ nonce,
2995
+ });
2996
+ // Sign intent
2997
+ const messageHash = sha256(intentMessage);
2998
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
2999
+ const request = {
3000
+ poolId: params.poolId,
3001
+ oldTickLower: params.oldTickLower,
3002
+ oldTickUpper: params.oldTickUpper,
3003
+ newTickLower: params.newTickLower,
3004
+ newTickUpper: params.newTickUpper,
3005
+ liquidityToMove: params.liquidityToMove,
3006
+ assetASparkTransferId: assetATransferId,
3007
+ assetBSparkTransferId: assetBTransferId,
3008
+ additionalAmountA: params.additionalAmountA,
3009
+ additionalAmountB: params.additionalAmountB,
3010
+ retainInBalance: params.retainInBalance,
3011
+ nonce,
3012
+ signature: getHexFromUint8Array(signature),
3013
+ };
3014
+ const response = await this.typedApi.rebalancePosition(request);
3015
+ if (!response.accepted) {
3016
+ const errorMessage = response.error || "Rebalance position rejected by the AMM";
3017
+ throw new FlashnetError(errorMessage, {
3018
+ response: {
3019
+ errorCode: "UNKNOWN",
3020
+ errorCategory: "System",
3021
+ message: errorMessage,
3022
+ requestId: response.requestId || "",
3023
+ timestamp: new Date().toISOString(),
3024
+ service: "amm-gateway",
3025
+ severity: "Error",
3026
+ },
3027
+ httpStatus: 400,
3028
+ transferIds,
3029
+ lpIdentityPublicKey: params.poolId,
3030
+ });
3031
+ }
3032
+ return response;
3033
+ };
3034
+ // Use auto-clawback if we made transfers
3035
+ if (transferIds.length > 0) {
3036
+ return this.executeWithAutoClawback(executeRebalance, transferIds, params.poolId);
3037
+ }
3038
+ return executeRebalance();
3039
+ }
3040
+ /**
3041
+ * List V3 concentrated liquidity positions
3042
+ *
3043
+ * @param query Optional query parameters
3044
+ * @param query.poolId - Filter by pool ID
3045
+ * @param query.page - Page number (default: 1)
3046
+ * @param query.pageSize - Page size (default: 20, max: 100)
3047
+ */
3048
+ async listConcentratedPositions(query) {
3049
+ await this.ensureInitialized();
3050
+ return this.typedApi.listConcentratedPositions(query);
3051
+ }
3052
+ /**
3053
+ * Get pool liquidity distribution for visualization
3054
+ *
3055
+ * Returns aggregated liquidity ranges for visualizing the liquidity distribution.
3056
+ *
3057
+ * @param poolId - Pool ID (LP identity public key)
3058
+ */
3059
+ async getPoolLiquidity(poolId) {
3060
+ await this.ensureInitialized();
3061
+ return this.typedApi.getPoolLiquidity(poolId);
3062
+ }
3063
+ /**
3064
+ * Get pool ticks for simulation
3065
+ *
3066
+ * Returns all initialized ticks with their liquidity deltas for swap simulation.
3067
+ *
3068
+ * @param poolId - Pool ID (LP identity public key)
3069
+ */
3070
+ async getPoolTicks(poolId) {
3071
+ await this.ensureInitialized();
3072
+ return this.typedApi.getPoolTicks(poolId);
3073
+ }
3074
+ // V3 Free Balance Methods
3075
+ /**
3076
+ * Get user's free balance for a specific V3 pool
3077
+ *
3078
+ * Returns the user's current free balance in the pool, which can be used for
3079
+ * liquidity operations without needing to transfer from the wallet.
3080
+ *
3081
+ * @param poolId - Pool ID (LP identity public key)
3082
+ */
3083
+ async getConcentratedBalance(poolId) {
3084
+ await this.ensureInitialized();
3085
+ return this.typedApi.getConcentratedBalance(poolId);
3086
+ }
3087
+ /**
3088
+ * Get user's free balances across all V3 pools
3089
+ *
3090
+ * Returns all free balances for the authenticated user across all V3 pools.
3091
+ */
3092
+ async getConcentratedBalances() {
3093
+ await this.ensureInitialized();
3094
+ return this.typedApi.getConcentratedBalances();
3095
+ }
3096
+ /**
3097
+ * Withdraw free balance from a V3 pool to user's Spark wallet
3098
+ *
3099
+ * Withdraws accumulated free balance from a pool. Use "0" to skip an asset,
3100
+ * or "max" to withdraw all available balance of that asset.
3101
+ *
3102
+ * @param params Withdrawal parameters
3103
+ * @param params.poolId - Pool ID (LP identity public key)
3104
+ * @param params.amountA - Amount of asset A to withdraw ("0" to skip, "max" to withdraw all)
3105
+ * @param params.amountB - Amount of asset B to withdraw ("0" to skip, "max" to withdraw all)
3106
+ */
3107
+ async withdrawConcentratedBalance(params) {
3108
+ await this.ensureInitialized();
3109
+ // Generate intent
3110
+ const nonce = generateNonce();
3111
+ const intentMessage = generateWithdrawBalanceIntentMessage({
3112
+ userPublicKey: this.publicKey,
3113
+ lpIdentityPublicKey: params.poolId,
3114
+ amountA: params.amountA,
3115
+ amountB: params.amountB,
3116
+ nonce,
3117
+ });
3118
+ // Sign intent
3119
+ const messageHash = sha256(intentMessage);
3120
+ const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
3121
+ const request = {
3122
+ poolId: params.poolId,
3123
+ amountA: params.amountA,
3124
+ amountB: params.amountB,
3125
+ nonce,
3126
+ signature: getHexFromUint8Array(signature),
3127
+ };
3128
+ const response = await this.typedApi.withdrawConcentratedBalance(request);
3129
+ if (!response.accepted) {
3130
+ const errorMessage = response.error || "Withdraw balance rejected by the AMM";
3131
+ throw new Error(errorMessage);
3132
+ }
3133
+ return response;
3134
+ }
2558
3135
  }
2559
3136
 
2560
3137
  export { FlashnetClient };