@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.
Files changed (56) hide show
  1. package/dist/cjs/index.d.ts +2 -2
  2. package/dist/cjs/index.d.ts.map +1 -1
  3. package/dist/cjs/src/api/typed-endpoints.d.ts +15 -0
  4. package/dist/cjs/src/api/typed-endpoints.d.ts.map +1 -1
  5. package/dist/cjs/src/api/typed-endpoints.js +22 -0
  6. package/dist/cjs/src/api/typed-endpoints.js.map +1 -1
  7. package/dist/cjs/src/client/FlashnetClient.d.ts +18 -0
  8. package/dist/cjs/src/client/FlashnetClient.d.ts.map +1 -1
  9. package/dist/cjs/src/client/FlashnetClient.js +338 -88
  10. package/dist/cjs/src/client/FlashnetClient.js.map +1 -1
  11. package/dist/cjs/src/config/index.js +2 -2
  12. package/dist/cjs/src/config/index.js.map +1 -1
  13. package/dist/cjs/src/types/index.d.ts +19 -0
  14. package/dist/cjs/src/types/index.d.ts.map +1 -1
  15. package/dist/cjs/src/types/index.js.map +1 -1
  16. package/dist/cjs/src/utils/auth.d.ts.map +1 -1
  17. package/dist/cjs/src/utils/auth.js +5 -3
  18. package/dist/cjs/src/utils/auth.js.map +1 -1
  19. package/dist/cjs/src/utils/hex.d.ts +3 -0
  20. package/dist/cjs/src/utils/hex.d.ts.map +1 -0
  21. package/dist/cjs/src/utils/hex.js +17 -0
  22. package/dist/cjs/src/utils/hex.js.map +1 -0
  23. package/dist/cjs/src/utils/index.js +2 -2
  24. package/dist/cjs/src/utils/spark-address.js +16 -16
  25. package/dist/cjs/src/utils/spark-address.js.map +1 -1
  26. package/dist/cjs/src/utils/tokenAddress.d.ts.map +1 -1
  27. package/dist/cjs/src/utils/tokenAddress.js +8 -8
  28. package/dist/cjs/src/utils/tokenAddress.js.map +1 -1
  29. package/dist/esm/index.d.ts +2 -2
  30. package/dist/esm/index.d.ts.map +1 -1
  31. package/dist/esm/src/api/typed-endpoints.d.ts +15 -0
  32. package/dist/esm/src/api/typed-endpoints.d.ts.map +1 -1
  33. package/dist/esm/src/api/typed-endpoints.js +22 -0
  34. package/dist/esm/src/api/typed-endpoints.js.map +1 -1
  35. package/dist/esm/src/client/FlashnetClient.d.ts +18 -0
  36. package/dist/esm/src/client/FlashnetClient.d.ts.map +1 -1
  37. package/dist/esm/src/client/FlashnetClient.js +338 -88
  38. package/dist/esm/src/client/FlashnetClient.js.map +1 -1
  39. package/dist/esm/src/config/index.js +2 -2
  40. package/dist/esm/src/config/index.js.map +1 -1
  41. package/dist/esm/src/types/index.d.ts +19 -0
  42. package/dist/esm/src/types/index.d.ts.map +1 -1
  43. package/dist/esm/src/types/index.js.map +1 -1
  44. package/dist/esm/src/utils/auth.d.ts.map +1 -1
  45. package/dist/esm/src/utils/auth.js +5 -3
  46. package/dist/esm/src/utils/auth.js.map +1 -1
  47. package/dist/esm/src/utils/hex.d.ts +3 -0
  48. package/dist/esm/src/utils/hex.d.ts.map +1 -0
  49. package/dist/esm/src/utils/hex.js +14 -0
  50. package/dist/esm/src/utils/hex.js.map +1 -0
  51. package/dist/esm/src/utils/index.js +2 -2
  52. package/dist/esm/src/utils/spark-address.js +8 -8
  53. package/dist/esm/src/utils/spark-address.js.map +1 -1
  54. package/dist/esm/src/utils/tokenAddress.d.ts.map +1 -1
  55. package/dist/esm/src/utils/tokenAddress.js +2 -2
  56. 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 = Buffer.from(info.rawTokenIdentifier).toString("hex");
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: Buffer.from(signature).toString("hex"),
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
- params.graduationThresholdPct <= 0) {
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
- // Check feasibility: f - g*(1-f) > 0 where f is graduationThresholdPct/100 and g is 1
424
- const MIN_GRADUATION_THRESHOLD_PCT = 50n;
425
- const MAX_GRADUATION_THRESHOLD_PCT = 95n;
426
- if (graduationThresholdPct <= MIN_GRADUATION_THRESHOLD_PCT ||
427
- graduationThresholdPct > MAX_GRADUATION_THRESHOLD_PCT) {
428
- throw new Error(`Graduation threshold percentage must be greater than ${MIN_GRADUATION_THRESHOLD_PCT} ` +
429
- `and not greater than ${MAX_GRADUATION_THRESHOLD_PCT}`);
430
- }
431
- // f is graduationThresholdPct / 100n
432
- // denom is (2n * graduationThresholdPct / 100n) - 1n
433
- // (2n * graduationThresholdPct - 100n) / 100n
434
- // 1/denom is 100n / (2n * graduationThresholdPct - 100n)
435
- // Calculate virtual reserves and round down to integers
436
- // virtualA is (supply * f * f) / denom
437
- const virtualANumerator = BigInt(supply) * graduationThresholdPct * graduationThresholdPct;
438
- const virtualADenominator = 200n * graduationThresholdPct - 10000n;
439
- const virtualARemainder = virtualANumerator % virtualADenominator;
440
- const virtualA = (virtualANumerator - virtualARemainder) / virtualADenominator;
441
- // virtualB is (targetB * g * (1 - f)) / denom
442
- const virtualBNumerator = BigInt(targetB) * (100n - graduationThresholdPct);
443
- const virtualBDenominator = 2n * graduationThresholdPct - 100n;
444
- const virtualBRemainder = virtualBNumerator % virtualBDenominator;
445
- const virtualB = (virtualBNumerator - virtualBRemainder) / virtualBDenominator;
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
- // Clip decimals off reserves before any operations
466
- const clippedAssetAInitialReserve = Math.floor(Number(params.assetAInitialReserve)).toString();
467
- const clippedVirtualReserveA = Math.floor(Number(params.virtualReserveA)).toString();
468
- const clippedVirtualReserveB = Math.floor(Number(params.virtualReserveB)).toString();
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: clippedAssetAInitialReserve,
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: clippedAssetAInitialReserve,
486
- virtualReserveA: clippedVirtualReserveA,
487
- virtualReserveB: clippedVirtualReserveB,
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: clippedAssetAInitialReserve,
501
- virtualReserveA: clippedVirtualReserveA,
502
- virtualReserveB: clippedVirtualReserveB,
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: Buffer.from(signature).toString("hex"),
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: clippedAssetAInitialReserve,
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: Buffer.from(signature).toString("hex"),
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: Buffer.from(signature).toString("hex"),
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: Buffer.from(signature).toString("hex"),
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: Buffer.from(signature).toString("hex"),
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: Buffer.from(signature).toString("hex"),
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: Buffer.from(signature).toString("hex"),
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: Buffer.from(signature).toString("hex"),
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: Buffer.from(signature).toString("hex"),
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: Buffer.from(signature).toString("hex"),
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: Buffer.from(signature).toString("hex"),
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: Buffer.from(signature).toString("hex"),
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: Buffer.from(signature).toString("hex"),
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: Buffer.from(signature).toString("hex"),
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;