@flashnet/sdk 0.5.4 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/client/FlashnetClient.d.ts +38 -4
- package/dist/cjs/src/client/FlashnetClient.d.ts.map +1 -1
- package/dist/cjs/src/client/FlashnetClient.js +95 -97
- package/dist/cjs/src/client/FlashnetClient.js.map +1 -1
- package/dist/cjs/src/utils/bigint.d.ts +7 -0
- package/dist/cjs/src/utils/bigint.d.ts.map +1 -0
- package/dist/cjs/src/utils/bigint.js +24 -0
- package/dist/cjs/src/utils/bigint.js.map +1 -0
- package/dist/cjs/src/utils/index.d.ts +1 -0
- package/dist/cjs/src/utils/index.d.ts.map +1 -1
- package/dist/esm/src/client/FlashnetClient.d.ts +38 -4
- package/dist/esm/src/client/FlashnetClient.d.ts.map +1 -1
- package/dist/esm/src/client/FlashnetClient.js +95 -97
- package/dist/esm/src/client/FlashnetClient.js.map +1 -1
- package/dist/esm/src/utils/bigint.d.ts +7 -0
- package/dist/esm/src/utils/bigint.d.ts.map +1 -0
- package/dist/esm/src/utils/bigint.js +22 -0
- package/dist/esm/src/utils/bigint.js.map +1 -0
- package/dist/esm/src/utils/index.d.ts +1 -0
- package/dist/esm/src/utils/index.d.ts.map +1 -1
- package/package.json +7 -5
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import sha256 from 'fast-sha256';
|
|
2
|
+
import { decode } from 'light-bolt11-decoder';
|
|
2
3
|
import { ApiClient } from '../api/client.js';
|
|
3
4
|
import { TypedAmmApi } from '../api/typed-endpoints.js';
|
|
4
5
|
import { getClientEnvironmentName, resolveClientNetworkConfig, getClientNetworkConfig, BTC_ASSET_PUBKEY } from '../config/index.js';
|
|
@@ -9,6 +10,7 @@ import { getHexFromUint8Array } from '../utils/hex.js';
|
|
|
9
10
|
import { generateConstantProductPoolInitializationIntentMessage, generatePoolInitializationIntentMessage, generatePoolConfirmInitialDepositIntentMessage, generatePoolSwapIntentMessage, generateRouteSwapIntentMessage, generateAddLiquidityIntentMessage, generateRemoveLiquidityIntentMessage, generateRegisterHostIntentMessage, generateWithdrawHostFeesIntentMessage, generateWithdrawIntegratorFeesIntentMessage, generateCreateEscrowIntentMessage, generateFundEscrowIntentMessage, generateClaimEscrowIntentMessage, generateClawbackIntentMessage, generateCreateConcentratedPoolIntentMessage, generateDecreaseLiquidityIntentMessage, generateCollectFeesIntentMessage, generateWithdrawBalanceIntentMessage, generateIncreaseLiquidityIntentMessage, generateRebalancePositionIntentMessage, generateDepositBalanceIntentMessage } from '../utils/intents.js';
|
|
10
11
|
import { getSparkNetworkFromAddress, encodeSparkAddressNew } from '../utils/spark-address.js';
|
|
11
12
|
import { encodeSparkHumanReadableTokenIdentifier, decodeSparkHumanReadableTokenIdentifier } from '../utils/tokenAddress.js';
|
|
13
|
+
import { safeBigInt } from '../utils/bigint.js';
|
|
12
14
|
import { FlashnetError } from '../types/errors.js';
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -239,7 +241,10 @@ class FlashnetClient {
|
|
|
239
241
|
const tokenIdentifierHex = getHexFromUint8Array(info.rawTokenIdentifier);
|
|
240
242
|
const tokenAddress = encodeSparkHumanReadableTokenIdentifier(info.rawTokenIdentifier, this.sparkNetwork);
|
|
241
243
|
tokenBalances.set(tokenPubkey, {
|
|
242
|
-
balance:
|
|
244
|
+
balance: safeBigInt(tokenData.ownedBalance ?? tokenData.balance),
|
|
245
|
+
availableToSendBalance: safeBigInt(tokenData.availableToSendBalance ??
|
|
246
|
+
tokenData.ownedBalance ??
|
|
247
|
+
tokenData.balance),
|
|
243
248
|
tokenInfo: {
|
|
244
249
|
tokenIdentifier: tokenIdentifierHex,
|
|
245
250
|
tokenAddress,
|
|
@@ -252,7 +257,7 @@ class FlashnetClient {
|
|
|
252
257
|
}
|
|
253
258
|
}
|
|
254
259
|
return {
|
|
255
|
-
balance:
|
|
260
|
+
balance: safeBigInt(balance.balance),
|
|
256
261
|
tokenBalances,
|
|
257
262
|
};
|
|
258
263
|
}
|
|
@@ -260,7 +265,7 @@ class FlashnetClient {
|
|
|
260
265
|
* Check if wallet has sufficient balance for an operation
|
|
261
266
|
*/
|
|
262
267
|
async checkBalance(params) {
|
|
263
|
-
const balance = await this.getBalance();
|
|
268
|
+
const balance = params.walletBalance ?? (await this.getBalance());
|
|
264
269
|
// Check balance
|
|
265
270
|
const requirements = {
|
|
266
271
|
tokens: new Map(),
|
|
@@ -284,11 +289,15 @@ class FlashnetClient {
|
|
|
284
289
|
// Check token balances
|
|
285
290
|
if (requirements.tokens) {
|
|
286
291
|
for (const [tokenPubkey, requiredAmount,] of requirements.tokens.entries()) {
|
|
287
|
-
//
|
|
292
|
+
// Support both hex and Bech32m token identifiers by trying all representations
|
|
288
293
|
const hrKey = this.toHumanReadableTokenIdentifier(tokenPubkey);
|
|
294
|
+
const hexKey = this.toHexTokenIdentifier(tokenPubkey);
|
|
289
295
|
const effectiveTokenBalance = balance.tokenBalances.get(tokenPubkey) ??
|
|
290
|
-
balance.tokenBalances.get(hrKey)
|
|
291
|
-
|
|
296
|
+
balance.tokenBalances.get(hrKey) ??
|
|
297
|
+
balance.tokenBalances.get(hexKey);
|
|
298
|
+
const available = params.useAvailableBalance
|
|
299
|
+
? (effectiveTokenBalance?.availableToSendBalance ?? 0n)
|
|
300
|
+
: (effectiveTokenBalance?.balance ?? 0n);
|
|
292
301
|
if (available < requiredAmount) {
|
|
293
302
|
throw new Error([
|
|
294
303
|
params.errorPrefix ?? "",
|
|
@@ -350,6 +359,7 @@ class FlashnetClient {
|
|
|
350
359
|
},
|
|
351
360
|
],
|
|
352
361
|
errorPrefix: "Insufficient balance for initial liquidity: ",
|
|
362
|
+
useAvailableBalance: params.useAvailableBalance,
|
|
353
363
|
});
|
|
354
364
|
}
|
|
355
365
|
const poolOwnerPublicKey = params.poolOwnerPublicKey ?? this.publicKey;
|
|
@@ -479,6 +489,7 @@ class FlashnetClient {
|
|
|
479
489
|
},
|
|
480
490
|
],
|
|
481
491
|
errorPrefix: "Insufficient balance for pool creation: ",
|
|
492
|
+
useAvailableBalance: params.useAvailableBalance,
|
|
482
493
|
});
|
|
483
494
|
const poolOwnerPublicKey = params.poolOwnerPublicKey ?? this.publicKey;
|
|
484
495
|
// Generate intent
|
|
@@ -599,10 +610,14 @@ class FlashnetClient {
|
|
|
599
610
|
});
|
|
600
611
|
// If using free balance (V3 pools only), skip the Spark transfer
|
|
601
612
|
if (params.useFreeBalance) {
|
|
602
|
-
|
|
613
|
+
const swapResponse = await this.executeSwapIntent({
|
|
603
614
|
...params,
|
|
604
615
|
// No transferId - triggers free balance mode
|
|
605
616
|
});
|
|
617
|
+
return {
|
|
618
|
+
...swapResponse,
|
|
619
|
+
inboundSparkTransferId: swapResponse.requestId,
|
|
620
|
+
};
|
|
606
621
|
}
|
|
607
622
|
// Transfer assets to pool using new address encoding
|
|
608
623
|
const lpSparkAddress = encodeSparkAddressNew({
|
|
@@ -613,12 +628,13 @@ class FlashnetClient {
|
|
|
613
628
|
receiverSparkAddress: lpSparkAddress,
|
|
614
629
|
assetAddress: params.assetInAddress,
|
|
615
630
|
amount: params.amountIn,
|
|
616
|
-
}, "Insufficient balance for swap: ");
|
|
631
|
+
}, "Insufficient balance for swap: ", params.useAvailableBalance);
|
|
617
632
|
// Execute with auto-clawback on failure
|
|
618
|
-
|
|
633
|
+
const swapResponse = await this.executeWithAutoClawback(() => this.executeSwapIntent({
|
|
619
634
|
...params,
|
|
620
635
|
transferId,
|
|
621
636
|
}), [transferId], params.poolId);
|
|
637
|
+
return { ...swapResponse, inboundSparkTransferId: transferId };
|
|
622
638
|
}
|
|
623
639
|
/**
|
|
624
640
|
* Execute a swap with a pre-created transfer or using free balance.
|
|
@@ -748,7 +764,7 @@ class FlashnetClient {
|
|
|
748
764
|
receiverSparkAddress: lpSparkAddress,
|
|
749
765
|
assetAddress: params.initialAssetAddress,
|
|
750
766
|
amount: params.inputAmount,
|
|
751
|
-
}, "Insufficient balance for route swap: ");
|
|
767
|
+
}, "Insufficient balance for route swap: ", params.useAvailableBalance);
|
|
752
768
|
// Execute with auto-clawback on failure
|
|
753
769
|
return this.executeWithAutoClawback(async () => {
|
|
754
770
|
// Prepare hops for validation
|
|
@@ -871,7 +887,7 @@ class FlashnetClient {
|
|
|
871
887
|
assetAddress: pool.assetBAddress,
|
|
872
888
|
amount: params.assetBAmount,
|
|
873
889
|
},
|
|
874
|
-
], "Insufficient balance for adding liquidity: ");
|
|
890
|
+
], "Insufficient balance for adding liquidity: ", params.useAvailableBalance);
|
|
875
891
|
// Execute with auto-clawback on failure
|
|
876
892
|
return this.executeWithAutoClawback(async () => {
|
|
877
893
|
// Generate add liquidity intent
|
|
@@ -1177,6 +1193,7 @@ class FlashnetClient {
|
|
|
1177
1193
|
depositAddress: createResponse.depositAddress,
|
|
1178
1194
|
assetId: params.assetId,
|
|
1179
1195
|
assetAmount: params.assetAmount,
|
|
1196
|
+
useAvailableBalance: params.useAvailableBalance,
|
|
1180
1197
|
});
|
|
1181
1198
|
}
|
|
1182
1199
|
/**
|
|
@@ -1194,6 +1211,7 @@ class FlashnetClient {
|
|
|
1194
1211
|
{ assetAddress: params.assetId, amount: params.assetAmount },
|
|
1195
1212
|
],
|
|
1196
1213
|
errorPrefix: "Insufficient balance to fund escrow: ",
|
|
1214
|
+
useAvailableBalance: params.useAvailableBalance,
|
|
1197
1215
|
});
|
|
1198
1216
|
// 2. Perform transfer
|
|
1199
1217
|
const escrowSparkAddress = encodeSparkAddressNew({
|
|
@@ -1708,19 +1726,20 @@ class FlashnetClient {
|
|
|
1708
1726
|
/**
|
|
1709
1727
|
* Performs asset transfer using generalized asset address for both BTC and tokens.
|
|
1710
1728
|
*/
|
|
1711
|
-
async transferAsset(recipient, checkBalanceErrorPrefix) {
|
|
1712
|
-
const transferIds = await this.transferAssets([recipient], checkBalanceErrorPrefix);
|
|
1729
|
+
async transferAsset(recipient, checkBalanceErrorPrefix, useAvailableBalance) {
|
|
1730
|
+
const transferIds = await this.transferAssets([recipient], checkBalanceErrorPrefix, useAvailableBalance);
|
|
1713
1731
|
return transferIds[0];
|
|
1714
1732
|
}
|
|
1715
1733
|
/**
|
|
1716
1734
|
* Performs asset transfers using generalized asset addresses for both BTC and tokens.
|
|
1717
1735
|
* Supports optional generic to hardcode recipients length so output list can be typed with same length.
|
|
1718
1736
|
*/
|
|
1719
|
-
async transferAssets(recipients, checkBalanceErrorPrefix) {
|
|
1737
|
+
async transferAssets(recipients, checkBalanceErrorPrefix, useAvailableBalance) {
|
|
1720
1738
|
if (checkBalanceErrorPrefix) {
|
|
1721
1739
|
await this.checkBalance({
|
|
1722
1740
|
balancesToCheck: recipients,
|
|
1723
1741
|
errorPrefix: checkBalanceErrorPrefix,
|
|
1742
|
+
useAvailableBalance,
|
|
1724
1743
|
});
|
|
1725
1744
|
}
|
|
1726
1745
|
const transferIds = [];
|
|
@@ -1869,7 +1888,7 @@ class FlashnetClient {
|
|
|
1869
1888
|
const tokenHex = this.toHexTokenIdentifier(tokenAddress).toLowerCase();
|
|
1870
1889
|
const tokenMinAmount = minAmounts.get(tokenHex);
|
|
1871
1890
|
if (tokenMinAmount &&
|
|
1872
|
-
|
|
1891
|
+
safeBigInt(poolQuote.tokenAmountRequired) < tokenMinAmount) {
|
|
1873
1892
|
const msg = `Token amount too small. Minimum input is ${tokenMinAmount} units, but calculated amount is only ${poolQuote.tokenAmountRequired} units.`;
|
|
1874
1893
|
throw new FlashnetError(msg, {
|
|
1875
1894
|
response: {
|
|
@@ -1886,7 +1905,7 @@ class FlashnetClient {
|
|
|
1886
1905
|
}
|
|
1887
1906
|
// BTC variable fee adjustment: difference between what the pool targets and unmasked base.
|
|
1888
1907
|
// For V3 pools this is 0 (no masking). For V2 it's the rounding overhead.
|
|
1889
|
-
const btcVariableFeeAdjustment = Number(
|
|
1908
|
+
const btcVariableFeeAdjustment = Number(safeBigInt(poolQuote.btcAmountUsed) - baseBtcNeeded);
|
|
1890
1909
|
return {
|
|
1891
1910
|
poolId: poolQuote.poolId,
|
|
1892
1911
|
tokenAddress: this.toHexTokenIdentifier(tokenAddress),
|
|
@@ -1958,7 +1977,7 @@ class FlashnetClient {
|
|
|
1958
1977
|
amountIn: tokenAmount,
|
|
1959
1978
|
integratorBps: options?.integratorFeeRateBps,
|
|
1960
1979
|
});
|
|
1961
|
-
const btcOut =
|
|
1980
|
+
const btcOut = safeBigInt(simulation.amountOut);
|
|
1962
1981
|
if (btcOut > bestBtcOut) {
|
|
1963
1982
|
bestBtcOut = btcOut;
|
|
1964
1983
|
bestResult = {
|
|
@@ -2046,7 +2065,7 @@ class FlashnetClient {
|
|
|
2046
2065
|
await this.ensureInitialized();
|
|
2047
2066
|
const { invoice, tokenAddress, tokenAmount, maxSlippageBps = 500, // 5% default
|
|
2048
2067
|
maxLightningFeeSats, preferSpark = true, integratorFeeRateBps, integratorPublicKey, transferTimeoutMs = 30000, // 30s default
|
|
2049
|
-
rollbackOnFailure = false, useExistingBtcBalance = false, } = options;
|
|
2068
|
+
rollbackOnFailure = false, useExistingBtcBalance = false, useAvailableBalance = false, } = options;
|
|
2050
2069
|
try {
|
|
2051
2070
|
// Step 1: Get a quote for the payment
|
|
2052
2071
|
const quote = await this.getPayLightningWithTokenQuote(invoice, tokenAddress, {
|
|
@@ -2063,6 +2082,7 @@ class FlashnetClient {
|
|
|
2063
2082
|
},
|
|
2064
2083
|
],
|
|
2065
2084
|
errorPrefix: "Insufficient token balance for Lightning payment: ",
|
|
2085
|
+
useAvailableBalance,
|
|
2066
2086
|
});
|
|
2067
2087
|
// Step 3: Get pool details
|
|
2068
2088
|
const pool = await this.getPool(quote.poolId);
|
|
@@ -2085,6 +2105,7 @@ class FlashnetClient {
|
|
|
2085
2105
|
minAmountOut: minBtcOut,
|
|
2086
2106
|
integratorFeeRateBps,
|
|
2087
2107
|
integratorPublicKey,
|
|
2108
|
+
useAvailableBalance,
|
|
2088
2109
|
});
|
|
2089
2110
|
if (!swapResponse.accepted || !swapResponse.outboundTransferId) {
|
|
2090
2111
|
return {
|
|
@@ -2094,6 +2115,7 @@ class FlashnetClient {
|
|
|
2094
2115
|
btcAmountReceived: "0",
|
|
2095
2116
|
swapTransferId: swapResponse.outboundTransferId || "",
|
|
2096
2117
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2118
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2097
2119
|
error: swapResponse.error || "Swap was not accepted",
|
|
2098
2120
|
};
|
|
2099
2121
|
}
|
|
@@ -2104,7 +2126,7 @@ class FlashnetClient {
|
|
|
2104
2126
|
const effectiveMaxLightningFee = maxLightningFeeSats ?? quote.estimatedLightningFee;
|
|
2105
2127
|
const btcNeededForPayment = invoiceAmountSats + effectiveMaxLightningFee;
|
|
2106
2128
|
const balance = await this.getBalance();
|
|
2107
|
-
canPayImmediately = balance.balance >=
|
|
2129
|
+
canPayImmediately = balance.balance >= safeBigInt(btcNeededForPayment);
|
|
2108
2130
|
}
|
|
2109
2131
|
if (!canPayImmediately) {
|
|
2110
2132
|
const transferComplete = await this.waitForTransferCompletion(swapResponse.outboundTransferId, transferTimeoutMs);
|
|
@@ -2116,6 +2138,7 @@ class FlashnetClient {
|
|
|
2116
2138
|
btcAmountReceived: swapResponse.amountOut || "0",
|
|
2117
2139
|
swapTransferId: swapResponse.outboundTransferId,
|
|
2118
2140
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2141
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2119
2142
|
error: "Transfer did not complete within timeout",
|
|
2120
2143
|
};
|
|
2121
2144
|
}
|
|
@@ -2129,8 +2152,8 @@ class FlashnetClient {
|
|
|
2129
2152
|
let invoiceAmountPaid;
|
|
2130
2153
|
if (quote.isZeroAmountInvoice) {
|
|
2131
2154
|
// Zero-amount invoice: pay whatever BTC we received minus lightning fee
|
|
2132
|
-
const actualBtc =
|
|
2133
|
-
const lnFee =
|
|
2155
|
+
const actualBtc = safeBigInt(btcReceived);
|
|
2156
|
+
const lnFee = safeBigInt(effectiveMaxLightningFee);
|
|
2134
2157
|
const amountToPay = actualBtc - lnFee;
|
|
2135
2158
|
if (amountToPay <= 0n) {
|
|
2136
2159
|
return {
|
|
@@ -2140,6 +2163,7 @@ class FlashnetClient {
|
|
|
2140
2163
|
btcAmountReceived: btcReceived,
|
|
2141
2164
|
swapTransferId: swapResponse.outboundTransferId,
|
|
2142
2165
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2166
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2143
2167
|
error: `BTC received (${btcReceived} sats) is not enough to cover lightning fee (${effectiveMaxLightningFee} sats).`,
|
|
2144
2168
|
};
|
|
2145
2169
|
}
|
|
@@ -2159,6 +2183,12 @@ class FlashnetClient {
|
|
|
2159
2183
|
preferSpark,
|
|
2160
2184
|
});
|
|
2161
2185
|
}
|
|
2186
|
+
// Extract the Spark transfer ID from the lightning payment result.
|
|
2187
|
+
// payLightningInvoice returns LightningSendRequest | WalletTransfer:
|
|
2188
|
+
// - LightningSendRequest has .transfer?.sparkId (the Sparkscan-visible transfer ID)
|
|
2189
|
+
// - WalletTransfer (Spark-to-Spark) has .id directly as the transfer ID
|
|
2190
|
+
// Note: lightningPayment.id (the SSP request ID) is already returned as lightningPaymentId
|
|
2191
|
+
const sparkLightningTransferId = lightningPayment.transfer?.sparkId;
|
|
2162
2192
|
return {
|
|
2163
2193
|
success: true,
|
|
2164
2194
|
poolId: quote.poolId,
|
|
@@ -2169,6 +2199,8 @@ class FlashnetClient {
|
|
|
2169
2199
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2170
2200
|
lightningFeePaid: effectiveMaxLightningFee,
|
|
2171
2201
|
invoiceAmountPaid,
|
|
2202
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2203
|
+
sparkLightningTransferId,
|
|
2172
2204
|
};
|
|
2173
2205
|
}
|
|
2174
2206
|
catch (lightningError) {
|
|
@@ -2188,6 +2220,7 @@ class FlashnetClient {
|
|
|
2188
2220
|
btcAmountReceived: "0",
|
|
2189
2221
|
swapTransferId: swapResponse.outboundTransferId,
|
|
2190
2222
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2223
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2191
2224
|
error: `Lightning payment failed: ${lightningErrorMessage}. Funds rolled back to ${rollbackResult.tokenAmount} tokens.`,
|
|
2192
2225
|
};
|
|
2193
2226
|
}
|
|
@@ -2203,6 +2236,7 @@ class FlashnetClient {
|
|
|
2203
2236
|
btcAmountReceived: btcReceived,
|
|
2204
2237
|
swapTransferId: swapResponse.outboundTransferId,
|
|
2205
2238
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2239
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2206
2240
|
error: `Lightning payment failed: ${lightningErrorMessage}. Rollback also failed: ${rollbackErrorMessage}. BTC remains in wallet.`,
|
|
2207
2241
|
};
|
|
2208
2242
|
}
|
|
@@ -2214,6 +2248,7 @@ class FlashnetClient {
|
|
|
2214
2248
|
btcAmountReceived: btcReceived,
|
|
2215
2249
|
swapTransferId: swapResponse.outboundTransferId,
|
|
2216
2250
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2251
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2217
2252
|
error: `Lightning payment failed: ${lightningErrorMessage}. BTC (${btcReceived} sats) remains in wallet.`,
|
|
2218
2253
|
};
|
|
2219
2254
|
}
|
|
@@ -2376,7 +2411,7 @@ class FlashnetClient {
|
|
|
2376
2411
|
tokenIsAssetA: pool.tokenIsAssetA,
|
|
2377
2412
|
integratorBps: integratorFeeRateBps,
|
|
2378
2413
|
});
|
|
2379
|
-
tokenAmount =
|
|
2414
|
+
tokenAmount = safeBigInt(v3Result.amountIn);
|
|
2380
2415
|
fee = v3Result.totalFee;
|
|
2381
2416
|
executionPrice = v3Result.simulation.executionPrice || "0";
|
|
2382
2417
|
priceImpactPct = v3Result.simulation.priceImpactPct || "0";
|
|
@@ -2385,7 +2420,7 @@ class FlashnetClient {
|
|
|
2385
2420
|
else {
|
|
2386
2421
|
// V2: constant product math + simulation verification
|
|
2387
2422
|
const calculation = this.calculateTokenAmountForBtcOutput(btcTarget.toString(), poolDetails.assetAReserve, poolDetails.assetBReserve, poolDetails.lpFeeBps, poolDetails.hostFeeBps, pool.tokenIsAssetA, integratorFeeRateBps);
|
|
2388
|
-
tokenAmount =
|
|
2423
|
+
tokenAmount = safeBigInt(calculation.amountIn);
|
|
2389
2424
|
// Verify with simulation
|
|
2390
2425
|
const simulation = await this.simulateSwap({
|
|
2391
2426
|
poolId: pool.lpPublicKey,
|
|
@@ -2394,7 +2429,7 @@ class FlashnetClient {
|
|
|
2394
2429
|
amountIn: calculation.amountIn,
|
|
2395
2430
|
integratorBps: integratorFeeRateBps,
|
|
2396
2431
|
});
|
|
2397
|
-
if (
|
|
2432
|
+
if (safeBigInt(simulation.amountOut) < btcTarget) {
|
|
2398
2433
|
const btcReserve = pool.tokenIsAssetA
|
|
2399
2434
|
? poolDetails.assetBReserve
|
|
2400
2435
|
: poolDetails.assetAReserve;
|
|
@@ -2482,9 +2517,9 @@ class FlashnetClient {
|
|
|
2482
2517
|
* @private
|
|
2483
2518
|
*/
|
|
2484
2519
|
calculateTokenAmountForBtcOutput(btcAmountOut, reserveA, reserveB, lpFeeBps, hostFeeBps, tokenIsAssetA, integratorFeeBps) {
|
|
2485
|
-
const amountOut =
|
|
2486
|
-
const resA =
|
|
2487
|
-
const resB =
|
|
2520
|
+
const amountOut = safeBigInt(btcAmountOut);
|
|
2521
|
+
const resA = safeBigInt(reserveA);
|
|
2522
|
+
const resB = safeBigInt(reserveB);
|
|
2488
2523
|
const totalFeeBps = lpFeeBps + hostFeeBps + (integratorFeeBps || 0);
|
|
2489
2524
|
const feeRate = Number(totalFeeBps) / 10000; // Convert bps to decimal
|
|
2490
2525
|
// Token is the input asset
|
|
@@ -2582,7 +2617,7 @@ class FlashnetClient {
|
|
|
2582
2617
|
amountIn: upperBound.toString(),
|
|
2583
2618
|
integratorBps,
|
|
2584
2619
|
});
|
|
2585
|
-
if (
|
|
2620
|
+
if (safeBigInt(sim.amountOut) >= desiredBtcOut) {
|
|
2586
2621
|
upperSim = sim;
|
|
2587
2622
|
break;
|
|
2588
2623
|
}
|
|
@@ -2593,7 +2628,7 @@ class FlashnetClient {
|
|
|
2593
2628
|
throw new Error(`V3 pool ${poolId} has insufficient liquidity for ${desiredBtcOut} sats`);
|
|
2594
2629
|
}
|
|
2595
2630
|
// Step 3: Refine estimate via linear interpolation
|
|
2596
|
-
const upperOut =
|
|
2631
|
+
const upperOut = safeBigInt(upperSim.amountOut);
|
|
2597
2632
|
// Scale proportionally: if upperBound produced upperOut, we need roughly
|
|
2598
2633
|
// (upperBound * desiredBtcOut / upperOut). Add +1 to avoid undershoot from truncation.
|
|
2599
2634
|
let refined = (upperBound * desiredBtcOut) / upperOut + 1n;
|
|
@@ -2611,7 +2646,7 @@ class FlashnetClient {
|
|
|
2611
2646
|
amountIn: refined.toString(),
|
|
2612
2647
|
integratorBps,
|
|
2613
2648
|
});
|
|
2614
|
-
if (
|
|
2649
|
+
if (safeBigInt(refinedSim.amountOut) >= desiredBtcOut) {
|
|
2615
2650
|
bestAmountIn = refined;
|
|
2616
2651
|
bestSim = refinedSim;
|
|
2617
2652
|
}
|
|
@@ -2645,7 +2680,7 @@ class FlashnetClient {
|
|
|
2645
2680
|
amountIn: mid.toString(),
|
|
2646
2681
|
integratorBps,
|
|
2647
2682
|
});
|
|
2648
|
-
if (
|
|
2683
|
+
if (safeBigInt(midSim.amountOut) >= desiredBtcOut) {
|
|
2649
2684
|
hi = mid;
|
|
2650
2685
|
bestAmountIn = mid;
|
|
2651
2686
|
bestSim = midSim;
|
|
@@ -2764,64 +2799,24 @@ class FlashnetClient {
|
|
|
2764
2799
|
}
|
|
2765
2800
|
/**
|
|
2766
2801
|
* Decode the amount from a Lightning invoice (in sats)
|
|
2802
|
+
* Uses light-bolt11-decoder (same library as Spark SDK) for reliable parsing.
|
|
2767
2803
|
* @private
|
|
2768
2804
|
*/
|
|
2769
2805
|
async decodeInvoiceAmount(invoice) {
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
else if (lowerInvoice.startsWith("lntb")) {
|
|
2780
|
-
amountStart = 4;
|
|
2781
|
-
}
|
|
2782
|
-
else if (lowerInvoice.startsWith("lnbcrt")) {
|
|
2783
|
-
amountStart = 6;
|
|
2784
|
-
}
|
|
2785
|
-
else if (lowerInvoice.startsWith("lntbs")) {
|
|
2786
|
-
amountStart = 5;
|
|
2806
|
+
try {
|
|
2807
|
+
const decoded = decode(invoice);
|
|
2808
|
+
const amountSection = decoded.sections.find((s) => s.name === "amount");
|
|
2809
|
+
if (!amountSection?.value) {
|
|
2810
|
+
return 0; // Zero-amount invoice
|
|
2811
|
+
}
|
|
2812
|
+
// The library returns amount in millisatoshis as a string
|
|
2813
|
+
const amountMSats = BigInt(amountSection.value);
|
|
2814
|
+
return Number(amountMSats / 1000n);
|
|
2787
2815
|
}
|
|
2788
|
-
|
|
2789
|
-
//
|
|
2790
|
-
|
|
2791
|
-
if (match) {
|
|
2792
|
-
amountStart = match[0].length;
|
|
2793
|
-
}
|
|
2794
|
-
}
|
|
2795
|
-
// Extract amount and multiplier
|
|
2796
|
-
const afterPrefix = lowerInvoice.substring(amountStart);
|
|
2797
|
-
const amountMatch = afterPrefix.match(/^(\d+)([munp]?)/);
|
|
2798
|
-
if (!amountMatch || !amountMatch[1]) {
|
|
2799
|
-
return 0; // Zero-amount invoice
|
|
2800
|
-
}
|
|
2801
|
-
const amount = parseInt(amountMatch[1], 10);
|
|
2802
|
-
const multiplier = amountMatch[2] ?? "";
|
|
2803
|
-
// Convert to satoshis (1 BTC = 100,000,000 sats)
|
|
2804
|
-
// Invoice amounts are in BTC by default
|
|
2805
|
-
let btcAmount;
|
|
2806
|
-
switch (multiplier) {
|
|
2807
|
-
case "m": // milli-BTC (0.001 BTC)
|
|
2808
|
-
btcAmount = amount * 0.001;
|
|
2809
|
-
break;
|
|
2810
|
-
case "u": // micro-BTC (0.000001 BTC)
|
|
2811
|
-
btcAmount = amount * 0.000001;
|
|
2812
|
-
break;
|
|
2813
|
-
case "n": // nano-BTC (0.000000001 BTC)
|
|
2814
|
-
btcAmount = amount * 0.000000001;
|
|
2815
|
-
break;
|
|
2816
|
-
case "p": // pico-BTC (0.000000000001 BTC)
|
|
2817
|
-
btcAmount = amount * 0.000000000001;
|
|
2818
|
-
break;
|
|
2819
|
-
default: // BTC
|
|
2820
|
-
btcAmount = amount;
|
|
2821
|
-
break;
|
|
2816
|
+
catch {
|
|
2817
|
+
// Fallback: if library fails, return 0 (treated as zero-amount invoice)
|
|
2818
|
+
return 0;
|
|
2822
2819
|
}
|
|
2823
|
-
// Convert BTC to sats
|
|
2824
|
-
return Math.round(btcAmount * 100000000);
|
|
2825
2820
|
}
|
|
2826
2821
|
/**
|
|
2827
2822
|
* Clean up wallet connections
|
|
@@ -2886,8 +2881,11 @@ class FlashnetClient {
|
|
|
2886
2881
|
const map = new Map();
|
|
2887
2882
|
for (const item of config) {
|
|
2888
2883
|
if (item.enabled) {
|
|
2884
|
+
if (item.min_amount == null) {
|
|
2885
|
+
continue;
|
|
2886
|
+
}
|
|
2889
2887
|
const key = item.asset_identifier.toLowerCase();
|
|
2890
|
-
const value =
|
|
2888
|
+
const value = safeBigInt(item.min_amount);
|
|
2891
2889
|
map.set(key, value);
|
|
2892
2890
|
}
|
|
2893
2891
|
}
|
|
@@ -2909,8 +2907,8 @@ class FlashnetClient {
|
|
|
2909
2907
|
const outHex = this.getHexAddress(params.assetOutAddress);
|
|
2910
2908
|
const minIn = minMap.get(inHex);
|
|
2911
2909
|
const minOut = minMap.get(outHex);
|
|
2912
|
-
const amountIn = BigInt(
|
|
2913
|
-
const minAmountOut = BigInt(
|
|
2910
|
+
const amountIn = BigInt(params.amountIn);
|
|
2911
|
+
const minAmountOut = BigInt(params.minAmountOut);
|
|
2914
2912
|
if (minIn && minOut) {
|
|
2915
2913
|
if (amountIn < minIn) {
|
|
2916
2914
|
throw new Error(`Minimum amount not met for input asset. Required \
|
|
@@ -2944,13 +2942,13 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
2944
2942
|
const aMin = minMap.get(aHex);
|
|
2945
2943
|
const bMin = minMap.get(bHex);
|
|
2946
2944
|
if (aMin) {
|
|
2947
|
-
const aAmt = BigInt(
|
|
2945
|
+
const aAmt = BigInt(params.assetAAmount);
|
|
2948
2946
|
if (aAmt < aMin) {
|
|
2949
2947
|
throw new Error(`Minimum amount not met for Asset A. Required ${aMin.toString()}, provided ${aAmt.toString()}`);
|
|
2950
2948
|
}
|
|
2951
2949
|
}
|
|
2952
2950
|
if (bMin) {
|
|
2953
|
-
const bAmt = BigInt(
|
|
2951
|
+
const bAmt = BigInt(params.assetBAmount);
|
|
2954
2952
|
if (bAmt < bMin) {
|
|
2955
2953
|
throw new Error(`Minimum amount not met for Asset B. Required ${bMin.toString()}, provided ${bAmt.toString()}`);
|
|
2956
2954
|
}
|
|
@@ -2972,14 +2970,14 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
2972
2970
|
const aMin = minMap.get(aHex);
|
|
2973
2971
|
const bMin = minMap.get(bHex);
|
|
2974
2972
|
if (aMin) {
|
|
2975
|
-
const predictedAOut =
|
|
2973
|
+
const predictedAOut = safeBigInt(simulation.assetAAmount);
|
|
2976
2974
|
const relaxedA = aMin / 2n; // apply 50% relaxation for outputs
|
|
2977
2975
|
if (predictedAOut < relaxedA) {
|
|
2978
2976
|
throw new Error(`Minimum amount not met for Asset A on withdrawal. Required at least ${relaxedA.toString()} (50% relaxed), predicted ${predictedAOut.toString()}`);
|
|
2979
2977
|
}
|
|
2980
2978
|
}
|
|
2981
2979
|
if (bMin) {
|
|
2982
|
-
const predictedBOut =
|
|
2980
|
+
const predictedBOut = safeBigInt(simulation.assetBAmount);
|
|
2983
2981
|
const relaxedB = bMin / 2n;
|
|
2984
2982
|
if (predictedBOut < relaxedB) {
|
|
2985
2983
|
throw new Error(`Minimum amount not met for Asset B on withdrawal. Required at least ${relaxedB.toString()} (50% relaxed), predicted ${predictedBOut.toString()}`);
|
|
@@ -3097,7 +3095,7 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
3097
3095
|
receiverSparkAddress: lpSparkAddress,
|
|
3098
3096
|
assetAddress: pool.assetAAddress,
|
|
3099
3097
|
amount: params.amountADesired,
|
|
3100
|
-
}, "Insufficient balance for adding V3 liquidity (Asset A): ");
|
|
3098
|
+
}, "Insufficient balance for adding V3 liquidity (Asset A): ", params.useAvailableBalance);
|
|
3101
3099
|
transferIds.push(assetATransferId);
|
|
3102
3100
|
}
|
|
3103
3101
|
if (BigInt(params.amountBDesired) > 0n) {
|
|
@@ -3105,7 +3103,7 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
3105
3103
|
receiverSparkAddress: lpSparkAddress,
|
|
3106
3104
|
assetAddress: pool.assetBAddress,
|
|
3107
3105
|
amount: params.amountBDesired,
|
|
3108
|
-
}, "Insufficient balance for adding V3 liquidity (Asset B): ");
|
|
3106
|
+
}, "Insufficient balance for adding V3 liquidity (Asset B): ", params.useAvailableBalance);
|
|
3109
3107
|
transferIds.push(assetBTransferId);
|
|
3110
3108
|
}
|
|
3111
3109
|
}
|
|
@@ -3298,14 +3296,14 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
3298
3296
|
receiverSparkAddress: lpSparkAddress,
|
|
3299
3297
|
assetAddress: pool.assetAAddress,
|
|
3300
3298
|
amount: params.additionalAmountA,
|
|
3301
|
-
}, "Insufficient balance for rebalance (Asset A): ");
|
|
3299
|
+
}, "Insufficient balance for rebalance (Asset A): ", params.useAvailableBalance);
|
|
3302
3300
|
}
|
|
3303
3301
|
if (params.additionalAmountB && BigInt(params.additionalAmountB) > 0n) {
|
|
3304
3302
|
assetBTransferId = await this.transferAsset({
|
|
3305
3303
|
receiverSparkAddress: lpSparkAddress,
|
|
3306
3304
|
assetAddress: pool.assetBAddress,
|
|
3307
3305
|
amount: params.additionalAmountB,
|
|
3308
|
-
}, "Insufficient balance for rebalance (Asset B): ");
|
|
3306
|
+
}, "Insufficient balance for rebalance (Asset B): ", params.useAvailableBalance);
|
|
3309
3307
|
}
|
|
3310
3308
|
// Collect transfer IDs for potential clawback
|
|
3311
3309
|
const transferIds = [];
|
|
@@ -3502,7 +3500,7 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
3502
3500
|
receiverSparkAddress: lpSparkAddress,
|
|
3503
3501
|
assetAddress: pool.assetAAddress,
|
|
3504
3502
|
amount: params.amountA,
|
|
3505
|
-
}, "Insufficient balance for depositing to V3 pool (Asset A): ");
|
|
3503
|
+
}, "Insufficient balance for depositing to V3 pool (Asset A): ", params.useAvailableBalance);
|
|
3506
3504
|
transferIds.push(assetATransferId);
|
|
3507
3505
|
}
|
|
3508
3506
|
if (BigInt(params.amountB) > 0n) {
|
|
@@ -3510,7 +3508,7 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
3510
3508
|
receiverSparkAddress: lpSparkAddress,
|
|
3511
3509
|
assetAddress: pool.assetBAddress,
|
|
3512
3510
|
amount: params.amountB,
|
|
3513
|
-
}, "Insufficient balance for depositing to V3 pool (Asset B): ");
|
|
3511
|
+
}, "Insufficient balance for depositing to V3 pool (Asset B): ", params.useAvailableBalance);
|
|
3514
3512
|
transferIds.push(assetBTransferId);
|
|
3515
3513
|
}
|
|
3516
3514
|
const executeDeposit = async () => {
|