mainnet-js 1.0.18 → 1.1.1

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.
@@ -110,6 +110,8 @@ describe(`Test cashtokens`, () => {
110
110
  expect(newTokenUtxos.length).toBe(2);
111
111
  expect(await alice.getTokenBalance(tokenId)).toBe(75);
112
112
  expect(await bob.getTokenBalance(tokenId)).toBe(25);
113
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(0);
114
+ expect((await bob.getAllNftTokenBalances())[tokenId] || 0).toBe(0);
113
115
  });
114
116
 
115
117
  test("Test NFT cashtoken genesis and sending", async () => {
@@ -167,7 +169,7 @@ describe(`Test cashtokens`, () => {
167
169
  commitment: "abcd02",
168
170
  }),
169
171
  ])
170
- ).rejects.toThrow("Can not change the commitment of an immutable token");
172
+ ).rejects.toThrow("No suitable token utxos available to send token");
171
173
  });
172
174
 
173
175
  test("Test mutable NFT cashtoken genesis and mutation", async () => {
@@ -615,6 +617,192 @@ describe(`Test cashtokens`, () => {
615
617
  expect(tokenUtxos2.length).toBe(1);
616
618
  });
617
619
 
620
+ test("Test minting NFTs not burn tokens", async () => {
621
+ const alice = await RegTestWallet.fromId(process.env.ALICE_ID!);
622
+ const bob = await RegTestWallet.newRandom();
623
+ const charlie = await RegTestWallet.newRandom();
624
+
625
+ // prepare inputs for two token geneses
626
+ await alice.send({ cashaddr: bob.cashaddr!, value: 10000, unit: "sat" });
627
+
628
+ const genesisResponse = await bob.tokenGenesis({
629
+ capability: "minting",
630
+ commitment: "",
631
+ });
632
+
633
+ const tokenId = genesisResponse.tokenIds![0];
634
+
635
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
636
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(1);
637
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(1);
638
+
639
+ await bob.tokenMint(tokenId, {
640
+ capability: "none",
641
+ commitment: "0a",
642
+ });
643
+
644
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
645
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(2);
646
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(2);
647
+
648
+ await bob.send(
649
+ new TokenSendRequest({
650
+ tokenId: tokenId,
651
+ capability: "none",
652
+ commitment: "0a",
653
+ cashaddr: charlie.cashaddr!,
654
+ })
655
+ );
656
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
657
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(1);
658
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(1);
659
+
660
+ expect(await charlie.getTokenBalance(tokenId)).toBe(0);
661
+ expect(await charlie.getNftTokenBalance(tokenId)).toBe(1);
662
+ expect((await charlie.getTokenUtxos(tokenId)).length).toBe(1);
663
+ });
664
+
665
+ test("Test sending NFTs after burning minting token", async () => {
666
+ const alice = await RegTestWallet.fromId(process.env.ALICE_ID!);
667
+ const bob = await RegTestWallet.newRandom();
668
+ const charlie = await RegTestWallet.newRandom();
669
+
670
+ // prepare inputs for two token geneses
671
+ await alice.send({ cashaddr: bob.cashaddr!, value: 10000, unit: "sat" });
672
+
673
+ const genesisResponse = await bob.tokenGenesis({
674
+ capability: "minting",
675
+ commitment: "",
676
+ });
677
+
678
+ const tokenId = genesisResponse.tokenIds![0];
679
+
680
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
681
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(1);
682
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(1);
683
+
684
+ await bob.tokenMint(tokenId, {
685
+ capability: "none",
686
+ commitment: "0a",
687
+ });
688
+
689
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
690
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(2);
691
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(2);
692
+
693
+ await bob.tokenMint(tokenId, {
694
+ capability: "none",
695
+ commitment: "0b",
696
+ });
697
+
698
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
699
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(3);
700
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(3);
701
+
702
+ await bob.tokenBurn({
703
+ tokenId: tokenId,
704
+ capability: "minting",
705
+ commitment: "",
706
+ });
707
+
708
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
709
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(2);
710
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(2);
711
+
712
+ await bob.send(
713
+ new TokenSendRequest({
714
+ tokenId: tokenId,
715
+ capability: "none",
716
+ commitment: "0a",
717
+ cashaddr: charlie.cashaddr!,
718
+ })
719
+ );
720
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
721
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(1);
722
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(1);
723
+
724
+ expect(await charlie.getTokenBalance(tokenId)).toBe(0);
725
+ expect(await charlie.getNftTokenBalance(tokenId)).toBe(1);
726
+ expect((await charlie.getTokenUtxos(tokenId)).length).toBe(1);
727
+
728
+ await bob.send(
729
+ new TokenSendRequest({
730
+ tokenId: tokenId,
731
+ capability: "none",
732
+ commitment: "0b",
733
+ cashaddr: charlie.cashaddr!,
734
+ })
735
+ );
736
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
737
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(0);
738
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(0);
739
+
740
+ expect(await charlie.getTokenBalance(tokenId)).toBe(0);
741
+ expect(await charlie.getNftTokenBalance(tokenId)).toBe(2);
742
+ expect((await charlie.getTokenUtxos(tokenId)).length).toBe(2);
743
+ });
744
+
745
+ test("Test sending NFTs with empty commitment", async () => {
746
+ const alice = await RegTestWallet.fromId(process.env.ALICE_ID!);
747
+ const bob = await RegTestWallet.newRandom();
748
+ const charlie = await RegTestWallet.newRandom();
749
+
750
+ // prepare inputs for two token geneses
751
+ await alice.send({ cashaddr: bob.cashaddr!, value: 10000, unit: "sat" });
752
+
753
+ const genesisResponse = await bob.tokenGenesis({
754
+ capability: "minting",
755
+ commitment: "00",
756
+ });
757
+
758
+ const tokenId = genesisResponse.tokenIds![0];
759
+
760
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
761
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(1);
762
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(1);
763
+
764
+ await bob.tokenMint(tokenId, {
765
+ capability: "none",
766
+ commitment: "0a",
767
+ });
768
+
769
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
770
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(2);
771
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(2);
772
+
773
+ await bob.tokenMint(tokenId, {
774
+ capability: "none",
775
+ commitment: "0b",
776
+ });
777
+
778
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
779
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(3);
780
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(3);
781
+
782
+ await bob.tokenBurn({
783
+ tokenId: tokenId,
784
+ capability: "minting",
785
+ commitment: "00",
786
+ });
787
+
788
+ expect(await bob.getTokenBalance(tokenId)).toBe(0);
789
+ expect(await bob.getNftTokenBalance(tokenId)).toBe(2);
790
+ expect((await bob.getTokenUtxos(tokenId)).length).toBe(2);
791
+
792
+ await expect(
793
+ bob.send(
794
+ new TokenSendRequest({
795
+ tokenId: tokenId,
796
+ capability: "none",
797
+ commitment: "",
798
+ cashaddr: charlie.cashaddr!,
799
+ })
800
+ )
801
+ ).rejects.toThrow(
802
+ "No suitable token utxos available to send token with id"
803
+ );
804
+ });
805
+
618
806
  test("Test enforcing token addresses", async () => {
619
807
  const bob = await RegTestWallet.newRandom();
620
808
 
@@ -31,21 +31,21 @@ test("Should throw on non-existent transaction and invalid hash", async () => {
31
31
 
32
32
  test("Should get raw transaction", async () => {
33
33
  let wallet = await RegTestWallet.fromId(process.env.ALICE_ID!);
34
- const utxo = (await wallet.getUtxos()).utxos![0];
34
+ const utxo = (await wallet.getUtxos())[0];
35
35
  const transaction = (await wallet.provider!.getRawTransactionObject(
36
- utxo.txId
36
+ utxo.txid
37
37
  )) as ElectrumRawTransaction;
38
38
  expect((await wallet.util.decodeTransaction(transaction.hash)).hash).toBe(
39
- utxo.txId
39
+ utxo.txid
40
40
  );
41
41
  expect((await wallet.util.decodeTransaction(transaction.hex)).txid).toBe(
42
- utxo.txId
42
+ utxo.txid
43
43
  );
44
44
 
45
45
  // test static accessor
46
46
  expect(
47
47
  (await RegTestWallet.util.decodeTransaction(transaction.hex)).txid
48
- ).toBe(utxo.txId);
48
+ ).toBe(utxo.txid);
49
49
  });
50
50
 
51
51
  test("Should decode a transaction from fist block", async () => {
package/src/wallet/Wif.ts CHANGED
@@ -33,6 +33,7 @@ import {
33
33
  } from "./interface.js";
34
34
 
35
35
  import {
36
+ fromUtxoId,
36
37
  OpReturnData,
37
38
  SendRequest,
38
39
  SendRequestArray,
@@ -42,8 +43,6 @@ import {
42
43
  TokenGenesisRequest,
43
44
  TokenMintRequest,
44
45
  TokenSendRequest,
45
- UtxoItem,
46
- UtxoResponse,
47
46
  XPubKey,
48
47
  } from "./model.js";
49
48
 
@@ -659,14 +658,7 @@ export class Wallet extends BaseWallet {
659
658
  if (!this.cashaddr) {
660
659
  throw Error("Attempted to get utxos without an address");
661
660
  }
662
- let utxos = await this.getAddressUtxos(this.cashaddr);
663
- let resp = new UtxoResponse();
664
- resp.utxos = await Promise.all(
665
- utxos.map(async (o: UtxoI) => {
666
- return UtxoItem.fromElectrum(o);
667
- })
668
- );
669
- return resp;
661
+ return await this.getAddressUtxos(this.cashaddr);
670
662
  }
671
663
 
672
664
  // gets wallet balance in sats, bch and usd
@@ -882,8 +874,8 @@ export class Wallet extends BaseWallet {
882
874
  // get inputs
883
875
  let utxos: UtxoI[];
884
876
  if (params.options && params.options.utxoIds) {
885
- utxos = params.options.utxoIds.map((utxoId) =>
886
- UtxoItem.fromId(utxoId).asElectrum()
877
+ utxos = params.options.utxoIds.map((utxoId: UtxoI | string) =>
878
+ typeof utxoId === "string" ? fromUtxoId(utxoId) : utxoId
887
879
  );
888
880
  } else {
889
881
  utxos = (await this.getAddressUtxos(this.cashaddr)).filter(
@@ -1117,10 +1109,8 @@ export class Wallet extends BaseWallet {
1117
1109
  // get inputs from options or query all inputs
1118
1110
  let utxos: UtxoI[];
1119
1111
  if (options && options.utxoIds) {
1120
- utxos = options.utxoIds.map((utxoId) =>
1121
- typeof utxoId === "string"
1122
- ? UtxoItem.fromId(utxoId).asElectrum()
1123
- : utxoId
1112
+ utxos = options.utxoIds.map((utxoId: UtxoI | string) =>
1113
+ typeof utxoId === "string" ? fromUtxoId(utxoId) : utxoId
1124
1114
  );
1125
1115
  } else {
1126
1116
  utxos = await this.getAddressUtxos(this.cashaddr);
@@ -1491,6 +1481,10 @@ export class Wallet extends BaseWallet {
1491
1481
  mintRequests: TokenMintRequest | Array<TokenMintRequest>,
1492
1482
  deductTokenAmount: boolean = false
1493
1483
  ): Promise<SendResponse> {
1484
+ if (tokenId?.length !== 64) {
1485
+ throw Error(`Invalid tokenId supplied: ${tokenId}`);
1486
+ }
1487
+
1494
1488
  if (!Array.isArray(mintRequests)) {
1495
1489
  mintRequests = [mintRequests];
1496
1490
  }
@@ -1560,6 +1554,10 @@ export class Wallet extends BaseWallet {
1560
1554
  burnRequest: TokenBurnRequest,
1561
1555
  message?: string
1562
1556
  ): Promise<SendResponse> {
1557
+ if (burnRequest.tokenId?.length !== 64) {
1558
+ throw Error(`Invalid tokenId supplied: ${burnRequest.tokenId}`);
1559
+ }
1560
+
1563
1561
  const utxos = await this.getAddressUtxos(this.cashaddr!);
1564
1562
  const tokenUtxos = utxos.filter(
1565
1563
  (val) =>
@@ -1651,7 +1649,9 @@ export class Wallet extends BaseWallet {
1651
1649
  * @returns {number} fungible token balance
1652
1650
  */
1653
1651
  public async getTokenBalance(tokenId: string): Promise<number> {
1654
- const utxos = await this.getAddressUtxos(this.cashaddr!);
1652
+ const utxos = (await this.getTokenUtxos(tokenId)).filter(
1653
+ (val) => val.token?.amount
1654
+ );
1655
1655
  return sumTokenAmounts(utxos, tokenId);
1656
1656
  }
1657
1657
 
@@ -1663,7 +1663,9 @@ export class Wallet extends BaseWallet {
1663
1663
  * @returns {number} non-fungible token balance
1664
1664
  */
1665
1665
  public async getNftTokenBalance(tokenId: string): Promise<number> {
1666
- const utxos = await this.getTokenUtxos(tokenId);
1666
+ const utxos = (await this.getTokenUtxos(tokenId)).filter(
1667
+ (val) => val.token?.commitment !== undefined
1668
+ );
1667
1669
  return utxos.length;
1668
1670
  }
1669
1671
 
@@ -1671,9 +1673,11 @@ export class Wallet extends BaseWallet {
1671
1673
  * getAllTokenBalances Gets all fungible token balances in this wallet
1672
1674
  * @returns {Object} a map [tokenId => balance] for all tokens in this wallet
1673
1675
  */
1674
- public async getAllTokenBalances(): Promise<Object> {
1676
+ public async getAllTokenBalances(): Promise<{ [tokenId: string]: number }> {
1675
1677
  const result = {};
1676
- const utxos = await this.getTokenUtxos();
1678
+ const utxos = (await this.getTokenUtxos()).filter(
1679
+ (val) => val.token?.amount
1680
+ );
1677
1681
  for (const utxo of utxos) {
1678
1682
  if (!result[utxo.token!.tokenId]) {
1679
1683
  result[utxo.token!.tokenId] = 0;
@@ -1687,9 +1691,13 @@ export class Wallet extends BaseWallet {
1687
1691
  * getAllNftTokenBalances Gets all non-fungible token (NFT) balances in this wallet
1688
1692
  * @returns {Object} a map [tokenId => balance] for all NFTs in this wallet
1689
1693
  */
1690
- public async getAllNftTokenBalances(): Promise<Object> {
1694
+ public async getAllNftTokenBalances(): Promise<{
1695
+ [tokenId: string]: number;
1696
+ }> {
1691
1697
  const result = {};
1692
- const utxos = await this.getTokenUtxos();
1698
+ const utxos = (await this.getTokenUtxos()).filter(
1699
+ (val) => val.token?.commitment !== undefined
1700
+ );
1693
1701
  for (const utxo of utxos) {
1694
1702
  if (!result[utxo.token!.tokenId]) {
1695
1703
  result[utxo.token!.tokenId] = 0;
@@ -1,32 +1,27 @@
1
1
  import { UtxoI } from "../interface";
2
- import { UtxoItem } from "./model";
2
+ import { fromUtxoId, toUtxoId } from "./model";
3
3
 
4
4
  test("Should serialize utxo", () => {
5
- let utxo = new UtxoItem({
6
- txId: "this",
7
- index: 42,
8
- value: 1,
5
+ const utxo = toUtxoId({
6
+ txid: "this",
7
+ vout: 42,
8
+ satoshis: 1,
9
9
  }).toString();
10
- expect(utxo.toString()).toBe("this:42:1");
10
+ expect(utxo).toBe("this:42:1");
11
11
  });
12
12
 
13
13
  test("Should deserialize utxo", () => {
14
- let utxo = UtxoItem.fromId("this:42:1");
15
- expect(utxo!.txId).toBe("this");
16
- expect(utxo!.index).toBe(42);
17
- expect(utxo!.value).toBe(1);
18
- expect(utxo!.toString()).toBe("this:42:1");
14
+ const utxo = fromUtxoId("this:42:1");
15
+ expect(utxo.txid).toBe("this");
16
+ expect(utxo.vout).toBe(42);
17
+ expect(utxo.satoshis).toBe(1);
19
18
  });
20
19
 
21
20
  test("Should deserialize utxo", () => {
22
- let u = {
21
+ const u = {
23
22
  txid: "this",
24
23
  vout: 42,
25
24
  satoshis: 1,
26
25
  } as UtxoI;
27
- let utxo = UtxoItem.fromElectrum(u);
28
- expect(utxo!.txId).toBe("this");
29
- expect(utxo!.index).toBe(42);
30
- expect(utxo!.value).toBe(1);
31
- expect(utxo!.toString()).toBe("this:42:1");
26
+ expect(toUtxoId(u)).toBe("this:42:1");
32
27
  });
@@ -312,60 +312,6 @@ export class OpReturnData {
312
312
 
313
313
  export type SendRequestArray = Array<string | number | UnitEnum | Buffer>;
314
314
 
315
- export class UtxoItem {
316
- index: number;
317
- value: number;
318
- utxoId: string;
319
- txId: string;
320
-
321
- constructor({
322
- index,
323
- value,
324
- txId,
325
- }: {
326
- index: number;
327
- value: number;
328
- txId: string;
329
- }) {
330
- this.value = value;
331
- this.txId = txId;
332
- this.index = index;
333
- this.utxoId = this.toString();
334
- }
335
-
336
- public toString() {
337
- return [this.txId, this.index, this.value].join(DELIMITER);
338
- }
339
-
340
- public static fromId(utxoId: string) {
341
- let [txid, vout, satoshis] = utxoId.split(DELIMITER);
342
- return new this({
343
- txId: txid,
344
- index: parseInt(vout),
345
- value: parseInt(satoshis),
346
- });
347
- }
348
- public static fromElectrum(u: UtxoI) {
349
- return new this({
350
- txId: u.txid,
351
- index: u.vout,
352
- value: u.satoshis,
353
- });
354
- }
355
-
356
- public asElectrum(): UtxoI {
357
- return {
358
- txid: this.txId,
359
- vout: this.index,
360
- satoshis: this.value,
361
- } as UtxoI;
362
- }
363
- }
364
-
365
- export class UtxoResponse {
366
- "utxos"?: Array<UtxoItem>;
367
- }
368
-
369
315
  export class SendResponse {
370
316
  txId?: string;
371
317
  balance?: BalanceResponse;
@@ -408,3 +354,35 @@ export class XPubKey {
408
354
  };
409
355
  }
410
356
  }
357
+
358
+ export const fromUtxoId = (utxoId: string): UtxoI => {
359
+ const [txid, vout, satoshis, tokenId, amount, capability, commitment] =
360
+ utxoId.split(DELIMITER);
361
+ return {
362
+ satoshis: satoshis ? parseInt(satoshis) : 0,
363
+ vout: parseInt(vout),
364
+ txid,
365
+ token: tokenId
366
+ ? {
367
+ tokenId,
368
+ amount: parseInt(amount),
369
+ capability: capability || undefined,
370
+ commitment: commitment || undefined,
371
+ }
372
+ : undefined,
373
+ } as UtxoI;
374
+ };
375
+
376
+ export const toUtxoId = (utxo: UtxoI): string => {
377
+ return [
378
+ utxo.txid,
379
+ utxo.vout,
380
+ utxo.satoshis,
381
+ utxo.token?.tokenId,
382
+ utxo.token?.amount,
383
+ utxo.token?.capability,
384
+ utxo.token?.commitment,
385
+ ]
386
+ .join(DELIMITER)
387
+ .replace(/:+$/, "");
388
+ };