public static async Task <(bool, (string, string))> VerifyNFTTicketOrigin(string txid, string eventId, List <string> allowedMintingAddresses) { var found = false; var intxid = txid; var mintingAddress = string.Empty; while (!found) { var info = await NeblioTransactionHelpers.GetTransactionInfo(intxid); if (info != null && info.Vin != null && info.Vin.Count > 0) { var vin = info.Vin.ToArray()?[0]; if (vin.Tokens != null && vin.Tokens.Count > 0) { var vintok = vin.Tokens.ToArray()?[0]; if (vintok != null) { var meta = await NeblioTransactionHelpers.GetTransactionMetadata(NFTHelpers.TokenId, intxid); if (meta.TryGetValue("NFT", out var isnft)) { if (isnft != "true") { throw new Exception("This is not NFT"); } } if (meta.TryGetValue("Type", out var type)) { if (type != "NFT Ticket") { throw new Exception("This is not NFT Ticket"); } } if (meta.TryGetValue("EventId", out var nfteventId)) { if (nfteventId != eventId) { throw new Exception($"Event Id on the ticket does not match the requested. Found during search for origin. Explored TxId {intxid}."); } } if (meta.TryGetValue("Used", out var used)) { if (used == "true") { throw new Exception($"This NFT Ticket is already used. Found during search for origin. Used TxId {intxid}."); } } if (vintok.Amount > 1) { mintingAddress = vin.PreviousOutput.Addresses.ToArray()?[0]; if (!string.IsNullOrEmpty(mintingAddress)) { if (allowedMintingAddresses.Contains(mintingAddress)) { return(true, (intxid, mintingAddress)); } } found = true; } else if (vintok.Amount == 1) { intxid = vin.Txid; } } } } } return(false, (intxid, mintingAddress)); }
public static async Task <VerifyNFTTicketDto> LoadNFTTicketToVerify(OwnershipVerificationCodeDto dto, string eventId, List <string> allowedMintingAddresses) { if (string.IsNullOrEmpty(dto.TxId)) { throw new Exception("Utxo id must be provided."); } if (string.IsNullOrEmpty(dto.Signature)) { throw new Exception("Signature id must be provided."); } if (string.IsNullOrEmpty(eventId)) { throw new Exception("Event Id must be provided."); } if (allowedMintingAddresses == null || allowedMintingAddresses.Count == 0) { throw new Exception("You must provide list of allowed minting addresses."); } var msg = CreateMessage(dto.TxId); // create verification message ASAP because of time relation var txi = await NeblioTransactionHelpers.GetTransactionInfo(dto.TxId); var Time = TimeHelpers.UnixTimestampToDateTime((double)txi.Blocktime); bool isYesterdayOrOlder = DateTime.Today - Time.Date >= TimeSpan.FromDays(1); if (isYesterdayOrOlder) { throw new Exception("This ticket was used earlier than today. Or it is not used at all."); } var metadata = await NeblioTransactionHelpers.GetTransactionMetadata(NFTHelpers.TokenId, dto.TxId); if (metadata.TryGetValue("NFT", out var isnft)) { if (isnft != "true") { throw new Exception("This is not NFT"); } } if (metadata.TryGetValue("Type", out var type)) { if (type != "NFT Ticket") { throw new Exception("This is not NFT Ticket"); } } if (metadata.TryGetValue("EventId", out var nfteventId)) { if (nfteventId != eventId) { throw new Exception("Event Id on the ticket does not match the requested."); } } if (metadata.TryGetValue("Used", out var used)) { if (used != "true") { throw new Exception("This NFT Ticket is not used."); } } else { throw new Exception("This NFT Ticket is not used."); } var tx = Transaction.Parse(txi.Hex, NeblioTransactionHelpers.Network); var outDto = new VerifyNFTTicketDto(); if (tx != null && tx.Outputs.Count > 0 && tx.Inputs.Count > 0) { var outp = tx.Outputs[0]; var inpt = tx.Inputs[0]; if (outp != null && inpt != null) { var scr = outp.ScriptPubKey; var add = scr.GetDestinationAddress(NeblioTransactionHelpers.Network); var addi = inpt.ScriptSig.GetSignerAddress(NeblioTransactionHelpers.Network); if (add != addi) { throw new Exception("This ticket was not used on this address."); } var pubkey = inpt.ScriptSig.GetAllPubKeys().FirstOrDefault(); if (pubkey == null) { throw new Exception("Cannot Load the owner Public Key."); } // verify of the signature of the NFT var verres = await ECDSAProvider.VerifyMessage(msg, dto.Signature, pubkey); //var vmsg = await ECDSAProvider.VerifyMessage(msg, dto.Signature, pubkey); if (!verres.Item1) { throw new Exception("Signature of the NFT is not valid."); } // check if the NFT is still as utxo on the address var utxos = await NeblioTransactionHelpers.GetAddressUtxosObjects(add.ToString()); if (utxos.FirstOrDefault(u => (u.Txid == dto.TxId && u.Value == 10000 && u.Tokens.Count > 0 && u.Tokens.FirstOrDefault()?.Amount == 1)) == null) { throw new Exception("This ticket is not available on the address as spendable."); } // check if in previous transaction the ticket was unused var prevmeta = await NeblioTransactionHelpers.GetTransactionMetadata(NFTHelpers.TokenId, inpt.PrevOut.Hash.ToString()); if (prevmeta.TryGetValue("NFT", out var isprevnft)) { if (isprevnft != "true") { throw new Exception("This is not NFT"); } } if (prevmeta.TryGetValue("Type", out var prevtype)) { if (prevtype != "NFT Ticket") { throw new Exception("This is not NFT Ticket"); } } if (prevmeta.TryGetValue("EventId", out var prevnfteventId)) { if (prevnfteventId != eventId) { throw new Exception("Event Id on the ticket does not match the requested."); } } if (prevmeta.TryGetValue("Used", out var prevused)) { if (prevused == "true") { throw new Exception("This NFT Ticket was already used in previous transaction."); } } // todo track origin for check minting address var res = await VerifyNFTTicketOrigin(inpt.PrevOut.Hash.ToString(), eventId, allowedMintingAddresses); if (!res.Item1) { throw new Exception($"Ticket was not minted on allowed address. The origin is from: {res.Item2.Item2}"); } outDto.IsMintedByAllowedAddress = true; outDto.MintAddress = res.Item2.Item2; var nft = new TicketNFT(""); await nft.LoadLastData(metadata); // fill with already loaded the newest NFT metadata nft.Time = Time; outDto.IsSignatureValid = true; outDto.NFT = nft; outDto.OwnerAddress = add.ToString(); outDto.IsUsedOnSameAddress = true; outDto.OwnerPubKey = pubkey; outDto.TxId = dto.TxId; } } return(outDto); }
/// <summary> /// Create and load the NFT from the transaction hash /// </summary> /// <param name="tokenId">Optional. If you know please provide to speed up the loading.</param> /// <param name="utxo">NFT transaction hash</param> /// <param name="utxoindex">Index of the NFT output. This is important for NFTs from multimint.</param> /// <param name="time">already parsed time</param> /// <param name="wait">await load - obsolete</param> /// <param name="loadJustType">load just specific type of NFT</param> /// <param name="justType">specify the type of the NFT which can be load - you must turn on justType flag</param> /// <param name="skipTheType">skip some type of NFT</param> /// <param name="skipType">specify the type of NFT which should be skipped - you must turn on skipTheType flag</param> /// <param name="address">Specify address of owner</param> /// <param name="txinfo">if you have loaded txinfo provide it to speed up the loading</param> /// <returns>INFT compatible object</returns> public static async Task <INFT> GetNFT(string tokenId, string utxo, int utxoindex = 0, double time = 0, bool wait = false, bool loadJustType = false, NFTTypes justType = NFTTypes.Image, bool skipTheType = false, NFTTypes skipType = NFTTypes.Image, string address = "", NeblioAPI.GetTransactionInfoResponse txinfo = null) { NFTTypes type = NFTTypes.Image; INFT nft = null; if (txinfo == null) { txinfo = await NeblioTransactionHelpers.GetTransactionInfo(utxo); } if (txinfo == null) { return(null); } if (txinfo.Vout == null) { return(null); } var tokid = tokenId; try { var tid = txinfo.Vout.ToList()[utxoindex]?.Tokens.ToList()[0]; if (tid == null) { return(null); } if (tid.Amount > 1) { return(null); } tokid = tid.TokenId; if (string.IsNullOrEmpty(tokid)) { tokid = NFTHelpers.TokenId; } } catch { return(null); } if (VEDLDataContext.AllowCache && tokid == NFTHelpers.TokenId) { // try to load it from cache try { if (VEDLDataContext.NFTCache.TryGetValue($"{utxo}:{utxoindex}", out var cachedMetadata)) { cachedMetadata.LastAccess = DateTime.UtcNow; cachedMetadata.NumberOfReads++; Console.WriteLine($"Loading {utxo}:{utxoindex} NFT from cache."); if (cachedMetadata.NFTType == NFTTypes.Receipt) { Console.WriteLine($"NFT Receipt"); } nft = await GetNFTFromCacheMetadata(cachedMetadata.Metadata, utxo, utxoindex, txinfo, true, cachedMetadata.NFTType); if (nft != null) { Console.WriteLine($"Loading {utxo}:{utxoindex} NFT from cache done."); } if (nft != null) { return(nft); } else { Console.WriteLine($"Loading {utxo}:{utxoindex} NFT from cache was not possible!"); } } } catch (Exception ex) { Console.WriteLine("Cannot load NFT from cache. " + ex.Message); return(null); } } var meta = await NeblioTransactionHelpers.GetTransactionMetadata(tokid, utxo); if (meta == null) { return(null); } else if (meta.Count == 0 || meta.Count == 1) { return(null); } try { if (!meta.TryGetValue("NFT", out var nftt)) { return(null); } else if (nftt != "true") { return(null); } type = ParseNFTType(meta); } catch (Exception ex) { Console.WriteLine($"Cannot load Type of NFT {utxo}. " + ex.Message); if (meta.TryGetValue("SourceUtxo", out var sourceutxo)) { type = NFTTypes.Image; } else { return(null); } } if (utxoindex == 1 && meta.TryGetValue("ReceiptFromPaymentUtxo", out var rfp)) { if (!string.IsNullOrEmpty(rfp)) { type = NFTTypes.Receipt; } } if (loadJustType) { if (justType != type) { return(null); } } if (skipTheType) { if (skipType == type || (skipType == NFTTypes.Message && type == NFTTypes.IoTMessage)) { return(null); } } var Time = TimeHelpers.UnixTimestampToDateTime(time); switch (type) { case NFTTypes.Image: nft = new ImageNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; //if (wait) await nft.ParseOriginData(meta); //else //nft.ParseOriginData(meta); nft.ParsePrice(meta); break; case NFTTypes.Profile: nft = new ProfileNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; await(nft as ProfileNFT).LoadLastData(meta); return(nft); case NFTTypes.Post: nft = new PostNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; await(nft as PostNFT).LoadLastData(meta); break; case NFTTypes.Music: nft = new MusicNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; //await ponft.ParseOriginData(); if (wait) { await nft.ParseOriginData(meta); } else { nft.ParseOriginData(meta); } nft.ParsePrice(meta); break; case NFTTypes.Payment: nft = new PaymentNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; await(nft as PaymentNFT).LoadLastData(meta); break; case NFTTypes.Receipt: nft = new ReceiptNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; await(nft as ReceiptNFT).LoadLastData(meta); break; case NFTTypes.Invoice: nft = new InvoiceNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; await(nft as InvoiceNFT).LoadLastData(meta); break; case NFTTypes.Order: nft = new OrderNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; await(nft as OrderNFT).LoadLastData(meta); break; case NFTTypes.Product: nft = new ProductNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; await(nft as ProductNFT).LoadLastData(meta); break; case NFTTypes.Message: nft = new MessageNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; await(nft as MessageNFT).LoadLastData(meta); break; case NFTTypes.Ticket: nft = new TicketNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; if (wait) { await nft.ParseOriginData(meta); } else { nft.ParseOriginData(meta); } nft.ParsePrice(meta); break; case NFTTypes.Event: nft = new EventNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; if (wait) { await nft.ParseOriginData(meta); } else { nft.ParseOriginData(meta); } nft.ParsePrice(meta); break; case NFTTypes.CoruzantArticle: nft = new CoruzantArticleNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; if (wait) { await(nft as CoruzantArticleNFT).LoadLastData(meta); } else { (nft as CoruzantArticleNFT).LoadLastData(meta); } nft.ParsePrice(meta); break; case NFTTypes.CoruzantProfile: nft = new CoruzantProfileNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; if (wait) { await(nft as CoruzantProfileNFT).LoadLastData(meta); } else { (nft as CoruzantProfileNFT).LoadLastData(meta); } break; case NFTTypes.Device: nft = new DeviceNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; if (wait) { await(nft as DeviceNFT).LoadLastData(meta); } else { (nft as DeviceNFT).LoadLastData(meta); } break; case NFTTypes.IoTDevice: nft = new IoTDeviceNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; if (wait) { await(nft as IoTDeviceNFT).LoadLastData(meta); } else { (nft as IoTDeviceNFT).LoadLastData(meta); } break; case NFTTypes.Protocol: nft = new ProtocolNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; if (wait) { await(nft as ProtocolNFT).LoadLastData(meta); } else { (nft as ProtocolNFT).LoadLastData(meta); } break; case NFTTypes.HWSrc: nft = new HWSrcNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; if (wait) { await(nft as HWSrcNFT).LoadLastData(meta); } else { (nft as HWSrcNFT).LoadLastData(meta); } break; case NFTTypes.FWSrc: nft = new FWSrcNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; if (wait) { await(nft as FWSrcNFT).LoadLastData(meta); } else { (nft as FWSrcNFT).LoadLastData(meta); } break; case NFTTypes.SWSrc: nft = new SWSrcNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; if (wait) { await(nft as SWSrcNFT).LoadLastData(meta); } else { (nft as SWSrcNFT).LoadLastData(meta); } break; case NFTTypes.MechSrc: nft = new MechSrcNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; if (wait) { await(nft as MechSrcNFT).LoadLastData(meta); } else { (nft as MechSrcNFT).LoadLastData(meta); } break; case NFTTypes.IoTMessage: nft = new IoTMessageNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; await(nft as IoTMessageNFT).LoadLastData(meta); break; case NFTTypes.Xray: nft = new XrayNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; await(nft as XrayNFT).LoadLastData(meta); break; case NFTTypes.XrayImage: nft = new XrayImageNFT(utxo); nft.TokenId = tokid; nft.Time = Time; nft.TxDetails = txinfo; nft.UtxoIndex = utxoindex; await(nft as XrayImageNFT).LoadLastData(meta); break; } if (VEDLDataContext.AllowCache && tokid == NFTHelpers.TokenId && VEDLDataContext.NFTCache.Count < VEDLDataContext.MaxCachedItems) { if (nft.Type != NFTTypes.IoTDevice) { try { var mtd = await nft.GetMetadata(); if (!VEDLDataContext.NFTCache.TryGetValue($"{nft.Utxo}:{nft.UtxoIndex}", out var m)) { VEDLDataContext.NFTCache.TryAdd($"{nft.Utxo}:{nft.UtxoIndex}", new Dto.NFTCacheDto() { Address = address, NFTType = nft.Type, Metadata = mtd, Utxo = nft.Utxo, UtxoIndex = nft.UtxoIndex, NumberOfReads = 0, LastAccess = DateTime.UtcNow, FirstSave = DateTime.UtcNow }); } } catch (Exception ex) { Console.WriteLine($"Cannot load NFT {utxo} of type {nft.TypeText} to NFTCache dictionary. " + ex.Message); } } } return(nft); }