@flashnet/sdk 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
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';
@@ -213,8 +214,7 @@ class FlashnetClient {
213
214
  }
214
215
  /**
215
216
  * Convert a token identifier into the raw hex string form expected by the Flashnet backend.
216
- * If the identifier is the BTC constant or already a hex string, it is returned unchanged.
217
- * If it is in Bech32m human-readable form, it is decoded to hex.
217
+ * Handles BTC constant, hex strings, and Bech32m human-readable format.
218
218
  */
219
219
  toHexTokenIdentifier(tokenIdentifier) {
220
220
  if (tokenIdentifier === BTC_ASSET_PUBKEY) {
@@ -364,7 +364,7 @@ class FlashnetClient {
364
364
  nonce,
365
365
  });
366
366
  // Sign intent
367
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
367
+ const messageHash = sha256(intentMessage);
368
368
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
369
369
  // Create pool
370
370
  const request = {
@@ -495,7 +495,7 @@ class FlashnetClient {
495
495
  totalHostFeeRateBps: params.totalHostFeeRateBps.toString(),
496
496
  nonce,
497
497
  });
498
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
498
+ const messageHash = sha256(intentMessage);
499
499
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
500
500
  // Create pool
501
501
  const request = {
@@ -551,7 +551,7 @@ class FlashnetClient {
551
551
  assetASparkTransferId,
552
552
  nonce,
553
553
  });
554
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
554
+ const messageHash = sha256(intentMessage);
555
555
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
556
556
  const request = {
557
557
  poolId,
@@ -635,7 +635,7 @@ class FlashnetClient {
635
635
  nonce,
636
636
  });
637
637
  // Sign intent
638
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
638
+ const messageHash = sha256(intentMessage);
639
639
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
640
640
  const request = {
641
641
  userPublicKey: this.publicKey,
@@ -771,7 +771,7 @@ class FlashnetClient {
771
771
  defaultIntegratorFeeRateBps: params.integratorFeeRateBps?.toString(),
772
772
  });
773
773
  // Sign intent
774
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
774
+ const messageHash = sha256(intentMessage);
775
775
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
776
776
  const request = {
777
777
  userPublicKey: this.publicKey,
@@ -870,7 +870,7 @@ class FlashnetClient {
870
870
  nonce,
871
871
  });
872
872
  // Sign intent
873
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
873
+ const messageHash = sha256(intentMessage);
874
874
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
875
875
  const request = {
876
876
  userPublicKey: this.publicKey,
@@ -945,7 +945,7 @@ class FlashnetClient {
945
945
  nonce,
946
946
  });
947
947
  // Sign intent
948
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
948
+ const messageHash = sha256(intentMessage);
949
949
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
950
950
  const request = {
951
951
  userPublicKey: this.publicKey,
@@ -979,7 +979,7 @@ class FlashnetClient {
979
979
  nonce,
980
980
  });
981
981
  // Sign intent
982
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
982
+ const messageHash = sha256(intentMessage);
983
983
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
984
984
  const request = {
985
985
  namespace: params.namespace,
@@ -1027,7 +1027,7 @@ class FlashnetClient {
1027
1027
  nonce,
1028
1028
  });
1029
1029
  // Sign intent
1030
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
1030
+ const messageHash = sha256(intentMessage);
1031
1031
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
1032
1032
  const request = {
1033
1033
  lpIdentityPublicKey: params.lpIdentityPublicKey,
@@ -1084,7 +1084,7 @@ class FlashnetClient {
1084
1084
  nonce,
1085
1085
  });
1086
1086
  // Sign intent
1087
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
1087
+ const messageHash = sha256(intentMessage);
1088
1088
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
1089
1089
  const request = {
1090
1090
  integratorPublicKey: this.publicKey,
@@ -1135,7 +1135,7 @@ class FlashnetClient {
1135
1135
  abandonConditions: params.abandonConditions || undefined,
1136
1136
  nonce,
1137
1137
  });
1138
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
1138
+ const messageHash = sha256(intentMessage);
1139
1139
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
1140
1140
  const request = {
1141
1141
  creatorPublicKey: this.publicKey,
@@ -1203,7 +1203,7 @@ class FlashnetClient {
1203
1203
  nonce,
1204
1204
  });
1205
1205
  // Sign
1206
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
1206
+ const messageHash = sha256(intentMessage);
1207
1207
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
1208
1208
  // Call API
1209
1209
  const request = {
@@ -1228,7 +1228,7 @@ class FlashnetClient {
1228
1228
  recipientPublicKey: this.publicKey,
1229
1229
  nonce,
1230
1230
  });
1231
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
1231
+ const messageHash = sha256(intentMessage);
1232
1232
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
1233
1233
  const request = {
1234
1234
  escrowId: params.escrowId,
@@ -1284,7 +1284,7 @@ class FlashnetClient {
1284
1284
  lpIdentityPublicKey: params.lpIdentityPublicKey,
1285
1285
  nonce,
1286
1286
  });
1287
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
1287
+ const messageHash = sha256(intentMessage);
1288
1288
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
1289
1289
  const request = {
1290
1290
  senderPublicKey: this.publicKey,
@@ -1765,7 +1765,7 @@ class FlashnetClient {
1765
1765
  assetBMinAmountIn: assetBMinAmountIn.toString(),
1766
1766
  nonce,
1767
1767
  });
1768
- const messageHash = new Uint8Array(await crypto.subtle.digest("SHA-256", intentMessage));
1768
+ const messageHash = sha256(intentMessage);
1769
1769
  const signature = await this._wallet.config.signer.signMessageWithIdentityKey(messageHash, true);
1770
1770
  const request = {
1771
1771
  userPublicKey: this.publicKey,
@@ -1802,7 +1802,18 @@ class FlashnetClient {
1802
1802
  // Decode the invoice to get the amount
1803
1803
  const invoiceAmountSats = await this.decodeInvoiceAmount(invoice);
1804
1804
  if (!invoiceAmountSats || invoiceAmountSats <= 0) {
1805
- 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
+ });
1806
1817
  }
1807
1818
  // Get Lightning fee estimate
1808
1819
  const lightningFeeEstimate = await this.getLightningFeeEstimate(invoice);
@@ -1821,9 +1832,19 @@ class FlashnetClient {
1821
1832
  // Check BTC minimum (output from swap)
1822
1833
  const btcMinAmount = minAmounts.get(BTC_ASSET_PUBKEY.toLowerCase());
1823
1834
  if (btcMinAmount && totalBtcNeeded < btcMinAmount) {
1824
- throw new Error(`Invoice amount too small. Flashnet minimum BTC output is ${btcMinAmount} sats, ` +
1825
- `but invoice + lightning fee totals only ${totalBtcNeeded} sats. ` +
1826
- `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
+ });
1827
1848
  }
1828
1849
  // Find the best pool to swap token -> BTC
1829
1850
  const poolQuote = await this.findBestPoolForTokenToBtc(tokenAddress, totalBtcNeeded.toString(), options?.integratorFeeRateBps);
@@ -1832,9 +1853,19 @@ class FlashnetClient {
1832
1853
  const tokenMinAmount = minAmounts.get(tokenHex);
1833
1854
  if (tokenMinAmount &&
1834
1855
  BigInt(poolQuote.tokenAmountRequired) < tokenMinAmount) {
1835
- throw new Error(`Token amount too small. Flashnet minimum input is ${tokenMinAmount} units, ` +
1836
- `but calculated amount is only ${poolQuote.tokenAmountRequired} units. ` +
1837
- `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
+ });
1838
1869
  }
1839
1870
  // Calculate the BTC variable fee adjustment (how much extra we're requesting)
1840
1871
  const btcVariableFeeAdjustment = Number(totalBtcNeeded - baseBtcNeeded);
@@ -2068,6 +2099,8 @@ class FlashnetClient {
2068
2099
  const tokenHex = this.toHexTokenIdentifier(tokenAddress);
2069
2100
  const btcHex = BTC_ASSET_PUBKEY;
2070
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
2071
2104
  const poolsWithTokenAsA = await this.listPools({
2072
2105
  assetAAddress: tokenHex,
2073
2106
  assetBAddress: btcHex,
@@ -2076,17 +2109,57 @@ class FlashnetClient {
2076
2109
  assetAAddress: btcHex,
2077
2110
  assetBAddress: tokenHex,
2078
2111
  });
2079
- const allPools = [
2080
- ...poolsWithTokenAsA.pools.map((p) => ({ ...p, tokenIsAssetA: true })),
2081
- ...poolsWithTokenAsB.pools.map((p) => ({ ...p, tokenIsAssetA: false })),
2082
- ];
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
+ }));
2083
2125
  if (allPools.length === 0) {
2084
- 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
+ });
2085
2156
  }
2086
2157
  // Find the best pool (lowest token cost for the required BTC)
2087
2158
  let bestPool = null;
2088
2159
  let bestTokenAmount = BigInt(Number.MAX_SAFE_INTEGER);
2089
2160
  let bestSimulation = null;
2161
+ // Track errors for each pool to provide better diagnostics
2162
+ const poolErrors = [];
2090
2163
  for (const pool of allPools) {
2091
2164
  try {
2092
2165
  // Get pool details for reserves
@@ -2120,12 +2193,53 @@ class FlashnetClient {
2120
2193
  warningMessage: simulation.warningMessage,
2121
2194
  };
2122
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
+ }
2123
2207
  }
2124
2208
  }
2125
- 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
+ }
2126
2217
  }
2127
2218
  if (!bestPool || !bestSimulation) {
2128
- 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
+ });
2129
2243
  }
2130
2244
  const poolDetails = await this.getPool(bestPool.lpPublicKey);
2131
2245
  return {