@buildonspark/issuer-sdk 0.0.78 → 0.0.80
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 +18 -0
- package/dist/{chunk-5MYKP7NN.js → chunk-7B4B24XF.js} +4 -0
- package/dist/index.cjs +189 -24
- package/dist/index.d.cts +31 -2
- package/dist/index.d.ts +31 -2
- package/dist/index.js +185 -16
- package/dist/proto/spark.js +1 -1
- package/package.json +4 -4
- package/src/issuer-wallet/issuer-spark-wallet.ts +103 -10
- package/src/services/token-transactions.ts +36 -4
- package/src/tests/integration/spark.test.ts +64 -18
- package/src/tests/stress/transfers.test.ts +13 -6
- package/src/tests/token-create.test.ts +85 -0
- package/src/utils/create-validation.ts +92 -0
package/dist/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
Buffer
|
|
3
|
+
} from "./chunk-7B4B24XF.js";
|
|
2
4
|
|
|
3
5
|
// src/issuer-wallet/issuer-spark-wallet.ts
|
|
4
6
|
import { TokenPubkey, TokenPubkeyAnnouncement } from "@buildonspark/lrc20-sdk";
|
|
5
7
|
import {
|
|
6
8
|
NetworkError as NetworkError2,
|
|
7
9
|
SparkWallet,
|
|
8
|
-
ValidationError as
|
|
10
|
+
ValidationError as ValidationError3
|
|
9
11
|
} from "@buildonspark/spark-sdk";
|
|
10
12
|
import { isNode } from "@lightsparkdev/core";
|
|
11
13
|
import {
|
|
@@ -157,20 +159,21 @@ var IssuerTokenTransactionService = class extends TokenTransactionService {
|
|
|
157
159
|
sparkOperatorIdentityPublicKeys: super.collectOperatorIdentityPublicKeys()
|
|
158
160
|
};
|
|
159
161
|
}
|
|
160
|
-
async constructMintTokenTransaction(
|
|
162
|
+
async constructMintTokenTransaction(rawTokenIdentifierBytes, issuerTokenPublicKey, tokenAmount) {
|
|
161
163
|
return {
|
|
162
164
|
version: 1,
|
|
163
165
|
network: this.config.getNetworkProto(),
|
|
164
166
|
tokenInputs: {
|
|
165
167
|
$case: "mintInput",
|
|
166
168
|
mintInput: {
|
|
167
|
-
issuerPublicKey:
|
|
169
|
+
issuerPublicKey: issuerTokenPublicKey,
|
|
170
|
+
tokenIdentifier: rawTokenIdentifierBytes
|
|
168
171
|
}
|
|
169
172
|
},
|
|
170
173
|
tokenOutputs: [
|
|
171
174
|
{
|
|
172
|
-
ownerPublicKey:
|
|
173
|
-
|
|
175
|
+
ownerPublicKey: issuerTokenPublicKey,
|
|
176
|
+
tokenIdentifier: rawTokenIdentifierBytes,
|
|
174
177
|
tokenAmount: numberToBytesBE(tokenAmount, 16)
|
|
175
178
|
}
|
|
176
179
|
],
|
|
@@ -179,10 +182,105 @@ var IssuerTokenTransactionService = class extends TokenTransactionService {
|
|
|
179
182
|
expiryTime: void 0
|
|
180
183
|
};
|
|
181
184
|
}
|
|
185
|
+
async constructCreateTokenTransaction(tokenPublicKey, tokenName, tokenTicker, decimals, maxSupply, isFreezable) {
|
|
186
|
+
return {
|
|
187
|
+
version: 1,
|
|
188
|
+
network: this.config.getNetworkProto(),
|
|
189
|
+
tokenInputs: {
|
|
190
|
+
$case: "createInput",
|
|
191
|
+
createInput: {
|
|
192
|
+
issuerPublicKey: tokenPublicKey,
|
|
193
|
+
tokenName,
|
|
194
|
+
tokenTicker,
|
|
195
|
+
decimals,
|
|
196
|
+
maxSupply: numberToBytesBE(maxSupply, 16),
|
|
197
|
+
isFreezable
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
tokenOutputs: [],
|
|
201
|
+
clientCreatedTimestamp: /* @__PURE__ */ new Date(),
|
|
202
|
+
sparkOperatorIdentityPublicKeys: super.collectOperatorIdentityPublicKeys(),
|
|
203
|
+
expiryTime: void 0
|
|
204
|
+
};
|
|
205
|
+
}
|
|
182
206
|
};
|
|
183
207
|
|
|
184
208
|
// src/issuer-wallet/issuer-spark-wallet.ts
|
|
185
209
|
import { NotImplementedError } from "@buildonspark/spark-sdk";
|
|
210
|
+
|
|
211
|
+
// src/utils/create-validation.ts
|
|
212
|
+
import { ValidationError as ValidationError2 } from "@buildonspark/spark-sdk";
|
|
213
|
+
function isNfcNormalized(value) {
|
|
214
|
+
return value.normalize("NFC") === value;
|
|
215
|
+
}
|
|
216
|
+
var MIN_NAME_SIZE = 3;
|
|
217
|
+
var MAX_NAME_SIZE = 20;
|
|
218
|
+
var MIN_SYMBOL_SIZE = 3;
|
|
219
|
+
var MAX_SYMBOL_SIZE = 6;
|
|
220
|
+
var MAX_DECIMALS = 255;
|
|
221
|
+
var MAXIMUM_MAX_SUPPLY = (1n << 128n) - 1n;
|
|
222
|
+
function validateTokenParameters(tokenName, tokenTicker, decimals, maxSupply) {
|
|
223
|
+
if (!isNfcNormalized(tokenName)) {
|
|
224
|
+
throw new ValidationError2("Token name must be NFC-normalised UTF-8", {
|
|
225
|
+
field: "tokenName",
|
|
226
|
+
value: tokenName,
|
|
227
|
+
expected: "NFC normalised string"
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (!isNfcNormalized(tokenTicker)) {
|
|
231
|
+
throw new ValidationError2("Token ticker must be NFC-normalised UTF-8", {
|
|
232
|
+
field: "tokenTicker",
|
|
233
|
+
value: tokenTicker,
|
|
234
|
+
expected: "NFC normalised string"
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const nameBytes = Buffer.from(tokenName, "utf-8").length;
|
|
238
|
+
if (nameBytes < MIN_NAME_SIZE || nameBytes > MAX_NAME_SIZE) {
|
|
239
|
+
throw new ValidationError2(
|
|
240
|
+
`Token name must be between ${MIN_NAME_SIZE} and ${MAX_NAME_SIZE} bytes`,
|
|
241
|
+
{
|
|
242
|
+
field: "tokenName",
|
|
243
|
+
value: tokenName,
|
|
244
|
+
actualLength: nameBytes,
|
|
245
|
+
expected: `>=${MIN_NAME_SIZE} and <=${MAX_NAME_SIZE}`
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
const tickerBytes = Buffer.from(tokenTicker, "utf-8").length;
|
|
250
|
+
if (tickerBytes < MIN_SYMBOL_SIZE || tickerBytes > MAX_SYMBOL_SIZE) {
|
|
251
|
+
throw new ValidationError2(
|
|
252
|
+
`Token ticker must be between ${MIN_SYMBOL_SIZE} and ${MAX_SYMBOL_SIZE} bytes`,
|
|
253
|
+
{
|
|
254
|
+
field: "tokenTicker",
|
|
255
|
+
value: tokenTicker,
|
|
256
|
+
actualLength: tickerBytes,
|
|
257
|
+
expected: `>=${MIN_SYMBOL_SIZE} and <=${MAX_SYMBOL_SIZE}`
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
if (!Number.isSafeInteger(decimals) || decimals < 0 || decimals > MAX_DECIMALS) {
|
|
262
|
+
throw new ValidationError2(
|
|
263
|
+
`Decimals must be an integer between 0 and ${MAX_DECIMALS}`,
|
|
264
|
+
{
|
|
265
|
+
field: "decimals",
|
|
266
|
+
value: decimals,
|
|
267
|
+
expected: `>=0 and <=${MAX_DECIMALS}`
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (maxSupply < 0n || maxSupply > MAXIMUM_MAX_SUPPLY) {
|
|
272
|
+
throw new ValidationError2(`maxSupply must be between 0 and 2^128-1`, {
|
|
273
|
+
field: "maxSupply",
|
|
274
|
+
value: maxSupply.toString(),
|
|
275
|
+
expected: `>=0 and <=${MAXIMUM_MAX_SUPPLY.toString()}`
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/issuer-wallet/issuer-spark-wallet.ts
|
|
281
|
+
import {
|
|
282
|
+
encodeBech32mTokenIdentifier
|
|
283
|
+
} from "@buildonspark/spark-sdk";
|
|
186
284
|
var BURN_ADDRESS = "02".repeat(33);
|
|
187
285
|
var IssuerSparkWallet = class _IssuerSparkWallet extends SparkWallet {
|
|
188
286
|
issuerTokenTransactionService;
|
|
@@ -218,6 +316,10 @@ var IssuerSparkWallet = class _IssuerSparkWallet extends SparkWallet {
|
|
|
218
316
|
"SparkIssuerWallet.getIssuerTokenMetadata",
|
|
219
317
|
this.getIssuerTokenMetadata.bind(this)
|
|
220
318
|
);
|
|
319
|
+
this.getIssuerTokenIdentifier = this.wrapWithOtelSpan(
|
|
320
|
+
"SparkIssuerWallet.getIssuerTokenIdentifier",
|
|
321
|
+
this.getIssuerTokenIdentifier.bind(this)
|
|
322
|
+
);
|
|
221
323
|
this.mintTokens = this.wrapWithOtelSpan(
|
|
222
324
|
"SparkIssuerWallet.mintTokens",
|
|
223
325
|
this.mintTokens.bind(this)
|
|
@@ -266,22 +368,28 @@ var IssuerSparkWallet = class _IssuerSparkWallet extends SparkWallet {
|
|
|
266
368
|
);
|
|
267
369
|
if (!balanceObj.tokenBalances || issuerBalance === void 0) {
|
|
268
370
|
return {
|
|
371
|
+
tokenIdentifier: void 0,
|
|
269
372
|
balance: 0n
|
|
270
373
|
};
|
|
271
374
|
}
|
|
272
375
|
return {
|
|
376
|
+
tokenIdentifier: issuerBalance[0] ?? void 0,
|
|
273
377
|
balance: issuerBalance[1].balance
|
|
274
378
|
};
|
|
275
379
|
}
|
|
276
380
|
/**
|
|
277
|
-
* Retrieves
|
|
381
|
+
* Retrieves information about the issuer's token.
|
|
278
382
|
* @returns An object containing token information including public key, name, symbol, decimals, max supply, and freeze status
|
|
279
383
|
* @throws {NetworkError} If the token metadata cannot be retrieved
|
|
280
384
|
*/
|
|
281
385
|
async getIssuerTokenMetadata() {
|
|
282
386
|
const issuerPublicKey = await super.getIdentityPublicKey();
|
|
283
|
-
|
|
284
|
-
|
|
387
|
+
const tokenMetadata = this.tokenMetadata;
|
|
388
|
+
const cachedIssuerTokenMetadata = [...tokenMetadata.entries()].find(
|
|
389
|
+
([, metadata]) => bytesToHex(metadata.issuerPublicKey) === issuerPublicKey
|
|
390
|
+
);
|
|
391
|
+
if (cachedIssuerTokenMetadata !== void 0) {
|
|
392
|
+
const metadata = cachedIssuerTokenMetadata[1];
|
|
285
393
|
return {
|
|
286
394
|
tokenPublicKey: bytesToHex(metadata.issuerPublicKey),
|
|
287
395
|
rawTokenIdentifier: metadata.tokenIdentifier,
|
|
@@ -300,7 +408,7 @@ var IssuerSparkWallet = class _IssuerSparkWallet extends SparkWallet {
|
|
|
300
408
|
issuerPublicKeys: Array.of(hexToBytes2(issuerPublicKey))
|
|
301
409
|
});
|
|
302
410
|
if (response.tokenMetadata.length === 0) {
|
|
303
|
-
throw new
|
|
411
|
+
throw new ValidationError3(
|
|
304
412
|
"Token metadata not found - If a token has not yet been announced, please announce. If a token was recently announced, it is being confirmed. Try again in a few seconds.",
|
|
305
413
|
{
|
|
306
414
|
field: "tokenMetadata",
|
|
@@ -312,7 +420,11 @@ var IssuerSparkWallet = class _IssuerSparkWallet extends SparkWallet {
|
|
|
312
420
|
);
|
|
313
421
|
}
|
|
314
422
|
const metadata = response.tokenMetadata[0];
|
|
315
|
-
|
|
423
|
+
const tokenIdentifier = encodeBech32mTokenIdentifier({
|
|
424
|
+
tokenIdentifier: metadata.tokenIdentifier,
|
|
425
|
+
network: this.config.getNetworkType()
|
|
426
|
+
});
|
|
427
|
+
this.tokenMetadata.set(tokenIdentifier, metadata);
|
|
316
428
|
return {
|
|
317
429
|
tokenPublicKey: bytesToHex(metadata.issuerPublicKey),
|
|
318
430
|
rawTokenIdentifier: metadata.tokenIdentifier,
|
|
@@ -329,22 +441,74 @@ var IssuerSparkWallet = class _IssuerSparkWallet extends SparkWallet {
|
|
|
329
441
|
});
|
|
330
442
|
}
|
|
331
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* Retrieves the bech32m encoded token identifier for the issuer's token.
|
|
446
|
+
* @returns The bech32m encoded token identifier for the issuer's token
|
|
447
|
+
* @throws {NetworkError} If the token identifier cannot be retrieved
|
|
448
|
+
*/
|
|
449
|
+
async getIssuerTokenIdentifier() {
|
|
450
|
+
const tokenMetadata = await this.getIssuerTokenMetadata();
|
|
451
|
+
return encodeBech32mTokenIdentifier({
|
|
452
|
+
tokenIdentifier: tokenMetadata.rawTokenIdentifier,
|
|
453
|
+
network: this.config.getNetworkType()
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Create a new token on Spark.
|
|
458
|
+
*
|
|
459
|
+
* @param params - Object containing token creation parameters.
|
|
460
|
+
* @param params.tokenName - The name of the token.
|
|
461
|
+
* @param params.tokenTicker - The ticker symbol for the token.
|
|
462
|
+
* @param params.decimals - The number of decimal places for the token.
|
|
463
|
+
* @param params.isFreezable - Whether the token can be frozen.
|
|
464
|
+
* @param [params.maxSupply=0n] - (Optional) The maximum supply of the token. Defaults to <code>0n</code>.
|
|
465
|
+
*
|
|
466
|
+
* @returns The transaction ID of the announcement.
|
|
467
|
+
*
|
|
468
|
+
* @throws {ValidationError} If `decimals` is not a safe integer or other validation fails.
|
|
469
|
+
* @throws {NetworkError} If the announcement transaction cannot be broadcast.
|
|
470
|
+
*/
|
|
471
|
+
async createToken({
|
|
472
|
+
tokenName,
|
|
473
|
+
tokenTicker,
|
|
474
|
+
decimals,
|
|
475
|
+
isFreezable,
|
|
476
|
+
maxSupply = 0n
|
|
477
|
+
}) {
|
|
478
|
+
validateTokenParameters(tokenName, tokenTicker, decimals, maxSupply);
|
|
479
|
+
const issuerPublicKey = await super.getIdentityPublicKey();
|
|
480
|
+
const tokenTransaction = await this.issuerTokenTransactionService.constructCreateTokenTransaction(
|
|
481
|
+
hexToBytes2(issuerPublicKey),
|
|
482
|
+
tokenName,
|
|
483
|
+
tokenTicker,
|
|
484
|
+
decimals,
|
|
485
|
+
maxSupply,
|
|
486
|
+
isFreezable
|
|
487
|
+
);
|
|
488
|
+
return await this.issuerTokenTransactionService.broadcastTokenTransaction(
|
|
489
|
+
tokenTransaction
|
|
490
|
+
);
|
|
491
|
+
}
|
|
332
492
|
/**
|
|
333
493
|
* Mints new tokens
|
|
334
494
|
* @param tokenAmount - The amount of tokens to mint
|
|
335
495
|
* @returns The transaction ID of the mint operation
|
|
336
496
|
*/
|
|
337
497
|
async mintTokens(tokenAmount) {
|
|
338
|
-
const tokenPublicKey = await super.getIdentityPublicKey();
|
|
339
498
|
let tokenTransaction;
|
|
499
|
+
const issuerTokenPublicKey = await super.getIdentityPublicKey();
|
|
500
|
+
const issuerTokenPublicKeyBytes = hexToBytes2(issuerTokenPublicKey);
|
|
501
|
+
const tokenMetadata = await this.getIssuerTokenMetadata();
|
|
502
|
+
const rawTokenIdentifier = tokenMetadata.rawTokenIdentifier;
|
|
340
503
|
if (this.config.getTokenTransactionVersion() === "V0") {
|
|
341
504
|
tokenTransaction = await this.issuerTokenTransactionService.constructMintTokenTransactionV0(
|
|
342
|
-
|
|
505
|
+
issuerTokenPublicKeyBytes,
|
|
343
506
|
tokenAmount
|
|
344
507
|
);
|
|
345
508
|
} else {
|
|
346
509
|
tokenTransaction = await this.issuerTokenTransactionService.constructMintTokenTransaction(
|
|
347
|
-
|
|
510
|
+
rawTokenIdentifier,
|
|
511
|
+
issuerTokenPublicKeyBytes,
|
|
348
512
|
tokenAmount
|
|
349
513
|
);
|
|
350
514
|
}
|
|
@@ -363,8 +527,12 @@ var IssuerSparkWallet = class _IssuerSparkWallet extends SparkWallet {
|
|
|
363
527
|
identityPublicKey: BURN_ADDRESS,
|
|
364
528
|
network: this.config.getNetworkType()
|
|
365
529
|
});
|
|
530
|
+
const issuerTokenIdentifier = await this.getIssuerTokenIdentifier();
|
|
531
|
+
if (issuerTokenIdentifier === null) {
|
|
532
|
+
throw new ValidationError3("Issuer token identifier not found");
|
|
533
|
+
}
|
|
366
534
|
return await this.transferTokens({
|
|
367
|
-
|
|
535
|
+
tokenIdentifier: issuerTokenIdentifier,
|
|
368
536
|
tokenAmount,
|
|
369
537
|
receiverSparkAddress: burnAddress,
|
|
370
538
|
selectedOutputs
|
|
@@ -434,8 +602,9 @@ var IssuerSparkWallet = class _IssuerSparkWallet extends SparkWallet {
|
|
|
434
602
|
* @throws {NetworkError} If the announcement transaction cannot be broadcast
|
|
435
603
|
*/
|
|
436
604
|
async announceTokenL1(tokenName, tokenTicker, decimals, maxSupply, isFreezable, feeRateSatsPerVb = 4) {
|
|
605
|
+
validateTokenParameters(tokenName, tokenTicker, decimals, maxSupply);
|
|
437
606
|
if (!Number.isSafeInteger(decimals)) {
|
|
438
|
-
throw new
|
|
607
|
+
throw new ValidationError3("Decimals must be less than 2^53", {
|
|
439
608
|
field: "decimals",
|
|
440
609
|
value: decimals,
|
|
441
610
|
expected: "smaller or equal to " + Number.MAX_SAFE_INTEGER
|
package/dist/proto/spark.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buildonspark/issuer-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.80",
|
|
4
4
|
"description": "Spark Issuer SDK for token issuance",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -47,15 +47,15 @@
|
|
|
47
47
|
"package:checks": "yarn depcheck && yarn attw --pack . && echo \"\nPackage checks passed successfully!\"",
|
|
48
48
|
"postversion": "yarn build",
|
|
49
49
|
"test-cmd": "node --experimental-vm-modules $(yarn bin jest) --no-cache --runInBand --detectOpenHandles --forceExit",
|
|
50
|
-
"test": "
|
|
50
|
+
"test": "yarn test-cmd src/tests/*.test.ts",
|
|
51
51
|
"test:integration": "HERMETIC_TEST=true yarn test-cmd src/tests/integration/*.test.ts",
|
|
52
52
|
"test:stress": "yarn test-cmd src/tests/stress/*.test.ts",
|
|
53
53
|
"types:watch": "tsc-absolute --watch",
|
|
54
54
|
"types": "tsc"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@buildonspark/lrc20-sdk": "0.0.
|
|
58
|
-
"@buildonspark/spark-sdk": "0.1
|
|
57
|
+
"@buildonspark/lrc20-sdk": "0.0.60",
|
|
58
|
+
"@buildonspark/spark-sdk": "0.2.1",
|
|
59
59
|
"@lightsparkdev/core": "^1.4.2",
|
|
60
60
|
"@noble/curves": "^1.8.0",
|
|
61
61
|
"@scure/btc-signer": "^1.5.0",
|
|
@@ -26,6 +26,11 @@ import { IssuerTokenTransactionService } from "../services/token-transactions.js
|
|
|
26
26
|
import { TokenDistribution, IssuerTokenMetadata } from "./types.js";
|
|
27
27
|
import { NotImplementedError } from "@buildonspark/spark-sdk";
|
|
28
28
|
import { SparkSigner } from "@buildonspark/spark-sdk";
|
|
29
|
+
import { validateTokenParameters } from "../utils/create-validation.js";
|
|
30
|
+
import {
|
|
31
|
+
encodeBech32mTokenIdentifier,
|
|
32
|
+
Bech32mTokenIdentifier,
|
|
33
|
+
} from "@buildonspark/spark-sdk";
|
|
29
34
|
|
|
30
35
|
const BURN_ADDRESS = "02".repeat(33);
|
|
31
36
|
|
|
@@ -73,6 +78,10 @@ export class IssuerSparkWallet extends SparkWallet {
|
|
|
73
78
|
"SparkIssuerWallet.getIssuerTokenMetadata",
|
|
74
79
|
this.getIssuerTokenMetadata.bind(this),
|
|
75
80
|
);
|
|
81
|
+
this.getIssuerTokenIdentifier = this.wrapWithOtelSpan(
|
|
82
|
+
"SparkIssuerWallet.getIssuerTokenIdentifier",
|
|
83
|
+
this.getIssuerTokenIdentifier.bind(this),
|
|
84
|
+
);
|
|
76
85
|
this.mintTokens = this.wrapWithOtelSpan(
|
|
77
86
|
"SparkIssuerWallet.mintTokens",
|
|
78
87
|
this.mintTokens.bind(this),
|
|
@@ -116,6 +125,7 @@ export class IssuerSparkWallet extends SparkWallet {
|
|
|
116
125
|
* @returns An object containing the token balance as a bigint
|
|
117
126
|
*/
|
|
118
127
|
public async getIssuerTokenBalance(): Promise<{
|
|
128
|
+
tokenIdentifier: Bech32mTokenIdentifier | undefined;
|
|
119
129
|
balance: bigint;
|
|
120
130
|
}> {
|
|
121
131
|
const publicKey = await super.getIdentityPublicKey();
|
|
@@ -126,24 +136,31 @@ export class IssuerSparkWallet extends SparkWallet {
|
|
|
126
136
|
|
|
127
137
|
if (!balanceObj.tokenBalances || issuerBalance === undefined) {
|
|
128
138
|
return {
|
|
139
|
+
tokenIdentifier: undefined,
|
|
129
140
|
balance: 0n,
|
|
130
141
|
};
|
|
131
142
|
}
|
|
132
143
|
return {
|
|
144
|
+
tokenIdentifier: issuerBalance[0] ?? undefined,
|
|
133
145
|
balance: issuerBalance[1].balance,
|
|
134
146
|
};
|
|
135
147
|
}
|
|
136
148
|
|
|
137
149
|
/**
|
|
138
|
-
* Retrieves
|
|
150
|
+
* Retrieves information about the issuer's token.
|
|
139
151
|
* @returns An object containing token information including public key, name, symbol, decimals, max supply, and freeze status
|
|
140
152
|
* @throws {NetworkError} If the token metadata cannot be retrieved
|
|
141
153
|
*/
|
|
142
154
|
public async getIssuerTokenMetadata(): Promise<IssuerTokenMetadata> {
|
|
143
155
|
const issuerPublicKey = await super.getIdentityPublicKey();
|
|
144
|
-
|
|
145
|
-
const metadata = this.tokenMetadata.get(issuerPublicKey)!;
|
|
156
|
+
const tokenMetadata = this.tokenMetadata;
|
|
146
157
|
|
|
158
|
+
const cachedIssuerTokenMetadata = [...tokenMetadata.entries()].find(
|
|
159
|
+
([, metadata]) =>
|
|
160
|
+
bytesToHex(metadata.issuerPublicKey) === issuerPublicKey,
|
|
161
|
+
);
|
|
162
|
+
if (cachedIssuerTokenMetadata !== undefined) {
|
|
163
|
+
const metadata = cachedIssuerTokenMetadata[1];
|
|
147
164
|
return {
|
|
148
165
|
tokenPublicKey: bytesToHex(metadata.issuerPublicKey),
|
|
149
166
|
rawTokenIdentifier: metadata.tokenIdentifier,
|
|
@@ -163,7 +180,6 @@ export class IssuerSparkWallet extends SparkWallet {
|
|
|
163
180
|
const response = await sparkTokenClient.query_token_metadata({
|
|
164
181
|
issuerPublicKeys: Array.of(hexToBytes(issuerPublicKey)),
|
|
165
182
|
});
|
|
166
|
-
|
|
167
183
|
if (response.tokenMetadata.length === 0) {
|
|
168
184
|
throw new ValidationError(
|
|
169
185
|
"Token metadata not found - If a token has not yet been announced, please announce. If a token was recently announced, it is being confirmed. Try again in a few seconds.",
|
|
@@ -176,9 +192,12 @@ export class IssuerSparkWallet extends SparkWallet {
|
|
|
176
192
|
},
|
|
177
193
|
);
|
|
178
194
|
}
|
|
179
|
-
|
|
180
195
|
const metadata = response.tokenMetadata[0];
|
|
181
|
-
|
|
196
|
+
const tokenIdentifier = encodeBech32mTokenIdentifier({
|
|
197
|
+
tokenIdentifier: metadata.tokenIdentifier,
|
|
198
|
+
network: this.config.getNetworkType(),
|
|
199
|
+
});
|
|
200
|
+
this.tokenMetadata.set(tokenIdentifier, metadata);
|
|
182
201
|
|
|
183
202
|
return {
|
|
184
203
|
tokenPublicKey: bytesToHex(metadata.issuerPublicKey),
|
|
@@ -197,25 +216,91 @@ export class IssuerSparkWallet extends SparkWallet {
|
|
|
197
216
|
}
|
|
198
217
|
}
|
|
199
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Retrieves the bech32m encoded token identifier for the issuer's token.
|
|
221
|
+
* @returns The bech32m encoded token identifier for the issuer's token
|
|
222
|
+
* @throws {NetworkError} If the token identifier cannot be retrieved
|
|
223
|
+
*/
|
|
224
|
+
public async getIssuerTokenIdentifier(): Promise<Bech32mTokenIdentifier | null> {
|
|
225
|
+
const tokenMetadata = await this.getIssuerTokenMetadata();
|
|
226
|
+
|
|
227
|
+
return encodeBech32mTokenIdentifier({
|
|
228
|
+
tokenIdentifier: tokenMetadata.rawTokenIdentifier,
|
|
229
|
+
network: this.config.getNetworkType(),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Create a new token on Spark.
|
|
235
|
+
*
|
|
236
|
+
* @param params - Object containing token creation parameters.
|
|
237
|
+
* @param params.tokenName - The name of the token.
|
|
238
|
+
* @param params.tokenTicker - The ticker symbol for the token.
|
|
239
|
+
* @param params.decimals - The number of decimal places for the token.
|
|
240
|
+
* @param params.isFreezable - Whether the token can be frozen.
|
|
241
|
+
* @param [params.maxSupply=0n] - (Optional) The maximum supply of the token. Defaults to <code>0n</code>.
|
|
242
|
+
*
|
|
243
|
+
* @returns The transaction ID of the announcement.
|
|
244
|
+
*
|
|
245
|
+
* @throws {ValidationError} If `decimals` is not a safe integer or other validation fails.
|
|
246
|
+
* @throws {NetworkError} If the announcement transaction cannot be broadcast.
|
|
247
|
+
*/
|
|
248
|
+
public async createToken({
|
|
249
|
+
tokenName,
|
|
250
|
+
tokenTicker,
|
|
251
|
+
decimals,
|
|
252
|
+
isFreezable,
|
|
253
|
+
maxSupply = 0n,
|
|
254
|
+
}: {
|
|
255
|
+
tokenName: string;
|
|
256
|
+
tokenTicker: string;
|
|
257
|
+
decimals: number;
|
|
258
|
+
isFreezable: boolean;
|
|
259
|
+
maxSupply?: bigint;
|
|
260
|
+
}): Promise<string> {
|
|
261
|
+
validateTokenParameters(tokenName, tokenTicker, decimals, maxSupply);
|
|
262
|
+
|
|
263
|
+
const issuerPublicKey = await super.getIdentityPublicKey();
|
|
264
|
+
|
|
265
|
+
const tokenTransaction =
|
|
266
|
+
await this.issuerTokenTransactionService.constructCreateTokenTransaction(
|
|
267
|
+
hexToBytes(issuerPublicKey),
|
|
268
|
+
tokenName,
|
|
269
|
+
tokenTicker,
|
|
270
|
+
decimals,
|
|
271
|
+
maxSupply,
|
|
272
|
+
isFreezable,
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
return await this.issuerTokenTransactionService.broadcastTokenTransaction(
|
|
276
|
+
tokenTransaction,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
200
280
|
/**
|
|
201
281
|
* Mints new tokens
|
|
202
282
|
* @param tokenAmount - The amount of tokens to mint
|
|
203
283
|
* @returns The transaction ID of the mint operation
|
|
204
284
|
*/
|
|
205
285
|
public async mintTokens(tokenAmount: bigint): Promise<string> {
|
|
206
|
-
const tokenPublicKey = await super.getIdentityPublicKey();
|
|
207
286
|
let tokenTransaction: TokenTransactionV0 | TokenTransaction;
|
|
287
|
+
const issuerTokenPublicKey = await super.getIdentityPublicKey();
|
|
288
|
+
const issuerTokenPublicKeyBytes = hexToBytes(issuerTokenPublicKey);
|
|
289
|
+
|
|
290
|
+
const tokenMetadata = await this.getIssuerTokenMetadata();
|
|
291
|
+
const rawTokenIdentifier: Uint8Array = tokenMetadata.rawTokenIdentifier;
|
|
208
292
|
|
|
209
293
|
if (this.config.getTokenTransactionVersion() === "V0") {
|
|
210
294
|
tokenTransaction =
|
|
211
295
|
await this.issuerTokenTransactionService.constructMintTokenTransactionV0(
|
|
212
|
-
|
|
296
|
+
issuerTokenPublicKeyBytes,
|
|
213
297
|
tokenAmount,
|
|
214
298
|
);
|
|
215
299
|
} else {
|
|
216
300
|
tokenTransaction =
|
|
217
301
|
await this.issuerTokenTransactionService.constructMintTokenTransaction(
|
|
218
|
-
|
|
302
|
+
rawTokenIdentifier,
|
|
303
|
+
issuerTokenPublicKeyBytes,
|
|
219
304
|
tokenAmount,
|
|
220
305
|
);
|
|
221
306
|
}
|
|
@@ -239,8 +324,14 @@ export class IssuerSparkWallet extends SparkWallet {
|
|
|
239
324
|
identityPublicKey: BURN_ADDRESS,
|
|
240
325
|
network: this.config.getNetworkType(),
|
|
241
326
|
});
|
|
327
|
+
const issuerTokenIdentifier: Bech32mTokenIdentifier | null =
|
|
328
|
+
await this.getIssuerTokenIdentifier();
|
|
329
|
+
if (issuerTokenIdentifier === null) {
|
|
330
|
+
throw new ValidationError("Issuer token identifier not found");
|
|
331
|
+
}
|
|
332
|
+
|
|
242
333
|
return await this.transferTokens({
|
|
243
|
-
|
|
334
|
+
tokenIdentifier: issuerTokenIdentifier,
|
|
244
335
|
tokenAmount,
|
|
245
336
|
receiverSparkAddress: burnAddress,
|
|
246
337
|
selectedOutputs,
|
|
@@ -329,6 +420,8 @@ export class IssuerSparkWallet extends SparkWallet {
|
|
|
329
420
|
isFreezable: boolean,
|
|
330
421
|
feeRateSatsPerVb: number = 4.0,
|
|
331
422
|
): Promise<string> {
|
|
423
|
+
validateTokenParameters(tokenName, tokenTicker, decimals, maxSupply);
|
|
424
|
+
|
|
332
425
|
if (!Number.isSafeInteger(decimals)) {
|
|
333
426
|
throw new ValidationError("Decimals must be less than 2^53", {
|
|
334
427
|
field: "decimals",
|
|
@@ -39,7 +39,8 @@ export class IssuerTokenTransactionService extends TokenTransactionService {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
async constructMintTokenTransaction(
|
|
42
|
-
|
|
42
|
+
rawTokenIdentifierBytes: Uint8Array,
|
|
43
|
+
issuerTokenPublicKey: Uint8Array,
|
|
43
44
|
tokenAmount: bigint,
|
|
44
45
|
): Promise<TokenTransaction> {
|
|
45
46
|
return {
|
|
@@ -48,13 +49,14 @@ export class IssuerTokenTransactionService extends TokenTransactionService {
|
|
|
48
49
|
tokenInputs: {
|
|
49
50
|
$case: "mintInput",
|
|
50
51
|
mintInput: {
|
|
51
|
-
issuerPublicKey:
|
|
52
|
+
issuerPublicKey: issuerTokenPublicKey,
|
|
53
|
+
tokenIdentifier: rawTokenIdentifierBytes,
|
|
52
54
|
},
|
|
53
55
|
},
|
|
54
56
|
tokenOutputs: [
|
|
55
57
|
{
|
|
56
|
-
ownerPublicKey:
|
|
57
|
-
|
|
58
|
+
ownerPublicKey: issuerTokenPublicKey,
|
|
59
|
+
tokenIdentifier: rawTokenIdentifierBytes,
|
|
58
60
|
tokenAmount: numberToBytesBE(tokenAmount, 16),
|
|
59
61
|
},
|
|
60
62
|
],
|
|
@@ -64,4 +66,34 @@ export class IssuerTokenTransactionService extends TokenTransactionService {
|
|
|
64
66
|
expiryTime: undefined,
|
|
65
67
|
};
|
|
66
68
|
}
|
|
69
|
+
|
|
70
|
+
async constructCreateTokenTransaction(
|
|
71
|
+
tokenPublicKey: Uint8Array,
|
|
72
|
+
tokenName: string,
|
|
73
|
+
tokenTicker: string,
|
|
74
|
+
decimals: number,
|
|
75
|
+
maxSupply: bigint,
|
|
76
|
+
isFreezable: boolean,
|
|
77
|
+
): Promise<TokenTransaction> {
|
|
78
|
+
return {
|
|
79
|
+
version: 1,
|
|
80
|
+
network: this.config.getNetworkProto(),
|
|
81
|
+
tokenInputs: {
|
|
82
|
+
$case: "createInput",
|
|
83
|
+
createInput: {
|
|
84
|
+
issuerPublicKey: tokenPublicKey,
|
|
85
|
+
tokenName: tokenName,
|
|
86
|
+
tokenTicker: tokenTicker,
|
|
87
|
+
decimals: decimals,
|
|
88
|
+
maxSupply: numberToBytesBE(maxSupply, 16),
|
|
89
|
+
isFreezable: isFreezable,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
tokenOutputs: [],
|
|
93
|
+
clientCreatedTimestamp: new Date(),
|
|
94
|
+
sparkOperatorIdentityPublicKeys:
|
|
95
|
+
super.collectOperatorIdentityPublicKeys(),
|
|
96
|
+
expiryTime: undefined,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
67
99
|
}
|