@buildonspark/issuer-sdk 0.1.2 → 0.1.4

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.
@@ -15,8 +15,13 @@ import { OutputWithPreviousTransactionData } from "@buildonspark/spark-sdk/proto
15
15
  import { bytesToHex, bytesToNumberBE, hexToBytes } from "@noble/curves/utils";
16
16
  import { TokenFreezeService } from "../services/freeze.js";
17
17
  import { IssuerTokenTransactionService } from "../services/token-transactions.js";
18
+ import { hashFinalTokenTransaction } from "@buildonspark/spark-sdk";
18
19
  import { validateTokenParameters } from "../utils/create-validation.js";
19
- import { IssuerTokenMetadata, TokenDistribution } from "./types.js";
20
+ import {
21
+ IssuerTokenMetadata,
22
+ TokenCreationDetails,
23
+ TokenDistribution,
24
+ } from "./types.js";
20
25
 
21
26
  const BURN_ADDRESS = "02".repeat(33);
22
27
 
@@ -50,7 +55,10 @@ export abstract class IssuerSparkWallet extends SparkWallet {
50
55
 
51
56
  /**
52
57
  * Gets the token balance for the issuer's token.
58
+ * @deprecated Use getIssuerTokenBalances() instead. This method will be removed in a future version.
53
59
  * @returns An object containing the token balance as a bigint
60
+ *
61
+ * @throws {SparkValidationError} If multiple tokens are found for this issuer
54
62
  */
55
63
  public async getIssuerTokenBalance(): Promise<{
56
64
  tokenIdentifier: Bech32mTokenIdentifier | undefined;
@@ -58,37 +66,112 @@ export abstract class IssuerSparkWallet extends SparkWallet {
58
66
  }> {
59
67
  const publicKey = await super.getIdentityPublicKey();
60
68
  const balanceObj = await this.getBalance();
61
- const issuerBalance = [...balanceObj.tokenBalances.entries()].find(
69
+ const issuerBalance = [...balanceObj.tokenBalances.entries()].filter(
62
70
  ([, info]) => info.tokenMetadata.tokenPublicKey === publicKey,
63
71
  ); // [tokenIdentifier, { balance, tokenMetadata }]
64
72
 
65
- if (!balanceObj.tokenBalances || issuerBalance === undefined) {
73
+ if (issuerBalance.length > 1) {
74
+ throw new SparkValidationError(
75
+ "Multiple tokens found for this issuer. Use getIssuerTokenBalances() instead.",
76
+ {
77
+ field: "issuerTokenBalance",
78
+ expected: "single token",
79
+ actual: `${issuerBalance.length} tokens`,
80
+ },
81
+ );
82
+ }
83
+
84
+ if (issuerBalance.length === 0) {
66
85
  return {
67
86
  tokenIdentifier: undefined,
68
87
  balance: 0n,
69
88
  };
70
89
  }
90
+
71
91
  return {
72
- tokenIdentifier: issuerBalance[0] ?? undefined,
73
- balance: issuerBalance[1].balance,
92
+ tokenIdentifier: issuerBalance[0][0],
93
+ balance: issuerBalance[0][1].balance,
74
94
  };
75
95
  }
76
96
 
97
+ /**
98
+ * Gets the token balances for the tokens that were issued by this user.
99
+ * @returns An array of objects containing the token identifier and balance
100
+ */
101
+ public async getIssuerTokenBalances(): Promise<
102
+ {
103
+ tokenIdentifier: Bech32mTokenIdentifier | undefined;
104
+ balance: bigint;
105
+ }[]
106
+ > {
107
+ const publicKey = await super.getIdentityPublicKey();
108
+ const balanceObj = await this.getBalance();
109
+ const issuerBalance = [...balanceObj.tokenBalances.entries()].filter(
110
+ ([, info]) => info.tokenMetadata.tokenPublicKey === publicKey,
111
+ ); // [tokenIdentifier, { balance, tokenMetadata }]
112
+
113
+ if (issuerBalance.length === 0) {
114
+ return [
115
+ {
116
+ tokenIdentifier: undefined,
117
+ balance: 0n,
118
+ },
119
+ ];
120
+ }
121
+
122
+ return issuerBalance.map(([tokenIdentifier, { balance }]) => ({
123
+ tokenIdentifier,
124
+ balance,
125
+ }));
126
+ }
127
+
77
128
  /**
78
129
  * Retrieves information about the issuer's token.
79
- * @returns An object containing token information including public key, name, symbol, decimals, max supply, and freeze status
130
+ * @deprecated Use getIssuerTokensMetadata() instead. This method will be removed in a future version.
131
+ * @returns An object containing token information including public key, name, symbol, decimals, max supply, freeze status, and extra metadata
80
132
  * @throws {SparkRequestError} If the token metadata cannot be retrieved
133
+ * @throws {SparkValidationError} If multiple tokens are found for this issuer
81
134
  */
82
135
  public async getIssuerTokenMetadata(): Promise<IssuerTokenMetadata> {
83
136
  const issuerPublicKey = await super.getIdentityPublicKey();
84
- const tokenMetadata = this.tokenMetadata;
85
137
 
86
- const cachedIssuerTokenMetadata = [...tokenMetadata.entries()].find(
87
- ([, metadata]) =>
88
- bytesToHex(metadata.issuerPublicKey) === issuerPublicKey,
89
- );
90
- if (cachedIssuerTokenMetadata !== undefined) {
91
- const metadata = cachedIssuerTokenMetadata[1];
138
+ const sparkTokenClient =
139
+ await this.connectionManager.createSparkTokenClient(
140
+ this.config.getCoordinatorAddress(),
141
+ );
142
+ try {
143
+ const response = await sparkTokenClient.query_token_metadata({
144
+ issuerPublicKeys: Array.of(hexToBytes(issuerPublicKey)),
145
+ });
146
+ if (response.tokenMetadata.length === 0) {
147
+ throw new SparkValidationError(
148
+ "Token metadata not found - If a token has not yet been created, please create it first. Try again in a few seconds.",
149
+ {
150
+ field: "tokenMetadata",
151
+ value: response.tokenMetadata,
152
+ expected: "non-empty array",
153
+ actualLength: response.tokenMetadata.length,
154
+ expectedLength: 1,
155
+ },
156
+ );
157
+ }
158
+ if (response.tokenMetadata.length > 1) {
159
+ throw new SparkValidationError(
160
+ "Multiple tokens found for this issuer. Please migrate to getIssuerTokensMetadata() instead.",
161
+ {
162
+ field: "tokenMetadata",
163
+ value: response.tokenMetadata,
164
+ },
165
+ );
166
+ }
167
+
168
+ const metadata = response.tokenMetadata[0];
169
+ const bech32mTokenIdentifier = encodeBech32mTokenIdentifier({
170
+ tokenIdentifier: metadata.tokenIdentifier,
171
+ network: this.config.getNetworkType(),
172
+ });
173
+ this.tokenMetadata.set(bech32mTokenIdentifier, metadata);
174
+
92
175
  return {
93
176
  tokenPublicKey: bytesToHex(metadata.issuerPublicKey),
94
177
  rawTokenIdentifier: metadata.tokenIdentifier,
@@ -100,8 +183,20 @@ export abstract class IssuerSparkWallet extends SparkWallet {
100
183
  extraMetadata: metadata.extraMetadata
101
184
  ? new Uint8Array(metadata.extraMetadata)
102
185
  : undefined,
186
+ bech32mTokenIdentifier,
103
187
  };
188
+ } catch (error) {
189
+ throw new SparkRequestError("Failed to fetch token metadata", { error });
104
190
  }
191
+ }
192
+
193
+ /**
194
+ * Retrieves information about the tokens that were issued by this user.
195
+ * @returns An array of objects containing token information including public key, name, symbol, decimals, max supply, freeze status, and extra metadata
196
+ * @throws {SparkRequestError} If the token metadata cannot be retrieved
197
+ */
198
+ public async getIssuerTokensMetadata(): Promise<IssuerTokenMetadata[]> {
199
+ const issuerPublicKey = await super.getIdentityPublicKey();
105
200
 
106
201
  const sparkTokenClient =
107
202
  await this.connectionManager.createSparkTokenClient(
@@ -123,23 +218,32 @@ export abstract class IssuerSparkWallet extends SparkWallet {
123
218
  },
124
219
  );
125
220
  }
126
- const metadata = response.tokenMetadata[0];
127
- const tokenIdentifier = encodeBech32mTokenIdentifier({
128
- tokenIdentifier: metadata.tokenIdentifier,
129
- network: this.config.getNetworkType(),
130
- });
131
- this.tokenMetadata.set(tokenIdentifier, metadata);
132
221
 
133
- return {
134
- tokenPublicKey: bytesToHex(metadata.issuerPublicKey),
135
- rawTokenIdentifier: metadata.tokenIdentifier,
136
- tokenName: metadata.tokenName,
137
- tokenTicker: metadata.tokenTicker,
138
- decimals: metadata.decimals,
139
- maxSupply: bytesToNumberBE(metadata.maxSupply),
140
- isFreezable: metadata.isFreezable,
141
- extraMetadata: metadata.extraMetadata,
142
- };
222
+ const tokenMetadata: IssuerTokenMetadata[] = [];
223
+
224
+ for (const metadata of response.tokenMetadata) {
225
+ const bech32mTokenIdentifier = encodeBech32mTokenIdentifier({
226
+ tokenIdentifier: metadata.tokenIdentifier,
227
+ network: this.config.getNetworkType(),
228
+ });
229
+
230
+ this.tokenMetadata.set(bech32mTokenIdentifier, metadata);
231
+
232
+ tokenMetadata.push({
233
+ tokenPublicKey: bytesToHex(metadata.issuerPublicKey),
234
+ rawTokenIdentifier: metadata.tokenIdentifier,
235
+ tokenName: metadata.tokenName,
236
+ tokenTicker: metadata.tokenTicker,
237
+ decimals: metadata.decimals,
238
+ maxSupply: bytesToNumberBE(metadata.maxSupply),
239
+ isFreezable: metadata.isFreezable,
240
+ extraMetadata: metadata.extraMetadata
241
+ ? new Uint8Array(metadata.extraMetadata)
242
+ : undefined,
243
+ bech32mTokenIdentifier,
244
+ });
245
+ }
246
+ return tokenMetadata;
143
247
  } catch (error) {
144
248
  throw new SparkRequestError("Failed to fetch token metadata", { error });
145
249
  }
@@ -147,16 +251,47 @@ export abstract class IssuerSparkWallet extends SparkWallet {
147
251
 
148
252
  /**
149
253
  * Retrieves the bech32m encoded token identifier for the issuer's token.
254
+ * @deprecated Use getIssuerTokenIdentifiers() instead. This method will be removed in a future version.
150
255
  * @returns The bech32m encoded token identifier for the issuer's token
151
256
  * @throws {SparkRequestError} If the token identifier cannot be retrieved
257
+ * @throws {SparkValidationError} If multiple tokens are found for this issuer
152
258
  */
153
259
  public async getIssuerTokenIdentifier(): Promise<Bech32mTokenIdentifier> {
154
- const tokenMetadata = await this.getIssuerTokenMetadata();
260
+ const tokensMetadata = await this.getIssuerTokensMetadata();
261
+
262
+ if (tokensMetadata.length > 1) {
263
+ throw new SparkValidationError(
264
+ "Multiple tokens found. Use getIssuerTokenIdentifiers() instead.",
265
+ {
266
+ method: "getIssuerTokenIdentifier",
267
+ availableTokens: tokensMetadata.map((t) => ({
268
+ tokenName: t.tokenName,
269
+ tokenTicker: t.tokenTicker,
270
+ bech32mTokenIdentifier: encodeBech32mTokenIdentifier({
271
+ tokenIdentifier: t.rawTokenIdentifier,
272
+ network: this.config.getNetworkType(),
273
+ }),
274
+ })),
275
+ },
276
+ );
277
+ }
155
278
 
156
- return encodeBech32mTokenIdentifier({
157
- tokenIdentifier: tokenMetadata.rawTokenIdentifier,
158
- network: this.config.getNetworkType(),
159
- });
279
+ if (tokensMetadata.length === 0) {
280
+ throw new SparkValidationError("No tokens found. Create a token first.");
281
+ }
282
+
283
+ return tokensMetadata[0].bech32mTokenIdentifier;
284
+ }
285
+
286
+ /**
287
+ * Retrieves the bech32m encoded token identifier for the issuer's token.
288
+ * @returns The bech32m encoded token identifier for the issuer's token
289
+ * @throws {SparkRequestError} If the token identifier cannot be retrieved
290
+ */
291
+ public async getIssuerTokenIdentifiers(): Promise<Bech32mTokenIdentifier[]> {
292
+ const tokensMetadata = await this.getIssuerTokensMetadata();
293
+
294
+ return tokensMetadata.map((metadata) => metadata.bech32mTokenIdentifier);
160
295
  }
161
296
 
162
297
  /**
@@ -169,12 +304,31 @@ export abstract class IssuerSparkWallet extends SparkWallet {
169
304
  * @param params.isFreezable - Whether the token can be frozen.
170
305
  * @param [params.maxSupply=0n] - (Optional) The maximum supply of the token. Defaults to <code>0n</code>.
171
306
  * @param params.extraMetadata - (Optional) This can be used to store additional bytes data to be associated with a token, like image data.
172
- *
173
- * @returns The transaction ID of the announcement.
174
- *
307
+ * @param params.returnIdentifierForCreate - (Optional) Whether to return the token identifier in addition to the transaction hash. Defaults to <code>false</code>.
308
+ * @returns The transaction hash of the announcement or TokenCreationDetails if `returnIdentifierForCreate` is true.
175
309
  * @throws {SparkValidationError} If `decimals` is not a safe integer or other validation fails.
176
310
  * @throws {SparkRequestError} If the announcement transaction cannot be broadcast.
177
311
  */
312
+ public async createToken(params: {
313
+ tokenName: string;
314
+ tokenTicker: string;
315
+ decimals: number;
316
+ isFreezable: boolean;
317
+ maxSupply?: bigint;
318
+ extraMetadata?: Uint8Array;
319
+ returnIdentifierForCreate?: false;
320
+ }): Promise<string>;
321
+
322
+ public async createToken(params: {
323
+ tokenName: string;
324
+ tokenTicker: string;
325
+ decimals: number;
326
+ isFreezable: boolean;
327
+ maxSupply?: bigint;
328
+ extraMetadata?: Uint8Array;
329
+ returnIdentifierForCreate: true;
330
+ }): Promise<TokenCreationDetails>;
331
+
178
332
  public async createToken({
179
333
  tokenName,
180
334
  tokenTicker,
@@ -182,6 +336,7 @@ export abstract class IssuerSparkWallet extends SparkWallet {
182
336
  isFreezable,
183
337
  maxSupply = 0n,
184
338
  extraMetadata,
339
+ returnIdentifierForCreate = false,
185
340
  }: {
186
341
  tokenName: string;
187
342
  tokenTicker: string;
@@ -189,7 +344,8 @@ export abstract class IssuerSparkWallet extends SparkWallet {
189
344
  isFreezable: boolean;
190
345
  maxSupply?: bigint;
191
346
  extraMetadata?: Uint8Array;
192
- }): Promise<string> {
347
+ returnIdentifierForCreate?: boolean;
348
+ }): Promise<string | TokenCreationDetails> {
193
349
  validateTokenParameters(
194
350
  tokenName,
195
351
  tokenTicker,
@@ -212,9 +368,32 @@ export abstract class IssuerSparkWallet extends SparkWallet {
212
368
  extraMetadata,
213
369
  );
214
370
 
215
- return await this.issuerTokenTransactionService.broadcastTokenTransaction(
216
- tokenTransaction,
217
- );
371
+ const { finalTokenTransactionHash, tokenIdentifier } =
372
+ await this.issuerTokenTransactionService.broadcastTokenTransactionDetailed(
373
+ tokenTransaction,
374
+ );
375
+ const txHash = bytesToHex(finalTokenTransactionHash);
376
+
377
+ if (returnIdentifierForCreate) {
378
+ if (!tokenIdentifier) {
379
+ throw new SparkRequestError(
380
+ "Server response missing expected field: tokenIdentifier",
381
+ {
382
+ operation: "broadcast_transaction",
383
+ field: "tokenIdentifier",
384
+ },
385
+ );
386
+ }
387
+ const bech32mTokenIdentifier = encodeBech32mTokenIdentifier({
388
+ tokenIdentifier: tokenIdentifier,
389
+ network: this.config.getNetworkType(),
390
+ });
391
+ return {
392
+ tokenIdentifier: bech32mTokenIdentifier,
393
+ transactionHash: txHash,
394
+ };
395
+ }
396
+ return txHash;
218
397
  } else {
219
398
  const partialTokenTransaction =
220
399
  await this.issuerTokenTransactionService.constructPartialCreateTokenTransaction(
@@ -227,23 +406,116 @@ export abstract class IssuerSparkWallet extends SparkWallet {
227
406
  extraMetadata,
228
407
  );
229
408
 
230
- return await this.issuerTokenTransactionService.broadcastTokenTransactionV3(
231
- partialTokenTransaction,
409
+ const broadcastResponse =
410
+ await this.issuerTokenTransactionService.broadcastTokenTransactionV3Detailed(
411
+ partialTokenTransaction,
412
+ );
413
+ const finalHash = await hashFinalTokenTransaction(
414
+ broadcastResponse.finalTokenTransaction!,
232
415
  );
416
+ const finalTransactionHash = bytesToHex(finalHash);
417
+
418
+ if (returnIdentifierForCreate) {
419
+ if (!broadcastResponse.tokenIdentifier) {
420
+ throw new SparkRequestError(
421
+ "Server response missing expected field: tokenIdentifier",
422
+ {
423
+ operation: "broadcast_transaction",
424
+ field: "tokenIdentifier",
425
+ },
426
+ );
427
+ }
428
+ const tokenIdentifier = encodeBech32mTokenIdentifier({
429
+ tokenIdentifier: broadcastResponse.tokenIdentifier!,
430
+ network: this.config.getNetworkType(),
431
+ });
432
+ return {
433
+ tokenIdentifier,
434
+ transactionHash: finalTransactionHash,
435
+ };
436
+ }
437
+
438
+ return finalTransactionHash;
233
439
  }
234
440
  }
235
441
 
442
+ /**
443
+ * @deprecated Use mintTokens({ tokenAmount, tokenIdentifier }) instead. This method will be removed in a future version.
444
+ * @param amount - The amount of tokens to mint
445
+ * @returns The transaction ID of the mint operation
446
+ * @throws {SparkValidationError} If multiple tokens are found for this issuer
447
+ * @throws {SparkValidationError} If no tokens are found for this issuer
448
+ */
449
+ public async mintTokens(amount: bigint): Promise<string>;
450
+
236
451
  /**
237
452
  * Mints new tokens
238
- * @param tokenAmount - The amount of tokens to mint
453
+ * @param params - Object containing token minting parameters.
454
+ * @param params.tokenAmount - The amount of tokens to mint
455
+ * @param params.tokenIdentifier - The bech32m encoded token identifier
239
456
  * @returns The transaction ID of the mint operation
457
+ * @throws {SparkValidationError} If no tokens are found for this issuer
240
458
  */
241
- public async mintTokens(tokenAmount: bigint): Promise<string> {
459
+ public async mintTokens({
460
+ tokenAmount,
461
+ tokenIdentifier,
462
+ }: {
463
+ tokenAmount: bigint;
464
+ tokenIdentifier?: Bech32mTokenIdentifier;
465
+ }): Promise<string>;
466
+
467
+ public async mintTokens(
468
+ tokenAmountOrParams:
469
+ | bigint
470
+ | {
471
+ tokenAmount: bigint;
472
+ tokenIdentifier?: Bech32mTokenIdentifier;
473
+ },
474
+ ): Promise<string> {
475
+ let tokenAmount: bigint;
476
+ let bech32mTokenIdentifier: Bech32mTokenIdentifier | undefined;
477
+
478
+ if (typeof tokenAmountOrParams === "bigint") {
479
+ tokenAmount = tokenAmountOrParams;
480
+ bech32mTokenIdentifier = undefined;
481
+ } else {
482
+ tokenAmount = tokenAmountOrParams.tokenAmount;
483
+ bech32mTokenIdentifier = tokenAmountOrParams.tokenIdentifier;
484
+ }
485
+
242
486
  const issuerTokenPublicKey = await super.getIdentityPublicKey();
243
487
  const issuerTokenPublicKeyBytes = hexToBytes(issuerTokenPublicKey);
244
488
 
245
- const tokenMetadata = await this.getIssuerTokenMetadata();
246
- const rawTokenIdentifier: Uint8Array = tokenMetadata.rawTokenIdentifier;
489
+ const tokensMetadata = await this.getIssuerTokensMetadata();
490
+ if (bech32mTokenIdentifier === undefined) {
491
+ if (tokensMetadata.length > 1) {
492
+ throw new SparkValidationError(
493
+ "Multiple tokens found. Please use mintTokens({ tokenAmount, tokenIdentifier }) instead.",
494
+ {
495
+ field: "tokenIdentifier",
496
+ availableTokens: tokensMetadata.map((t) => ({
497
+ tokenName: t.tokenName,
498
+ tokenTicker: t.tokenTicker,
499
+ bech32mTokenIdentifier: encodeBech32mTokenIdentifier({
500
+ tokenIdentifier: t.rawTokenIdentifier,
501
+ network: this.config.getNetworkType(),
502
+ }),
503
+ })),
504
+ },
505
+ );
506
+ }
507
+
508
+ const encodedTokenIdentifier = encodeBech32mTokenIdentifier({
509
+ tokenIdentifier: tokensMetadata[0].rawTokenIdentifier,
510
+ network: this.config.getNetworkType(),
511
+ });
512
+ bech32mTokenIdentifier = encodedTokenIdentifier;
513
+ }
514
+
515
+ const rawTokenIdentifier = decodeBech32mTokenIdentifier(
516
+ bech32mTokenIdentifier,
517
+ this.config.getNetworkType(),
518
+ ).tokenIdentifier;
247
519
 
248
520
  if (this.config.getTokenTransactionVersion() === "V2") {
249
521
  const tokenTransaction =
@@ -272,47 +544,191 @@ export abstract class IssuerSparkWallet extends SparkWallet {
272
544
 
273
545
  /**
274
546
  * Burns issuer's tokens
547
+ * @deprecated Use burnTokens({ tokenAmount, tokenIdentifier, selectedOutputs }) instead. This method will be removed in a future version.
275
548
  * @param tokenAmount - The amount of tokens to burn
276
549
  * @param selectedOutputs - Optional array of outputs to use for the burn operation
277
550
  * @returns The transaction ID of the burn operation
551
+ * @throws {SparkValidationError} If multiple tokens are found for this issuer
552
+ * @throws {SparkValidationError} If no tokens are found for this issuer
278
553
  */
279
554
  public async burnTokens(
280
555
  tokenAmount: bigint,
281
556
  selectedOutputs?: OutputWithPreviousTransactionData[],
557
+ ): Promise<string>;
558
+
559
+ /**
560
+ * Burns issuer's tokens
561
+ * @param params - Object containing token burning parameters.
562
+ * @param params.tokenAmount - The amount of tokens to burn
563
+ * @param params.tokenIdentifier - The bech32m encoded token identifier
564
+ * @param params.selectedOutputs - Optional array of outputs to use for the burn operation
565
+ * @returns The transaction ID of the burn operation
566
+ */
567
+ public async burnTokens({
568
+ tokenAmount,
569
+ tokenIdentifier,
570
+ selectedOutputs,
571
+ }: {
572
+ tokenAmount: bigint;
573
+ tokenIdentifier?: Bech32mTokenIdentifier;
574
+ selectedOutputs?: OutputWithPreviousTransactionData[];
575
+ }): Promise<string>;
576
+
577
+ public async burnTokens(
578
+ tokenAmountOrParams:
579
+ | bigint
580
+ | {
581
+ tokenAmount: bigint;
582
+ tokenIdentifier?: Bech32mTokenIdentifier;
583
+ selectedOutputs?: OutputWithPreviousTransactionData[];
584
+ },
585
+ selectedOutputs?: OutputWithPreviousTransactionData[],
282
586
  ): Promise<string> {
587
+ let bech32mTokenIdentifier: Bech32mTokenIdentifier;
588
+ let tokenAmount: bigint;
589
+ let outputs: OutputWithPreviousTransactionData[] | undefined;
590
+
591
+ if (typeof tokenAmountOrParams === "bigint") {
592
+ tokenAmount = tokenAmountOrParams;
593
+ outputs = selectedOutputs;
594
+
595
+ const tokenIdentifiers = await this.getIssuerTokenIdentifiers();
596
+ if (tokenIdentifiers.length > 1) {
597
+ throw new SparkValidationError(
598
+ "Multiple tokens found. Use burnTokens({ tokenIdentifier, tokenAmount, selectedOutputs }) to specify which token to burn.",
599
+ {
600
+ field: "tokenIdentifier",
601
+ availableTokens: tokenIdentifiers,
602
+ },
603
+ );
604
+ }
605
+ if (tokenIdentifiers.length === 0) {
606
+ throw new SparkValidationError(
607
+ "No tokens found. Create a token first.",
608
+ );
609
+ }
610
+ bech32mTokenIdentifier = tokenIdentifiers[0];
611
+ } else {
612
+ tokenAmount = tokenAmountOrParams.tokenAmount;
613
+ outputs = tokenAmountOrParams.selectedOutputs;
614
+
615
+ if (tokenAmountOrParams.tokenIdentifier) {
616
+ const tokenIdentifiers = await this.getIssuerTokenIdentifiers();
617
+ const tokenIdentifier = tokenIdentifiers.find(
618
+ (identifier) => identifier === tokenAmountOrParams.tokenIdentifier,
619
+ );
620
+ if (!tokenIdentifier) {
621
+ throw new SparkValidationError("Token not found for this issuer", {
622
+ field: "tokenIdentifier",
623
+ value: tokenAmountOrParams.tokenIdentifier,
624
+ });
625
+ }
626
+ bech32mTokenIdentifier = tokenAmountOrParams.tokenIdentifier;
627
+ } else {
628
+ const tokenIdentifiers = await this.getIssuerTokenIdentifiers();
629
+ if (tokenIdentifiers.length === 0) {
630
+ throw new SparkValidationError(
631
+ "No tokens found. Create a token first.",
632
+ );
633
+ }
634
+ if (tokenIdentifiers.length > 1) {
635
+ throw new SparkValidationError(
636
+ "Multiple tokens found. Please specify tokenIdentifier in parameters.",
637
+ {
638
+ field: "tokenIdentifier",
639
+ availableTokens: tokenIdentifiers,
640
+ },
641
+ );
642
+ }
643
+ bech32mTokenIdentifier = tokenIdentifiers[0];
644
+ }
645
+ }
646
+
283
647
  const burnAddress = encodeSparkAddress({
284
648
  identityPublicKey: BURN_ADDRESS,
285
649
  network: this.config.getNetworkType(),
286
650
  });
287
- const issuerTokenIdentifier: Bech32mTokenIdentifier =
288
- await this.getIssuerTokenIdentifier();
289
651
 
290
652
  return await this.transferTokens({
291
- tokenIdentifier: issuerTokenIdentifier,
653
+ tokenIdentifier: bech32mTokenIdentifier,
292
654
  tokenAmount,
293
655
  receiverSparkAddress: burnAddress,
294
- selectedOutputs,
656
+ selectedOutputs: outputs,
295
657
  });
296
658
  }
297
659
 
298
660
  /**
299
661
  * Freezes tokens associated with a specific Spark address.
662
+ * @deprecated Use freezeToken({ tokenIdentifier, sparkAddress }) instead. This method will be removed in a future version.
300
663
  * @param sparkAddress - The Spark address whose tokens should be frozen
301
664
  * @returns An object containing the IDs of impacted outputs and the total amount of frozen tokens
665
+ * @throws {SparkValidationError} If multiple tokens are found for this issuer
666
+ * @throws {SparkValidationError} If no tokens are found for this issuer
302
667
  */
303
668
  public async freezeTokens(
304
669
  sparkAddress: string,
670
+ ): Promise<{ impactedOutputIds: string[]; impactedTokenAmount: bigint }>;
671
+
672
+ /**
673
+ * Freezes tokens associated with a specific Spark address.
674
+ * @param params - Object containing token freezing parameters.
675
+ * @param params.tokenIdentifier - The bech32m encoded token identifier
676
+ * @param params.sparkAddress - The Spark address whose tokens should be frozen
677
+ * @returns An object containing the IDs of impacted outputs and the total amount of frozen tokens
678
+ */
679
+ public async freezeTokens({
680
+ tokenIdentifier,
681
+ sparkAddress,
682
+ }: {
683
+ tokenIdentifier: Bech32mTokenIdentifier;
684
+ sparkAddress: string;
685
+ }): Promise<{ impactedOutputIds: string[]; impactedTokenAmount: bigint }>;
686
+
687
+ public async freezeTokens(
688
+ sparkAddressOrParams:
689
+ | string
690
+ | {
691
+ tokenIdentifier: Bech32mTokenIdentifier;
692
+ sparkAddress: string;
693
+ },
305
694
  ): Promise<{ impactedOutputIds: string[]; impactedTokenAmount: bigint }> {
306
- await this.syncTokenOutputs();
695
+ let bech32mTokenIdentifier: Bech32mTokenIdentifier | undefined;
696
+ let sparkAddress: string;
697
+
698
+ if (typeof sparkAddressOrParams === "string") {
699
+ sparkAddress = sparkAddressOrParams;
700
+ bech32mTokenIdentifier = undefined;
701
+ } else {
702
+ sparkAddress = sparkAddressOrParams.sparkAddress;
703
+ bech32mTokenIdentifier = sparkAddressOrParams.tokenIdentifier;
704
+ }
705
+
307
706
  const decodedOwnerPubkey = decodeSparkAddress(
308
707
  sparkAddress,
309
708
  this.config.getNetworkType(),
310
709
  );
311
710
 
312
- const issuerTokenIdentifier = await this.getIssuerTokenIdentifier();
711
+ if (bech32mTokenIdentifier === undefined) {
712
+ const tokenIdentifiers = await this.getIssuerTokenIdentifiers();
713
+ if (tokenIdentifiers.length === 0) {
714
+ throw new SparkValidationError(
715
+ "No tokens found. Create a token first.",
716
+ );
717
+ }
718
+ if (tokenIdentifiers.length > 1) {
719
+ throw new SparkValidationError(
720
+ "Multiple tokens found. Use freezeTokens({ tokenIdentifier, sparkAddress }) instead.",
721
+ {
722
+ field: "tokenIdentifier",
723
+ availableTokens: tokenIdentifiers,
724
+ },
725
+ );
726
+ }
727
+ bech32mTokenIdentifier = tokenIdentifiers[0];
728
+ }
313
729
 
314
730
  const rawTokenIdentifier = decodeBech32mTokenIdentifier(
315
- issuerTokenIdentifier,
731
+ bech32mTokenIdentifier,
316
732
  this.config.getNetworkType(),
317
733
  ).tokenIdentifier;
318
734
 
@@ -332,22 +748,73 @@ export abstract class IssuerSparkWallet extends SparkWallet {
332
748
 
333
749
  /**
334
750
  * Unfreezes previously frozen tokens associated with a specific Spark address.
751
+ * @deprecated Use unfreezeToken({ tokenIdentifier, sparkAddress }) instead. This method will be removed in a future version.
335
752
  * @param sparkAddress - The Spark address whose tokens should be unfrozen
336
753
  * @returns An object containing the IDs of impacted outputs and the total amount of unfrozen tokens
754
+ * @throws {SparkValidationError} If multiple tokens are found for this issuer
755
+ * @throws {SparkValidationError} If no tokens are found for this issuer
337
756
  */
338
757
  public async unfreezeTokens(
339
758
  sparkAddress: string,
759
+ ): Promise<{ impactedOutputIds: string[]; impactedTokenAmount: bigint }>;
760
+
761
+ /**
762
+ * Unfreezes previously frozen tokens associated with a specific Spark address.
763
+ * @param params - Object containing token unfreezing parameters.
764
+ * @param params.tokenIdentifier - The bech32m encoded token identifier
765
+ * @param params.sparkAddress - The Spark address whose tokens should be unfrozen
766
+ * @returns An object containing the IDs of impacted outputs and the total amount of unfrozen tokens
767
+ * @throws {SparkValidationError} If multiple tokens are found for this issuer
768
+ * @throws {SparkValidationError} If no tokens are found for this issuer
769
+ */
770
+ public async unfreezeTokens({
771
+ tokenIdentifier,
772
+ sparkAddress,
773
+ }: {
774
+ tokenIdentifier: Bech32mTokenIdentifier;
775
+ sparkAddress: string;
776
+ }): Promise<{ impactedOutputIds: string[]; impactedTokenAmount: bigint }>;
777
+
778
+ public async unfreezeTokens(
779
+ sparkAddressOrParams:
780
+ | string
781
+ | {
782
+ tokenIdentifier: Bech32mTokenIdentifier;
783
+ sparkAddress: string;
784
+ },
340
785
  ): Promise<{ impactedOutputIds: string[]; impactedTokenAmount: bigint }> {
341
- await this.syncTokenOutputs();
786
+ let bech32mTokenIdentifier: Bech32mTokenIdentifier | undefined;
787
+ let sparkAddress: string;
788
+
789
+ if (typeof sparkAddressOrParams === "string") {
790
+ sparkAddress = sparkAddressOrParams;
791
+ bech32mTokenIdentifier = undefined;
792
+ } else {
793
+ sparkAddress = sparkAddressOrParams.sparkAddress;
794
+ bech32mTokenIdentifier = sparkAddressOrParams.tokenIdentifier;
795
+ }
796
+
797
+ if (bech32mTokenIdentifier === undefined) {
798
+ const tokenIdentifiers = await this.getIssuerTokenIdentifiers();
799
+ if (tokenIdentifiers.length > 1) {
800
+ throw new SparkValidationError(
801
+ "Multiple tokens found. Use unfreezeTokens({ tokenIdentifier, sparkAddress }) instead.",
802
+ {
803
+ field: "tokenIdentifier",
804
+ availableTokens: tokenIdentifiers,
805
+ },
806
+ );
807
+ }
808
+ bech32mTokenIdentifier = tokenIdentifiers[0];
809
+ }
810
+
342
811
  const decodedOwnerPubkey = decodeSparkAddress(
343
812
  sparkAddress,
344
813
  this.config.getNetworkType(),
345
814
  );
346
815
 
347
- const issuerTokenIdentifier = await this.getIssuerTokenIdentifier();
348
-
349
816
  const rawTokenIdentifier = decodeBech32mTokenIdentifier(
350
- issuerTokenIdentifier,
817
+ bech32mTokenIdentifier,
351
818
  this.config.getNetworkType(),
352
819
  ).tokenIdentifier;
353
820
 
@@ -421,8 +888,11 @@ type IssuerSparkWalletFunctionKeys = Extract<
421
888
 
422
889
  const PUBLIC_ISSUER_SPARK_WALLET_METHODS = [
423
890
  "getIssuerTokenBalance",
891
+ "getIssuerTokenBalances",
424
892
  "getIssuerTokenMetadata",
893
+ "getIssuerTokensMetadata",
425
894
  "getIssuerTokenIdentifier",
895
+ "getIssuerTokenIdentifiers",
426
896
  "createToken",
427
897
  "mintTokens",
428
898
  "burnTokens",