UNPKG

@silvana-one/abi

Version:

Silvana ABI Experimental Library

1,006 lines 40.9 kB
import { Whitelist, Storage } from "@silvana-one/storage"; import { getCurrentZekoSlot, } from "@silvana-one/api"; import { fetchMinaAccount } from "../fetch.js"; import { Collection, NFT, AdvancedCollection, NFTAdmin, NFTAdvancedAdmin, Offer, Metadata, pinMetadata, fieldFromString, CollectionData, MintParams, NFTData, fieldToString, UInt64Option, NFTTransactionContext, } from "@silvana-one/nft"; import { tokenVerificationKeys } from "../vk/index.js"; import { PublicKey, Mina, AccountUpdate, UInt64, Field, TokenId, UInt32, fetchLastBlock, } from "o1js"; export async function buildNftCollectionLaunchTransaction(params) { const { chain, args } = params; const ACCOUNT_CREATION_FEE = chain === "zeko:testnet" ? 100000000n : 1000000000n; const { url = chain === "mina:mainnet" ? "https://minanft.io" : `https://${chain}.minanft.io`, symbol = "NFT", memo, nonce, adminContract: adminType, masterNFT, } = args; if (memo && typeof memo !== "string") throw new Error("Memo must be a string"); if (memo && memo.length > 30) throw new Error("Memo must be less than 30 characters"); if (!symbol || typeof symbol !== "string") throw new Error("Symbol must be a string"); if (symbol.length >= 7) throw new Error("Symbol must be less than 7 characters"); if (masterNFT === undefined) { throw new Error("masterNFT is required"); } if (masterNFT.metadata === undefined) { throw new Error("masterNFT.metadata is required"); } if (typeof masterNFT.metadata === "string" && masterNFT.storage === undefined) { throw new Error("masterNFT.storage is required if metadata is a string"); } const sender = PublicKey.fromBase58(args.sender); if (nonce === undefined) throw new Error("Nonce is required"); if (typeof nonce !== "number") throw new Error("Nonce must be a number"); const fee = args.fee ?? 200_000_000; if (url && typeof url !== "string") throw new Error("Url must be a string"); if (!args.collectionAddress || typeof args.collectionAddress !== "string") throw new Error("collectionAddress is required"); if (!args.collectionName || typeof args.collectionName !== "string") throw new Error("collectionName is required"); const collectionName = args.collectionName; if (!args.adminContractAddress || typeof args.adminContractAddress !== "string") throw new Error("adminContractAddress is required"); const adminContract = adminType === "advanced" ? NFTAdvancedAdmin : NFTAdmin; const collectionContract = adminType === "advanced" ? AdvancedCollection : Collection; const vk = tokenVerificationKeys[chain === "mina:mainnet" ? "mainnet" : "devnet"].vk; if (!vk || !vk.Collection || !vk.Collection.hash || !vk.Collection.data || !vk.AdvancedCollection || !vk.AdvancedCollection.hash || !vk.AdvancedCollection.data || !vk.NFT || !vk.NFT.hash || !vk.NFT.data || !vk.NFTAdmin || !vk.NFTAdmin.hash || !vk.NFTAdmin.data || !vk.NFTAdvancedAdmin || !vk.NFTAdvancedAdmin.hash || !vk.NFTAdvancedAdmin.data) throw new Error("Cannot get verification key from vk"); const adminVerificationKey = adminType === "advanced" ? vk.NFTAdvancedAdmin : vk.NFTAdmin; const collectionVerificationKey = adminType === "advanced" ? vk.AdvancedCollection : vk.Collection; if (!adminVerificationKey || !collectionVerificationKey) throw new Error("Cannot get verification keys"); await fetchMinaAccount({ publicKey: sender, force: true, }); if (!Mina.hasAccount(sender)) { throw new Error("Sender does not have account"); } const whitelist = "whitelist" in args && args.whitelist ? typeof args.whitelist === "string" ? Whitelist.fromString(args.whitelist) : (await Whitelist.create({ list: args.whitelist, name: symbol })) .whitelist : Whitelist.empty(); const collectionAddress = PublicKey.fromBase58(args.collectionAddress); const adminContractAddress = PublicKey.fromBase58(args.adminContractAddress); const creator = args.creator ? PublicKey.fromBase58(args.creator) : sender; const zkCollection = new collectionContract(collectionAddress); const zkAdmin = new adminContract(adminContractAddress); const metadataVerificationKeyHash = masterNFT.metadataVerificationKeyHash ? Field.fromJSON(masterNFT.metadataVerificationKeyHash) : Field(0); const provingKey = params.provingKey ? PublicKey.fromBase58(params.provingKey) : sender; const provingFee = params.provingFee ? UInt64.from(Math.round(params.provingFee)) : undefined; const developerFee = args.developerFee ? UInt64.from(Math.round(args.developerFee)) : undefined; const developerAddress = params.developerAddress ? PublicKey.fromBase58(params.developerAddress) : undefined; const { name, ipfsHash, metadataRoot, privateMetadata, serializedMap } = typeof masterNFT.metadata === "string" ? { name: masterNFT.name, ipfsHash: masterNFT.storage, metadataRoot: Field.fromJSON(masterNFT.metadata), privateMetadata: undefined, serializedMap: undefined, } : await pinMetadata(Metadata.fromOpenApiJSON({ json: masterNFT.metadata })); if (ipfsHash === undefined) throw new Error("storage is required, but not provided"); if (metadataRoot === undefined) throw new Error("metadataRoot is required"); const slot = chain === "mina:local" ? Mina.currentSlot() : chain === "zeko:testnet" ? UInt32.from((await getCurrentZekoSlot("zeko:testnet")) ?? 2_000_000) : (await fetchLastBlock()).globalSlotSinceGenesis; const expiry = slot.add(UInt32.from(100)); if (chain === "zeko:testnet") { console.log("zeko slot", slot.toBigint()); console.log("zeko expiry", expiry.toBigint()); } const nftData = NFTData.new({ owner: creator, approved: undefined, version: undefined, id: masterNFT.data.id ? BigInt(masterNFT.data.id) : undefined, canChangeOwnerByProof: masterNFT.data.canChangeOwnerByProof, canTransfer: masterNFT.data.canTransfer, canApprove: masterNFT.data.canApprove, canChangeMetadata: masterNFT.data.canChangeMetadata, canChangeStorage: masterNFT.data.canChangeStorage, canChangeName: masterNFT.data.canChangeName, canChangeMetadataVerificationKeyHash: masterNFT.data.canChangeMetadataVerificationKeyHash, canPause: masterNFT.data.canPause, isPaused: masterNFT.data.isPaused, requireOwnerAuthorizationToUpgrade: masterNFT.data.requireOwnerAuthorizationToUpgrade, }); const mintParams = new MintParams({ name: fieldFromString(name), address: collectionAddress, tokenId: TokenId.derive(collectionAddress), data: nftData, fee: args.masterNFT.fee ? UInt64.from(Math.round(args.masterNFT.fee * 1_000_000_000)) : UInt64.zero, metadata: metadataRoot, storage: Storage.fromString(ipfsHash), metadataVerificationKeyHash, expiry: masterNFT.expiry ? UInt32.from(Math.round(masterNFT.expiry)) : expiry, }); const collectionData = CollectionData.new(args.collectionData ?? {}); const tx = await Mina.transaction({ sender, fee, memo: memo ?? `${args.collectionName} NFT collection launch`.substring(0, 30), nonce, }, async () => { const feeAccountUpdate = AccountUpdate.createSigned(sender); feeAccountUpdate.balance.subInPlace(3n * ACCOUNT_CREATION_FEE); if (provingFee && provingKey) feeAccountUpdate.send({ to: provingKey, amount: provingFee, }); if (developerAddress && developerFee) { feeAccountUpdate.send({ to: developerAddress, amount: developerFee, }); } if (zkAdmin instanceof NFTAdvancedAdmin) { throw new Error("Advanced admin is not supported"); } else if (zkAdmin instanceof NFTAdmin) { await zkAdmin.deploy({ admin: sender, uri: `NFT Admin`, verificationKey: adminVerificationKey, }); // deploy() and initialize() create 2 account updates for the same publicKey, it is intended await zkCollection.deploy({ creator, collectionName: fieldFromString(collectionName), baseURL: fieldFromString(args.baseURL ?? "ipfs"), admin: adminContractAddress, symbol, url, verificationKey: collectionVerificationKey, }); await zkCollection.initialize(mintParams, collectionData); } }); return { request: adminType === "advanced" ? { ...args, whitelist: whitelist.toString(), } : args, tx, adminType, collectionName, nftName: name, verificationKeyHashes: [ adminVerificationKey.hash, collectionVerificationKey.hash, ], metadataRoot: metadataRoot.toJSON(), storage: ipfsHash, privateMetadata, map: serializedMap, }; } export async function buildNftTransaction(params) { const { chain, args } = params; const ACCOUNT_CREATION_FEE = chain === "zeko:testnet" ? 100000000n : 1000000000n; const { nonce, txType } = args; if (nonce === undefined) throw new Error("Nonce is required"); if (typeof nonce !== "number") throw new Error("Nonce must be a number"); if (txType === undefined) throw new Error("Tx type is required"); if (typeof txType !== "string") throw new Error("Tx type must be a string"); if (args.sender === undefined || typeof args.sender !== "string") throw new Error("Sender is required"); if (args.collectionAddress === undefined || typeof args.collectionAddress !== "string") throw new Error("Collection address is required"); if (args.nftAddress === undefined || typeof args.nftAddress !== "string") throw new Error("NFT address is required"); const sender = PublicKey.fromBase58(args.sender); const collectionAddress = PublicKey.fromBase58(args.collectionAddress); if (txType === "nft:transfer" && args.nftTransferParams === undefined) { throw new Error("NFT transfer params are required"); } if (txType === "nft:sell" && args.nftSellParams === undefined) { throw new Error("NFT sell params are required"); } if (txType === "nft:buy" && args.nftBuyParams === undefined) { throw new Error("NFT buy params are required"); } const nftAddress = PublicKey.fromBase58(args.nftAddress); let offerAddress = "nftSellParams" in args && args.nftSellParams && args.nftSellParams.offerAddress ? PublicKey.fromBase58(args.nftSellParams.offerAddress) : undefined; if (!offerAddress && txType === "nft:sell") throw new Error("Offer address is required"); // const bidAddress = // "bidAddress" in args && args.bidAddress // ? PublicKey.fromBase58(args.bidAddress) // : undefined; // if ( // !bidAddress && // (txType === "token:bid:create" || // txType === "token:bid:sell" || // txType === "token:bid:withdraw") // ) // throw new Error("Bid address is required"); let to = txType === "nft:transfer" ? "nftTransferParams" in args && args.nftTransferParams && args.nftTransferParams.to && typeof args.nftTransferParams.to === "string" ? PublicKey.fromBase58(args.nftTransferParams.to) : undefined : txType === "nft:approve" ? "nftApproveParams" in args && args.nftApproveParams && args.nftApproveParams.to && typeof args.nftApproveParams.to === "string" ? PublicKey.fromBase58(args.nftApproveParams.to) : undefined : undefined; if (!to && txType === "nft:transfer") throw new Error("To address is required for nft:transfer"); if (!to && txType === "nft:approve") to = PublicKey.empty(); let buyer = "nftBuyParams" in args && args.nftBuyParams && args.nftBuyParams.buyer && typeof args.nftBuyParams.buyer === "string" ? PublicKey.fromBase58(args.nftBuyParams.buyer) : undefined; if (!buyer && txType === "nft:buy") buyer = sender; const from = "nftTransferParams" in args && args.nftTransferParams && args.nftTransferParams.from && typeof args.nftTransferParams.from === "string" ? PublicKey.fromBase58(args.nftTransferParams.from) : undefined; if (!from && txType === "nft:transfer") throw new Error("From address is required for nft:transfer"); const price = "nftSellParams" in args ? args.nftSellParams && args.nftSellParams.price && typeof args.nftSellParams.price === "number" ? UInt64.from(Math.round(args.nftSellParams.price * 1_000_000_000)) : undefined : "nftTransferParams" in args && args.nftTransferParams && args.nftTransferParams.price && typeof args.nftTransferParams.price === "number" ? UInt64.from(Math.round(args.nftTransferParams.price * 1_000_000_000)) : undefined; if (price === undefined && txType === "nft:sell") throw new Error("Price is required for nft:sell"); await fetchMinaAccount({ publicKey: sender, force: true, }); if (!Mina.hasAccount(sender)) { throw new Error("Sender does not have account"); } const { symbol, adminContractAddress, adminType, verificationKeyHashes, collectionName, nftName, storage, metadataRoot, approved, } = await getNftSymbolAndAdmin({ txType, collectionAddress, chain, nftAddress, }); if (storage === undefined) throw new Error("storage is required, but not provided"); if (metadataRoot === undefined) throw new Error("metadataRoot is required"); if (nftName === undefined) throw new Error("nftName is required"); if (approved === undefined) throw new Error("approved is required"); if (txType === "nft:buy" && approved.equals(PublicKey.empty()).toBoolean()) throw new Error("approved is required to be non-empty for nft:buy"); if (txType === "nft:buy" && offerAddress !== undefined && !offerAddress.equals(approved).toBoolean()) throw new Error("offerAddress is required to be the same as approved for nft:buy"); if (txType === "nft:buy" && offerAddress === undefined) offerAddress = approved; if (offerAddress !== undefined) { await fetchMinaAccount({ publicKey: offerAddress, force: txType === "nft:buy", }); } const memo = args.memo ?? `${txType.split(":")[1]} ${symbol} ${nftName}`; const fee = args.fee ?? 200_000_000; const provingKey = params.provingKey ? PublicKey.fromBase58(params.provingKey) : sender; const provingFee = params.provingFee ? UInt64.from(Math.round(params.provingFee)) : undefined; const developerFee = args.developerFee ? UInt64.from(Math.round(args.developerFee)) : undefined; const developerAddress = params.developerAddress ? PublicKey.fromBase58(params.developerAddress) : undefined; //const adminContract = new FungibleTokenAdmin(adminContractAddress); const advancedAdminContract = new NFTAdvancedAdmin(adminContractAddress); const adminContract = new NFTAdmin(adminContractAddress); const collectionContract = adminType === "advanced" ? AdvancedCollection : Collection; // if ( // (txType === "token:admin:whitelist" || // txType === "token:bid:whitelist" || // txType === "token:offer:whitelist") && // !args.whitelist // ) { // throw new Error("Whitelist is required"); // } // const whitelist = // "whitelist" in args && args.whitelist // ? typeof args.whitelist === "string" // ? Whitelist.fromString(args.whitelist) // : (await Whitelist.create({ list: args.whitelist, name: symbol })) // .whitelist // : Whitelist.empty(); const zkCollection = new collectionContract(collectionAddress); const tokenId = zkCollection.deriveTokenId(); // if ( // txType === "nft:mint" && // adminType === "standard" && // adminAddress.toBase58() !== sender.toBase58() // ) // throw new Error( // "Invalid sender for FungibleToken mint with standard admin" // ); // await fetchMinaAccount({ // publicKey: nftAddress, // tokenId, // force: ( // ["nft:transfer"] satisfies NftTransactionType[] as NftTransactionType[] // ).includes(txType), // }); // if (to) { // await fetchMinaAccount({ // publicKey: to, // force: false, // }); // } // if (from) { // await fetchMinaAccount({ // publicKey: from, // tokenId, // force: false, // }); // } // if (offerAddress) // await fetchMinaAccount({ // publicKey: offerAddress, // tokenId, // force: ( // [ // "token:offer:whitelist", // "token:offer:buy", // "token:offer:withdraw", // ] satisfies TokenTransactionType[] as TokenTransactionType[] // ).includes(txType), // }); // if (bidAddress) // await fetchMinaAccount({ // publicKey: bidAddress, // force: ( // [ // "token:bid:whitelist", // "token:bid:sell", // "token:bid:withdraw", // ] satisfies TokenTransactionType[] as TokenTransactionType[] // ).includes(txType), // }); // const offerContract = offerAddress // ? new FungibleTokenOfferContract(offerAddress, tokenId) // : undefined; // const bidContract = bidAddress // ? new FungibleTokenBidContract(bidAddress) // : undefined; // const offerContractDeployment = offerAddress // ? new FungibleTokenOfferContract(offerAddress, tokenId) // : undefined; // const bidContractDeployment = bidAddress // ? new FungibleTokenBidContract(bidAddress) // : undefined; const vk = tokenVerificationKeys[chain === "mina:mainnet" ? "mainnet" : "devnet"].vk; if (!vk || !vk.Collection || !vk.Collection.hash || !vk.Collection.data || !vk.AdvancedCollection || !vk.AdvancedCollection.hash || !vk.AdvancedCollection.data || !vk.NFT || !vk.NFT.hash || !vk.NFT.data || !vk.NFTAdmin || !vk.NFTAdmin.hash || !vk.NFTAdmin.data || !vk.NFTAdvancedAdmin || !vk.NFTAdvancedAdmin.hash || !vk.NFTAdvancedAdmin.data || !vk.NonFungibleTokenOfferContract || !vk.NonFungibleTokenOfferContract.hash || !vk.NonFungibleTokenOfferContract.data || !vk.NonFungibleTokenAdvancedOfferContract || !vk.NonFungibleTokenAdvancedOfferContract.hash || !vk.NonFungibleTokenAdvancedOfferContract.data) throw new Error("Cannot get verification key from vk"); if (txType === "nft:sell" || txType === "nft:buy") { verificationKeyHashes.push(vk.NonFungibleTokenOfferContract.hash); } // const offerVerificationKey = FungibleTokenOfferContract._verificationKey ?? { // hash: Field(vk.FungibleTokenOfferContract.hash), // data: vk.FungibleTokenOfferContract.data, // }; // const bidVerificationKey = FungibleTokenBidContract._verificationKey ?? { // hash: Field(vk.FungibleTokenBidContract.hash), // data: vk.FungibleTokenBidContract.data, // }; // const isNewBidOfferAccount = // txType === "token:offer:create" && offerAddress // ? !Mina.hasAccount(offerAddress, tokenId) // : txType === "token:bid:create" && bidAddress // ? !Mina.hasAccount(bidAddress) // : false; // const isNewBuyAccount = // txType === "token:offer:buy" ? !Mina.hasAccount(sender, tokenId) : false; // let isNewSellAccount: boolean = false; // if (txType === "token:bid:sell") { // if (!bidAddress || !bidContract) throw new Error("Bid address is required"); // await fetchMinaAccount({ // publicKey: bidAddress, // force: true, // }); // const buyer = bidContract.buyer.get(); // await fetchMinaAccount({ // publicKey: buyer, // tokenId, // force: false, // }); // isNewSellAccount = !Mina.hasAccount(buyer, tokenId); // } // if (txType === "token:burn") { // await fetchMinaAccount({ // publicKey: sender, // force: true, // }); // await fetchMinaAccount({ // publicKey: sender, // tokenId, // force: false, // }); // if (!Mina.hasAccount(sender, tokenId)) // throw new Error("Sender does not have tokens to burn"); // } // const isNewTransferMintAccount = // (txType === "token:transfer" || // txType === "token:airdrop" || // txType === "token:mint") && // to // ? !Mina.hasAccount(to, tokenId) // : false; // const accountCreationFee = // (isNewBidOfferAccount ? 1_000_000_000 : 0) + // (isNewBuyAccount ? 1_000_000_000 : 0) + // (isNewSellAccount ? 1_000_000_000 : 0) + // (isNewTransferMintAccount ? 1_000_000_000 : 0) + // (isToNewAccount && // txType === "token:mint" && // adminType === "advanced" && // advancedAdminContract.whitelist.get().isSome().toBoolean() // ? 1_000_000_000 // : 0); // console.log("accountCreationFee", accountCreationFee / 1_000_000_000); // switch (txType) { // case "token:offer:buy": // case "token:offer:withdraw": // case "token:offer:whitelist": // if (offerContract === undefined) // throw new Error("Offer contract is required"); // if ( // Mina.getAccount( // offerContract.address, // tokenId // ).zkapp?.verificationKey?.hash.toJSON() !== // vk.FungibleTokenOfferContract.hash // ) // throw new Error( // "Invalid offer verification key, offer contract has to be upgraded" // ); // break; // } // switch (txType) { // case "token:bid:sell": // case "token:bid:withdraw": // case "token:bid:whitelist": // if (bidContract === undefined) // throw new Error("Bid contract is required"); // if ( // Mina.getAccount( // bidContract.address // ).zkapp?.verificationKey?.hash.toJSON() !== // vk.FungibleTokenBidContract.hash // ) // throw new Error( // "Invalid bid verification key, bid contract has to be upgraded" // ); // break; // } // switch (txType) { // case "token:mint": // case "token:burn": // case "token:redeem": // case "token:transfer": // case "token:airdrop": // case "token:offer:create": // case "token:bid:create": // case "token:offer:buy": // case "token:offer:withdraw": // case "token:bid:sell": // if ( // Mina.getAccount( // zkToken.address // ).zkapp?.verificationKey?.hash.toJSON() !== vk.FungibleToken.hash // ) // throw new Error( // "Invalid token verification key, token contract has to be upgraded" // ); // break; // } const accountCreationFee = txType === "nft:sell" ? ACCOUNT_CREATION_FEE : 0n; const zkOffer = offerAddress ? new Offer(offerAddress) : undefined; const tx = await Mina.transaction({ sender, fee, memo, nonce }, async () => { if (txType !== "nft:buy") { const feeAccountUpdate = AccountUpdate.createSigned(sender); if (accountCreationFee > 0) { feeAccountUpdate.balance.subInPlace(accountCreationFee); } if (provingKey && provingFee) feeAccountUpdate.send({ to: provingKey, amount: provingFee, }); if (developerAddress && developerFee) { feeAccountUpdate.send({ to: developerAddress, amount: developerFee, }); } } switch (txType) { case "nft:transfer": if (!from || !to) throw new Error("From and to are required for nft:transfer"); const context = args.nftTransferParams?.context?.custom ? new NFTTransactionContext({ custom: args.nftTransferParams.context.custom.map((x) => Field.fromJSON(x)), }) : new NFTTransactionContext({ custom: [Field(0), Field(0), Field(0)], }); const transferParams = { address: nftAddress, to, price: price ? UInt64Option.from(price) : UInt64Option.none(), context, }; if (args.nftTransferParams.requireApproval === true) await zkCollection.adminApprovedTransferBySignature(transferParams); else await zkCollection.transferBySignature(transferParams); break; case "nft:approve": if (!to) throw new Error("To address is required for nft:approve"); await zkCollection.approveAddress(nftAddress, to); break; case "nft:sell": if (!price) throw new Error("Price is required for nft:sell"); if (!offerAddress) throw new Error("Offer address is required for nft:sell"); if (zkOffer === undefined) throw new Error("Offer contract is required for nft:sell"); await zkCollection.approveAddress(nftAddress, offerAddress); await zkOffer.deploy({ verificationKey: vk.NonFungibleTokenOfferContract, collection: zkCollection.address, nft: nftAddress, owner: sender, price, }); break; case "nft:buy": if (!offerAddress) throw new Error("Offer address is required for nft:buy"); if (zkOffer === undefined) throw new Error("Offer contract is required for nft:buy"); if (!buyer) throw new Error("Buyer is required for nft:buy"); await zkOffer.buy(buyer); break; default: throw new Error(`Unknown transaction type: ${txType}`); } }); return { request: args, tx, adminType, adminContractAddress, symbol, collectionName, nftName, verificationKeyHashes, metadataRoot, privateMetadata: undefined, storage, map: undefined, }; } export async function buildNftMintTransaction(params) { const { chain, args } = params; const ACCOUNT_CREATION_FEE = chain === "zeko:testnet" ? 100000000n : 1000000000n; const { nonce, txType } = args; if (nonce === undefined) throw new Error("Nonce is required"); if (typeof nonce !== "number") throw new Error("Nonce must be a number"); if (txType === undefined) throw new Error("Tx type is required"); if (typeof txType !== "string") throw new Error("Tx type must be a string"); const sender = PublicKey.fromBase58(args.sender); const collectionAddress = PublicKey.fromBase58(args.collectionAddress); if (args?.nftMintParams?.address === undefined) { throw new Error("NFT address is required"); } if (args?.nftMintParams?.data?.owner === undefined) { throw new Error("NFT owner is required"); } const nftAddress = PublicKey.fromBase58(args.nftMintParams.address); await fetchMinaAccount({ publicKey: sender, force: true, }); if (!Mina.hasAccount(sender)) { throw new Error("Sender does not have account"); } const { symbol, adminContractAddress, adminType, verificationKeyHashes, collectionName, } = await getNftSymbolAndAdmin({ txType, collectionAddress, chain, nftAddress: undefined, // TODO: add nft address }); const nftName = args.nftMintParams.name; const memo = args.memo ?? `${txType.split(":")[1]} ${symbol} ${nftName}`; const fee = args.fee ?? 200_000_000; const provingKey = params.provingKey ? PublicKey.fromBase58(params.provingKey) : sender; const provingFee = params.provingFee ? UInt64.from(Math.round(params.provingFee)) : undefined; const developerFee = args.developerFee ? UInt64.from(Math.round(args.developerFee)) : undefined; const developerAddress = params.developerAddress ? PublicKey.fromBase58(params.developerAddress) : undefined; const advancedAdminContract = new NFTAdvancedAdmin(adminContractAddress); const adminContract = new NFTAdmin(adminContractAddress); const collectionContract = adminType === "advanced" ? AdvancedCollection : Collection; const zkCollection = new collectionContract(collectionAddress); const tokenId = zkCollection.deriveTokenId(); await fetchMinaAccount({ publicKey: nftAddress, tokenId, force: ["nft:transfer"].includes(txType), }); const vk = tokenVerificationKeys[chain === "mina:mainnet" ? "mainnet" : "devnet"].vk; if (!vk || !vk.Collection || !vk.Collection.hash || !vk.Collection.data || !vk.AdvancedCollection || !vk.AdvancedCollection.hash || !vk.AdvancedCollection.data || !vk.NFT || !vk.NFT.hash || !vk.NFT.data || !vk.NFTAdmin || !vk.NFTAdmin.hash || !vk.NFTAdmin.data || !vk.NFTAdvancedAdmin || !vk.NFTAdvancedAdmin.hash || !vk.NFTAdvancedAdmin.data) throw new Error("Cannot get verification key from vk"); const { name, ipfsHash, metadataRoot, privateMetadata, serializedMap } = typeof args.nftMintParams.metadata === "string" ? { name: args.nftMintParams.name, ipfsHash: args.nftMintParams.storage, metadataRoot: Field.fromJSON(args.nftMintParams.metadata), privateMetadata: undefined, serializedMap: undefined, } : await pinMetadata(Metadata.fromOpenApiJSON({ json: args.nftMintParams.metadata })); if (ipfsHash === undefined) throw new Error("storage is required, but not provided"); if (metadataRoot === undefined) throw new Error("metadataRoot is required"); const slot = chain === "mina:local" ? Mina.currentSlot() : chain === "zeko:testnet" ? UInt32.from((await getCurrentZekoSlot("zeko:testnet")) ?? 2_000_000) : (await fetchLastBlock()).globalSlotSinceGenesis; const expiry = slot.add(UInt32.from(100)); if (chain === "zeko:testnet") { console.log("zeko slot", slot.toBigint()); console.log("zeko expiry", expiry.toBigint()); } const nftDataArgs = args.nftMintParams.data; if (nftDataArgs.owner === undefined) { throw new Error("NFT owner is required"); } const nftData = NFTData.new({ ...nftDataArgs, owner: nftDataArgs.owner }); if (!args.nftMintParams.address) throw new Error("NFT address is required"); const mintParams = new MintParams({ name: fieldFromString(name), address: PublicKey.fromBase58(args.nftMintParams.address), tokenId: TokenId.derive(collectionAddress), data: nftData, fee: args.nftMintParams.fee ? UInt64.from(Math.round(args.nftMintParams.fee * 1_000_000_000)) : UInt64.zero, metadata: metadataRoot, storage: Storage.fromString(ipfsHash), metadataVerificationKeyHash: args.nftMintParams.metadataVerificationKeyHash ? Field.fromJSON(args.nftMintParams.metadataVerificationKeyHash) : Field(0), expiry: args.nftMintParams.expiry ? UInt32.from(Math.round(args.nftMintParams.expiry)) : expiry, }); const tx = await Mina.transaction({ sender, fee, memo, nonce }, async () => { const feeAccountUpdate = AccountUpdate.createSigned(sender); if (ACCOUNT_CREATION_FEE < 1000000000n) { feeAccountUpdate.balance.addInPlace(1000000000n - ACCOUNT_CREATION_FEE); } if (provingKey && provingFee) feeAccountUpdate.send({ to: provingKey, amount: provingFee, }); if (developerAddress && developerFee) { feeAccountUpdate.send({ to: developerAddress, amount: developerFee, }); } switch (txType) { case "nft:mint": await zkCollection.mintByCreator(mintParams); break; default: throw new Error(`Unknown transaction type: ${txType}`); } }); return { request: args, tx, adminType, adminContractAddress, symbol, collectionName, nftName, verificationKeyHashes, metadataRoot: metadataRoot.toJSON(), privateMetadata, storage: ipfsHash, map: serializedMap, }; } export async function getNftSymbolAndAdmin(params) { const { txType, collectionAddress, chain, nftAddress } = params; const vk = tokenVerificationKeys[chain === "mina:mainnet" ? "mainnet" : "devnet"].vk; let verificationKeyHashes = []; let nftName = undefined; let storage = undefined; let metadataRoot = undefined; let approved = undefined; // if (bidAddress) { // verificationKeyHashes.push(vk.FungibleTokenBidContract.hash); // } // if (offerAddress) { // verificationKeyHashes.push(vk.FungibleTokenOfferContract.hash); // } await fetchMinaAccount({ publicKey: collectionAddress, force: true }); if (!Mina.hasAccount(collectionAddress)) { throw new Error("Collection contract account not found"); } const tokenId = TokenId.derive(collectionAddress); if (nftAddress) { await fetchMinaAccount({ publicKey: nftAddress, tokenId, force: true, }); if (!Mina.hasAccount(nftAddress, tokenId)) { throw new Error("NFT account not found"); } const nftAccount = Mina.getAccount(nftAddress, tokenId); const verificationKey = nftAccount.zkapp?.verificationKey; if (!verificationKey) { throw new Error("NFT contract verification key not found"); } if (!verificationKeyHashes.includes(verificationKey.hash.toJSON())) { verificationKeyHashes.push(verificationKey.hash.toJSON()); } if (nftAccount.zkapp?.appState === undefined) { throw new Error("NFT contract state not found"); } const nft = new NFT(nftAddress, tokenId); nftName = fieldToString(nft.name.get()); storage = nft.storage.get().toString(); metadataRoot = nft.metadata.get().toJSON(); const data = NFTData.unpack(nft.packedData.get()); approved = data.approved; } const account = Mina.getAccount(collectionAddress); const verificationKey = account.zkapp?.verificationKey; if (!verificationKey) { throw new Error("Collection contract verification key not found"); } if (!verificationKeyHashes.includes(verificationKey.hash.toJSON())) { verificationKeyHashes.push(verificationKey.hash.toJSON()); } if (account.zkapp?.appState === undefined) { throw new Error("Collection contract state not found"); } const collection = new Collection(collectionAddress); const symbol = account.tokenSymbol; const collectionName = fieldToString(collection.collectionName.get()); const adminContractPublicKey = collection.admin.get(); await fetchMinaAccount({ publicKey: adminContractPublicKey, force: true, }); if (!Mina.hasAccount(adminContractPublicKey)) { throw new Error("Admin contract account not found"); } const adminContract = Mina.getAccount(adminContractPublicKey); const adminVerificationKey = adminContract.zkapp?.verificationKey; if (!adminVerificationKey) { throw new Error("Admin verification key not found"); } if (!verificationKeyHashes.includes(adminVerificationKey.hash.toJSON())) { verificationKeyHashes.push(adminVerificationKey.hash.toJSON()); } let adminType = "unknown"; if (vk.NFTAdvancedAdmin.hash === adminVerificationKey.hash.toJSON() && vk.NFTAdvancedAdmin.data === adminVerificationKey.data) { adminType = "advanced"; } else if (vk.NFTAdmin.hash === adminVerificationKey.hash.toJSON() && vk.NFTAdmin.data === adminVerificationKey.data) { adminType = "standard"; } else { console.error("Unknown admin verification key", { hash: adminVerificationKey.hash.toJSON(), symbol, address: adminContractPublicKey.toBase58(), }); } // let isToNewAccount: boolean | undefined = undefined; // if (to) { // if (adminType === "advanced") { // const adminTokenId = TokenId.derive(adminContractPublicKey); // await fetchMinaAccount({ // publicKey: to, // tokenId: adminTokenId, // force: false, // }); // isToNewAccount = !Mina.hasAccount(to, adminTokenId); // } // if (adminType === "bondingCurve") { // const adminTokenId = TokenId.derive(adminContractPublicKey); // await fetchMinaAccount({ // publicKey: adminContractPublicKey, // tokenId: adminTokenId, // force: true, // }); // } // } // const adminAddress0 = adminContract.zkapp?.appState[0]; // const adminAddress1 = adminContract.zkapp?.appState[1]; // if (adminAddress0 === undefined || adminAddress1 === undefined) { // throw new Error("Cannot fetch admin address from admin contract"); // } // const adminAddress = PublicKey.fromFields([adminAddress0, adminAddress1]); for (const hash of verificationKeyHashes) { const found = Object.values(vk).some((key) => key.hash === hash); if (!found) { console.error(`Final check: unknown verification key hash: ${hash}`); verificationKeyHashes = verificationKeyHashes.filter((h) => h !== hash); } } // Sort verification key hashes by type verificationKeyHashes.sort((a, b) => { const typeA = Object.values(vk).find((key) => key.hash === a)?.type; const typeB = Object.values(vk).find((key) => key.hash === b)?.type; if (typeA === undefined || typeB === undefined) { throw new Error("Unknown verification key hash"); } const typeOrder = { upgrade: 0, nft: 1, admin: 2, collection: 3, token: 4, user: 5, }; return typeOrder[typeA] - typeOrder[typeB]; }); return { adminContractAddress: adminContractPublicKey, symbol, collectionName, adminType, verificationKeyHashes, nftName, storage, metadataRoot, approved, }; } //# sourceMappingURL=build.js.map