@silvana-one/abi 0.1.17 → 0.1.19

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/src/nft/build.ts CHANGED
@@ -12,6 +12,14 @@ import {
12
12
  LaunchNftCollectionStandardAdminParams,
13
13
  LaunchNftCollectionAdvancedAdminParams,
14
14
  NftMintTransactionParams,
15
+ NftSellTransactionParams,
16
+ NftBuyTransactionParams,
17
+ NftTransferTransactionParams,
18
+ NftApproveTransactionParams,
19
+ NftMintParams,
20
+ NftSellParams,
21
+ NftBuyParams,
22
+ NftTransferParams,
15
23
  } from "@silvana-one/api";
16
24
  import { blockchain } from "../types.js";
17
25
  import { fetchMinaAccount } from "../fetch.js";
@@ -29,6 +37,9 @@ import {
29
37
  MintParams,
30
38
  NFTData,
31
39
  fieldToString,
40
+ TransferParams,
41
+ UInt64Option,
42
+ NFTTransactionContext,
32
43
  } from "@silvana-one/nft";
33
44
  import { tokenVerificationKeys } from "../vk/index.js";
34
45
  import {
@@ -328,6 +339,7 @@ export async function buildNftTransaction(params: {
328
339
  NftTransactionParams,
329
340
  | LaunchNftCollectionStandardAdminParams
330
341
  | LaunchNftCollectionAdvancedAdminParams
342
+ | NftMintTransactionParams
331
343
  >;
332
344
  developerAddress?: string;
333
345
  provingKey?: string;
@@ -337,6 +349,7 @@ export async function buildNftTransaction(params: {
337
349
  NftTransactionParams,
338
350
  | LaunchNftCollectionStandardAdminParams
339
351
  | LaunchNftCollectionAdvancedAdminParams
352
+ | NftMintTransactionParams
340
353
  >;
341
354
  tx: Transaction<false, false>;
342
355
  adminType: NftAdminType;
@@ -349,6 +362,484 @@ export async function buildNftTransaction(params: {
349
362
  storage: string;
350
363
  privateMetadata?: string;
351
364
  map?: IndexedMapSerialized;
365
+ }> {
366
+ const { chain, args } = params;
367
+ const { nonce, txType } = args;
368
+ if (nonce === undefined) throw new Error("Nonce is required");
369
+ if (typeof nonce !== "number") throw new Error("Nonce must be a number");
370
+ if (txType === undefined) throw new Error("Tx type is required");
371
+ if (typeof txType !== "string") throw new Error("Tx type must be a string");
372
+ if (args.sender === undefined || typeof args.sender !== "string")
373
+ throw new Error("Sender is required");
374
+ if (
375
+ args.collectionAddress === undefined ||
376
+ typeof args.collectionAddress !== "string"
377
+ )
378
+ throw new Error("Collection address is required");
379
+ if (args.nftAddress === undefined || typeof args.nftAddress !== "string")
380
+ throw new Error("NFT address is required");
381
+ const sender = PublicKey.fromBase58(args.sender);
382
+ const collectionAddress = PublicKey.fromBase58(args.collectionAddress);
383
+ if (txType === "nft:transfer" && args.nftTransferParams === undefined) {
384
+ throw new Error("NFT transfer params are required");
385
+ }
386
+ if (txType === "nft:sell" && args.nftSellParams === undefined) {
387
+ throw new Error("NFT sell params are required");
388
+ }
389
+ if (txType === "nft:buy" && args.nftBuyParams === undefined) {
390
+ throw new Error("NFT buy params are required");
391
+ }
392
+ const nftAddress = PublicKey.fromBase58(args.nftAddress);
393
+
394
+ // const offerAddress =
395
+ // "offerAddress" in args && args.offerAddress
396
+ // ? PublicKey.fromBase58(args.offerAddress)
397
+ // : undefined;
398
+ // if (
399
+ // !offerAddress &&
400
+ // (txType === "token:offer:create" ||
401
+ // txType === "token:offer:buy" ||
402
+ // txType === "token:offer:withdraw")
403
+ // )
404
+ // throw new Error("Offer address is required");
405
+
406
+ // const bidAddress =
407
+ // "bidAddress" in args && args.bidAddress
408
+ // ? PublicKey.fromBase58(args.bidAddress)
409
+ // : undefined;
410
+ // if (
411
+ // !bidAddress &&
412
+ // (txType === "token:bid:create" ||
413
+ // txType === "token:bid:sell" ||
414
+ // txType === "token:bid:withdraw")
415
+ // )
416
+ // throw new Error("Bid address is required");
417
+
418
+ const to =
419
+ "nftTransferParams" in args &&
420
+ args.nftTransferParams &&
421
+ args.nftTransferParams.to &&
422
+ typeof args.nftTransferParams.to === "string"
423
+ ? PublicKey.fromBase58(args.nftTransferParams.to)
424
+ : undefined;
425
+ if (!to && txType === "nft:transfer")
426
+ throw new Error("To address is required");
427
+
428
+ let buyer =
429
+ "nftBuyParams" in args &&
430
+ args.nftBuyParams &&
431
+ args.nftBuyParams.buyer &&
432
+ typeof args.nftBuyParams.buyer === "string"
433
+ ? PublicKey.fromBase58(args.nftBuyParams.buyer)
434
+ : undefined;
435
+ if (!buyer && txType === "nft:buy") buyer = sender;
436
+
437
+ const from =
438
+ "nftTransferParams" in args &&
439
+ args.nftTransferParams &&
440
+ args.nftTransferParams.from &&
441
+ typeof args.nftTransferParams.from === "string"
442
+ ? PublicKey.fromBase58(args.nftTransferParams.from)
443
+ : undefined;
444
+ if (!from && txType === "nft:transfer")
445
+ throw new Error("From address is required for nft:transfer");
446
+
447
+ const price =
448
+ "nftSellParams" in args
449
+ ? args.nftSellParams &&
450
+ args.nftSellParams.price &&
451
+ typeof args.nftSellParams.price === "number"
452
+ ? UInt64.from(Math.round(args.nftSellParams.price * 1_000_000_000))
453
+ : undefined
454
+ : "nftTransferParams" in args &&
455
+ args.nftTransferParams &&
456
+ args.nftTransferParams.price &&
457
+ typeof args.nftTransferParams.price === "number"
458
+ ? UInt64.from(Math.round(args.nftTransferParams.price * 1_000_000_000))
459
+ : undefined;
460
+
461
+ if (price === undefined && txType === "nft:sell")
462
+ throw new Error("Price is required for nft:sell");
463
+
464
+ await fetchMinaAccount({
465
+ publicKey: sender,
466
+ force: true,
467
+ });
468
+
469
+ if (!Mina.hasAccount(sender)) {
470
+ throw new Error("Sender does not have account");
471
+ }
472
+
473
+ const {
474
+ symbol,
475
+ adminContractAddress,
476
+ adminType,
477
+ verificationKeyHashes,
478
+ collectionName,
479
+ nftName,
480
+ storage,
481
+ metadataRoot,
482
+ } = await getNftSymbolAndAdmin({
483
+ txType,
484
+ collectionAddress,
485
+ chain,
486
+ nftAddress,
487
+ });
488
+
489
+ if (storage === undefined)
490
+ throw new Error("storage is required, but not provided");
491
+ if (metadataRoot === undefined) throw new Error("metadataRoot is required");
492
+ if (nftName === undefined) throw new Error("nftName is required");
493
+
494
+ const memo = args.memo ?? `${txType.split(":")[1]} ${symbol} ${nftName}`;
495
+ const fee = 100_000_000;
496
+ const provingKey = params.provingKey
497
+ ? PublicKey.fromBase58(params.provingKey)
498
+ : sender;
499
+ const provingFee = params.provingFee
500
+ ? UInt64.from(Math.round(params.provingFee))
501
+ : undefined;
502
+ const developerFee = args.developerFee
503
+ ? UInt64.from(Math.round(args.developerFee))
504
+ : undefined;
505
+ const developerAddress = params.developerAddress
506
+ ? PublicKey.fromBase58(params.developerAddress)
507
+ : undefined;
508
+
509
+ //const adminContract = new FungibleTokenAdmin(adminContractAddress);
510
+ const advancedAdminContract = new NFTAdvancedAdmin(adminContractAddress);
511
+ const adminContract = new NFTAdmin(adminContractAddress);
512
+ const collectionContract =
513
+ adminType === "advanced" ? AdvancedCollection : Collection;
514
+
515
+ // if (
516
+ // (txType === "token:admin:whitelist" ||
517
+ // txType === "token:bid:whitelist" ||
518
+ // txType === "token:offer:whitelist") &&
519
+ // !args.whitelist
520
+ // ) {
521
+ // throw new Error("Whitelist is required");
522
+ // }
523
+
524
+ // const whitelist =
525
+ // "whitelist" in args && args.whitelist
526
+ // ? typeof args.whitelist === "string"
527
+ // ? Whitelist.fromString(args.whitelist)
528
+ // : (await Whitelist.create({ list: args.whitelist, name: symbol }))
529
+ // .whitelist
530
+ // : Whitelist.empty();
531
+
532
+ const zkCollection = new collectionContract(collectionAddress);
533
+ const tokenId = zkCollection.deriveTokenId();
534
+
535
+ // if (
536
+ // txType === "nft:mint" &&
537
+ // adminType === "standard" &&
538
+ // adminAddress.toBase58() !== sender.toBase58()
539
+ // )
540
+ // throw new Error(
541
+ // "Invalid sender for FungibleToken mint with standard admin"
542
+ // );
543
+
544
+ // await fetchMinaAccount({
545
+ // publicKey: nftAddress,
546
+ // tokenId,
547
+ // force: (
548
+ // ["nft:transfer"] satisfies NftTransactionType[] as NftTransactionType[]
549
+ // ).includes(txType),
550
+ // });
551
+
552
+ // if (to) {
553
+ // await fetchMinaAccount({
554
+ // publicKey: to,
555
+ // force: false,
556
+ // });
557
+ // }
558
+
559
+ // if (from) {
560
+ // await fetchMinaAccount({
561
+ // publicKey: from,
562
+ // tokenId,
563
+ // force: false,
564
+ // });
565
+ // }
566
+
567
+ // if (offerAddress)
568
+ // await fetchMinaAccount({
569
+ // publicKey: offerAddress,
570
+ // tokenId,
571
+ // force: (
572
+ // [
573
+ // "token:offer:whitelist",
574
+ // "token:offer:buy",
575
+ // "token:offer:withdraw",
576
+ // ] satisfies TokenTransactionType[] as TokenTransactionType[]
577
+ // ).includes(txType),
578
+ // });
579
+ // if (bidAddress)
580
+ // await fetchMinaAccount({
581
+ // publicKey: bidAddress,
582
+ // force: (
583
+ // [
584
+ // "token:bid:whitelist",
585
+ // "token:bid:sell",
586
+ // "token:bid:withdraw",
587
+ // ] satisfies TokenTransactionType[] as TokenTransactionType[]
588
+ // ).includes(txType),
589
+ // });
590
+
591
+ // const offerContract = offerAddress
592
+ // ? new FungibleTokenOfferContract(offerAddress, tokenId)
593
+ // : undefined;
594
+
595
+ // const bidContract = bidAddress
596
+ // ? new FungibleTokenBidContract(bidAddress)
597
+ // : undefined;
598
+ // const offerContractDeployment = offerAddress
599
+ // ? new FungibleTokenOfferContract(offerAddress, tokenId)
600
+ // : undefined;
601
+ // const bidContractDeployment = bidAddress
602
+ // ? new FungibleTokenBidContract(bidAddress)
603
+ // : undefined;
604
+ const vk =
605
+ tokenVerificationKeys[chain === "mainnet" ? "mainnet" : "devnet"].vk;
606
+ if (
607
+ !vk ||
608
+ !vk.Collection ||
609
+ !vk.Collection.hash ||
610
+ !vk.Collection.data ||
611
+ !vk.AdvancedCollection ||
612
+ !vk.AdvancedCollection.hash ||
613
+ !vk.AdvancedCollection.data ||
614
+ !vk.NFT ||
615
+ !vk.NFT.hash ||
616
+ !vk.NFT.data ||
617
+ !vk.NFTAdmin ||
618
+ !vk.NFTAdmin.hash ||
619
+ !vk.NFTAdmin.data ||
620
+ !vk.NFTAdvancedAdmin ||
621
+ !vk.NFTAdvancedAdmin.hash ||
622
+ !vk.NFTAdvancedAdmin.data
623
+ )
624
+ throw new Error("Cannot get verification key from vk");
625
+
626
+ // const offerVerificationKey = FungibleTokenOfferContract._verificationKey ?? {
627
+ // hash: Field(vk.FungibleTokenOfferContract.hash),
628
+ // data: vk.FungibleTokenOfferContract.data,
629
+ // };
630
+ // const bidVerificationKey = FungibleTokenBidContract._verificationKey ?? {
631
+ // hash: Field(vk.FungibleTokenBidContract.hash),
632
+ // data: vk.FungibleTokenBidContract.data,
633
+ // };
634
+
635
+ // const isNewBidOfferAccount =
636
+ // txType === "token:offer:create" && offerAddress
637
+ // ? !Mina.hasAccount(offerAddress, tokenId)
638
+ // : txType === "token:bid:create" && bidAddress
639
+ // ? !Mina.hasAccount(bidAddress)
640
+ // : false;
641
+
642
+ // const isNewBuyAccount =
643
+ // txType === "token:offer:buy" ? !Mina.hasAccount(sender, tokenId) : false;
644
+ // let isNewSellAccount: boolean = false;
645
+ // if (txType === "token:bid:sell") {
646
+ // if (!bidAddress || !bidContract) throw new Error("Bid address is required");
647
+ // await fetchMinaAccount({
648
+ // publicKey: bidAddress,
649
+ // force: true,
650
+ // });
651
+ // const buyer = bidContract.buyer.get();
652
+ // await fetchMinaAccount({
653
+ // publicKey: buyer,
654
+ // tokenId,
655
+ // force: false,
656
+ // });
657
+ // isNewSellAccount = !Mina.hasAccount(buyer, tokenId);
658
+ // }
659
+
660
+ // if (txType === "token:burn") {
661
+ // await fetchMinaAccount({
662
+ // publicKey: sender,
663
+ // force: true,
664
+ // });
665
+ // await fetchMinaAccount({
666
+ // publicKey: sender,
667
+ // tokenId,
668
+ // force: false,
669
+ // });
670
+ // if (!Mina.hasAccount(sender, tokenId))
671
+ // throw new Error("Sender does not have tokens to burn");
672
+ // }
673
+
674
+ // const isNewTransferMintAccount =
675
+ // (txType === "token:transfer" ||
676
+ // txType === "token:airdrop" ||
677
+ // txType === "token:mint") &&
678
+ // to
679
+ // ? !Mina.hasAccount(to, tokenId)
680
+ // : false;
681
+
682
+ // const accountCreationFee =
683
+ // (isNewBidOfferAccount ? 1_000_000_000 : 0) +
684
+ // (isNewBuyAccount ? 1_000_000_000 : 0) +
685
+ // (isNewSellAccount ? 1_000_000_000 : 0) +
686
+ // (isNewTransferMintAccount ? 1_000_000_000 : 0) +
687
+ // (isToNewAccount &&
688
+ // txType === "token:mint" &&
689
+ // adminType === "advanced" &&
690
+ // advancedAdminContract.whitelist.get().isSome().toBoolean()
691
+ // ? 1_000_000_000
692
+ // : 0);
693
+ // console.log("accountCreationFee", accountCreationFee / 1_000_000_000);
694
+
695
+ // switch (txType) {
696
+ // case "token:offer:buy":
697
+ // case "token:offer:withdraw":
698
+ // case "token:offer:whitelist":
699
+ // if (offerContract === undefined)
700
+ // throw new Error("Offer contract is required");
701
+ // if (
702
+ // Mina.getAccount(
703
+ // offerContract.address,
704
+ // tokenId
705
+ // ).zkapp?.verificationKey?.hash.toJSON() !==
706
+ // vk.FungibleTokenOfferContract.hash
707
+ // )
708
+ // throw new Error(
709
+ // "Invalid offer verification key, offer contract has to be upgraded"
710
+ // );
711
+ // break;
712
+ // }
713
+ // switch (txType) {
714
+ // case "token:bid:sell":
715
+ // case "token:bid:withdraw":
716
+ // case "token:bid:whitelist":
717
+ // if (bidContract === undefined)
718
+ // throw new Error("Bid contract is required");
719
+ // if (
720
+ // Mina.getAccount(
721
+ // bidContract.address
722
+ // ).zkapp?.verificationKey?.hash.toJSON() !==
723
+ // vk.FungibleTokenBidContract.hash
724
+ // )
725
+ // throw new Error(
726
+ // "Invalid bid verification key, bid contract has to be upgraded"
727
+ // );
728
+ // break;
729
+ // }
730
+
731
+ // switch (txType) {
732
+ // case "token:mint":
733
+ // case "token:burn":
734
+ // case "token:redeem":
735
+ // case "token:transfer":
736
+ // case "token:airdrop":
737
+ // case "token:offer:create":
738
+ // case "token:bid:create":
739
+ // case "token:offer:buy":
740
+ // case "token:offer:withdraw":
741
+ // case "token:bid:sell":
742
+ // if (
743
+ // Mina.getAccount(
744
+ // zkToken.address
745
+ // ).zkapp?.verificationKey?.hash.toJSON() !== vk.FungibleToken.hash
746
+ // )
747
+ // throw new Error(
748
+ // "Invalid token verification key, token contract has to be upgraded"
749
+ // );
750
+ // break;
751
+ // }
752
+
753
+ const accountCreationFee = 0;
754
+
755
+ const tx = await Mina.transaction({ sender, fee, memo, nonce }, async () => {
756
+ const feeAccountUpdate = AccountUpdate.createSigned(sender);
757
+ if (accountCreationFee > 0) {
758
+ feeAccountUpdate.balance.subInPlace(accountCreationFee);
759
+ }
760
+ if (provingKey && provingFee)
761
+ feeAccountUpdate.send({
762
+ to: provingKey,
763
+ amount: provingFee,
764
+ });
765
+ if (developerAddress && developerFee) {
766
+ feeAccountUpdate.send({
767
+ to: developerAddress,
768
+ amount: developerFee,
769
+ });
770
+ }
771
+
772
+ switch (txType) {
773
+ case "nft:transfer":
774
+ if (!from || !to || !price)
775
+ throw new Error("From, to and price are required for nft:transfer");
776
+ const context = args.nftTransferParams?.context?.custom
777
+ ? new NFTTransactionContext({
778
+ custom: args.nftTransferParams.context.custom.map((x) =>
779
+ Field.fromJSON(x)
780
+ ),
781
+ })
782
+ : new NFTTransactionContext({
783
+ custom: [Field(0), Field(0), Field(0)],
784
+ });
785
+
786
+ const transferParams: TransferParams = {
787
+ address: nftAddress,
788
+ from,
789
+ to,
790
+ price: price ? UInt64Option.from(price) : UInt64Option.none(),
791
+ context,
792
+ };
793
+ if (args.nftTransferParams.requireApproval === true)
794
+ await zkCollection.approvedTransferBySignature(transferParams);
795
+ else await zkCollection.transferBySignature(transferParams);
796
+ break;
797
+
798
+ case "nft:approve":
799
+ if (!to) throw new Error("To address is required for nft:approve");
800
+
801
+ await zkCollection.approveAddress(nftAddress, to);
802
+ break;
803
+
804
+ default:
805
+ throw new Error(`Unknown transaction type: ${txType}`);
806
+ }
807
+ });
808
+ return {
809
+ request: args,
810
+ tx,
811
+ adminType,
812
+ adminContractAddress,
813
+ symbol,
814
+ collectionName,
815
+ nftName,
816
+ verificationKeyHashes,
817
+ metadataRoot,
818
+ privateMetadata: undefined,
819
+ storage,
820
+ map: undefined,
821
+ };
822
+ }
823
+
824
+ export async function buildNftMintTransaction(params: {
825
+ chain: blockchain;
826
+ args: NftMintTransactionParams;
827
+ developerAddress?: string;
828
+ provingKey?: string;
829
+ provingFee?: number;
830
+ }): Promise<{
831
+ request: NftMintTransactionParams;
832
+ tx: Transaction<false, false>;
833
+ adminType: NftAdminType;
834
+ adminContractAddress: PublicKey;
835
+ symbol: string;
836
+ collectionName: string;
837
+ nftName: string;
838
+ verificationKeyHashes: string[];
839
+ metadataRoot: string;
840
+ storage: string;
841
+ privateMetadata?: string;
842
+ map?: IndexedMapSerialized;
352
843
  }> {
353
844
  const { chain, args } = params;
354
845
  const { nonce, txType } = args;
@@ -803,11 +1294,17 @@ export async function getNftSymbolAndAdmin(params: {
803
1294
  collectionName: string;
804
1295
  adminType: NftAdminType;
805
1296
  verificationKeyHashes: string[];
1297
+ nftName?: string;
1298
+ storage?: string;
1299
+ metadataRoot?: string;
806
1300
  }> {
807
1301
  const { txType, collectionAddress, chain, nftAddress } = params;
808
1302
  const vk =
809
1303
  tokenVerificationKeys[chain === "mainnet" ? "mainnet" : "devnet"].vk;
810
1304
  let verificationKeyHashes: string[] = [];
1305
+ let nftName: string | undefined = undefined;
1306
+ let storage: string | undefined = undefined;
1307
+ let metadataRoot: string | undefined = undefined;
811
1308
  // if (bidAddress) {
812
1309
  // verificationKeyHashes.push(vk.FungibleTokenBidContract.hash);
813
1310
  // }
@@ -829,6 +1326,21 @@ export async function getNftSymbolAndAdmin(params: {
829
1326
  if (!Mina.hasAccount(nftAddress, tokenId)) {
830
1327
  throw new Error("NFT account not found");
831
1328
  }
1329
+ const nftAccount = Mina.getAccount(nftAddress, tokenId);
1330
+ const verificationKey = nftAccount.zkapp?.verificationKey;
1331
+ if (!verificationKey) {
1332
+ throw new Error("NFT contract verification key not found");
1333
+ }
1334
+ if (!verificationKeyHashes.includes(verificationKey.hash.toJSON())) {
1335
+ verificationKeyHashes.push(verificationKey.hash.toJSON());
1336
+ }
1337
+ if (nftAccount.zkapp?.appState === undefined) {
1338
+ throw new Error("NFT contract state not found");
1339
+ }
1340
+ const nft = new NFT(nftAddress, tokenId);
1341
+ nftName = fieldToString(nft.name.get());
1342
+ storage = nft.storage.get().toString();
1343
+ metadataRoot = nft.metadata.get().toJSON();
832
1344
  }
833
1345
 
834
1346
  const account = Mina.getAccount(collectionAddress);
@@ -942,5 +1454,8 @@ export async function getNftSymbolAndAdmin(params: {
942
1454
  collectionName,
943
1455
  adminType,
944
1456
  verificationKeyHashes,
1457
+ nftName,
1458
+ storage,
1459
+ metadataRoot,
945
1460
  };
946
1461
  }