From 18c0da1e563b51737a99929e70c32c1b722c5541 Mon Sep 17 00:00:00 2001 From: Nathan Martell Date: Tue, 8 Nov 2022 07:49:24 -0500 Subject: [PATCH] Handles offchain json data retrieval and stores it as a variable in the MetadataAccount class --- Solnet.Metaplex/MetadataAccount.cs | 110 ++++++++++++------------- Solnet.Metaplex/MetadataJson.cs | 96 +++++++++++++++++++++ Solnet.Metaplex/Solnet.Metaplex.csproj | 1 + Solnet.Metaplex/packages.lock.json | 6 ++ 4 files changed, 157 insertions(+), 56 deletions(-) create mode 100644 Solnet.Metaplex/MetadataJson.cs diff --git a/Solnet.Metaplex/MetadataAccount.cs b/Solnet.Metaplex/MetadataAccount.cs index 81eaee4..65da563 100644 --- a/Solnet.Metaplex/MetadataAccount.cs +++ b/Solnet.Metaplex/MetadataAccount.cs @@ -7,6 +7,8 @@ using System.Text; using System.Threading.Tasks; using System.Net.Http; +using Solnet.Metaplex.Json; +using Newtonsoft.Json; namespace Solnet.Metaplex { @@ -72,7 +74,7 @@ public class OnchainDataV1 public bool isMutable; /// Edition Type public int editionNonce; - /// Token Standard - Fungible / non-fungible + /// primarySaleHappened public bool primarySaleHappened; /// metadata json public string metadata; @@ -90,21 +92,6 @@ public OnchainDataV1(string _name, string _symbol, string _uri, uint _sellerFee, primarySaleHappened = _primarySaleHappened; } - /// Tries to get a json file from the uri - public async Task FetchMetadata() - { - if (uri is null) - return null; - - if (metadata is null) - { - using var http = new HttpClient(); - var res = await http.GetStringAsync(uri); - metadata = res; - } - - return metadata; - } } /// Metadata Onchain DataV3 structure public class OnChainDataV3 @@ -149,21 +136,7 @@ public OnChainDataV3(string _name, string _symbol, string _uri, uint _sellerFee, tokenStandard = _tokenStandard; } - /// Tries to get a json file from the uri - public async Task FetchMetadata() - { - if (uri is null) - return null; - - if (metadata is null) - { - using var http = new HttpClient(); - var res = await http.GetStringAsync(uri); - metadata = res; - } - return metadata; - } } /// Metadata account class V2 public class MetadataAccount @@ -178,6 +151,8 @@ public class MetadataAccount public OnchainDataV1 metadataV1; /// data struct public OnChainDataV3 metadataV3; + /// Off Chain Metadata + public MetaplexTokenStandard offchainData; /// version of metadata. V1 or V3 public int MetadataVersion; /// standard Solana account info @@ -188,25 +163,47 @@ public class MetadataAccount /// Constructor /// Soloana account info /// /// Metadata Account Version - Either 1 or 3 - public MetadataAccount(AccountInfo accInfo, int MetadataVersion) + public MetadataAccount(AccountInfo accInfo, int _MetadataVersion) { try { - this.owner = new PublicKey(accInfo.Owner); - if(MetadataVersion == 1) - this.metadataV1 = ParseDataV1(accInfo.Data); + owner = new PublicKey(accInfo.Owner); + MetadataVersion = _MetadataVersion; + if (MetadataVersion == 1) + metadataV1 = ParseDataV1(accInfo.Data); if (MetadataVersion == 3) - this.metadataV3 = ParseDataV3(accInfo.Data); + metadataV3 = ParseDataV3(accInfo.Data); var data = Convert.FromBase64String(accInfo.Data[0]); - this.updateAuthority = new PublicKey(data[1..33]); - this.mint = new PublicKey(data[33..65]); + var UA = data.AsSpan(1, 32); + updateAuthority = new PublicKey(UA); + if (MetadataVersion == 1) + offchainData = FetchOffChainMetadata(1); + if (MetadataVersion == 3) + offchainData = FetchOffChainMetadata(3); + var _mint = data.AsSpan(33, 32); + mint = new PublicKey(_mint); } catch (Exception ex) { Console.WriteLine(ex); } } + /// Tries to get a json file from the uri + public MetaplexTokenStandard FetchOffChainMetadata(int version) + { + MetaplexTokenStandard metadata = null; + string assetURI = metadataV3.uri; + if (version == 1) + assetURI = metadataV1.uri; + using (var httpClient = new HttpClient()) + { + httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"); + var offsiteTokenRetrieval = httpClient.GetStringAsync(new Uri(assetURI)).Result; + MetaplexTokenStandard _Metadata = JsonConvert.DeserializeObject(offsiteTokenRetrieval); + } + return metadata; + } /// Parse version 1 Data used for V1 metadata accounts /// data /// data struct @@ -234,7 +231,7 @@ public static OnchainDataV1 ParseDataV1(List data) if (hasCreators == true) { creators = MetadataProgramData.DecodeCreators(binData.GetSpan(MetadataAccountLayout.creatorsCountOffset + 5, numOfCreators * (32 + 1 + 1))); - o = MetadataAccountLayout.creatorsCountOffset + 5 + (numOfCreators * (32 + 1 + 1)); + o = MetadataAccountLayout.creatorsCountOffset + 5 + numOfCreators * (32 + 1 + 1); } else { @@ -287,8 +284,8 @@ public static OnChainDataV3 ParseDataV3(List data) if (hasCreators == true) { creators = MetadataProgramData.DecodeCreators(binData.GetSpan(MetadataAccountLayout.creatorsCountOffset + 5, numOfCreators * (32 + 1 + 1))); - o = MetadataAccountLayout.creatorsCountOffset + 5 + (numOfCreators * (32 + 1 + 1)); - + o = MetadataAccountLayout.creatorsCountOffset + 5 + numOfCreators * (32 + 1 + 1); + } else { @@ -307,7 +304,7 @@ public static OnChainDataV3 ParseDataV3(List data) o++; bool hasCollectionlink = binData.GetBool(o); o++; - + Collection collectionLink = null; if (hasCollectionlink == true) { @@ -315,33 +312,33 @@ public static OnChainDataV3 ParseDataV3(List data) o++; var key = binData.GetPubKey(o); o = o + 32; - - collectionLink = new Collection(key, verified); + + collectionLink = new Collection(key, verified); } else { o++; } - + bool isConsumable = binData.GetBool(o); Uses usesInfo = null; if (isConsumable == true) { - o++; - var useMethodENUM = binData.GetBytes(o, 1)[0]; - o++; - var remaining = binData.GetU64(o).ToString("x"); - o = o + 8; - var total = binData.GetU64(o).ToString("x"); - o = o + 8; - o++; - usesInfo = new Uses(useMethodENUM, remaining, total); + o++; + var useMethodENUM = binData.GetBytes(o, 1)[0]; + o++; + var remaining = binData.GetU64(o).ToString("x"); + o = o + 8; + var total = binData.GetU64(o).ToString("x"); + o = o + 8; + o++; + usesInfo = new Uses(useMethodENUM, remaining, total); } else { o++; } - + name = name.TrimEnd('\0'); symbol = symbol.TrimEnd('\0'); uri = uri.TrimEnd('\0'); @@ -382,7 +379,8 @@ async public static Task GetAccount(IRpcClient client, PublicKe if (readdata.Length == 165) { - mintAccount = new PublicKey(readdata[..32]); + byte[] _mint = readdata.AsSpan(0, 32).ToArray(); + mintAccount = new PublicKey(_mint); } else { @@ -391,7 +389,7 @@ async public static Task GetAccount(IRpcClient client, PublicKe PublicKey metadataAddress; byte nonce; - PublicKey.TryFindProgramAddress(new List() + PublicKey.TryFindProgramAddress(new List() { Encoding.UTF8.GetBytes("metadata"), MetadataProgram.ProgramIdKey, diff --git a/Solnet.Metaplex/MetadataJson.cs b/Solnet.Metaplex/MetadataJson.cs new file mode 100644 index 0000000..d823c08 --- /dev/null +++ b/Solnet.Metaplex/MetadataJson.cs @@ -0,0 +1,96 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Solnet.Metaplex.Json +{ + public class MetaplexTokenStandard + { + [JsonProperty("name")] + public string name { get; set; } + + [JsonProperty("symbol")] + public string symbol { get; set; } + + [JsonProperty("description")] + public string description { get; set; } + + [JsonProperty("seller_fee_basis_points")] + public int seller_fee_basis_points { get; set; } + + [JsonProperty("image")] + public string image { get; set; } + + [JsonProperty("animation_url")] + public string animation_url { get; set; } + + [JsonProperty("external_url")] + public string external_url { get; set; } + + [JsonProperty("attributes")] + public List attributes { get; set; } + + [JsonProperty("collection")] + public Collection collection { get; set; } + + [JsonProperty("properties")] + public Properties properties { get; set; } + } + + public class Attribute + { + [JsonProperty("trait_type")] + public string trait_type { get; set; } + + [JsonProperty("value")] + public string value { get; set; } + + } + public class Collection + { + [JsonProperty("name")] + public string name { get; set; } + + [JsonProperty("family")] + public string family { get; set; } + + } + public class Files + { + [JsonProperty("0")] + public FileType file { get; set; } + + [JsonProperty("data")] + public string category { get; set; } + } + public class FileType + { + [JsonProperty("uri")] + public string uri { get; set; } + + [JsonProperty("type")] + public string type { get; set; } + } + public class Creators + { + [JsonProperty("0")] + public Creator creator { get; set; } + } + public class Creator + { + [JsonProperty("address")] + public string address { get; set; } + + [JsonProperty("share")] + public int share { get; set; } + } + public class Properties + { + [JsonProperty("files")] + public List files { get; set; } + + [JsonProperty("creators")] + public List creators { get; set; } + } +} diff --git a/Solnet.Metaplex/Solnet.Metaplex.csproj b/Solnet.Metaplex/Solnet.Metaplex.csproj index b01c327..63bf2d0 100644 --- a/Solnet.Metaplex/Solnet.Metaplex.csproj +++ b/Solnet.Metaplex/Solnet.Metaplex.csproj @@ -21,6 +21,7 @@ + diff --git a/Solnet.Metaplex/packages.lock.json b/Solnet.Metaplex/packages.lock.json index e2a4ee0..8562709 100644 --- a/Solnet.Metaplex/packages.lock.json +++ b/Solnet.Metaplex/packages.lock.json @@ -35,6 +35,12 @@ "System.Text.Json": "6.0.0" } }, + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.1, )", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, "Solnet.Extensions": { "type": "Direct", "requested": "[6.1.0, )",