@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,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var sha256 = require('fast-sha256');
|
|
4
|
+
var lightBolt11Decoder = require('light-bolt11-decoder');
|
|
4
5
|
var client = require('../api/client.js');
|
|
5
6
|
var typedEndpoints = require('../api/typed-endpoints.js');
|
|
6
7
|
var index$1 = require('../config/index.js');
|
|
@@ -11,6 +12,7 @@ var hex = require('../utils/hex.js');
|
|
|
11
12
|
var intents = require('../utils/intents.js');
|
|
12
13
|
var sparkAddress = require('../utils/spark-address.js');
|
|
13
14
|
var tokenAddress = require('../utils/tokenAddress.js');
|
|
15
|
+
var bigint = require('../utils/bigint.js');
|
|
14
16
|
var errors = require('../types/errors.js');
|
|
15
17
|
|
|
16
18
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -245,7 +247,10 @@ class FlashnetClient {
|
|
|
245
247
|
const tokenIdentifierHex = hex.getHexFromUint8Array(info.rawTokenIdentifier);
|
|
246
248
|
const tokenAddress$1 = tokenAddress.encodeSparkHumanReadableTokenIdentifier(info.rawTokenIdentifier, this.sparkNetwork);
|
|
247
249
|
tokenBalances.set(tokenPubkey, {
|
|
248
|
-
balance:
|
|
250
|
+
balance: bigint.safeBigInt(tokenData.ownedBalance ?? tokenData.balance),
|
|
251
|
+
availableToSendBalance: bigint.safeBigInt(tokenData.availableToSendBalance ??
|
|
252
|
+
tokenData.ownedBalance ??
|
|
253
|
+
tokenData.balance),
|
|
249
254
|
tokenInfo: {
|
|
250
255
|
tokenIdentifier: tokenIdentifierHex,
|
|
251
256
|
tokenAddress: tokenAddress$1,
|
|
@@ -258,7 +263,7 @@ class FlashnetClient {
|
|
|
258
263
|
}
|
|
259
264
|
}
|
|
260
265
|
return {
|
|
261
|
-
balance:
|
|
266
|
+
balance: bigint.safeBigInt(balance.balance),
|
|
262
267
|
tokenBalances,
|
|
263
268
|
};
|
|
264
269
|
}
|
|
@@ -266,7 +271,7 @@ class FlashnetClient {
|
|
|
266
271
|
* Check if wallet has sufficient balance for an operation
|
|
267
272
|
*/
|
|
268
273
|
async checkBalance(params) {
|
|
269
|
-
const balance = await this.getBalance();
|
|
274
|
+
const balance = params.walletBalance ?? (await this.getBalance());
|
|
270
275
|
// Check balance
|
|
271
276
|
const requirements = {
|
|
272
277
|
tokens: new Map(),
|
|
@@ -290,11 +295,15 @@ class FlashnetClient {
|
|
|
290
295
|
// Check token balances
|
|
291
296
|
if (requirements.tokens) {
|
|
292
297
|
for (const [tokenPubkey, requiredAmount,] of requirements.tokens.entries()) {
|
|
293
|
-
//
|
|
298
|
+
// Support both hex and Bech32m token identifiers by trying all representations
|
|
294
299
|
const hrKey = this.toHumanReadableTokenIdentifier(tokenPubkey);
|
|
300
|
+
const hexKey = this.toHexTokenIdentifier(tokenPubkey);
|
|
295
301
|
const effectiveTokenBalance = balance.tokenBalances.get(tokenPubkey) ??
|
|
296
|
-
balance.tokenBalances.get(hrKey)
|
|
297
|
-
|
|
302
|
+
balance.tokenBalances.get(hrKey) ??
|
|
303
|
+
balance.tokenBalances.get(hexKey);
|
|
304
|
+
const available = params.useAvailableBalance
|
|
305
|
+
? (effectiveTokenBalance?.availableToSendBalance ?? 0n)
|
|
306
|
+
: (effectiveTokenBalance?.balance ?? 0n);
|
|
298
307
|
if (available < requiredAmount) {
|
|
299
308
|
throw new Error([
|
|
300
309
|
params.errorPrefix ?? "",
|
|
@@ -356,6 +365,7 @@ class FlashnetClient {
|
|
|
356
365
|
},
|
|
357
366
|
],
|
|
358
367
|
errorPrefix: "Insufficient balance for initial liquidity: ",
|
|
368
|
+
useAvailableBalance: params.useAvailableBalance,
|
|
359
369
|
});
|
|
360
370
|
}
|
|
361
371
|
const poolOwnerPublicKey = params.poolOwnerPublicKey ?? this.publicKey;
|
|
@@ -485,6 +495,7 @@ class FlashnetClient {
|
|
|
485
495
|
},
|
|
486
496
|
],
|
|
487
497
|
errorPrefix: "Insufficient balance for pool creation: ",
|
|
498
|
+
useAvailableBalance: params.useAvailableBalance,
|
|
488
499
|
});
|
|
489
500
|
const poolOwnerPublicKey = params.poolOwnerPublicKey ?? this.publicKey;
|
|
490
501
|
// Generate intent
|
|
@@ -605,10 +616,14 @@ class FlashnetClient {
|
|
|
605
616
|
});
|
|
606
617
|
// If using free balance (V3 pools only), skip the Spark transfer
|
|
607
618
|
if (params.useFreeBalance) {
|
|
608
|
-
|
|
619
|
+
const swapResponse = await this.executeSwapIntent({
|
|
609
620
|
...params,
|
|
610
621
|
// No transferId - triggers free balance mode
|
|
611
622
|
});
|
|
623
|
+
return {
|
|
624
|
+
...swapResponse,
|
|
625
|
+
inboundSparkTransferId: swapResponse.requestId,
|
|
626
|
+
};
|
|
612
627
|
}
|
|
613
628
|
// Transfer assets to pool using new address encoding
|
|
614
629
|
const lpSparkAddress = sparkAddress.encodeSparkAddressNew({
|
|
@@ -619,12 +634,13 @@ class FlashnetClient {
|
|
|
619
634
|
receiverSparkAddress: lpSparkAddress,
|
|
620
635
|
assetAddress: params.assetInAddress,
|
|
621
636
|
amount: params.amountIn,
|
|
622
|
-
}, "Insufficient balance for swap: ");
|
|
637
|
+
}, "Insufficient balance for swap: ", params.useAvailableBalance);
|
|
623
638
|
// Execute with auto-clawback on failure
|
|
624
|
-
|
|
639
|
+
const swapResponse = await this.executeWithAutoClawback(() => this.executeSwapIntent({
|
|
625
640
|
...params,
|
|
626
641
|
transferId,
|
|
627
642
|
}), [transferId], params.poolId);
|
|
643
|
+
return { ...swapResponse, inboundSparkTransferId: transferId };
|
|
628
644
|
}
|
|
629
645
|
/**
|
|
630
646
|
* Execute a swap with a pre-created transfer or using free balance.
|
|
@@ -754,7 +770,7 @@ class FlashnetClient {
|
|
|
754
770
|
receiverSparkAddress: lpSparkAddress,
|
|
755
771
|
assetAddress: params.initialAssetAddress,
|
|
756
772
|
amount: params.inputAmount,
|
|
757
|
-
}, "Insufficient balance for route swap: ");
|
|
773
|
+
}, "Insufficient balance for route swap: ", params.useAvailableBalance);
|
|
758
774
|
// Execute with auto-clawback on failure
|
|
759
775
|
return this.executeWithAutoClawback(async () => {
|
|
760
776
|
// Prepare hops for validation
|
|
@@ -877,7 +893,7 @@ class FlashnetClient {
|
|
|
877
893
|
assetAddress: pool.assetBAddress,
|
|
878
894
|
amount: params.assetBAmount,
|
|
879
895
|
},
|
|
880
|
-
], "Insufficient balance for adding liquidity: ");
|
|
896
|
+
], "Insufficient balance for adding liquidity: ", params.useAvailableBalance);
|
|
881
897
|
// Execute with auto-clawback on failure
|
|
882
898
|
return this.executeWithAutoClawback(async () => {
|
|
883
899
|
// Generate add liquidity intent
|
|
@@ -1183,6 +1199,7 @@ class FlashnetClient {
|
|
|
1183
1199
|
depositAddress: createResponse.depositAddress,
|
|
1184
1200
|
assetId: params.assetId,
|
|
1185
1201
|
assetAmount: params.assetAmount,
|
|
1202
|
+
useAvailableBalance: params.useAvailableBalance,
|
|
1186
1203
|
});
|
|
1187
1204
|
}
|
|
1188
1205
|
/**
|
|
@@ -1200,6 +1217,7 @@ class FlashnetClient {
|
|
|
1200
1217
|
{ assetAddress: params.assetId, amount: params.assetAmount },
|
|
1201
1218
|
],
|
|
1202
1219
|
errorPrefix: "Insufficient balance to fund escrow: ",
|
|
1220
|
+
useAvailableBalance: params.useAvailableBalance,
|
|
1203
1221
|
});
|
|
1204
1222
|
// 2. Perform transfer
|
|
1205
1223
|
const escrowSparkAddress = sparkAddress.encodeSparkAddressNew({
|
|
@@ -1714,19 +1732,20 @@ class FlashnetClient {
|
|
|
1714
1732
|
/**
|
|
1715
1733
|
* Performs asset transfer using generalized asset address for both BTC and tokens.
|
|
1716
1734
|
*/
|
|
1717
|
-
async transferAsset(recipient, checkBalanceErrorPrefix) {
|
|
1718
|
-
const transferIds = await this.transferAssets([recipient], checkBalanceErrorPrefix);
|
|
1735
|
+
async transferAsset(recipient, checkBalanceErrorPrefix, useAvailableBalance) {
|
|
1736
|
+
const transferIds = await this.transferAssets([recipient], checkBalanceErrorPrefix, useAvailableBalance);
|
|
1719
1737
|
return transferIds[0];
|
|
1720
1738
|
}
|
|
1721
1739
|
/**
|
|
1722
1740
|
* Performs asset transfers using generalized asset addresses for both BTC and tokens.
|
|
1723
1741
|
* Supports optional generic to hardcode recipients length so output list can be typed with same length.
|
|
1724
1742
|
*/
|
|
1725
|
-
async transferAssets(recipients, checkBalanceErrorPrefix) {
|
|
1743
|
+
async transferAssets(recipients, checkBalanceErrorPrefix, useAvailableBalance) {
|
|
1726
1744
|
if (checkBalanceErrorPrefix) {
|
|
1727
1745
|
await this.checkBalance({
|
|
1728
1746
|
balancesToCheck: recipients,
|
|
1729
1747
|
errorPrefix: checkBalanceErrorPrefix,
|
|
1748
|
+
useAvailableBalance,
|
|
1730
1749
|
});
|
|
1731
1750
|
}
|
|
1732
1751
|
const transferIds = [];
|
|
@@ -1875,7 +1894,7 @@ class FlashnetClient {
|
|
|
1875
1894
|
const tokenHex = this.toHexTokenIdentifier(tokenAddress).toLowerCase();
|
|
1876
1895
|
const tokenMinAmount = minAmounts.get(tokenHex);
|
|
1877
1896
|
if (tokenMinAmount &&
|
|
1878
|
-
|
|
1897
|
+
bigint.safeBigInt(poolQuote.tokenAmountRequired) < tokenMinAmount) {
|
|
1879
1898
|
const msg = `Token amount too small. Minimum input is ${tokenMinAmount} units, but calculated amount is only ${poolQuote.tokenAmountRequired} units.`;
|
|
1880
1899
|
throw new errors.FlashnetError(msg, {
|
|
1881
1900
|
response: {
|
|
@@ -1892,7 +1911,7 @@ class FlashnetClient {
|
|
|
1892
1911
|
}
|
|
1893
1912
|
// BTC variable fee adjustment: difference between what the pool targets and unmasked base.
|
|
1894
1913
|
// For V3 pools this is 0 (no masking). For V2 it's the rounding overhead.
|
|
1895
|
-
const btcVariableFeeAdjustment = Number(
|
|
1914
|
+
const btcVariableFeeAdjustment = Number(bigint.safeBigInt(poolQuote.btcAmountUsed) - baseBtcNeeded);
|
|
1896
1915
|
return {
|
|
1897
1916
|
poolId: poolQuote.poolId,
|
|
1898
1917
|
tokenAddress: this.toHexTokenIdentifier(tokenAddress),
|
|
@@ -1964,7 +1983,7 @@ class FlashnetClient {
|
|
|
1964
1983
|
amountIn: tokenAmount,
|
|
1965
1984
|
integratorBps: options?.integratorFeeRateBps,
|
|
1966
1985
|
});
|
|
1967
|
-
const btcOut =
|
|
1986
|
+
const btcOut = bigint.safeBigInt(simulation.amountOut);
|
|
1968
1987
|
if (btcOut > bestBtcOut) {
|
|
1969
1988
|
bestBtcOut = btcOut;
|
|
1970
1989
|
bestResult = {
|
|
@@ -2052,7 +2071,7 @@ class FlashnetClient {
|
|
|
2052
2071
|
await this.ensureInitialized();
|
|
2053
2072
|
const { invoice, tokenAddress, tokenAmount, maxSlippageBps = 500, // 5% default
|
|
2054
2073
|
maxLightningFeeSats, preferSpark = true, integratorFeeRateBps, integratorPublicKey, transferTimeoutMs = 30000, // 30s default
|
|
2055
|
-
rollbackOnFailure = false, useExistingBtcBalance = false, } = options;
|
|
2074
|
+
rollbackOnFailure = false, useExistingBtcBalance = false, useAvailableBalance = false, } = options;
|
|
2056
2075
|
try {
|
|
2057
2076
|
// Step 1: Get a quote for the payment
|
|
2058
2077
|
const quote = await this.getPayLightningWithTokenQuote(invoice, tokenAddress, {
|
|
@@ -2069,6 +2088,7 @@ class FlashnetClient {
|
|
|
2069
2088
|
},
|
|
2070
2089
|
],
|
|
2071
2090
|
errorPrefix: "Insufficient token balance for Lightning payment: ",
|
|
2091
|
+
useAvailableBalance,
|
|
2072
2092
|
});
|
|
2073
2093
|
// Step 3: Get pool details
|
|
2074
2094
|
const pool = await this.getPool(quote.poolId);
|
|
@@ -2091,6 +2111,7 @@ class FlashnetClient {
|
|
|
2091
2111
|
minAmountOut: minBtcOut,
|
|
2092
2112
|
integratorFeeRateBps,
|
|
2093
2113
|
integratorPublicKey,
|
|
2114
|
+
useAvailableBalance,
|
|
2094
2115
|
});
|
|
2095
2116
|
if (!swapResponse.accepted || !swapResponse.outboundTransferId) {
|
|
2096
2117
|
return {
|
|
@@ -2100,6 +2121,7 @@ class FlashnetClient {
|
|
|
2100
2121
|
btcAmountReceived: "0",
|
|
2101
2122
|
swapTransferId: swapResponse.outboundTransferId || "",
|
|
2102
2123
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2124
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2103
2125
|
error: swapResponse.error || "Swap was not accepted",
|
|
2104
2126
|
};
|
|
2105
2127
|
}
|
|
@@ -2110,7 +2132,7 @@ class FlashnetClient {
|
|
|
2110
2132
|
const effectiveMaxLightningFee = maxLightningFeeSats ?? quote.estimatedLightningFee;
|
|
2111
2133
|
const btcNeededForPayment = invoiceAmountSats + effectiveMaxLightningFee;
|
|
2112
2134
|
const balance = await this.getBalance();
|
|
2113
|
-
canPayImmediately = balance.balance >=
|
|
2135
|
+
canPayImmediately = balance.balance >= bigint.safeBigInt(btcNeededForPayment);
|
|
2114
2136
|
}
|
|
2115
2137
|
if (!canPayImmediately) {
|
|
2116
2138
|
const transferComplete = await this.waitForTransferCompletion(swapResponse.outboundTransferId, transferTimeoutMs);
|
|
@@ -2122,6 +2144,7 @@ class FlashnetClient {
|
|
|
2122
2144
|
btcAmountReceived: swapResponse.amountOut || "0",
|
|
2123
2145
|
swapTransferId: swapResponse.outboundTransferId,
|
|
2124
2146
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2147
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2125
2148
|
error: "Transfer did not complete within timeout",
|
|
2126
2149
|
};
|
|
2127
2150
|
}
|
|
@@ -2135,8 +2158,8 @@ class FlashnetClient {
|
|
|
2135
2158
|
let invoiceAmountPaid;
|
|
2136
2159
|
if (quote.isZeroAmountInvoice) {
|
|
2137
2160
|
// Zero-amount invoice: pay whatever BTC we received minus lightning fee
|
|
2138
|
-
const actualBtc =
|
|
2139
|
-
const lnFee =
|
|
2161
|
+
const actualBtc = bigint.safeBigInt(btcReceived);
|
|
2162
|
+
const lnFee = bigint.safeBigInt(effectiveMaxLightningFee);
|
|
2140
2163
|
const amountToPay = actualBtc - lnFee;
|
|
2141
2164
|
if (amountToPay <= 0n) {
|
|
2142
2165
|
return {
|
|
@@ -2146,6 +2169,7 @@ class FlashnetClient {
|
|
|
2146
2169
|
btcAmountReceived: btcReceived,
|
|
2147
2170
|
swapTransferId: swapResponse.outboundTransferId,
|
|
2148
2171
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2172
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2149
2173
|
error: `BTC received (${btcReceived} sats) is not enough to cover lightning fee (${effectiveMaxLightningFee} sats).`,
|
|
2150
2174
|
};
|
|
2151
2175
|
}
|
|
@@ -2165,6 +2189,12 @@ class FlashnetClient {
|
|
|
2165
2189
|
preferSpark,
|
|
2166
2190
|
});
|
|
2167
2191
|
}
|
|
2192
|
+
// Extract the Spark transfer ID from the lightning payment result.
|
|
2193
|
+
// payLightningInvoice returns LightningSendRequest | WalletTransfer:
|
|
2194
|
+
// - LightningSendRequest has .transfer?.sparkId (the Sparkscan-visible transfer ID)
|
|
2195
|
+
// - WalletTransfer (Spark-to-Spark) has .id directly as the transfer ID
|
|
2196
|
+
// Note: lightningPayment.id (the SSP request ID) is already returned as lightningPaymentId
|
|
2197
|
+
const sparkLightningTransferId = lightningPayment.transfer?.sparkId;
|
|
2168
2198
|
return {
|
|
2169
2199
|
success: true,
|
|
2170
2200
|
poolId: quote.poolId,
|
|
@@ -2175,6 +2205,8 @@ class FlashnetClient {
|
|
|
2175
2205
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2176
2206
|
lightningFeePaid: effectiveMaxLightningFee,
|
|
2177
2207
|
invoiceAmountPaid,
|
|
2208
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2209
|
+
sparkLightningTransferId,
|
|
2178
2210
|
};
|
|
2179
2211
|
}
|
|
2180
2212
|
catch (lightningError) {
|
|
@@ -2194,6 +2226,7 @@ class FlashnetClient {
|
|
|
2194
2226
|
btcAmountReceived: "0",
|
|
2195
2227
|
swapTransferId: swapResponse.outboundTransferId,
|
|
2196
2228
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2229
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2197
2230
|
error: `Lightning payment failed: ${lightningErrorMessage}. Funds rolled back to ${rollbackResult.tokenAmount} tokens.`,
|
|
2198
2231
|
};
|
|
2199
2232
|
}
|
|
@@ -2209,6 +2242,7 @@ class FlashnetClient {
|
|
|
2209
2242
|
btcAmountReceived: btcReceived,
|
|
2210
2243
|
swapTransferId: swapResponse.outboundTransferId,
|
|
2211
2244
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2245
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2212
2246
|
error: `Lightning payment failed: ${lightningErrorMessage}. Rollback also failed: ${rollbackErrorMessage}. BTC remains in wallet.`,
|
|
2213
2247
|
};
|
|
2214
2248
|
}
|
|
@@ -2220,6 +2254,7 @@ class FlashnetClient {
|
|
|
2220
2254
|
btcAmountReceived: btcReceived,
|
|
2221
2255
|
swapTransferId: swapResponse.outboundTransferId,
|
|
2222
2256
|
ammFeePaid: quote.estimatedAmmFee,
|
|
2257
|
+
sparkTokenTransferId: swapResponse.inboundSparkTransferId,
|
|
2223
2258
|
error: `Lightning payment failed: ${lightningErrorMessage}. BTC (${btcReceived} sats) remains in wallet.`,
|
|
2224
2259
|
};
|
|
2225
2260
|
}
|
|
@@ -2382,7 +2417,7 @@ class FlashnetClient {
|
|
|
2382
2417
|
tokenIsAssetA: pool.tokenIsAssetA,
|
|
2383
2418
|
integratorBps: integratorFeeRateBps,
|
|
2384
2419
|
});
|
|
2385
|
-
tokenAmount =
|
|
2420
|
+
tokenAmount = bigint.safeBigInt(v3Result.amountIn);
|
|
2386
2421
|
fee = v3Result.totalFee;
|
|
2387
2422
|
executionPrice = v3Result.simulation.executionPrice || "0";
|
|
2388
2423
|
priceImpactPct = v3Result.simulation.priceImpactPct || "0";
|
|
@@ -2391,7 +2426,7 @@ class FlashnetClient {
|
|
|
2391
2426
|
else {
|
|
2392
2427
|
// V2: constant product math + simulation verification
|
|
2393
2428
|
const calculation = this.calculateTokenAmountForBtcOutput(btcTarget.toString(), poolDetails.assetAReserve, poolDetails.assetBReserve, poolDetails.lpFeeBps, poolDetails.hostFeeBps, pool.tokenIsAssetA, integratorFeeRateBps);
|
|
2394
|
-
tokenAmount =
|
|
2429
|
+
tokenAmount = bigint.safeBigInt(calculation.amountIn);
|
|
2395
2430
|
// Verify with simulation
|
|
2396
2431
|
const simulation = await this.simulateSwap({
|
|
2397
2432
|
poolId: pool.lpPublicKey,
|
|
@@ -2400,7 +2435,7 @@ class FlashnetClient {
|
|
|
2400
2435
|
amountIn: calculation.amountIn,
|
|
2401
2436
|
integratorBps: integratorFeeRateBps,
|
|
2402
2437
|
});
|
|
2403
|
-
if (
|
|
2438
|
+
if (bigint.safeBigInt(simulation.amountOut) < btcTarget) {
|
|
2404
2439
|
const btcReserve = pool.tokenIsAssetA
|
|
2405
2440
|
? poolDetails.assetBReserve
|
|
2406
2441
|
: poolDetails.assetAReserve;
|
|
@@ -2488,9 +2523,9 @@ class FlashnetClient {
|
|
|
2488
2523
|
* @private
|
|
2489
2524
|
*/
|
|
2490
2525
|
calculateTokenAmountForBtcOutput(btcAmountOut, reserveA, reserveB, lpFeeBps, hostFeeBps, tokenIsAssetA, integratorFeeBps) {
|
|
2491
|
-
const amountOut =
|
|
2492
|
-
const resA =
|
|
2493
|
-
const resB =
|
|
2526
|
+
const amountOut = bigint.safeBigInt(btcAmountOut);
|
|
2527
|
+
const resA = bigint.safeBigInt(reserveA);
|
|
2528
|
+
const resB = bigint.safeBigInt(reserveB);
|
|
2494
2529
|
const totalFeeBps = lpFeeBps + hostFeeBps + (integratorFeeBps || 0);
|
|
2495
2530
|
const feeRate = Number(totalFeeBps) / 10000; // Convert bps to decimal
|
|
2496
2531
|
// Token is the input asset
|
|
@@ -2588,7 +2623,7 @@ class FlashnetClient {
|
|
|
2588
2623
|
amountIn: upperBound.toString(),
|
|
2589
2624
|
integratorBps,
|
|
2590
2625
|
});
|
|
2591
|
-
if (
|
|
2626
|
+
if (bigint.safeBigInt(sim.amountOut) >= desiredBtcOut) {
|
|
2592
2627
|
upperSim = sim;
|
|
2593
2628
|
break;
|
|
2594
2629
|
}
|
|
@@ -2599,7 +2634,7 @@ class FlashnetClient {
|
|
|
2599
2634
|
throw new Error(`V3 pool ${poolId} has insufficient liquidity for ${desiredBtcOut} sats`);
|
|
2600
2635
|
}
|
|
2601
2636
|
// Step 3: Refine estimate via linear interpolation
|
|
2602
|
-
const upperOut =
|
|
2637
|
+
const upperOut = bigint.safeBigInt(upperSim.amountOut);
|
|
2603
2638
|
// Scale proportionally: if upperBound produced upperOut, we need roughly
|
|
2604
2639
|
// (upperBound * desiredBtcOut / upperOut). Add +1 to avoid undershoot from truncation.
|
|
2605
2640
|
let refined = (upperBound * desiredBtcOut) / upperOut + 1n;
|
|
@@ -2617,7 +2652,7 @@ class FlashnetClient {
|
|
|
2617
2652
|
amountIn: refined.toString(),
|
|
2618
2653
|
integratorBps,
|
|
2619
2654
|
});
|
|
2620
|
-
if (
|
|
2655
|
+
if (bigint.safeBigInt(refinedSim.amountOut) >= desiredBtcOut) {
|
|
2621
2656
|
bestAmountIn = refined;
|
|
2622
2657
|
bestSim = refinedSim;
|
|
2623
2658
|
}
|
|
@@ -2651,7 +2686,7 @@ class FlashnetClient {
|
|
|
2651
2686
|
amountIn: mid.toString(),
|
|
2652
2687
|
integratorBps,
|
|
2653
2688
|
});
|
|
2654
|
-
if (
|
|
2689
|
+
if (bigint.safeBigInt(midSim.amountOut) >= desiredBtcOut) {
|
|
2655
2690
|
hi = mid;
|
|
2656
2691
|
bestAmountIn = mid;
|
|
2657
2692
|
bestSim = midSim;
|
|
@@ -2770,64 +2805,24 @@ class FlashnetClient {
|
|
|
2770
2805
|
}
|
|
2771
2806
|
/**
|
|
2772
2807
|
* Decode the amount from a Lightning invoice (in sats)
|
|
2808
|
+
* Uses light-bolt11-decoder (same library as Spark SDK) for reliable parsing.
|
|
2773
2809
|
* @private
|
|
2774
2810
|
*/
|
|
2775
2811
|
async decodeInvoiceAmount(invoice) {
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
else if (lowerInvoice.startsWith("lntb")) {
|
|
2786
|
-
amountStart = 4;
|
|
2787
|
-
}
|
|
2788
|
-
else if (lowerInvoice.startsWith("lnbcrt")) {
|
|
2789
|
-
amountStart = 6;
|
|
2790
|
-
}
|
|
2791
|
-
else if (lowerInvoice.startsWith("lntbs")) {
|
|
2792
|
-
amountStart = 5;
|
|
2812
|
+
try {
|
|
2813
|
+
const decoded = lightBolt11Decoder.decode(invoice);
|
|
2814
|
+
const amountSection = decoded.sections.find((s) => s.name === "amount");
|
|
2815
|
+
if (!amountSection?.value) {
|
|
2816
|
+
return 0; // Zero-amount invoice
|
|
2817
|
+
}
|
|
2818
|
+
// The library returns amount in millisatoshis as a string
|
|
2819
|
+
const amountMSats = BigInt(amountSection.value);
|
|
2820
|
+
return Number(amountMSats / 1000n);
|
|
2793
2821
|
}
|
|
2794
|
-
|
|
2795
|
-
//
|
|
2796
|
-
|
|
2797
|
-
if (match) {
|
|
2798
|
-
amountStart = match[0].length;
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2801
|
-
// Extract amount and multiplier
|
|
2802
|
-
const afterPrefix = lowerInvoice.substring(amountStart);
|
|
2803
|
-
const amountMatch = afterPrefix.match(/^(\d+)([munp]?)/);
|
|
2804
|
-
if (!amountMatch || !amountMatch[1]) {
|
|
2805
|
-
return 0; // Zero-amount invoice
|
|
2806
|
-
}
|
|
2807
|
-
const amount = parseInt(amountMatch[1], 10);
|
|
2808
|
-
const multiplier = amountMatch[2] ?? "";
|
|
2809
|
-
// Convert to satoshis (1 BTC = 100,000,000 sats)
|
|
2810
|
-
// Invoice amounts are in BTC by default
|
|
2811
|
-
let btcAmount;
|
|
2812
|
-
switch (multiplier) {
|
|
2813
|
-
case "m": // milli-BTC (0.001 BTC)
|
|
2814
|
-
btcAmount = amount * 0.001;
|
|
2815
|
-
break;
|
|
2816
|
-
case "u": // micro-BTC (0.000001 BTC)
|
|
2817
|
-
btcAmount = amount * 0.000001;
|
|
2818
|
-
break;
|
|
2819
|
-
case "n": // nano-BTC (0.000000001 BTC)
|
|
2820
|
-
btcAmount = amount * 0.000000001;
|
|
2821
|
-
break;
|
|
2822
|
-
case "p": // pico-BTC (0.000000000001 BTC)
|
|
2823
|
-
btcAmount = amount * 0.000000000001;
|
|
2824
|
-
break;
|
|
2825
|
-
default: // BTC
|
|
2826
|
-
btcAmount = amount;
|
|
2827
|
-
break;
|
|
2822
|
+
catch {
|
|
2823
|
+
// Fallback: if library fails, return 0 (treated as zero-amount invoice)
|
|
2824
|
+
return 0;
|
|
2828
2825
|
}
|
|
2829
|
-
// Convert BTC to sats
|
|
2830
|
-
return Math.round(btcAmount * 100000000);
|
|
2831
2826
|
}
|
|
2832
2827
|
/**
|
|
2833
2828
|
* Clean up wallet connections
|
|
@@ -2892,8 +2887,11 @@ class FlashnetClient {
|
|
|
2892
2887
|
const map = new Map();
|
|
2893
2888
|
for (const item of config) {
|
|
2894
2889
|
if (item.enabled) {
|
|
2890
|
+
if (item.min_amount == null) {
|
|
2891
|
+
continue;
|
|
2892
|
+
}
|
|
2895
2893
|
const key = item.asset_identifier.toLowerCase();
|
|
2896
|
-
const value =
|
|
2894
|
+
const value = bigint.safeBigInt(item.min_amount);
|
|
2897
2895
|
map.set(key, value);
|
|
2898
2896
|
}
|
|
2899
2897
|
}
|
|
@@ -2915,8 +2913,8 @@ class FlashnetClient {
|
|
|
2915
2913
|
const outHex = this.getHexAddress(params.assetOutAddress);
|
|
2916
2914
|
const minIn = minMap.get(inHex);
|
|
2917
2915
|
const minOut = minMap.get(outHex);
|
|
2918
|
-
const amountIn = BigInt(
|
|
2919
|
-
const minAmountOut = BigInt(
|
|
2916
|
+
const amountIn = BigInt(params.amountIn);
|
|
2917
|
+
const minAmountOut = BigInt(params.minAmountOut);
|
|
2920
2918
|
if (minIn && minOut) {
|
|
2921
2919
|
if (amountIn < minIn) {
|
|
2922
2920
|
throw new Error(`Minimum amount not met for input asset. Required \
|
|
@@ -2950,13 +2948,13 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
2950
2948
|
const aMin = minMap.get(aHex);
|
|
2951
2949
|
const bMin = minMap.get(bHex);
|
|
2952
2950
|
if (aMin) {
|
|
2953
|
-
const aAmt = BigInt(
|
|
2951
|
+
const aAmt = BigInt(params.assetAAmount);
|
|
2954
2952
|
if (aAmt < aMin) {
|
|
2955
2953
|
throw new Error(`Minimum amount not met for Asset A. Required ${aMin.toString()}, provided ${aAmt.toString()}`);
|
|
2956
2954
|
}
|
|
2957
2955
|
}
|
|
2958
2956
|
if (bMin) {
|
|
2959
|
-
const bAmt = BigInt(
|
|
2957
|
+
const bAmt = BigInt(params.assetBAmount);
|
|
2960
2958
|
if (bAmt < bMin) {
|
|
2961
2959
|
throw new Error(`Minimum amount not met for Asset B. Required ${bMin.toString()}, provided ${bAmt.toString()}`);
|
|
2962
2960
|
}
|
|
@@ -2978,14 +2976,14 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
2978
2976
|
const aMin = minMap.get(aHex);
|
|
2979
2977
|
const bMin = minMap.get(bHex);
|
|
2980
2978
|
if (aMin) {
|
|
2981
|
-
const predictedAOut =
|
|
2979
|
+
const predictedAOut = bigint.safeBigInt(simulation.assetAAmount);
|
|
2982
2980
|
const relaxedA = aMin / 2n; // apply 50% relaxation for outputs
|
|
2983
2981
|
if (predictedAOut < relaxedA) {
|
|
2984
2982
|
throw new Error(`Minimum amount not met for Asset A on withdrawal. Required at least ${relaxedA.toString()} (50% relaxed), predicted ${predictedAOut.toString()}`);
|
|
2985
2983
|
}
|
|
2986
2984
|
}
|
|
2987
2985
|
if (bMin) {
|
|
2988
|
-
const predictedBOut =
|
|
2986
|
+
const predictedBOut = bigint.safeBigInt(simulation.assetBAmount);
|
|
2989
2987
|
const relaxedB = bMin / 2n;
|
|
2990
2988
|
if (predictedBOut < relaxedB) {
|
|
2991
2989
|
throw new Error(`Minimum amount not met for Asset B on withdrawal. Required at least ${relaxedB.toString()} (50% relaxed), predicted ${predictedBOut.toString()}`);
|
|
@@ -3103,7 +3101,7 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
3103
3101
|
receiverSparkAddress: lpSparkAddress,
|
|
3104
3102
|
assetAddress: pool.assetAAddress,
|
|
3105
3103
|
amount: params.amountADesired,
|
|
3106
|
-
}, "Insufficient balance for adding V3 liquidity (Asset A): ");
|
|
3104
|
+
}, "Insufficient balance for adding V3 liquidity (Asset A): ", params.useAvailableBalance);
|
|
3107
3105
|
transferIds.push(assetATransferId);
|
|
3108
3106
|
}
|
|
3109
3107
|
if (BigInt(params.amountBDesired) > 0n) {
|
|
@@ -3111,7 +3109,7 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
3111
3109
|
receiverSparkAddress: lpSparkAddress,
|
|
3112
3110
|
assetAddress: pool.assetBAddress,
|
|
3113
3111
|
amount: params.amountBDesired,
|
|
3114
|
-
}, "Insufficient balance for adding V3 liquidity (Asset B): ");
|
|
3112
|
+
}, "Insufficient balance for adding V3 liquidity (Asset B): ", params.useAvailableBalance);
|
|
3115
3113
|
transferIds.push(assetBTransferId);
|
|
3116
3114
|
}
|
|
3117
3115
|
}
|
|
@@ -3304,14 +3302,14 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
3304
3302
|
receiverSparkAddress: lpSparkAddress,
|
|
3305
3303
|
assetAddress: pool.assetAAddress,
|
|
3306
3304
|
amount: params.additionalAmountA,
|
|
3307
|
-
}, "Insufficient balance for rebalance (Asset A): ");
|
|
3305
|
+
}, "Insufficient balance for rebalance (Asset A): ", params.useAvailableBalance);
|
|
3308
3306
|
}
|
|
3309
3307
|
if (params.additionalAmountB && BigInt(params.additionalAmountB) > 0n) {
|
|
3310
3308
|
assetBTransferId = await this.transferAsset({
|
|
3311
3309
|
receiverSparkAddress: lpSparkAddress,
|
|
3312
3310
|
assetAddress: pool.assetBAddress,
|
|
3313
3311
|
amount: params.additionalAmountB,
|
|
3314
|
-
}, "Insufficient balance for rebalance (Asset B): ");
|
|
3312
|
+
}, "Insufficient balance for rebalance (Asset B): ", params.useAvailableBalance);
|
|
3315
3313
|
}
|
|
3316
3314
|
// Collect transfer IDs for potential clawback
|
|
3317
3315
|
const transferIds = [];
|
|
@@ -3508,7 +3506,7 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
3508
3506
|
receiverSparkAddress: lpSparkAddress,
|
|
3509
3507
|
assetAddress: pool.assetAAddress,
|
|
3510
3508
|
amount: params.amountA,
|
|
3511
|
-
}, "Insufficient balance for depositing to V3 pool (Asset A): ");
|
|
3509
|
+
}, "Insufficient balance for depositing to V3 pool (Asset A): ", params.useAvailableBalance);
|
|
3512
3510
|
transferIds.push(assetATransferId);
|
|
3513
3511
|
}
|
|
3514
3512
|
if (BigInt(params.amountB) > 0n) {
|
|
@@ -3516,7 +3514,7 @@ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toStri
|
|
|
3516
3514
|
receiverSparkAddress: lpSparkAddress,
|
|
3517
3515
|
assetAddress: pool.assetBAddress,
|
|
3518
3516
|
amount: params.amountB,
|
|
3519
|
-
}, "Insufficient balance for depositing to V3 pool (Asset B): ");
|
|
3517
|
+
}, "Insufficient balance for depositing to V3 pool (Asset B): ", params.useAvailableBalance);
|
|
3520
3518
|
transferIds.push(assetBTransferId);
|
|
3521
3519
|
}
|
|
3522
3520
|
const executeDeposit = async () => {
|