@flashnet/sdk 0.3.23 → 0.3.25

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 (52) hide show
  1. package/README.md +2 -1
  2. package/dist/cjs/index.d.ts +2 -2
  3. package/dist/cjs/index.d.ts.map +1 -1
  4. package/dist/cjs/src/api/typed-endpoints.d.ts +15 -0
  5. package/dist/cjs/src/api/typed-endpoints.d.ts.map +1 -1
  6. package/dist/cjs/src/api/typed-endpoints.js +22 -0
  7. package/dist/cjs/src/api/typed-endpoints.js.map +1 -1
  8. package/dist/cjs/src/client/FlashnetClient.d.ts +18 -0
  9. package/dist/cjs/src/client/FlashnetClient.d.ts.map +1 -1
  10. package/dist/cjs/src/client/FlashnetClient.js +329 -65
  11. package/dist/cjs/src/client/FlashnetClient.js.map +1 -1
  12. package/dist/cjs/src/config/index.js +2 -2
  13. package/dist/cjs/src/config/index.js.map +1 -1
  14. package/dist/cjs/src/types/index.d.ts +19 -0
  15. package/dist/cjs/src/types/index.d.ts.map +1 -1
  16. package/dist/cjs/src/types/index.js.map +1 -1
  17. package/dist/cjs/src/utils/auth.d.ts.map +1 -1
  18. package/dist/cjs/src/utils/auth.js +5 -3
  19. package/dist/cjs/src/utils/auth.js.map +1 -1
  20. package/dist/cjs/src/utils/hex.d.ts +3 -0
  21. package/dist/cjs/src/utils/hex.d.ts.map +1 -0
  22. package/dist/cjs/src/utils/hex.js +17 -0
  23. package/dist/cjs/src/utils/hex.js.map +1 -0
  24. package/dist/cjs/src/utils/index.js +1 -1
  25. package/dist/cjs/src/utils/tokenAddress.d.ts.map +1 -1
  26. package/dist/cjs/src/utils/tokenAddress.js +1 -1
  27. package/dist/esm/index.d.ts +2 -2
  28. package/dist/esm/index.d.ts.map +1 -1
  29. package/dist/esm/src/api/typed-endpoints.d.ts +15 -0
  30. package/dist/esm/src/api/typed-endpoints.d.ts.map +1 -1
  31. package/dist/esm/src/api/typed-endpoints.js +22 -0
  32. package/dist/esm/src/api/typed-endpoints.js.map +1 -1
  33. package/dist/esm/src/client/FlashnetClient.d.ts +18 -0
  34. package/dist/esm/src/client/FlashnetClient.d.ts.map +1 -1
  35. package/dist/esm/src/client/FlashnetClient.js +329 -65
  36. package/dist/esm/src/client/FlashnetClient.js.map +1 -1
  37. package/dist/esm/src/config/index.js +2 -2
  38. package/dist/esm/src/config/index.js.map +1 -1
  39. package/dist/esm/src/types/index.d.ts +19 -0
  40. package/dist/esm/src/types/index.d.ts.map +1 -1
  41. package/dist/esm/src/types/index.js.map +1 -1
  42. package/dist/esm/src/utils/auth.d.ts.map +1 -1
  43. package/dist/esm/src/utils/auth.js +5 -3
  44. package/dist/esm/src/utils/auth.js.map +1 -1
  45. package/dist/esm/src/utils/hex.d.ts +3 -0
  46. package/dist/esm/src/utils/hex.d.ts.map +1 -0
  47. package/dist/esm/src/utils/hex.js +14 -0
  48. package/dist/esm/src/utils/hex.js.map +1 -0
  49. package/dist/esm/src/utils/index.js +1 -1
  50. package/dist/esm/src/utils/tokenAddress.d.ts.map +1 -1
  51. package/dist/esm/src/utils/tokenAddress.js +1 -1
  52. package/package.json +1 -1
@@ -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,38 +425,13 @@ 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
429
  !Number.isInteger(params.graduationThresholdPct) ||
417
430
  params.graduationThresholdPct <= 0) {
418
431
  throw new Error("Graduation threshold percentage must be a positive integer");
419
432
  }
420
- const supply = parsePositiveIntegerToBigInt(params.initialTokenSupply, "Initial token supply");
421
- const targetB = parsePositiveIntegerToBigInt(params.targetRaise, "Target raise");
433
+ const supply = FlashnetClient.parsePositiveIntegerToBigInt(params.initialTokenSupply, "Initial token supply");
434
+ const targetB = FlashnetClient.parsePositiveIntegerToBigInt(params.targetRaise, "Target raise");
422
435
  const graduationThresholdPct = BigInt(params.graduationThresholdPct);
