@buildonspark/issuer-sdk 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @buildonspark/issuer-sdk
2
2
 
3
+ ## 0.1.6
4
+
5
+ ### Patch Changes
6
+
7
+ - ### Improved Token Ownership Validation
8
+
9
+ Token metadata fetches now return only tokens owned by the issuer, with centralized ownership validation to prevent operating on non-owned tokens.
10
+
11
+ ### Stricter Token Identifier Requirements
12
+
13
+ Mint, burn, freeze, and unfreeze flows now require explicit token identifiers or validate that only a single token is available. This provides clearer validation and more descriptive error messages when operations fail.
14
+
15
+ - Updated dependencies
16
+ - @buildonspark/spark-sdk@0.5.6
17
+
18
+ ## 0.1.5
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies
23
+ - @buildonspark/spark-sdk@0.5.5
24
+
3
25
  ## 0.1.4
4
26
 
5
27
  ### Patch Changes
@@ -114,10 +114,12 @@ declare abstract class IssuerSparkWallet extends SparkWallet {
114
114
  getIssuerTokenMetadata(): Promise<IssuerTokenMetadata>;
115
115
  /**
116
116
  * Retrieves information about the tokens that were issued by this user.
117
+ * @param tokenIdentifiers - Optional array of specific token identifiers to fetch.
118
+ * If omitted, all tokens for this issuer are fetched.
117
119
  * @returns An array of objects containing token information including public key, name, symbol, decimals, max supply, freeze status, and extra metadata
118
120
  * @throws {SparkRequestError} If the token metadata cannot be retrieved
119
121
  */
120
- getIssuerTokensMetadata(): Promise<IssuerTokenMetadata[]>;
122
+ getIssuerTokensMetadata(tokenIdentifiers?: Bech32mTokenIdentifier[]): Promise<IssuerTokenMetadata[]>;
121
123
  /**
122
124
  * Retrieves the bech32m encoded token identifier for the issuer's token.
123
125
  * @deprecated Use getIssuerTokenIdentifiers() instead. This method will be removed in a future version.
@@ -183,7 +185,7 @@ declare abstract class IssuerSparkWallet extends SparkWallet {
183
185
  */
184
186
  mintTokens({ tokenAmount, tokenIdentifier, }: {
185
187
  tokenAmount: bigint;
186
- tokenIdentifier?: Bech32mTokenIdentifier;
188
+ tokenIdentifier: Bech32mTokenIdentifier;
187
189
  }): Promise<string>;
188
190
  /**
189
191
  * Burns issuer's tokens
@@ -205,7 +207,7 @@ declare abstract class IssuerSparkWallet extends SparkWallet {
205
207
  */
206
208
  burnTokens({ tokenAmount, tokenIdentifier, selectedOutputs, }: {
207
209
  tokenAmount: bigint;
208
- tokenIdentifier?: Bech32mTokenIdentifier;
210
+ tokenIdentifier: Bech32mTokenIdentifier;
209
211
  selectedOutputs?: OutputWithPreviousTransactionData[];
210
212
  }): Promise<string>;
211
213
  /**
@@ -267,6 +269,14 @@ declare abstract class IssuerSparkWallet extends SparkWallet {
267
269
  * @throws {SparkError} This feature is not yet supported
268
270
  */
269
271
  getIssuerTokenDistribution(): Promise<TokenDistribution>;
272
+ /**
273
+ * This validates that the token belongs to this issuer.
274
+ * If a token is in the cache, it must belong to this issuer.
275
+ * @param tokenIdentifier - The bech32m encoded token identifier
276
+ * @throws {SparkValidationError} If the token is not found for this issuer
277
+ * @private
278
+ */
279
+ private validateTokenIssuer;
270
280
  protected getTraceName(methodName: string): string;
271
281
  private wrapIssuerPublicMethod;
272
282
  private wrapIssuerSparkWalletMethods;
@@ -433,88 +433,57 @@ var IssuerSparkWallet = class extends SparkWallet {
433
433
  * @throws {SparkValidationError} If multiple tokens are found for this issuer
434
434
  */
435
435
  async getIssuerTokenMetadata() {
436
- const issuerPublicKey = await super.getIdentityPublicKey();
437
- const sparkTokenClient = await this.connectionManager.createSparkTokenClient(
438
- this.config.getCoordinatorAddress()
439
- );
440
- try {
441
- const response = await sparkTokenClient.query_token_metadata({
442
- issuerPublicKeys: Array.of(hexToBytes2(issuerPublicKey))
443
- });
444
- if (response.tokenMetadata.length === 0) {
445
- throw new SparkValidationError3(
446
- "Token metadata not found - If a token has not yet been created, please create it first. Try again in a few seconds.",
447
- {
448
- field: "tokenMetadata",
449
- value: response.tokenMetadata,
450
- expected: "non-empty array",
451
- actualLength: response.tokenMetadata.length,
452
- expectedLength: 1
453
- }
454
- );
455
- }
456
- if (response.tokenMetadata.length > 1) {
457
- throw new SparkValidationError3(
458
- "Multiple tokens found for this issuer. Please migrate to getIssuerTokensMetadata() instead.",
459
- {
460
- field: "tokenMetadata",
461
- value: response.tokenMetadata
462
- }
463
- );
464
- }
465
- const metadata = response.tokenMetadata[0];
466
- const bech32mTokenIdentifier = encodeBech32mTokenIdentifier({
467
- tokenIdentifier: metadata.tokenIdentifier,
468
- network: this.config.getNetworkType()
469
- });
470
- this.tokenMetadata.set(bech32mTokenIdentifier, metadata);
471
- return {
472
- tokenPublicKey: bytesToHex(metadata.issuerPublicKey),
473
- rawTokenIdentifier: metadata.tokenIdentifier,
474
- tokenName: metadata.tokenName,
475
- tokenTicker: metadata.tokenTicker,
476
- decimals: metadata.decimals,
477
- maxSupply: bytesToNumberBE(metadata.maxSupply),
478
- isFreezable: metadata.isFreezable,
479
- extraMetadata: metadata.extraMetadata ? new Uint8Array(metadata.extraMetadata) : void 0,
480
- bech32mTokenIdentifier
481
- };
482
- } catch (error) {
483
- throw new SparkRequestError2("Failed to fetch token metadata", { error });
436
+ const tokensMetadata = await this.getIssuerTokensMetadata();
437
+ if (tokensMetadata.length === 0) {
438
+ throw new SparkValidationError3("No tokens found. Create a token first.");
439
+ }
440
+ if (tokensMetadata.length > 1) {
441
+ throw new SparkValidationError3(
442
+ "Multiple tokens found for this issuer. Please migrate to getIssuerTokensMetadata() instead.",
443
+ {
444
+ field: "tokenMetadata",
445
+ value: tokensMetadata
446
+ }
447
+ );
484
448
  }
449
+ return tokensMetadata[0];
485
450
  }
486
451
  /**
487
452
  * Retrieves information about the tokens that were issued by this user.
453
+ * @param tokenIdentifiers - Optional array of specific token identifiers to fetch.
454
+ * If omitted, all tokens for this issuer are fetched.
488
455
  * @returns An array of objects containing token information including public key, name, symbol, decimals, max supply, freeze status, and extra metadata
489
456
  * @throws {SparkRequestError} If the token metadata cannot be retrieved
490
457
  */
491
- async getIssuerTokensMetadata() {
458
+ async getIssuerTokensMetadata(tokenIdentifiers) {
492
459
  const issuerPublicKey = await super.getIdentityPublicKey();
493
460
  const sparkTokenClient = await this.connectionManager.createSparkTokenClient(
494
461
  this.config.getCoordinatorAddress()
495
462
  );
463
+ const filterByIdentifiers = Array.isArray(tokenIdentifiers) && tokenIdentifiers.length > 0;
464
+ const tokenIdentifierSet = filterByIdentifiers ? new Set(tokenIdentifiers) : void 0;
465
+ const request = {};
466
+ if (filterByIdentifiers) {
467
+ request.tokenIdentifiers = tokenIdentifiers.map(
468
+ (id) => decodeBech32mTokenIdentifier(id, this.config.getNetworkType()).tokenIdentifier
469
+ );
470
+ } else {
471
+ request.issuerPublicKeys = Array.of(hexToBytes2(issuerPublicKey));
472
+ }
496
473
  try {
497
- const response = await sparkTokenClient.query_token_metadata({
498
- issuerPublicKeys: Array.of(hexToBytes2(issuerPublicKey))
499
- });
500
- if (response.tokenMetadata.length === 0) {
501
- throw new SparkValidationError3(
502
- "Token metadata not found - If a token has not yet been created, please create it first. Try again in a few seconds.",
503
- {
504
- field: "tokenMetadata",
505
- value: response.tokenMetadata,
506
- expected: "non-empty array",
507
- actualLength: response.tokenMetadata.length,
508
- expectedLength: 1
509
- }
510
- );
511
- }
474
+ const response = await sparkTokenClient.query_token_metadata(request);
512
475
  const tokenMetadata = [];
513
476
  for (const metadata of response.tokenMetadata) {
514
477
  const bech32mTokenIdentifier = encodeBech32mTokenIdentifier({
515
478
  tokenIdentifier: metadata.tokenIdentifier,
516
479
  network: this.config.getNetworkType()
517
480
  });
481
+ if (bytesToHex(metadata.issuerPublicKey) !== issuerPublicKey) {
482
+ continue;
483
+ }
484
+ if (filterByIdentifiers && !tokenIdentifierSet.has(bech32mTokenIdentifier)) {
485
+ continue;
486
+ }
518
487
  this.tokenMetadata.set(bech32mTokenIdentifier, metadata);
519
488
  tokenMetadata.push({
520
489
  tokenPublicKey: bytesToHex(metadata.issuerPublicKey),
@@ -530,6 +499,9 @@ var IssuerSparkWallet = class extends SparkWallet {
530
499
  }
531
500
  return tokenMetadata;
532
501
  } catch (error) {
502
+ if (error instanceof SparkError) {
503
+ throw error;
504
+ }
533
505
  throw new SparkRequestError2("Failed to fetch token metadata", { error });
534
506
  }
535
507
  }
@@ -542,6 +514,9 @@ var IssuerSparkWallet = class extends SparkWallet {
542
514
  */
543
515
  async getIssuerTokenIdentifier() {
544
516
  const tokensMetadata = await this.getIssuerTokensMetadata();
517
+ if (tokensMetadata.length === 0) {
518
+ throw new SparkValidationError3("No tokens found. Create a token first.");
519
+ }
545
520
  if (tokensMetadata.length > 1) {
546
521
  throw new SparkValidationError3(
547
522
  "Multiple tokens found. Use getIssuerTokenIdentifiers() instead.",
@@ -558,9 +533,6 @@ var IssuerSparkWallet = class extends SparkWallet {
558
533
  }
559
534
  );
560
535
  }
561
- if (tokensMetadata.length === 0) {
562
- throw new SparkValidationError3("No tokens found. Create a token first.");
563
- }
564
536
  return tokensMetadata[0].bech32mTokenIdentifier;
565
537
  }
566
538
  /**
@@ -674,8 +646,13 @@ var IssuerSparkWallet = class extends SparkWallet {
674
646
  }
675
647
  const issuerTokenPublicKey = await super.getIdentityPublicKey();
676
648
  const issuerTokenPublicKeyBytes = hexToBytes2(issuerTokenPublicKey);
677
- const tokensMetadata = await this.getIssuerTokensMetadata();
678
649
  if (bech32mTokenIdentifier === void 0) {
650
+ const tokensMetadata = await this.getIssuerTokensMetadata();
651
+ if (tokensMetadata.length === 0) {
652
+ throw new SparkValidationError3(
653
+ "No tokens found. Create a token first."
654
+ );
655
+ }
679
656
  if (tokensMetadata.length > 1) {
680
657
  throw new SparkValidationError3(
681
658
  "Multiple tokens found. Please use mintTokens({ tokenAmount, tokenIdentifier }) instead.",
@@ -684,19 +661,14 @@ var IssuerSparkWallet = class extends SparkWallet {
684
661
  availableTokens: tokensMetadata.map((t) => ({
685
662
  tokenName: t.tokenName,
686
663
  tokenTicker: t.tokenTicker,
687
- bech32mTokenIdentifier: encodeBech32mTokenIdentifier({
688
- tokenIdentifier: t.rawTokenIdentifier,
689
- network: this.config.getNetworkType()
690
- })
664
+ bech32mTokenIdentifier: t.bech32mTokenIdentifier
691
665
  }))
692
666
  }
693
667
  );
694
668
  }
695
- const encodedTokenIdentifier = encodeBech32mTokenIdentifier({
696
- tokenIdentifier: tokensMetadata[0].rawTokenIdentifier,
697
- network: this.config.getNetworkType()
698
- });
699
- bech32mTokenIdentifier = encodedTokenIdentifier;
669
+ bech32mTokenIdentifier = tokensMetadata[0].bech32mTokenIdentifier;
670
+ } else {
671
+ await this.validateTokenIssuer(bech32mTokenIdentifier);
700
672
  }
701
673
  const rawTokenIdentifier = decodeBech32mTokenIdentifier(
702
674
  bech32mTokenIdentifier,
@@ -723,13 +695,18 @@ var IssuerSparkWallet = class extends SparkWallet {
723
695
  }
724
696
  }
725
697
  async burnTokens(tokenAmountOrParams, selectedOutputs) {
726
- let bech32mTokenIdentifier;
698
+ let burnTokenIdentifier;
727
699
  let tokenAmount;
728
700
  let outputs;
729
701
  if (typeof tokenAmountOrParams === "bigint") {
730
702
  tokenAmount = tokenAmountOrParams;
731
703
  outputs = selectedOutputs;
732
704
  const tokenIdentifiers = await this.getIssuerTokenIdentifiers();
705
+ if (tokenIdentifiers.length === 0) {
706
+ throw new SparkValidationError3(
707
+ "No tokens found. Create a token first."
708
+ );
709
+ }
733
710
  if (tokenIdentifiers.length > 1) {
734
711
  throw new SparkValidationError3(
735
712
  "Multiple tokens found. Use burnTokens({ tokenIdentifier, tokenAmount, selectedOutputs }) to specify which token to burn.",
@@ -739,52 +716,19 @@ var IssuerSparkWallet = class extends SparkWallet {
739
716
  }
740
717
  );
741
718
  }
742
- if (tokenIdentifiers.length === 0) {
743
- throw new SparkValidationError3(
744
- "No tokens found. Create a token first."
745
- );
746
- }
747
- bech32mTokenIdentifier = tokenIdentifiers[0];
719
+ burnTokenIdentifier = tokenIdentifiers[0];
748
720
  } else {
749
721
  tokenAmount = tokenAmountOrParams.tokenAmount;
750
722
  outputs = tokenAmountOrParams.selectedOutputs;
751
- if (tokenAmountOrParams.tokenIdentifier) {
752
- const tokenIdentifiers = await this.getIssuerTokenIdentifiers();
753
- const tokenIdentifier = tokenIdentifiers.find(
754
- (identifier) => identifier === tokenAmountOrParams.tokenIdentifier
755
- );
756
- if (!tokenIdentifier) {
757
- throw new SparkValidationError3("Token not found for this issuer", {
758
- field: "tokenIdentifier",
759
- value: tokenAmountOrParams.tokenIdentifier
760
- });
761
- }
762
- bech32mTokenIdentifier = tokenAmountOrParams.tokenIdentifier;
763
- } else {
764
- const tokenIdentifiers = await this.getIssuerTokenIdentifiers();
765
- if (tokenIdentifiers.length === 0) {
766
- throw new SparkValidationError3(
767
- "No tokens found. Create a token first."
768
- );
769
- }
770
- if (tokenIdentifiers.length > 1) {
771
- throw new SparkValidationError3(
772
- "Multiple tokens found. Please specify tokenIdentifier in parameters.",
773
- {
774
- field: "tokenIdentifier",
775
- availableTokens: tokenIdentifiers
776
- }
777
- );
778
- }
779
- bech32mTokenIdentifier = tokenIdentifiers[0];
780
- }
723
+ await this.validateTokenIssuer(tokenAmountOrParams.tokenIdentifier);
724
+ burnTokenIdentifier = tokenAmountOrParams.tokenIdentifier;
781
725
  }
782
726
  const burnAddress = encodeSparkAddress({
783
727
  identityPublicKey: BURN_ADDRESS,
784
728
  network: this.config.getNetworkType()
785
729
  });
786
730
  return await this.transferTokens({
787
- tokenIdentifier: bech32mTokenIdentifier,
731
+ tokenIdentifier: burnTokenIdentifier,
788
732
  tokenAmount,
789
733
  receiverSparkAddress: burnAddress,
790
734
  selectedOutputs: outputs
@@ -821,6 +765,8 @@ var IssuerSparkWallet = class extends SparkWallet {
821
765
  );
822
766
  }
823
767
  bech32mTokenIdentifier = tokenIdentifiers[0];
768
+ } else {
769
+ await this.validateTokenIssuer(bech32mTokenIdentifier);
824
770
  }
825
771
  const rawTokenIdentifier = decodeBech32mTokenIdentifier(
826
772
  bech32mTokenIdentifier,
@@ -848,6 +794,11 @@ var IssuerSparkWallet = class extends SparkWallet {
848
794
  }
849
795
  if (bech32mTokenIdentifier === void 0) {
850
796
  const tokenIdentifiers = await this.getIssuerTokenIdentifiers();
797
+ if (tokenIdentifiers.length === 0) {
798
+ throw new SparkValidationError3(
799
+ "No tokens found. Create a token first."
800
+ );
801
+ }
851
802
  if (tokenIdentifiers.length > 1) {
852
803
  throw new SparkValidationError3(
853
804
  "Multiple tokens found. Use unfreezeTokens({ tokenIdentifier, sparkAddress }) instead.",
@@ -858,6 +809,8 @@ var IssuerSparkWallet = class extends SparkWallet {
858
809
  );
859
810
  }
860
811
  bech32mTokenIdentifier = tokenIdentifiers[0];
812
+ } else {
813
+ await this.validateTokenIssuer(bech32mTokenIdentifier);
861
814
  }
862
815
  const decodedOwnerPubkey = decodeSparkAddress(
863
816
  sparkAddress,
@@ -884,6 +837,45 @@ var IssuerSparkWallet = class extends SparkWallet {
884
837
  async getIssuerTokenDistribution() {
885
838
  throw new SparkError("Token distribution is not yet supported");
886
839
  }
840
+ /**
841
+ * This validates that the token belongs to this issuer.
842
+ * If a token is in the cache, it must belong to this issuer.
843
+ * @param tokenIdentifier - The bech32m encoded token identifier
844
+ * @throws {SparkValidationError} If the token is not found for this issuer
845
+ * @private
846
+ */
847
+ async validateTokenIssuer(tokenIdentifier) {
848
+ const issuerPublicKey = await super.getIdentityPublicKey();
849
+ const cachedMetadata = this.tokenMetadata.get(tokenIdentifier);
850
+ if (cachedMetadata) {
851
+ if (bytesToHex(cachedMetadata.issuerPublicKey) !== issuerPublicKey) {
852
+ throw new SparkValidationError3("Token was not issued by this issuer", {
853
+ field: "issuerPublicKey",
854
+ tokenIdentifier,
855
+ expected: issuerPublicKey,
856
+ actual: bytesToHex(cachedMetadata.issuerPublicKey)
857
+ });
858
+ }
859
+ } else {
860
+ const tokensMetadata = await this.getIssuerTokensMetadata([
861
+ tokenIdentifier
862
+ ]);
863
+ if (tokensMetadata.length === 0) {
864
+ throw new SparkValidationError3("Token not found for this issuer", {
865
+ field: "tokenIdentifier",
866
+ value: tokenIdentifier
867
+ });
868
+ }
869
+ if (tokensMetadata[0].tokenPublicKey !== issuerPublicKey) {
870
+ throw new SparkValidationError3("Token was not issued by this issuer", {
871
+ field: "issuerPublicKey",
872
+ tokenIdentifier,
873
+ expected: issuerPublicKey,
874
+ actual: tokensMetadata[0].tokenPublicKey
875
+ });
876
+ }
877
+ }
878
+ }
887
879
  getTraceName(methodName) {
888
880
  return `IssuerSparkWallet.${methodName}`;
889
881
  }