@flashnet/sdk 0.3.24 → 0.3.26
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/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/src/api/typed-endpoints.d.ts +15 -0
- package/dist/cjs/src/api/typed-endpoints.d.ts.map +1 -1
- package/dist/cjs/src/api/typed-endpoints.js +22 -0
- package/dist/cjs/src/api/typed-endpoints.js.map +1 -1
- package/dist/cjs/src/client/FlashnetClient.d.ts +18 -0
- package/dist/cjs/src/client/FlashnetClient.d.ts.map +1 -1
- package/dist/cjs/src/client/FlashnetClient.js +338 -88
- package/dist/cjs/src/client/FlashnetClient.js.map +1 -1
- package/dist/cjs/src/config/index.js +2 -2
- package/dist/cjs/src/config/index.js.map +1 -1
- package/dist/cjs/src/types/index.d.ts +19 -0
- package/dist/cjs/src/types/index.d.ts.map +1 -1
- package/dist/cjs/src/types/index.js.map +1 -1
- package/dist/cjs/src/utils/auth.d.ts.map +1 -1
- package/dist/cjs/src/utils/auth.js +5 -3
- package/dist/cjs/src/utils/auth.js.map +1 -1
- package/dist/cjs/src/utils/hex.d.ts +3 -0
- package/dist/cjs/src/utils/hex.d.ts.map +1 -0
- package/dist/cjs/src/utils/hex.js +17 -0
- package/dist/cjs/src/utils/hex.js.map +1 -0
- package/dist/cjs/src/utils/index.js +2 -2
- package/dist/cjs/src/utils/spark-address.js +16 -16
- package/dist/cjs/src/utils/spark-address.js.map +1 -1
- package/dist/cjs/src/utils/tokenAddress.d.ts.map +1 -1
- package/dist/cjs/src/utils/tokenAddress.js +8 -8
- package/dist/cjs/src/utils/tokenAddress.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/src/api/typed-endpoints.d.ts +15 -0
- package/dist/esm/src/api/typed-endpoints.d.ts.map +1 -1
- package/dist/esm/src/api/typed-endpoints.js +22 -0
- package/dist/esm/src/api/typed-endpoints.js.map +1 -1
- package/dist/esm/src/client/FlashnetClient.d.ts +18 -0
- package/dist/esm/src/client/FlashnetClient.d.ts.map +1 -1
- package/dist/esm/src/client/FlashnetClient.js +338 -88
- package/dist/esm/src/client/FlashnetClient.js.map +1 -1
- package/dist/esm/src/config/index.js +2 -2
- package/dist/esm/src/config/index.js.map +1 -1
- package/dist/esm/src/types/index.d.ts +19 -0
- package/dist/esm/src/types/index.d.ts.map +1 -1
- package/dist/esm/src/types/index.js.map +1 -1
- package/dist/esm/src/utils/auth.d.ts.map +1 -1
- package/dist/esm/src/utils/auth.js +5 -3
- package/dist/esm/src/utils/auth.js.map +1 -1
- package/dist/esm/src/utils/hex.d.ts +3 -0
- package/dist/esm/src/utils/hex.d.ts.map +1 -0
- package/dist/esm/src/utils/hex.js +14 -0
- package/dist/esm/src/utils/hex.js.map +1 -0
- package/dist/esm/src/utils/index.js +2 -2
- package/dist/esm/src/utils/spark-address.js +8 -8
- package/dist/esm/src/utils/spark-address.js.map +1 -1
- package/dist/esm/src/utils/tokenAddress.d.ts.map +1 -1
- package/dist/esm/src/utils/tokenAddress.js +2 -2
- package/package.json +7 -7
|
@@ -6,6 +6,7 @@ var index$1 = require('../config/index.js');
|
|
|
6
6
|
var index = require('../types/index.js');
|
|
7
7
|
var index$2 = require('../utils/index.js');
|
|
8
8
|
var auth = require('../utils/auth.js');
|
|
9
|
+
var hex = require('../utils/hex.js');
|
|
9
10
|
var intents = require('../utils/intents.js');
|
|
10
11
|
var signer = require('../utils/signer.js');
|
|
11
12
|
var sparkAddress = require('../utils/spark-address.js');
|
|
@@ -31,6 +32,16 @@ class FlashnetClient {
|
|
|
31
32
|
publicKey = "";
|
|
32
33
|
sparkAddress = "";
|
|
33
34
|
isAuthenticated = false;
|
|
35
|
+
// Ephemeral caches for config endpoints and ping
|
|
36
|
+
featureStatusCache;
|
|
37
|
+
minAmountsCache;
|
|
38
|
+
allowedAssetsCache;
|
|
39
|
+
pingCache;
|
|
40
|
+
// TTLs (milliseconds)
|
|
41
|
+
static FEATURE_STATUS_TTL_MS = 5000; // 5s
|
|
42
|
+
static MIN_AMOUNTS_TTL_MS = 5000; // 5s
|
|
43
|
+
static ALLOWED_ASSETS_TTL_MS = 60000; // 60s
|
|
44
|
+
static PING_TTL_MS = 2000; // 2s
|
|
34
45
|
/**
|
|
35
46
|
* Get the underlying wallet instance for direct wallet operations
|
|
36
47
|
*/
|
|
@@ -227,7 +238,7 @@ class FlashnetClient {
|
|
|
227
238
|
for (const [tokenPubkey, tokenData] of balance.tokenBalances.entries()) {
|
|
228
239
|
const info = tokenData.tokenMetadata;
|
|
229
240
|
// Convert raw token identifier to hex and human-readable forms
|
|
230
|
-
const tokenIdentifierHex =
|
|
241
|
+
const tokenIdentifierHex = hex.getHexFromUint8Array(info.rawTokenIdentifier);
|
|
231
242
|
const tokenAddress$1 = tokenAddress.encodeSparkHumanReadableTokenIdentifier(info.rawTokenIdentifier, this.sparkNetwork);
|
|
232
243
|
tokenBalances.set(tokenPubkey, {
|
|
233
244
|
balance: BigInt(tokenData.balance),
|
|
@@ -325,6 +336,8 @@ class FlashnetClient {
|
|
|
325
336
|
*/
|
|
326
337
|
async createConstantProductPool(params) {
|
|
327
338
|
await this.ensureInitialized();
|
|
339
|
+
await this.ensureAmmOperationAllowed("allow_pool_creation");
|
|
340
|
+
await this.assertAllowedAssetBForPoolCreation(this.toHexTokenIdentifier(params.assetBAddress));
|
|
328
341
|
// Check if we need to add initial liquidity
|
|
329
342
|
if (params.initialLiquidity) {
|
|
330
343
|
await this.checkBalance({
|
|
@@ -364,7 +377,7 @@ class FlashnetClient {
|
|
|
364
377
|
totalHostFeeRateBps: params.totalHostFeeRateBps.toString(),
|
|
365
378
|
hostNamespace: params.hostNamespace || "",
|
|
366
379
|
nonce,
|
|
367
|
-
signature:
|
|
380
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
368
381
|
};
|
|
369
382
|
const response = await this.typedApi.createConstantProductPool(request);
|
|
370
383
|
// Add initial liquidity if specified
|
|
@@ -373,6 +386,31 @@ class FlashnetClient {
|
|
|
373
386
|
}
|
|
374
387
|
return response;
|
|
375
388
|
}
|
|
389
|
+
// Validate and normalize inputs to bigint
|
|
390
|
+
static parsePositiveIntegerToBigInt(value, name) {
|
|
391
|
+
if (typeof value === "bigint") {
|
|
392
|
+
if (value <= 0n) {
|
|
393
|
+
throw new Error(`${name} must be positive integer`);
|
|
394
|
+
}
|
|
395
|
+
return value;
|
|
396
|
+
}
|
|
397
|
+
if (typeof value === "number") {
|
|
398
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
399
|
+
throw new Error(`${name} must be positive integer`);
|
|
400
|
+
}
|
|
401
|
+
return BigInt(value);
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
const v = BigInt(value);
|
|
405
|
+
if (v <= 0n) {
|
|
406
|
+
throw new Error(`${name} must be positive integer`);
|
|
407
|
+
}
|
|
408
|
+
return v;
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
throw new Error(`${name} must be positive integer`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
376
414
|
/**
|
|
377
415
|
* Calculates virtual reserves for a bonding curve AMM.
|
|
378
416
|
*
|
|
@@ -387,69 +425,36 @@ class FlashnetClient {
|
|
|
387
425
|
* @returns An object containing `virtualReserveA`, `virtualReserveB`, and `threshold`.
|
|
388
426
|
*/
|
|
389
427
|
static calculateVirtualReserves(params) {
|
|
390
|
-
// Validate and normalize inputs to bigint
|
|
391
|
-
const parsePositiveIntegerToBigInt = (value, name) => {
|
|
392
|
-
if (typeof value === "bigint") {
|
|
393
|
-
if (value <= 0n) {
|
|
394
|
-
throw new Error(`${name} must be positive integer`);
|
|
395
|
-
}
|
|
396
|
-
return value;
|
|
397
|
-
}
|
|
398
|
-
if (typeof value === "number") {
|
|
399
|
-
if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
400
|
-
throw new Error(`${name} must be positive integer`);
|
|
401
|
-
}
|
|
402
|
-
return BigInt(value);
|
|
403
|
-
}
|
|
404
|
-
try {
|
|
405
|
-
const v = BigInt(value);
|
|
406
|
-
if (v <= 0n) {
|
|
407
|
-
throw new Error(`${name} must be positive integer`);
|
|
408
|
-
}
|
|
409
|
-
return v;
|
|
410
|
-
}
|
|
411
|
-
catch {
|
|
412
|
-
throw new Error(`${name} must be positive integer`);
|
|
413
|
-
}
|
|
414
|
-
};
|
|
415
428
|
if (!Number.isFinite(params.graduationThresholdPct) ||
|
|
416
|
-
!Number.isInteger(params.graduationThresholdPct)
|
|
417
|
-
|
|
418
|
-
throw new Error("Graduation threshold percentage must be a positive integer");
|
|
429
|
+
!Number.isInteger(params.graduationThresholdPct)) {
|
|
430
|
+
throw new Error("Graduation threshold percentage must be an integer number of percent");
|
|
419
431
|
}
|
|
420
|
-
const supply = parsePositiveIntegerToBigInt(params.initialTokenSupply, "Initial token supply");
|
|
421
|
-
const targetB = parsePositiveIntegerToBigInt(params.targetRaise, "Target raise");
|
|
432
|
+
const supply = FlashnetClient.parsePositiveIntegerToBigInt(params.initialTokenSupply, "Initial token supply");
|
|
433
|
+
const targetB = FlashnetClient.parsePositiveIntegerToBigInt(params.targetRaise, "Target raise");
|
|
422
434
|
const graduationThresholdPct = BigInt(params.graduationThresholdPct);
|
|
423
|
-
//
|
|
424
|
-
const
|
|
425
|
-
const
|
|
426
|
-
if (graduationThresholdPct
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
|
|
437
|
-
const
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
// Calculate threshold as amount of asset A (not percentage)
|
|
447
|
-
const threshold = (BigInt(supply) * graduationThresholdPct) / 100n;
|
|
448
|
-
return {
|
|
449
|
-
virtualReserveA: virtualA,
|
|
450
|
-
virtualReserveB: virtualB,
|
|
451
|
-
threshold: threshold,
|
|
452
|
-
};
|
|
435
|
+
// Align bounds with Rust AMM (20%..95%), then check feasibility for g=1 (requires >50%).
|
|
436
|
+
const MIN_PCT = 20n;
|
|
437
|
+
const MAX_PCT = 95n;
|
|
438
|
+
if (graduationThresholdPct < MIN_PCT || graduationThresholdPct > MAX_PCT) {
|
|
439
|
+
throw new Error(`Graduation threshold percentage must be between ${MIN_PCT} and ${MAX_PCT}`);
|
|
440
|
+
}
|
|
441
|
+
// Feasibility: denom = f - g*(1-f) > 0 with g=1 -> 2f - 1 > 0 -> pct > 50
|
|
442
|
+
const denomNormalized = 2n * graduationThresholdPct - 100n; // equals 100*(f - (1-f))
|
|
443
|
+
if (denomNormalized <= 0n) {
|
|
444
|
+
throw new Error("Invalid configuration: threshold must be greater than 50% when LP fraction is 1.0");
|
|
445
|
+
}
|
|
446
|
+
// v_A = S * f^2 / (f - (1-f)) ; using integer math with pct where
|
|
447
|
+
// v_A = S * p^2 / (100 * (2p - 100))
|
|
448
|
+
const vANumerator = supply * graduationThresholdPct * graduationThresholdPct;
|
|
449
|
+
const vADenominator = 100n * denomNormalized;
|
|
450
|
+
const virtualA = vANumerator / vADenominator; // floor
|
|
451
|
+
// v_B = T * (1 - f) / (f - (1-f)) ; with pct => T * (100 - p) / (2p - 100)
|
|
452
|
+
const vBNumerator = targetB * (100n - graduationThresholdPct);
|
|
453
|
+
const vBDenominator = denomNormalized;
|
|
454
|
+
const virtualB = vBNumerator / vBDenominator; // floor
|
|
455
|
+
// Threshold amount in A
|
|
456
|
+
const threshold = (supply * graduationThresholdPct) / 100n;
|
|
457
|
+
return { virtualReserveA: virtualA, virtualReserveB: virtualB, threshold };
|
|
453
458
|
}
|
|
454
459
|
/**
|
|
455
460
|
* Create a single-sided pool with automatic initial deposit
|
|
@@ -459,18 +464,20 @@ class FlashnetClient {
|
|
|
459
464
|
*/
|
|
460
465
|
async createSingleSidedPool(params) {
|
|
461
466
|
await this.ensureInitialized();
|
|
467
|
+
await this.ensureAmmOperationAllowed("allow_pool_creation");
|
|
468
|
+
await this.assertAllowedAssetBForPoolCreation(this.toHexTokenIdentifier(params.assetBAddress));
|
|
462
469
|
if (!params.hostNamespace && params.totalHostFeeRateBps < 10) {
|
|
463
470
|
throw new Error(`Host fee must be greater than 10 bps when no host namespace is provided`);
|
|
464
471
|
}
|
|
465
|
-
//
|
|
466
|
-
const
|
|
467
|
-
const
|
|
468
|
-
const
|
|
472
|
+
// Validate reserves are valid positive integers before any operations
|
|
473
|
+
const assetAInitialReserve = FlashnetClient.parsePositiveIntegerToBigInt(params.assetAInitialReserve, "Asset A Initial Reserve").toString();
|
|
474
|
+
const virtualReserveA = FlashnetClient.parsePositiveIntegerToBigInt(params.virtualReserveA, "Virtual Reserve A").toString();
|
|
475
|
+
const virtualReserveB = FlashnetClient.parsePositiveIntegerToBigInt(params.virtualReserveB, "Virtual Reserve B").toString();
|
|
469
476
|
await this.checkBalance({
|
|
470
477
|
balancesToCheck: [
|
|
471
478
|
{
|
|
472
479
|
assetAddress: params.assetAAddress,
|
|
473
|
-
amount:
|
|
480
|
+
amount: assetAInitialReserve,
|
|
474
481
|
},
|
|
475
482
|
],
|
|
476
483
|
errorPrefix: "Insufficient balance for pool creation: ",
|
|
@@ -482,9 +489,9 @@ class FlashnetClient {
|
|
|
482
489
|
poolOwnerPublicKey,
|
|
483
490
|
assetAAddress: this.toHexTokenIdentifier(params.assetAAddress),
|
|
484
491
|
assetBAddress: this.toHexTokenIdentifier(params.assetBAddress),
|
|
485
|
-
assetAInitialReserve
|
|
486
|
-
virtualReserveA
|
|
487
|
-
virtualReserveB
|
|
492
|
+
assetAInitialReserve,
|
|
493
|
+
virtualReserveA,
|
|
494
|
+
virtualReserveB,
|
|
488
495
|
threshold: params.threshold.toString(),
|
|
489
496
|
lpFeeRateBps: params.lpFeeRateBps.toString(),
|
|
490
497
|
totalHostFeeRateBps: params.totalHostFeeRateBps.toString(),
|
|
@@ -497,15 +504,15 @@ class FlashnetClient {
|
|
|
497
504
|
poolOwnerPublicKey,
|
|
498
505
|
assetAAddress: this.toHexTokenIdentifier(params.assetAAddress),
|
|
499
506
|
assetBAddress: this.toHexTokenIdentifier(params.assetBAddress),
|
|
500
|
-
assetAInitialReserve
|
|
501
|
-
virtualReserveA
|
|
502
|
-
virtualReserveB
|
|
507
|
+
assetAInitialReserve,
|
|
508
|
+
virtualReserveA,
|
|
509
|
+
virtualReserveB,
|
|
503
510
|
threshold: params.threshold.toString(),
|
|
504
511
|
lpFeeRateBps: params.lpFeeRateBps.toString(),
|
|
505
512
|
totalHostFeeRateBps: params.totalHostFeeRateBps.toString(),
|
|
506
513
|
hostNamespace: params.hostNamespace,
|
|
507
514
|
nonce,
|
|
508
|
-
signature:
|
|
515
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
509
516
|
};
|
|
510
517
|
const createResponse = await this.typedApi.createSingleSidedPool(request);
|
|
511
518
|
if (params.disableInitialDeposit) {
|
|
@@ -519,7 +526,7 @@ class FlashnetClient {
|
|
|
519
526
|
const assetATransferId = await this.transferAsset({
|
|
520
527
|
receiverSparkAddress: lpSparkAddress,
|
|
521
528
|
assetAddress: params.assetAAddress,
|
|
522
|
-
amount:
|
|
529
|
+
amount: assetAInitialReserve,
|
|
523
530
|
});
|
|
524
531
|
const confirmResponse = await this.confirmInitialDeposit(createResponse.poolId, assetATransferId, poolOwnerPublicKey);
|
|
525
532
|
if (!confirmResponse.confirmed) {
|
|
@@ -548,7 +555,7 @@ class FlashnetClient {
|
|
|
548
555
|
poolId,
|
|
549
556
|
assetASparkTransferId,
|
|
550
557
|
nonce,
|
|
551
|
-
signature:
|
|
558
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
552
559
|
poolOwnerPublicKey: poolOwnerPublicKey ?? this.publicKey,
|
|
553
560
|
};
|
|
554
561
|
return this.typedApi.confirmInitialDeposit(request);
|
|
@@ -559,6 +566,7 @@ class FlashnetClient {
|
|
|
559
566
|
*/
|
|
560
567
|
async simulateSwap(params) {
|
|
561
568
|
await this.ensureInitialized();
|
|
569
|
+
await this.ensurePingOk();
|
|
562
570
|
return this.typedApi.simulateSwap(params);
|
|
563
571
|
}
|
|
564
572
|
/**
|
|
@@ -566,6 +574,14 @@ class FlashnetClient {
|
|
|
566
574
|
*/
|
|
567
575
|
async executeSwap(params) {
|
|
568
576
|
await this.ensureInitialized();
|
|
577
|
+
// Gate by feature flags and ping, and enforce min-amount policy before transfers
|
|
578
|
+
await this.ensureAmmOperationAllowed("allow_swaps");
|
|
579
|
+
await this.assertSwapMeetsMinAmounts({
|
|
580
|
+
assetInAddress: params.assetInAddress,
|
|
581
|
+
assetOutAddress: params.assetOutAddress,
|
|
582
|
+
amountIn: params.amountIn,
|
|
583
|
+
minAmountOut: params.minAmountOut,
|
|
584
|
+
});
|
|
569
585
|
// Transfer assets to pool using new address encoding
|
|
570
586
|
const lpSparkAddress = sparkAddress.encodeSparkAddressNew({
|
|
571
587
|
identityPublicKey: params.poolId,
|
|
@@ -584,6 +600,14 @@ class FlashnetClient {
|
|
|
584
600
|
}
|
|
585
601
|
async executeSwapIntent(params) {
|
|
586
602
|
await this.ensureInitialized();
|
|
603
|
+
// Also enforce gating and min amounts for direct intent usage
|
|
604
|
+
await this.ensureAmmOperationAllowed("allow_swaps");
|
|
605
|
+
await this.assertSwapMeetsMinAmounts({
|
|
606
|
+
assetInAddress: params.assetInAddress,
|
|
607
|
+
assetOutAddress: params.assetOutAddress,
|
|
608
|
+
amountIn: params.amountIn,
|
|
609
|
+
minAmountOut: params.minAmountOut,
|
|
610
|
+
});
|
|
587
611
|
// Generate swap intent
|
|
588
612
|
const nonce = index$2.generateNonce();
|
|
589
613
|
const intentMessage = intents.generatePoolSwapIntentMessage({
|
|
@@ -613,7 +637,7 @@ class FlashnetClient {
|
|
|
613
637
|
totalIntegratorFeeRateBps: params.integratorFeeRateBps?.toString() || "0",
|
|
614
638
|
integratorPublicKey: params.integratorPublicKey || "",
|
|
615
639
|
nonce,
|
|
616
|
-
signature:
|
|
640
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
617
641
|
};
|
|
618
642
|
const response = await this.typedApi.executeSwap(request);
|
|
619
643
|
// Check if the swap was accepted
|
|
@@ -634,6 +658,7 @@ class FlashnetClient {
|
|
|
634
658
|
throw new Error("Route swap cannot have more than 4 hops");
|
|
635
659
|
}
|
|
636
660
|
await this.ensureInitialized();
|
|
661
|
+
await this.ensurePingOk();
|
|
637
662
|
return this.typedApi.simulateRouteSwap(params);
|
|
638
663
|
}
|
|
639
664
|
/**
|
|
@@ -641,6 +666,18 @@ class FlashnetClient {
|
|
|
641
666
|
*/
|
|
642
667
|
async executeRouteSwap(params) {
|
|
643
668
|
await this.ensureInitialized();
|
|
669
|
+
await this.ensureAmmOperationAllowed("allow_route_swaps");
|
|
670
|
+
// Validate min-amount policy for route: check initial input and final output asset
|
|
671
|
+
const finalOutputAsset = params.hops[params.hops.length - 1]?.assetOutAddress;
|
|
672
|
+
if (!finalOutputAsset) {
|
|
673
|
+
throw new Error("Route swap requires at least one hop with output asset");
|
|
674
|
+
}
|
|
675
|
+
await this.assertSwapMeetsMinAmounts({
|
|
676
|
+
assetInAddress: params.initialAssetAddress,
|
|
677
|
+
assetOutAddress: finalOutputAsset,
|
|
678
|
+
amountIn: params.inputAmount,
|
|
679
|
+
minAmountOut: params.minAmountOut,
|
|
680
|
+
});
|
|
644
681
|
// Validate hops array
|
|
645
682
|
if (params.hops.length > 4) {
|
|
646
683
|
throw new Error("Route swap cannot have more than 4 hops");
|
|
@@ -710,7 +747,7 @@ class FlashnetClient {
|
|
|
710
747
|
maxRouteSlippageBps: params.maxRouteSlippageBps.toString(),
|
|
711
748
|
minAmountOut: params.minAmountOut,
|
|
712
749
|
nonce,
|
|
713
|
-
signature:
|
|
750
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
714
751
|
integratorFeeRateBps: params.integratorFeeRateBps?.toString() || "0",
|
|
715
752
|
integratorPublicKey: params.integratorPublicKey || "",
|
|
716
753
|
};
|
|
@@ -731,6 +768,7 @@ class FlashnetClient {
|
|
|
731
768
|
*/
|
|
732
769
|
async simulateAddLiquidity(params) {
|
|
733
770
|
await this.ensureInitialized();
|
|
771
|
+
await this.ensurePingOk();
|
|
734
772
|
return this.typedApi.simulateAddLiquidity(params);
|
|
735
773
|
}
|
|
736
774
|
/**
|
|
@@ -738,8 +776,15 @@ class FlashnetClient {
|
|
|
738
776
|
*/
|
|
739
777
|
async addLiquidity(params) {
|
|
740
778
|
await this.ensureInitialized();
|
|
779
|
+
await this.ensureAmmOperationAllowed("allow_add_liquidity");
|
|
741
780
|
// Get pool details to know which assets we're dealing with
|
|
742
781
|
const pool = await this.getPool(params.poolId);
|
|
782
|
+
// Enforce min-amount policy for inputs based on pool assets
|
|
783
|
+
await this.assertAddLiquidityMeetsMinAmounts({
|
|
784
|
+
poolId: params.poolId,
|
|
785
|
+
assetAAmount: params.assetAAmount,
|
|
786
|
+
assetBAmount: params.assetBAmount,
|
|
787
|
+
});
|
|
743
788
|
// Transfer assets to pool using new address encoding
|
|
744
789
|
const lpSparkAddress = sparkAddress.encodeSparkAddressNew({
|
|
745
790
|
identityPublicKey: params.poolId,
|
|
@@ -783,7 +828,7 @@ class FlashnetClient {
|
|
|
783
828
|
assetAMinAmountIn: params.assetAMinAmountIn.toString(),
|
|
784
829
|
assetBMinAmountIn: params.assetBMinAmountIn.toString(),
|
|
785
830
|
nonce,
|
|
786
|
-
signature:
|
|
831
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
787
832
|
};
|
|
788
833
|
const response = await this.typedApi.addLiquidity(request);
|
|
789
834
|
// Check if the liquidity addition was accepted
|
|
@@ -801,6 +846,7 @@ class FlashnetClient {
|
|
|
801
846
|
*/
|
|
802
847
|
async simulateRemoveLiquidity(params) {
|
|
803
848
|
await this.ensureInitialized();
|
|
849
|
+
await this.ensurePingOk();
|
|
804
850
|
return this.typedApi.simulateRemoveLiquidity(params);
|
|
805
851
|
}
|
|
806
852
|
/**
|
|
@@ -808,6 +854,7 @@ class FlashnetClient {
|
|
|
808
854
|
*/
|
|
809
855
|
async removeLiquidity(params) {
|
|
810
856
|
await this.ensureInitialized();
|
|
857
|
+
await this.ensureAmmOperationAllowed("allow_withdraw_liquidity");
|
|
811
858
|
// Check LP token balance
|
|
812
859
|
const position = await this.getLpPosition(params.poolId);
|
|
813
860
|
const lpTokensOwned = position.lpTokensOwned;
|
|
@@ -815,6 +862,11 @@ class FlashnetClient {
|
|
|
815
862
|
if (index$2.compareDecimalStrings(lpTokensOwned, tokensToRemove) < 0) {
|
|
816
863
|
throw new Error(`Insufficient LP tokens. Owned: ${lpTokensOwned}, Requested: ${tokensToRemove}`);
|
|
817
864
|
}
|
|
865
|
+
// Pre-simulate and enforce min-amount policy for outputs
|
|
866
|
+
await this.assertRemoveLiquidityMeetsMinAmounts({
|
|
867
|
+
poolId: params.poolId,
|
|
868
|
+
lpTokensToRemove: params.lpTokensToRemove,
|
|
869
|
+
});
|
|
818
870
|
// Generate remove liquidity intent
|
|
819
871
|
const nonce = index$2.generateNonce();
|
|
820
872
|
const intentMessage = intents.generateRemoveLiquidityIntentMessage({
|
|
@@ -831,7 +883,7 @@ class FlashnetClient {
|
|
|
831
883
|
poolId: params.poolId,
|
|
832
884
|
lpTokensToRemove: params.lpTokensToRemove,
|
|
833
885
|
nonce,
|
|
834
|
-
signature:
|
|
886
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
835
887
|
};
|
|
836
888
|
const response = await this.typedApi.removeLiquidity(request);
|
|
837
889
|
// Check if the liquidity removal was accepted
|
|
@@ -847,6 +899,7 @@ class FlashnetClient {
|
|
|
847
899
|
*/
|
|
848
900
|
async registerHost(params) {
|
|
849
901
|
await this.ensureInitialized();
|
|
902
|
+
await this.ensurePingOk();
|
|
850
903
|
const feeRecipient = params.feeRecipientPublicKey || this.publicKey;
|
|
851
904
|
const nonce = index$2.generateNonce();
|
|
852
905
|
// Generate intent
|
|
@@ -864,7 +917,7 @@ class FlashnetClient {
|
|
|
864
917
|
minFeeBps: params.minFeeBps,
|
|
865
918
|
feeRecipientPublicKey: feeRecipient,
|
|
866
919
|
nonce,
|
|
867
|
-
signature:
|
|
920
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
868
921
|
};
|
|
869
922
|
return this.typedApi.registerHost(request);
|
|
870
923
|
}
|
|
@@ -880,6 +933,7 @@ class FlashnetClient {
|
|
|
880
933
|
*/
|
|
881
934
|
async getPoolHostFees(hostNamespace, poolId) {
|
|
882
935
|
await this.ensureInitialized();
|
|
936
|
+
await this.ensurePingOk();
|
|
883
937
|
return this.typedApi.getPoolHostFees({ hostNamespace, poolId });
|
|
884
938
|
}
|
|
885
939
|
/**
|
|
@@ -894,6 +948,7 @@ class FlashnetClient {
|
|
|
894
948
|
*/
|
|
895
949
|
async withdrawHostFees(params) {
|
|
896
950
|
await this.ensureInitialized();
|
|
951
|
+
await this.ensureAmmOperationAllowed("allow_withdraw_fees");
|
|
897
952
|
const nonce = index$2.generateNonce();
|
|
898
953
|
const intentMessage = intents.generateWithdrawHostFeesIntentMessage({
|
|
899
954
|
hostPublicKey: this.publicKey,
|
|
@@ -908,7 +963,7 @@ class FlashnetClient {
|
|
|
908
963
|
lpIdentityPublicKey: params.lpIdentityPublicKey,
|
|
909
964
|
assetBAmount: params.assetBAmount,
|
|
910
965
|
nonce,
|
|
911
|
-
signature:
|
|
966
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
912
967
|
};
|
|
913
968
|
const response = await this.typedApi.withdrawHostFees(request);
|
|
914
969
|
// Check if the withdrawal was accepted
|
|
@@ -923,6 +978,7 @@ class FlashnetClient {
|
|
|
923
978
|
*/
|
|
924
979
|
async getHostFees(hostNamespace) {
|
|
925
980
|
await this.ensureInitialized();
|
|
981
|
+
await this.ensurePingOk();
|
|
926
982
|
const request = {
|
|
927
983
|
hostNamespace,
|
|
928
984
|
};
|
|
@@ -940,6 +996,7 @@ class FlashnetClient {
|
|
|
940
996
|
*/
|
|
941
997
|
async getPoolIntegratorFees(poolId) {
|
|
942
998
|
await this.ensureInitialized();
|
|
999
|
+
await this.ensurePingOk();
|
|
943
1000
|
return this.typedApi.getPoolIntegratorFees({ poolId });
|
|
944
1001
|
}
|
|
945
1002
|
/**
|
|
@@ -947,6 +1004,7 @@ class FlashnetClient {
|
|
|
947
1004
|
*/
|
|
948
1005
|
async withdrawIntegratorFees(params) {
|
|
949
1006
|
await this.ensureInitialized();
|
|
1007
|
+
await this.ensureAmmOperationAllowed("allow_withdraw_fees");
|
|
950
1008
|
const nonce = index$2.generateNonce();
|
|
951
1009
|
const intentMessage = intents.generateWithdrawIntegratorFeesIntentMessage({
|
|
952
1010
|
integratorPublicKey: this.publicKey,
|
|
@@ -962,7 +1020,7 @@ class FlashnetClient {
|
|
|
962
1020
|
lpIdentityPublicKey: params.lpIdentityPublicKey,
|
|
963
1021
|
assetBAmount: params.assetBAmount,
|
|
964
1022
|
nonce,
|
|
965
|
-
signature:
|
|
1023
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
966
1024
|
};
|
|
967
1025
|
const response = await this.typedApi.withdrawIntegratorFees(request);
|
|
968
1026
|
// Check if the withdrawal was accepted
|
|
@@ -988,6 +1046,7 @@ class FlashnetClient {
|
|
|
988
1046
|
*/
|
|
989
1047
|
async createEscrow(params) {
|
|
990
1048
|
await this.ensureInitialized();
|
|
1049
|
+
await this.ensurePingOk();
|
|
991
1050
|
const nonce = index$2.generateNonce();
|
|
992
1051
|
// The intent message requires a different structure for recipients and conditions
|
|
993
1052
|
const intentRecipients = params.recipients.map((r) => ({
|
|
@@ -1016,7 +1075,7 @@ class FlashnetClient {
|
|
|
1016
1075
|
abandonHost: params.abandonHost,
|
|
1017
1076
|
abandonConditions: params.abandonConditions,
|
|
1018
1077
|
nonce,
|
|
1019
|
-
signature:
|
|
1078
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
1020
1079
|
};
|
|
1021
1080
|
const createResponse = await this.typedApi.createEscrow(request);
|
|
1022
1081
|
const autoFund = params.autoFund !== false;
|
|
@@ -1039,6 +1098,7 @@ class FlashnetClient {
|
|
|
1039
1098
|
*/
|
|
1040
1099
|
async fundEscrow(params) {
|
|
1041
1100
|
await this.ensureInitialized();
|
|
1101
|
+
await this.ensurePingOk();
|
|
1042
1102
|
// 1. Balance check
|
|
1043
1103
|
await this.checkBalance({
|
|
1044
1104
|
balancesToCheck: [
|
|
@@ -1063,6 +1123,7 @@ class FlashnetClient {
|
|
|
1063
1123
|
});
|
|
1064
1124
|
}
|
|
1065
1125
|
async executeFundEscrowIntent(params) {
|
|
1126
|
+
await this.ensurePingOk();
|
|
1066
1127
|
// Generate intent
|
|
1067
1128
|
const nonce = index$2.generateNonce();
|
|
1068
1129
|
const intentMessage = intents.generateFundEscrowIntentMessage({
|
|
@@ -1077,7 +1138,7 @@ class FlashnetClient {
|
|
|
1077
1138
|
const request = {
|
|
1078
1139
|
...params,
|
|
1079
1140
|
nonce,
|
|
1080
|
-
signature:
|
|
1141
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
1081
1142
|
};
|
|
1082
1143
|
return this.typedApi.fundEscrow(request);
|
|
1083
1144
|
}
|
|
@@ -1089,6 +1150,7 @@ class FlashnetClient {
|
|
|
1089
1150
|
*/
|
|
1090
1151
|
async claimEscrow(params) {
|
|
1091
1152
|
await this.ensureInitialized();
|
|
1153
|
+
await this.ensurePingOk();
|
|
1092
1154
|
const nonce = index$2.generateNonce();
|
|
1093
1155
|
const intentMessage = intents.generateClaimEscrowIntentMessage({
|
|
1094
1156
|
escrowId: params.escrowId,
|
|
@@ -1100,7 +1162,7 @@ class FlashnetClient {
|
|
|
1100
1162
|
const request = {
|
|
1101
1163
|
escrowId: params.escrowId,
|
|
1102
1164
|
nonce,
|
|
1103
|
-
signature:
|
|
1165
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
1104
1166
|
};
|
|
1105
1167
|
return this.typedApi.claimEscrow(request);
|
|
1106
1168
|
}
|
|
@@ -1143,6 +1205,7 @@ class FlashnetClient {
|
|
|
1143
1205
|
*/
|
|
1144
1206
|
async clawback(params) {
|
|
1145
1207
|
await this.ensureInitialized();
|
|
1208
|
+
await this.ensurePingOk();
|
|
1146
1209
|
const nonce = index$2.generateNonce();
|
|
1147
1210
|
const intentMessage = intents.generateClawbackIntentMessage({
|
|
1148
1211
|
senderPublicKey: this.publicKey,
|
|
@@ -1157,7 +1220,7 @@ class FlashnetClient {
|
|
|
1157
1220
|
sparkTransferId: params.sparkTransferId,
|
|
1158
1221
|
lpIdentityPublicKey: params.lpIdentityPublicKey,
|
|
1159
1222
|
nonce,
|
|
1160
|
-
signature:
|
|
1223
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
1161
1224
|
};
|
|
1162
1225
|
const response = await this.typedApi.clawback(request);
|
|
1163
1226
|
if (!response.accepted) {
|
|
@@ -1252,6 +1315,13 @@ class FlashnetClient {
|
|
|
1252
1315
|
* Helper method to add initial liquidity after pool creation
|
|
1253
1316
|
*/
|
|
1254
1317
|
async addInitialLiquidity(poolId, assetAAddress, assetBAddress, assetAAmount, assetBAmount, assetAMinAmountIn, assetBMinAmountIn) {
|
|
1318
|
+
// Enforce gating and min-amount policy for initial liquidity
|
|
1319
|
+
await this.ensureAmmOperationAllowed("allow_add_liquidity");
|
|
1320
|
+
await this.assertAddLiquidityMeetsMinAmounts({
|
|
1321
|
+
poolId,
|
|
1322
|
+
assetAAmount,
|
|
1323
|
+
assetBAmount,
|
|
1324
|
+
});
|
|
1255
1325
|
const lpSparkAddress = sparkAddress.encodeSparkAddressNew({
|
|
1256
1326
|
identityPublicKey: poolId,
|
|
1257
1327
|
network: this.sparkNetwork,
|
|
@@ -1293,7 +1363,7 @@ class FlashnetClient {
|
|
|
1293
1363
|
assetAMinAmountIn: assetAMinAmountIn.toString(),
|
|
1294
1364
|
assetBMinAmountIn: assetBMinAmountIn.toString(),
|
|
1295
1365
|
nonce,
|
|
1296
|
-
signature:
|
|
1366
|
+
signature: hex.getHexFromUint8Array(signature),
|
|
1297
1367
|
};
|
|
1298
1368
|
const response = await this.typedApi.addLiquidity(request);
|
|
1299
1369
|
// Check if the initial liquidity addition was accepted
|
|
@@ -1308,6 +1378,186 @@ class FlashnetClient {
|
|
|
1308
1378
|
async cleanup() {
|
|
1309
1379
|
await this._wallet.cleanupConnections();
|
|
1310
1380
|
}
|
|
1381
|
+
// ===== Config and Policy Enforcement Helpers =====
|
|
1382
|
+
async ensureAmmOperationAllowed(requiredFeature) {
|
|
1383
|
+
await this.ensurePingOk();
|
|
1384
|
+
const featureMap = await this.getFeatureStatusMap();
|
|
1385
|
+
if (featureMap.get("master_kill_switch")) {
|
|
1386
|
+
throw new Error("Service is temporarily disabled by master kill switch");
|
|
1387
|
+
}
|
|
1388
|
+
if (!featureMap.get(requiredFeature)) {
|
|
1389
|
+
throw new Error(`Operation not allowed: feature '${requiredFeature}' is disabled`);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
async ensurePingOk() {
|
|
1393
|
+
const now = Date.now();
|
|
1394
|
+
if (this.pingCache && this.pingCache.expiryMs > now) {
|
|
1395
|
+
if (!this.pingCache.ok) {
|
|
1396
|
+
throw new Error("Settlement service unavailable. Only read (GET) operations are allowed right now.");
|
|
1397
|
+
}
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
const ping = await this.typedApi.ping();
|
|
1401
|
+
const ok = !!ping &&
|
|
1402
|
+
typeof ping.status === "string" &&
|
|
1403
|
+
ping.status.toLowerCase() === "ok";
|
|
1404
|
+
this.pingCache = { ok, expiryMs: now + FlashnetClient.PING_TTL_MS };
|
|
1405
|
+
if (!ok) {
|
|
1406
|
+
throw new Error("Settlement service unavailable. Only read (GET) operations are allowed right now.");
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
async getFeatureStatusMap() {
|
|
1410
|
+
const now = Date.now();
|
|
1411
|
+
if (this.featureStatusCache && this.featureStatusCache.expiryMs > now) {
|
|
1412
|
+
const map = new Map();
|
|
1413
|
+
for (const item of this.featureStatusCache.data) {
|
|
1414
|
+
map.set(item.feature_name, Boolean(item.enabled));
|
|
1415
|
+
}
|
|
1416
|
+
return map;
|
|
1417
|
+
}
|
|
1418
|
+
const data = await this.typedApi.getFeatureStatus();
|
|
1419
|
+
this.featureStatusCache = {
|
|
1420
|
+
data,
|
|
1421
|
+
expiryMs: now + FlashnetClient.FEATURE_STATUS_TTL_MS,
|
|
1422
|
+
};
|
|
1423
|
+
const map = new Map();
|
|
1424
|
+
for (const item of data) {
|
|
1425
|
+
map.set(item.feature_name, Boolean(item.enabled));
|
|
1426
|
+
}
|
|
1427
|
+
return map;
|
|
1428
|
+
}
|
|
1429
|
+
async getEnabledMinAmountsMap() {
|
|
1430
|
+
const now = Date.now();
|
|
1431
|
+
if (this.minAmountsCache && this.minAmountsCache.expiryMs > now) {
|
|
1432
|
+
return this.minAmountsCache.map;
|
|
1433
|
+
}
|
|
1434
|
+
const config = await this.typedApi.getMinAmounts();
|
|
1435
|
+
const map = new Map();
|
|
1436
|
+
for (const item of config) {
|
|
1437
|
+
if (item.enabled) {
|
|
1438
|
+
const key = item.asset_identifier.toLowerCase();
|
|
1439
|
+
const value = BigInt(String(item.min_amount));
|
|
1440
|
+
map.set(key, value);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
this.minAmountsCache = {
|
|
1444
|
+
map,
|
|
1445
|
+
expiryMs: now + FlashnetClient.MIN_AMOUNTS_TTL_MS,
|
|
1446
|
+
};
|
|
1447
|
+
return map;
|
|
1448
|
+
}
|
|
1449
|
+
getHexAddress(addr) {
|
|
1450
|
+
return this.toHexTokenIdentifier(addr).toLowerCase();
|
|
1451
|
+
}
|
|
1452
|
+
async assertSwapMeetsMinAmounts(params) {
|
|
1453
|
+
const minMap = await this.getEnabledMinAmountsMap();
|
|
1454
|
+
if (minMap.size === 0) {
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
const inHex = this.getHexAddress(params.assetInAddress);
|
|
1458
|
+
const outHex = this.getHexAddress(params.assetOutAddress);
|
|
1459
|
+
const minIn = minMap.get(inHex);
|
|
1460
|
+
const minOut = minMap.get(outHex);
|
|
1461
|
+
const amountIn = BigInt(String(params.amountIn));
|
|
1462
|
+
const minAmountOut = BigInt(String(params.minAmountOut));
|
|
1463
|
+
if (minIn && minOut) {
|
|
1464
|
+
if (amountIn < minIn) {
|
|
1465
|
+
throw new Error(`Minimum amount not met for input asset. Required \
|
|
1466
|
+
${minIn.toString()}, provided ${amountIn.toString()}`);
|
|
1467
|
+
}
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (minIn) {
|
|
1471
|
+
if (amountIn < minIn) {
|
|
1472
|
+
throw new Error(`Minimum amount not met for input asset. Required \
|
|
1473
|
+
${minIn.toString()}, provided ${amountIn.toString()}`);
|
|
1474
|
+
}
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
if (minOut) {
|
|
1478
|
+
const relaxed = minOut / 2n; // 50% relaxation for slippage
|
|
1479
|
+
if (minAmountOut < relaxed) {
|
|
1480
|
+
throw new Error(`Minimum amount not met for output asset. Required at least \
|
|
1481
|
+
${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toString()}`);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
async assertAddLiquidityMeetsMinAmounts(params) {
|
|
1486
|
+
const minMap = await this.getEnabledMinAmountsMap();
|
|
1487
|
+
if (minMap.size === 0) {
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
const pool = await this.getPool(params.poolId);
|
|
1491
|
+
const aHex = pool.assetAAddress.toLowerCase();
|
|
1492
|
+
const bHex = pool.assetBAddress.toLowerCase();
|
|
1493
|
+
const aMin = minMap.get(aHex);
|
|
1494
|
+
const bMin = minMap.get(bHex);
|
|
1495
|
+
if (aMin) {
|
|
1496
|
+
const aAmt = BigInt(String(params.assetAAmount));
|
|
1497
|
+
if (aAmt < aMin) {
|
|
1498
|
+
throw new Error(`Minimum amount not met for Asset A. Required ${aMin.toString()}, provided ${aAmt.toString()}`);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
if (bMin) {
|
|
1502
|
+
const bAmt = BigInt(String(params.assetBAmount));
|
|
1503
|
+
if (bAmt < bMin) {
|
|
1504
|
+
throw new Error(`Minimum amount not met for Asset B. Required ${bMin.toString()}, provided ${bAmt.toString()}`);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
async assertRemoveLiquidityMeetsMinAmounts(params) {
|
|
1509
|
+
const minMap = await this.getEnabledMinAmountsMap();
|
|
1510
|
+
if (minMap.size === 0) {
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
const simulation = await this.simulateRemoveLiquidity({
|
|
1514
|
+
poolId: params.poolId,
|
|
1515
|
+
providerPublicKey: this.publicKey,
|
|
1516
|
+
lpTokensToRemove: String(params.lpTokensToRemove),
|
|
1517
|
+
});
|
|
1518
|
+
const pool = await this.getPool(params.poolId);
|
|
1519
|
+
const aHex = pool.assetAAddress.toLowerCase();
|
|
1520
|
+
const bHex = pool.assetBAddress.toLowerCase();
|
|
1521
|
+
const aMin = minMap.get(aHex);
|
|
1522
|
+
const bMin = minMap.get(bHex);
|
|
1523
|
+
if (aMin) {
|
|
1524
|
+
const predictedAOut = BigInt(String(simulation.assetAAmount));
|
|
1525
|
+
const relaxedA = aMin / 2n; // apply 50% relaxation for outputs
|
|
1526
|
+
if (predictedAOut < relaxedA) {
|
|
1527
|
+
throw new Error(`Minimum amount not met for Asset A on withdrawal. Required at least ${relaxedA.toString()} (50% relaxed), predicted ${predictedAOut.toString()}`);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
if (bMin) {
|
|
1531
|
+
const predictedBOut = BigInt(String(simulation.assetBAmount));
|
|
1532
|
+
const relaxedB = bMin / 2n;
|
|
1533
|
+
if (predictedBOut < relaxedB) {
|
|
1534
|
+
throw new Error(`Minimum amount not met for Asset B on withdrawal. Required at least ${relaxedB.toString()} (50% relaxed), predicted ${predictedBOut.toString()}`);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
async assertAllowedAssetBForPoolCreation(assetBHex) {
|
|
1539
|
+
const now = Date.now();
|
|
1540
|
+
let allowed;
|
|
1541
|
+
if (this.allowedAssetsCache && this.allowedAssetsCache.expiryMs > now) {
|
|
1542
|
+
allowed = this.allowedAssetsCache.data;
|
|
1543
|
+
}
|
|
1544
|
+
else {
|
|
1545
|
+
allowed = await this.typedApi.getAllowedAssets();
|
|
1546
|
+
this.allowedAssetsCache = {
|
|
1547
|
+
data: allowed,
|
|
1548
|
+
expiryMs: now + FlashnetClient.ALLOWED_ASSETS_TTL_MS,
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
if (!allowed || allowed.length === 0) {
|
|
1552
|
+
// Wildcard allowance
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
const isAllowed = allowed.some((it) => it.enabled &&
|
|
1556
|
+
it.asset_identifier.toLowerCase() === assetBHex.toLowerCase());
|
|
1557
|
+
if (!isAllowed) {
|
|
1558
|
+
throw new Error(`Asset B is not allowed for pool creation: ${assetBHex}`);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1311
1561
|
}
|
|
1312
1562
|
|
|
1313
1563
|
exports.FlashnetClient = FlashnetClient;
|