423
436
  // Check feasibility: f - g*(1-f) > 0 where f is graduationThresholdPct/100 and g is 1
424
437
  const MIN_GRADUATION_THRESHOLD_PCT = 50n;
@@ -433,16 +446,22 @@ class FlashnetClient {
433
446
  // (2n * graduationThresholdPct - 100n) / 100n
434
447
  // 1/denom is 100n / (2n * graduationThresholdPct - 100n)
435
448
  // 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;
449
+ // totalA is (supply * f * f) / denom
450
+ const totalANumerator = BigInt(supply) * graduationThresholdPct * graduationThresholdPct;
451
+ const totalADenominator = 200n * graduationThresholdPct - 10000n;
452
+ const totalARemainder = totalANumerator % totalADenominator;
453
+ const totalA = (totalANumerator - totalARemainder) / totalADenominator;
454
+ // Since there is an initial supply of tokens, we subtract that amount
455
+ // to get the remaining "virtual" amount for pricing
456
+ const virtualA = totalA - supply;
457
+ // totalB is (targetB * g * (1 - f)) / denom
458
+ const totalBNumerator = BigInt(targetB) * (100n - graduationThresholdPct);
459
+ const totalBDenominator = 2n * graduationThresholdPct - 100n;
460
+ const totalBRemainder = totalBNumerator % totalBDenominator;
461
+ const totalB = (totalBNumerator - totalBRemainder) / totalBDenominator;
462
+ // Bonding curve starts with no deposited amount of asset B,
463
+ // so virtualB is all of totalB
464
+ const virtualB = totalB;
446
465
  // Calculate threshold as amount of asset A (not percentage)
447
466
  const threshold = (BigInt(supply) * graduationThresholdPct) / 100n;
448
467
  return {
@@ -459,18 +478,20 @@ class FlashnetClient {
459
478
  */
460
479
  async createSingleSidedPool(params) {
461
480
  await this.ensureInitialized();
481
+ await this.ensureAmmOperationAllowed("allow_pool_creation");
482
+ await this.assertAllowedAssetBForPoolCreation(this.toHexTokenIdentifier(params.assetBAddress));
462
483
  if (!params.hostNamespace && params.totalHostFeeRateBps < 10) {
463
484
  throw new Error(`Host fee must be greater than 10 bps when no host namespace is provided`);
464
485
  }
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();
486
+ // Validate reserves are valid positive integers before any operations
487
+ const assetAInitialReserve = FlashnetClient.parsePositiveIntegerToBigInt(params.assetAInitialReserve, "Asset A Initial Reserve").toString();
488
+ const virtualReserveA = FlashnetClient.parsePositiveIntegerToBigInt(params.virtualReserveA, "Virtual Reserve A").toString();
489
+ const virtualReserveB = FlashnetClient.parsePositiveIntegerToBigInt(params.virtualReserveB, "Virtual Reserve B").toString();
469
490
  await this.checkBalance({
470
491
  balancesToCheck: [
471
492
  {
472
493
  assetAddress: params.assetAAddress,
473
- amount: clippedAssetAInitialReserve,
494
+ amount: assetAInitialReserve,
474
495
  },
475
496
  ],
476
497
  errorPrefix: "Insufficient balance for pool creation: ",
@@ -482,9 +503,9 @@ class FlashnetClient {
482
503
  poolOwnerPublicKey,
483
504
  assetAAddress: this.toHexTokenIdentifier(params.assetAAddress),
484
505
  assetBAddress: this.toHexTokenIdentifier(params.assetBAddress),
485
- assetAInitialReserve: clippedAssetAInitialReserve,
486
- virtualReserveA: clippedVirtualReserveA,
487
- virtualReserveB: clippedVirtualReserveB,
506
+ assetAInitialReserve,
507
+ virtualReserveA,
508
+ virtualReserveB,
488
509
  threshold: params.threshold.toString(),
489
510
  lpFeeRateBps: params.lpFeeRateBps.toString(),
490
511
  totalHostFeeRateBps: params.totalHostFeeRateBps.toString(),
@@ -497,15 +518,15 @@ class FlashnetClient {
497
518
  poolOwnerPublicKey,
498
519
  assetAAddress: this.toHexTokenIdentifier(params.assetAAddress),
499
520
  assetBAddress: this.toHexTokenIdentifier(params.assetBAddress),
500
- assetAInitialReserve: clippedAssetAInitialReserve,
501
- virtualReserveA: clippedVirtualReserveA,
502
- virtualReserveB: clippedVirtualReserveB,
521
+ assetAInitialReserve,
522
+ virtualReserveA,
523
+ virtualReserveB,
503
524
  threshold: params.threshold.toString(),
504
525
  lpFeeRateBps: params.lpFeeRateBps.toString(),
505
526
  totalHostFeeRateBps: params.totalHostFeeRateBps.toString(),
506
527
  hostNamespace: params.hostNamespace,
507
528
  nonce,
508
- signature: Buffer.from(signature).toString("hex"),
529
+ signature: hex.getHexFromUint8Array(signature),
509
530
  };
510
531
  const createResponse = await this.typedApi.createSingleSidedPool(request);
511
532
  if (params.disableInitialDeposit) {
@@ -519,7 +540,7 @@ class FlashnetClient {
519
540
  const assetATransferId = await this.transferAsset({
520
541
  receiverSparkAddress: lpSparkAddress,
521
542
  assetAddress: params.assetAAddress,
522
- amount: clippedAssetAInitialReserve,
543
+ amount: assetAInitialReserve,
523
544
  });
524
545
  const confirmResponse = await this.confirmInitialDeposit(createResponse.poolId, assetATransferId, poolOwnerPublicKey);
525
546
  if (!confirmResponse.confirmed) {
@@ -548,7 +569,7 @@ class FlashnetClient {
548
569
  poolId,
549
570
  assetASparkTransferId,
550
571
  nonce,
551
- signature: Buffer.from(signature).toString("hex"),
572
+ signature: hex.getHexFromUint8Array(signature),
552
573
  poolOwnerPublicKey: poolOwnerPublicKey ?? this.publicKey,
553
574
  };
554
575
  return this.typedApi.confirmInitialDeposit(request);
@@ -559,6 +580,7 @@ class FlashnetClient {
559
580
  */
560
581
  async simulateSwap(params) {
561
582
  await this.ensureInitialized();
583
+ await this.ensurePingOk();
562
584
  return this.typedApi.simulateSwap(params);
563
585
  }
564
586
  /**
@@ -566,6 +588,14 @@ class FlashnetClient {
566
588
  */
567
589
  async executeSwap(params) {
568
590
  await this.ensureInitialized();
591
+ // Gate by feature flags and ping, and enforce min-amount policy before transfers
592
+ await this.ensureAmmOperationAllowed("allow_swaps");
593
+ await this.assertSwapMeetsMinAmounts({
594
+ assetInAddress: params.assetInAddress,
595
+ assetOutAddress: params.assetOutAddress,
596
+ amountIn: params.amountIn,
597
+ minAmountOut: params.minAmountOut,
598
+ });
569
599
  // Transfer assets to pool using new address encoding
570
600
  const lpSparkAddress = sparkAddress.encodeSparkAddressNew({
571
601
  identityPublicKey: params.poolId,
@@ -584,6 +614,14 @@ class FlashnetClient {
584
614
  }
585
615
  async executeSwapIntent(params) {
586
616
  await this.ensureInitialized();
617
+ // Also enforce gating and min amounts for direct intent usage
618
+ await this.ensureAmmOperationAllowed("allow_swaps");
619
+ await this.assertSwapMeetsMinAmounts({
620
+ assetInAddress: params.assetInAddress,
621
+ assetOutAddress: params.assetOutAddress,
622
+ amountIn: params.amountIn,
623
+ minAmountOut: params.minAmountOut,
624
+ });
587
625
  // Generate swap intent
588
626
  const nonce = index$2.generateNonce();
589
627
  const intentMessage = intents.generatePoolSwapIntentMessage({
@@ -613,7 +651,7 @@ class FlashnetClient {
613
651
  totalIntegratorFeeRateBps: params.integratorFeeRateBps?.toString() || "0",
614
652
  integratorPublicKey: params.integratorPublicKey || "",
615
653
  nonce,
616
- signature: Buffer.from(signature).toString("hex"),
654
+ signature: hex.getHexFromUint8Array(signature),
617
655
  };
618
656
  const response = await this.typedApi.executeSwap(request);
619
657
  // Check if the swap was accepted
@@ -634,6 +672,7 @@ class FlashnetClient {
634
672
  throw new Error("Route swap cannot have more than 4 hops");
635
673
  }
636
674
  await this.ensureInitialized();
675
+ await this.ensurePingOk();
637
676
  return this.typedApi.simulateRouteSwap(params);
638
677
  }
639
678
  /**
@@ -641,6 +680,18 @@ class FlashnetClient {
641
680
  */
642
681
  async executeRouteSwap(params) {
643
682
  await this.ensureInitialized();
683
+ await this.ensureAmmOperationAllowed("allow_route_swaps");
684
+ // Validate min-amount policy for route: check initial input and final output asset
685
+ const finalOutputAsset = params.hops[params.hops.length - 1]?.assetOutAddress;
686
+ if (!finalOutputAsset) {
687
+ throw new Error("Route swap requires at least one hop with output asset");
688
+ }
689
+ await this.assertSwapMeetsMinAmounts({
690
+ assetInAddress: params.initialAssetAddress,
691
+ assetOutAddress: finalOutputAsset,
692
+ amountIn: params.inputAmount,
693
+ minAmountOut: params.minAmountOut,
694
+ });
644
695
  // Validate hops array
645
696
  if (params.hops.length > 4) {
646
697
  throw new Error("Route swap cannot have more than 4 hops");
@@ -710,7 +761,7 @@ class FlashnetClient {
710
761
  maxRouteSlippageBps: params.maxRouteSlippageBps.toString(),
711
762
  minAmountOut: params.minAmountOut,
712
763
  nonce,
713
- signature: Buffer.from(signature).toString("hex"),
764
+ signature: hex.getHexFromUint8Array(signature),
714
765
  integratorFeeRateBps: params.integratorFeeRateBps?.toString() || "0",
715
766
  integratorPublicKey: params.integratorPublicKey || "",
716
767
  };
@@ -731,6 +782,7 @@ class FlashnetClient {
731
782
  */
732
783
  async simulateAddLiquidity(params) {
733
784
  await this.ensureInitialized();
785
+ await this.ensurePingOk();
734
786
  return this.typedApi.simulateAddLiquidity(params);
735
787
  }
736
788
  /**
@@ -738,8 +790,15 @@ class FlashnetClient {
738
790
  */
739
791
  async addLiquidity(params) {
740
792
  await this.ensureInitialized();
793
+ await this.ensureAmmOperationAllowed("allow_add_liquidity");
741
794
  // Get pool details to know which assets we're dealing with
742
795
  const pool = await this.getPool(params.poolId);
796
+ // Enforce min-amount policy for inputs based on pool assets
797
+ await this.assertAddLiquidityMeetsMinAmounts({
798
+ poolId: params.poolId,
799
+ assetAAmount: params.assetAAmount,
800
+ assetBAmount: params.assetBAmount,
801
+ });
743
802
  // Transfer assets to pool using new address encoding
744
803
  const lpSparkAddress = sparkAddress.encodeSparkAddressNew({
745
804
  identityPublicKey: params.poolId,
@@ -783,7 +842,7 @@ class FlashnetClient {
783
842
  assetAMinAmountIn: params.assetAMinAmountIn.toString(),
784
843
  assetBMinAmountIn: params.assetBMinAmountIn.toString(),
785
844
  nonce,
786
- signature: Buffer.from(signature).toString("hex"),
845
+ signature: hex.getHexFromUint8Array(signature),
787
846
  };
788
847
  const response = await this.typedApi.addLiquidity(request);
789
848
  // Check if the liquidity addition was accepted
@@ -801,6 +860,7 @@ class FlashnetClient {
801
860
  */
802
861
  async simulateRemoveLiquidity(params) {
803
862
  await this.ensureInitialized();
863
+ await this.ensurePingOk();
804
864
  return this.typedApi.simulateRemoveLiquidity(params);
805
865
  }
806
866
  /**
@@ -808,6 +868,7 @@ class FlashnetClient {
808
868
  */
809
869
  async removeLiquidity(params) {
810
870
  await this.ensureInitialized();
871
+ await this.ensureAmmOperationAllowed("allow_withdraw_liquidity");
811
872
  // Check LP token balance
812
873
  const position = await this.getLpPosition(params.poolId);
813
874
  const lpTokensOwned = position.lpTokensOwned;
@@ -815,6 +876,11 @@ class FlashnetClient {
815
876
  if (index$2.compareDecimalStrings(lpTokensOwned, tokensToRemove) < 0) {
816
877
  throw new Error(`Insufficient LP tokens. Owned: ${lpTokensOwned}, Requested: ${tokensToRemove}`);
817
878
  }
879
+ // Pre-simulate and enforce min-amount policy for outputs
880
+ await this.assertRemoveLiquidityMeetsMinAmounts({
881
+ poolId: params.poolId,
882
+ lpTokensToRemove: params.lpTokensToRemove,
883
+ });
818
884
  // Generate remove liquidity intent
819
885
  const nonce = index$2.generateNonce();
820
886
  const intentMessage = intents.generateRemoveLiquidityIntentMessage({
@@ -831,7 +897,7 @@ class FlashnetClient {
831
897
  poolId: params.poolId,
832
898
  lpTokensToRemove: params.lpTokensToRemove,
833
899
  nonce,
834
- signature: Buffer.from(signature).toString("hex"),
900
+ signature: hex.getHexFromUint8Array(signature),
835
901
  };
836
902
  const response = await this.typedApi.removeLiquidity(request);
837
903
  // Check if the liquidity removal was accepted
@@ -847,6 +913,7 @@ class FlashnetClient {
847
913
  */
848
914
  async registerHost(params) {
849
915
  await this.ensureInitialized();
916
+ await this.ensurePingOk();
850
917
  const feeRecipient = params.feeRecipientPublicKey || this.publicKey;
851
918
  const nonce = index$2.generateNonce();
852
919
  // Generate intent
@@ -864,7 +931,7 @@ class FlashnetClient {
864
931
  minFeeBps: params.minFeeBps,
865
932
  feeRecipientPublicKey: feeRecipient,
866
933
  nonce,
867
- signature: Buffer.from(signature).toString("hex"),
934
+ signature: hex.getHexFromUint8Array(signature),
868
935
  };
869
936
  return this.typedApi.registerHost(request);
870
937
  }
@@ -880,6 +947,7 @@ class FlashnetClient {
880
947
  */
881
948
  async getPoolHostFees(hostNamespace, poolId) {
882
949
  await this.ensureInitialized();
950
+ await this.ensurePingOk();
883
951
  return this.typedApi.getPoolHostFees({ hostNamespace, poolId });
884
952
  }
885
953
  /**
@@ -894,6 +962,7 @@ class FlashnetClient {
894
962
  */
895
963
  async withdrawHostFees(params) {
896
964
  await this.ensureInitialized();
965
+ await this.ensureAmmOperationAllowed("allow_withdraw_fees");
897
966
  const nonce = index$2.generateNonce();
898
967
  const intentMessage = intents.generateWithdrawHostFeesIntentMessage({
899
968
  hostPublicKey: this.publicKey,
@@ -908,7 +977,7 @@ class FlashnetClient {
908
977
  lpIdentityPublicKey: params.lpIdentityPublicKey,
909
978
  assetBAmount: params.assetBAmount,
910
979
  nonce,
911
- signature: Buffer.from(signature).toString("hex"),
980
+ signature: hex.getHexFromUint8Array(signature),
912
981
  };
913
982
  const response = await this.typedApi.withdrawHostFees(request);
914
983
  // Check if the withdrawal was accepted
@@ -923,6 +992,7 @@ class FlashnetClient {
923
992
  */
924
993
  async getHostFees(hostNamespace) {
925
994
  await this.ensureInitialized();
995
+ await this.ensurePingOk();
926
996
  const request = {
927
997
  hostNamespace,
928
998
  };
@@ -940,6 +1010,7 @@ class FlashnetClient {
940
1010
  */
941
1011
  async getPoolIntegratorFees(poolId) {
942
1012
  await this.ensureInitialized();
1013
+ await this.ensurePingOk();
943
1014
  return this.typedApi.getPoolIntegratorFees({ poolId });
944
1015
  }
945
1016
  /**
@@ -947,6 +1018,7 @@ class FlashnetClient {
947
1018
  */
948
1019
  async withdrawIntegratorFees(params) {
949
1020
  await this.ensureInitialized();
1021
+ await this.ensureAmmOperationAllowed("allow_withdraw_fees");
950
1022
  const nonce = index$2.generateNonce();
951
1023
  const intentMessage = intents.generateWithdrawIntegratorFeesIntentMessage({
952
1024
  integratorPublicKey: this.publicKey,
@@ -962,7 +1034,7 @@ class FlashnetClient {
962
1034
  lpIdentityPublicKey: params.lpIdentityPublicKey,
963
1035
  assetBAmount: params.assetBAmount,
964
1036
  nonce,
965
- signature: Buffer.from(signature).toString("hex"),
1037
+ signature: hex.getHexFromUint8Array(signature),
966
1038
  };
967
1039
  const response = await this.typedApi.withdrawIntegratorFees(request);
968
1040
  // Check if the withdrawal was accepted
@@ -988,6 +1060,7 @@ class FlashnetClient {
988
1060
  */
989
1061
  async createEscrow(params) {
990
1062
  await this.ensureInitialized();
1063
+ await this.ensurePingOk();
991
1064
  const nonce = index$2.generateNonce();
992
1065
  // The intent message requires a different structure for recipients and conditions
993
1066
  const intentRecipients = params.recipients.map((r) => ({
@@ -1016,7 +1089,7 @@ class FlashnetClient {
1016
1089
  abandonHost: params.abandonHost,
1017
1090
  abandonConditions: params.abandonConditions,
1018
1091
  nonce,
1019
- signature: Buffer.from(signature).toString("hex"),
1092
+ signature: hex.getHexFromUint8Array(signature),
1020
1093
  };
1021
1094
  const createResponse = await this.typedApi.createEscrow(request);
1022
1095
  const autoFund = params.autoFund !== false;
@@ -1039,6 +1112,7 @@ class FlashnetClient {
1039
1112
  */
1040
1113
  async fundEscrow(params) {
1041
1114
  await this.ensureInitialized();
1115
+ await this.ensurePingOk();
1042
1116
  // 1. Balance check
1043
1117
  await this.checkBalance({
1044
1118
  balancesToCheck: [
@@ -1063,6 +1137,7 @@ class FlashnetClient {
1063
1137
  });
1064
1138
  }
1065
1139
  async executeFundEscrowIntent(params) {
1140
+ await this.ensurePingOk();
1066
1141
  // Generate intent
1067
1142
  const nonce = index$2.generateNonce();
1068
1143
  const intentMessage = intents.generateFundEscrowIntentMessage({
@@ -1077,7 +1152,7 @@ class FlashnetClient {
1077
1152
  const request = {
1078
1153
  ...params,
1079
1154
  nonce,
1080
- signature: Buffer.from(signature).toString("hex"),
1155
+ signature: hex.getHexFromUint8Array(signature),
1081
1156
  };
1082
1157
  return this.typedApi.fundEscrow(request);
1083
1158
  }
@@ -1089,6 +1164,7 @@ class FlashnetClient {
1089
1164
  */
1090
1165
  async claimEscrow(params) {
1091
1166
  await this.ensureInitialized();
1167
+ await this.ensurePingOk();
1092
1168
  const nonce = index$2.generateNonce();
1093
1169
  const intentMessage = intents.generateClaimEscrowIntentMessage({
1094
1170
  escrowId: params.escrowId,
@@ -1100,7 +1176,7 @@ class FlashnetClient {
1100
1176
  const request = {
1101
1177
  escrowId: params.escrowId,
1102
1178
  nonce,
1103
- signature: Buffer.from(signature).toString("hex"),
1179
+ signature: hex.getHexFromUint8Array(signature),
1104
1180
  };
1105
1181
  return this.typedApi.claimEscrow(request);
1106
1182
  }
@@ -1143,6 +1219,7 @@ class FlashnetClient {
1143
1219
  */
1144
1220
  async clawback(params) {
1145
1221
  await this.ensureInitialized();
1222
+ await this.ensurePingOk();
1146
1223
  const nonce = index$2.generateNonce();
1147
1224
  const intentMessage = intents.generateClawbackIntentMessage({
1148
1225
  senderPublicKey: this.publicKey,
@@ -1157,7 +1234,7 @@ class FlashnetClient {
1157
1234
  sparkTransferId: params.sparkTransferId,
1158
1235
  lpIdentityPublicKey: params.lpIdentityPublicKey,
1159
1236
  nonce,
1160
- signature: Buffer.from(signature).toString("hex"),
1237
+ signature: hex.getHexFromUint8Array(signature),
1161
1238
  };
1162
1239
  const response = await this.typedApi.clawback(request);
1163
1240
  if (!response.accepted) {
@@ -1252,6 +1329,13 @@ class FlashnetClient {
1252
1329
  * Helper method to add initial liquidity after pool creation
1253
1330
  */
1254
1331
  async addInitialLiquidity(poolId, assetAAddress, assetBAddress, assetAAmount, assetBAmount, assetAMinAmountIn, assetBMinAmountIn) {
1332
+ // Enforce gating and min-amount policy for initial liquidity
1333
+ await this.ensureAmmOperationAllowed("allow_add_liquidity");
1334
+ await this.assertAddLiquidityMeetsMinAmounts({
1335
+ poolId,
1336
+ assetAAmount,
1337
+ assetBAmount,
1338
+ });
1255
1339
  const lpSparkAddress = sparkAddress.encodeSparkAddressNew({
1256
1340
  identityPublicKey: poolId,
1257
1341
  network: this.sparkNetwork,
@@ -1293,7 +1377,7 @@ class FlashnetClient {
1293
1377
  assetAMinAmountIn: assetAMinAmountIn.toString(),
1294
1378
  assetBMinAmountIn: assetBMinAmountIn.toString(),
1295
1379
  nonce,
1296
- signature: Buffer.from(signature).toString("hex"),
1380
+ signature: hex.getHexFromUint8Array(signature),
1297
1381
  };
1298
1382
  const response = await this.typedApi.addLiquidity(request);
1299
1383
  // Check if the initial liquidity addition was accepted
@@ -1308,6 +1392,186 @@ class FlashnetClient {
1308
1392
  async cleanup() {
1309
1393
  await this._wallet.cleanupConnections();
1310
1394
  }
1395
+ // ===== Config and Policy Enforcement Helpers =====
1396
+ async ensureAmmOperationAllowed(requiredFeature) {
1397
+ await this.ensurePingOk();
1398
+ const featureMap = await this.getFeatureStatusMap();
1399
+ if (featureMap.get("master_kill_switch")) {
1400
+ throw new Error("Service is temporarily disabled by master kill switch");
1401
+ }
1402
+ if (!featureMap.get(requiredFeature)) {
1403
+ throw new Error(`Operation not allowed: feature '${requiredFeature}' is disabled`);
1404
+ }
1405
+ }
1406
+ async ensurePingOk() {
1407
+ const now = Date.now();
1408
+ if (this.pingCache && this.pingCache.expiryMs > now) {
1409
+ if (!this.pingCache.ok) {
1410
+ throw new Error("Settlement service unavailable. Only read (GET) operations are allowed right now.");
1411
+ }
1412
+ return;
1413
+ }
1414
+ const ping = await this.typedApi.ping();
1415
+ const ok = !!ping &&
1416
+ typeof ping.status === "string" &&
1417
+ ping.status.toLowerCase() === "ok";
1418
+ this.pingCache = { ok, expiryMs: now + FlashnetClient.PING_TTL_MS };
1419
+ if (!ok) {
1420
+ throw new Error("Settlement service unavailable. Only read (GET) operations are allowed right now.");
1421
+ }
1422
+ }
1423
+ async getFeatureStatusMap() {
1424
+ const now = Date.now();
1425
+ if (this.featureStatusCache && this.featureStatusCache.expiryMs > now) {
1426
+ const map = new Map();
1427
+ for (const item of this.featureStatusCache.data) {
1428
+ map.set(item.feature_name, Boolean(item.enabled));
1429
+ }
1430
+ return map;
1431
+ }
1432
+ const data = await this.typedApi.getFeatureStatus();
1433
+ this.featureStatusCache = {
1434
+ data,
1435
+ expiryMs: now + FlashnetClient.FEATURE_STATUS_TTL_MS,
1436
+ };
1437
+ const map = new Map();
1438
+ for (const item of data) {
1439
+ map.set(item.feature_name, Boolean(item.enabled));
1440
+ }
1441
+ return map;
1442
+ }
1443
+ async getEnabledMinAmountsMap() {
1444
+ const now = Date.now();
1445
+ if (this.minAmountsCache && this.minAmountsCache.expiryMs > now) {
1446
+ return this.minAmountsCache.map;
1447
+ }
1448
+ const config = await this.typedApi.getMinAmounts();
1449
+ const map = new Map();
1450
+ for (const item of config) {
1451
+ if (item.enabled) {
1452
+ const key = item.asset_identifier.toLowerCase();
1453
+ const value = BigInt(String(item.min_amount));
1454
+ map.set(key, value);
1455
+ }
1456
+ }
1457
+ this.minAmountsCache = {
1458
+ map,
1459
+ expiryMs: now + FlashnetClient.MIN_AMOUNTS_TTL_MS,
1460
+ };
1461
+ return map;
1462
+ }
1463
+ getHexAddress(addr) {
1464
+ return this.toHexTokenIdentifier(addr).toLowerCase();
1465
+ }
1466
+ async assertSwapMeetsMinAmounts(params) {
1467
+ const minMap = await this.getEnabledMinAmountsMap();
1468
+ if (minMap.size === 0) {
1469
+ return;
1470
+ }
1471
+ const inHex = this.getHexAddress(params.assetInAddress);
1472
+ const outHex = this.getHexAddress(params.assetOutAddress);
1473
+ const minIn = minMap.get(inHex);
1474
+ const minOut = minMap.get(outHex);
1475
+ const amountIn = BigInt(String(params.amountIn));
1476
+ const minAmountOut = BigInt(String(params.minAmountOut));
1477
+ if (minIn && minOut) {
1478
+ if (amountIn < minIn) {
1479
+ throw new Error(`Minimum amount not met for input asset. Required \
1480
+ ${minIn.toString()}, provided ${amountIn.toString()}`);
1481
+ }
1482
+ return;
1483
+ }
1484
+ if (minIn) {
1485
+ if (amountIn < minIn) {
1486
+ throw new Error(`Minimum amount not met for input asset. Required \
1487
+ ${minIn.toString()}, provided ${amountIn.toString()}`);
1488
+ }
1489
+ return;
1490
+ }
1491
+ if (minOut) {
1492
+ const relaxed = minOut / 2n; // 50% relaxation for slippage
1493
+ if (minAmountOut < relaxed) {
1494
+ throw new Error(`Minimum amount not met for output asset. Required at least \
1495
+ ${relaxed.toString()} (50% relaxed), provided minAmountOut ${minAmountOut.toString()}`);
1496
+ }
1497
+ }
1498
+ }
1499
+ async assertAddLiquidityMeetsMinAmounts(params) {
1500
+ const minMap = await this.getEnabledMinAmountsMap();
1501
+ if (minMap.size === 0) {
1502
+ return;
1503
+ }
1504
+ const pool = await this.getPool(params.poolId);
1505
+ const aHex = pool.assetAAddress.toLowerCase();
1506
+ const bHex = pool.assetBAddress.toLowerCase();
1507
+ const aMin = minMap.get(aHex);
1508
+ const bMin = minMap.get(bHex);
1509
+ if (aMin) {
1510
+ const aAmt = BigInt(String(params.assetAAmount));
1511
+ if (aAmt < aMin) {
1512
+ throw new Error(`Minimum amount not met for Asset A. Required ${aMin.toString()}, provided ${aAmt.toString()}`);
1513
+ }
1514
+ }
1515
+ if (bMin) {
1516
+ const bAmt = BigInt(String(params.assetBAmount));
1517
+ if (bAmt < bMin) {
1518
+ throw new Error(`Minimum amount not met for Asset B. Required ${bMin.toString()}, provided ${bAmt.toString()}`);
1519
+ }
1520
+ }
1521
+ }
1522
+ async assertRemoveLiquidityMeetsMinAmounts(params) {
1523
+ const minMap = await this.getEnabledMinAmountsMap();
1524
+ if (minMap.size === 0) {
1525
+ return;
1526
+ }
1527
+ const simulation = await this.simulateRemoveLiquidity({
1528
+ poolId: params.poolId,
1529
+ providerPublicKey: this.publicKey,
1530
+ lpTokensToRemove: String(params.lpTokensToRemove),
1531
+ });
1532
+ const pool = await this.getPool(params.poolId);
1533
+ const aHex = pool.assetAAddress.toLowerCase();
1534
+ const bHex = pool.assetBAddress.toLowerCase();
1535
+ const aMin = minMap.get(aHex);
1536
+ const bMin = minMap.get(bHex);
1537
+ if (aMin) {
1538
+ const predictedAOut = BigInt(String(simulation.assetAAmount));
1539
+ const relaxedA = aMin / 2n; // apply 50% relaxation for outputs
1540
+ if (predictedAOut < relaxedA) {
1541
+ throw new Error(`Minimum amount not met for Asset A on withdrawal. Required at least ${relaxedA.toString()} (50% relaxed), predicted ${predictedAOut.toString()}`);
1542
+ }
1543
+ }
1544
+ if (bMin) {
1545
+ const predictedBOut = BigInt(String(simulation.assetBAmount));
1546
+ const relaxedB = bMin / 2n;
1547
+ if (predictedBOut < relaxedB) {
1548
+ throw new Error(`Minimum amount not met for Asset B on withdrawal. Required at least ${relaxedB.toString()} (50% relaxed), predicted ${predictedBOut.toString()}`);
1549
+ }
1550
+ }
1551
+ }
1552
+ async assertAllowedAssetBForPoolCreation(assetBHex) {
1553
+ const now = Date.now();
1554
+ let allowed;
1555
+ if (this.allowedAssetsCache && this.allowedAssetsCache.expiryMs > now) {
1556
+ allowed = this.allowedAssetsCache.data;
1557
+ }
1558
+ else {
1559
+ allowed = await this.typedApi.getAllowedAssets();
1560
+ this.allowedAssetsCache = {
1561
+ data: allowed,
1562
+ expiryMs: now + FlashnetClient.ALLOWED_ASSETS_TTL_MS,
1563
+ };
1564
+ }
1565
+ if (!allowed || allowed.length === 0) {
1566
+ // Wildcard allowance
1567
+ return;
1568
+ }
1569
+ const isAllowed = allowed.some((it) => it.enabled &&
1570
+ it.asset_identifier.toLowerCase() === assetBHex.toLowerCase());
1571
+ if (!isAllowed) {
1572
+ throw new Error(`Asset B is not allowed for pool creation: ${assetBHex}`);
1573
+ }
1574
+ }
1311
1575
  }
1312
1576
 
1313
1577
  exports.FlashnetClient = FlashnetClient;