diff --git a/docs/release_notes.md b/docs/release_notes.md index ae6e5c0..4f6f5fe 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -35,6 +35,12 @@ Token-id - Each token in your collection has a unique text-based namespace id. Library-id - Each library item in your token's asset library has a unique text-based namespace id. +v0.1.2-3 + +* DIP721 - Added v2 functions that seem to be supported by plug +* EXT and DIP721 - Added endpoint at /collection/translate and /-/{token_id}/translate to retrieve ext and dip721 token id mappings. +* Collection Info - added created_at, upgrade_at_unique_holder count, and transaction_count + v0.1.2-2 * Adds gateway principal to the storage_info_nft_origyn query diff --git a/docs/specification.md b/docs/specification.md index a19aaa3..fc91885 100644 --- a/docs/specification.md +++ b/docs/specification.md @@ -29,7 +29,10 @@ type BalanceResult = { * alternative mappings * query balanceOfDip721(user: principal) -> Nat64; - only supports principals + * query balanceOf(user: principal) -> Nat64; - only supports principals - dip721 legacy + * query dip721_balanceOf(user: principal) -> Nat64; - only supports principals - dip721 v2 * query balanceEXT(request: EXTBalanceRequest) -> EXTBalanceResponse; Token Identifier is a text from Principal of [10, 116, 105, 100] + CanisterID as [Nat8] + Nat32 as bytes of Text.hash of token_id as each canister has only one token identifier + * query balance(request: EXTBalanceRequest) -> EXTBalanceResponse; Token Identifier is a text from Principal of [10, 116, 105, 100] + CanisterID as [Nat8] + Nat32 as bytes of Text.hash of token_id as each canister has only one token identifier ## Owner @@ -43,7 +46,8 @@ Returns the owner of the NFT indicated by token_id. * Alternative mappings * query ownerOfDip721(token_id: Nat) -> DIP721OwnerResult; - will compare Nat64 hash of text token IDs to the token_id - * query ownerOf(token_id: Nat) -> DIP721OwnerResult; - will compare Nat64 hash of text token IDs to the token_id //for questionable "v2" upgrade where the standard is now compatable with fewer web 3 tools + * query ownerOf(token_id: Nat) -> DIP721OwnerResult; - will compare Nat64 hash of text token IDs to the token_id //legacy + * query dip721_ownerOf(token_id: Nat) -> DIP721OwnerResult; - will compare Nat64 hash of text token IDs to the token_id //for questionable "v2" upgrade where the standard is now compatable with fewer web 3 tools * query bearerEXT(token: TokenIdentifier) -> Result; bearer() also exists for legacy native ext support * query bearer(token: TokenIdentifier) -> Result; bearer() also exists for legacy native ext support //for legacy support @@ -175,7 +179,9 @@ Note: For alternative mappings the existence of an escrow is the approval for th * alternative mappings * transferFromDip721(from: principal, to: principal, tokenAsNat: nat) -> Result; - token_id will be converted from the Nat representation. * transferFrom(from: principal, to: principal, tokenAsNat: nat) -> Result; - token_id will be converted from the Nat representation. //v2 + * dip721_transferFrom(from: principal, to: principal, tokenAsNat: nat) -> Result; - token_id will be converted from the Nat representation. //v2 * transferDip721(to: principal, tokenAsNat: nat) -> Result; - token_id will be converted from the Nat representation. + * dip721_transfer(to: principal, tokenAsNat: nat) -> Result; - token_id will be converted from the Nat representation. * transferEXT(request : EXTTransferRequest) -> EXTTransferResponse; transfer() also exists for legacy native ext support * transfer(request : EXTTransferRequest) -> EXTTransferResponse; transfer() also exists for legacy native ext support @@ -410,6 +416,12 @@ owner - required - principal - the owner of the NFT. is_soulbound - optional - #Bool(true) if you don't want the NFT to change owners + +##### Alternative mappings + +* dip721_token_metadata(token_id : Nat) : async DIP721.Metadata_3 query; //token metadata for dip721 +* tokenMetadata(token_id : Nat) : async DIP721.Metadata_3 query; //token metadata for dip721 + #### Collection Level Metadata com.origyn.node - suggested - Node that endorses and authenticates NFTs minted from this collection - used for paying node royalties @@ -470,8 +482,14 @@ com.origyn.royalties.secondary.default - Array(frozen) - List of Classes of rate ``` * alternative mappings - * getMetaDataDip721() -> DIP721MetadataResult query //nyi + * metadata() -> DIP721MetadataResult query //metadata for plug + * dip721_metadata() -> DIP721MetadataResult query //metadata for plug + * dip721_owner_token_metadata(owner : Principal) : async DIP721.Metadata_2 query; //gets owner token info + * ownerTokenMetadata(owner : Principal) : async DIP721.Metadata_2 //gets owner token info + * dip721_operator_token_metadata(owner : Principal) : async DIP721.Metadata_2 query; //gets owner token info + * operatorTokenMetadata(owner : Principal) : async DIP721.Metadata_2 //gets owner token info * metadataEXT(Text) -> ?Blob - supports metadata() for legacy support. - the collection properties should be converted to a blob standard that a client can decipher(cbor?/protobuf?); //will have to manage multi chunks //nyi + * tokens_ext(request: Text) : async Result.Result<[Types.EXTTokensResult]; //used by stoic to get tokens for a principal. ### Large NFT Assets @@ -1997,27 +2015,31 @@ Features: }; ``` - - ### HTTP NFT Information -prptl.io/_/canister_id/_/token_id - Returns the primary asset +prptl.io/-/canister-id/-/token_id - Returns the primary asset -prptl.io/_/canister_id/_/token_id/preview - Returns the preview asset +prptl.io/-/canister_id/-/token_id/preview - Returns the preview asset -prptl.io/_/canister_id/_/token_id/ex - Origyn NFTs are self contained internet addressable objects. All the data for rendering is contained inside the NFT (authors can choose to host data on other platforms). Returns an HTML interface that displays the NFT according to the NFT authors specification. +prptl.io/-/canister_id/-/token_id/ex - Origyn NFTs are self contained internet addressable objects. All the data for rendering is contained inside the NFT (authors can choose to host data on other platforms). Returns an HTML interface that displays the NFT according to the NFT authors specification. -prptl.io/_/canister_id/_/token_id/_/library_id - Returns the asset in the library +prptl.io/-/canister_id/-/token_id/-/library_id - Returns the asset in the library -prptl.io/_/canister_id/_/token_id/_/library_id/info - Returns a json representation of assets in the library +prptl.io/-/canister_id/-/token_id/-/library_id/info - Returns a json representation of assets in the library -prptl.io/_/canister_id/_/token_id/info - Returns a json representation of the metadata, including the library items +prptl.io/-/canister_id/-/token_id/info - Returns a json representation of the metadata, including the library items -prptl.io/_/canister_id/_/token_id/info?query=[Query] - Returns a json representation of the metadata passed through a query +prptl.io/-/canister_id/-/token_id/info?query=[Query] - Returns a json representation of the metadata passed through a query + +prptl.io/-/canister_id/-/token_id/translate - Returns a json representation of translations to ext and dip721 ### Collection Information -prptl.io/_/canister_id/collection - Returns a json representation of collection information +prptl.io/-/canister_id/collection - Returns a json representation of collection information + +prptl.io/-/canister_id/collection/translate - Returns a json representation of token ids with translations to ext and dip721 + + * http routes - /ledger_info/{page}/{page_size} now returns the ledger json for the collection level * http routes - /-/token_id/ledger_info/{page}/{page_size} now returns the ledger json for the token level diff --git a/package-set.dhall b/package-set.dhall index 46d85bf..c43cdbd 100644 --- a/package-set.dhall +++ b/package-set.dhall @@ -13,6 +13,11 @@ let additions = , version = "v0.1.10" , dependencies = ["base"] }, + { name = "candy_0_1_12" + , repo = "https://github.com/aramakme/candy_library.git" + , version = "v0.1.12" + , dependencies = ["base"] + }, { name="principalmo", repo = "https://github.com/aviate-labs/principal.mo.git", diff --git a/src/origyn_nft_reference/http.mo b/src/origyn_nft_reference/http.mo index 1bed873..f972924 100644 --- a/src/origyn_nft_reference/http.mo +++ b/src/origyn_nft_reference/http.mo @@ -1582,6 +1582,22 @@ module { return json(#Array(#frozen(Metadata.ledger_to_candy(ledger, page, size))), null); }; + + if(path_array[2] == "translate"){ + + debug if(debug_channel.request) D.print("render translate " # token_id ); + + let translation = #Class([ + {name="origyn_nft"; value=#Text(token_id); immutable=true;}, + {name="ext"; value=#Text(Types._getEXTTokenIdentifier(token_id, state.canister())); immutable=true;}, + {name="dip721"; value=#Text(Nat.toText(NFTUtils.get_token_id_as_nat(token_id))); immutable=true;} + ]); + + + + return json(translation, null); + }; + if(path_size > 3){ @@ -1763,7 +1779,30 @@ module { return json(#Array(#frozen(Metadata.ledger_to_candy(ledger, page, size))), null); }; + if(path_array[1]== "translate"){ + let rawkeys = if(NFTUtils.is_owner_manager_network(state, caller) == true){ + Iter.toArray(Iter.filter(Map.keys(state.state.nft_metadata), func (x : Text){ x != ""})); + + } else { + Iter.toArray(Iter.filter(Map.keys(state.state.nft_ledgers), func (x : Text){ x != ""})); + }; + + let translation = Array.map(rawkeys, func(x){ + + return #Class([ + {name="origyn_nft"; value=#Text(x); immutable=true;}, + {name="ext"; value=#Text(Types._getEXTTokenIdentifier(x, state.canister())); immutable=true;}, + {name="dip721"; value=#Text(Nat.toText(NFTUtils.get_token_id_as_nat(x))); immutable=true;} + ]); + }); + + + return json(#Array(#frozen(translation)), null); + + }; + } else { + debug if(debug_channel.request) D.print("collection info"); let keys = let keys = if(NFTUtils.is_owner_manager_network(state, caller) == true){ Array.map(Iter.toArray(Iter.filter(Map.keys(state.state.nft_metadata), func (x : Text){ x != ""})), func (x:Text){#Text(x)}); // Should always have the "" item and need to remove it diff --git a/src/origyn_nft_reference/main.mo b/src/origyn_nft_reference/main.mo index 25537d0..b1ea3da 100644 --- a/src/origyn_nft_reference/main.mo +++ b/src/origyn_nft_reference/main.mo @@ -5,20 +5,24 @@ import Cycles "mo:base/ExperimentalCycles"; import D "mo:base/Debug"; import Error "mo:base/Error"; import Iter "mo:base/Iter"; +import Int "mo:base/Int"; import Nat "mo:base/Nat"; import Nat32 "mo:base/Nat32"; import Nat8 "mo:base/Nat8"; +import Nat64 "mo:base/Nat64"; import Option "mo:base/Option"; import Principal "mo:base/Principal"; import Result "mo:base/Result"; import Text "mo:base/Text"; import Time "mo:base/Time"; + import TrieMap "mo:base/TrieMap"; import CandyTypes "mo:candy_0_1_10/types"; import Conversions "mo:candy_0_1_10/conversion"; import EXT "mo:ext/Core"; import EXTCommon "mo:ext/Common"; import Map "mo:map_6_0_0/Map"; +import Set "mo:map_6_0_0/Set"; import Properties "mo:candy_0_1_10/properties"; import Workspace "mo:candy_0_1_10/workspace"; import Current "migrations/v000_001_000/types"; @@ -36,6 +40,8 @@ import data "data"; import http "http"; import Char "mo:base/Char"; import Canistergeek "mo:canistergeek/canistergeek"; +import JSON "mo:candy_0_1_12/json"; + @@ -54,6 +60,11 @@ shared (deployer) actor class Nft_Canister(__initargs : Types.InitArgs) = this { // A standard file chunk size. The IC limits intercanister messages to ~2MB+ so we set that here stable var SIZE_CHUNK = 2048000; //max message size + stable let created_at = Nat64.fromNat(Int.abs(Time.now())); + stable var upgraded_at = Nat64.fromNat(Int.abs(Time.now())); + + let {thash} = Map; + // Canisters can support multiple storage nodes // If you have a small collection you don't need to use a storage collection @@ -415,43 +426,49 @@ shared (deployer) actor class Nft_Canister(__initargs : Types.InitArgs) = this { return Governance.governance_nft_origyn(get_state(), request, msg.caller); }; - // Dip721 transferFrom - must have a valid escrow - public shared (msg) func transferFromDip721(from: Principal, to: Principal, tokenAsNat: Nat) : async DIP721.Result{ - if(halt == true){throw Error.reject("canister is in maintenance mode");}; - let log_data : Text = "From : " # Principal.toText(from) # " to " # Principal.toText(to) # " - Token : " # Nat.toText(tokenAsNat); - canistergeekLogger.logMessage("transferFromDip721",#Text(log_data),?msg.caller); + private func _dip_721_transfer(caller: Principal, to: Principal, tokenAsNat: Nat) : async* DIP721.Result{ + let log_data : Text = "To :" # Principal.toText(to) # " - Token : " # Nat.toText(tokenAsNat); + canistergeekLogger.logMessage("transferDip721",#Text("transferDip721"),?caller); canistergeekMonitor.collectMetrics(); debug if(debug_channel.function_announce) D.print("in transferFromDip721"); // Existing escrow acts as approval - if(msg.caller != to){ - return #Err(#UnauthorizedOperator); - }; - return await Owner.transferDip721(get_state(),from, to, tokenAsNat, msg.caller); + return await Owner.transferDip721(get_state(), caller, to, tokenAsNat, caller); + }; + + + // Dip721 transferFrom - must have a valid escrow + public shared (msg) func transferFromDip721(from: Principal, to: Principal, tokenAsNat: Nat) : async DIP721.Result{ + if(halt == true){throw Error.reject("canister is in maintenance mode");}; + await* _dip_721_transfer(msg.caller, to, tokenAsNat); }; // Dip721 transfer - must have a valid escrow public shared (msg) func transferDip721(to: Principal, tokenAsNat: Nat) : async DIP721.Result{ - if(halt == true){throw Error.reject("canister is in maintenance mode");}; - let log_data : Text = "To :" # Principal.toText(to) # " - Token : " # Nat.toText(tokenAsNat); - canistergeekLogger.logMessage("transferDip721",#Text("transferDip721"),?msg.caller); - canistergeekMonitor.collectMetrics(); - debug if(debug_channel.function_announce) D.print("in transferFromDip721"); - // Existing escrow acts as approval - return await Owner.transferDip721(get_state(),msg.caller, to, tokenAsNat, msg.caller); + if(halt == true){throw Error.reject("canister is in maintenance mode");}; + await* _dip_721_transfer(msg.caller, to, tokenAsNat); + }; + + public shared (msg) func dip721_transfer(to: Principal, tokenAsNat: Nat) : async DIP721.Result{ + if(halt == true){throw Error.reject("canister is in maintenance mode");}; + await* _dip_721_transfer(msg.caller, to, tokenAsNat); + }; + + private func _dip_721_transferFrom(caller: Principal, from: Principal, to: Principal, tokenAsNat: Nat) : async* DIP721.Result{ + let log_data : Text = "From : " # Principal.toText(from) # " to " # Principal.toText(to) # " - Token : " # Nat.toText(tokenAsNat); + canistergeekLogger.logMessage("transferFrom", #Text("transferFrom"),?caller); + canistergeekMonitor.collectMetrics(); + debug if(debug_channel.function_announce) D.print("in transferFrom"); + if(caller != to){ + return #Err(#UnauthorizedOperator); + }; + // Existing escrow acts as approval + return await Owner.transferDip721(get_state(),from, to, tokenAsNat, caller); }; - // Dip721 transferFrom "v2" downgrade - must have a valid escrow + // Dip721 transferFrom legacy - must have a valid escrow public shared (msg) func transferFrom(from: Principal, to: Principal, tokenAsNat: Nat) : async DIP721.Result{ - if(halt == true){throw Error.reject("canister is in maintenance mode");}; - let log_data : Text = "From : " # Principal.toText(from) # " to " # Principal.toText(to) # " - Token : " # Nat.toText(tokenAsNat); - canistergeekLogger.logMessage("transferFrom",#Text("transferFrom"),?msg.caller); - canistergeekMonitor.collectMetrics(); - debug if(debug_channel.function_announce) D.print("in transferFrom"); - if(msg.caller != to){ - return #Err(#UnauthorizedOperator); - }; - // Existing escrow acts as approval - return await Owner.transferDip721(get_state(),from, to, tokenAsNat, msg.caller); + if(halt == true){throw Error.reject("canister is in maintenance mode");}; + await* _dip_721_transferFrom(msg.caller, from, to, tokenAsNat); }; @@ -921,31 +938,59 @@ shared (deployer) actor class Nft_Canister(__initargs : Types.InitArgs) = this { debug if(debug_channel.function_announce) D.print("in collection_nft_origyn"); let state = get_state(); - let keys = if(NFTUtils.is_owner_manager_network(get_state(), msg.caller) == true){ - Iter.toArray(Iter.filter(Map.keys(state.state.nft_metadata), func (x : Text){ x != ""})); // Should always have the "" item and need to remove it + let keys = if(NFTUtils.is_owner_manager_network(state, msg.caller) == true){ + Iter.filter(Map.keys(state.state.nft_metadata), func (x : Text){ x != ""}); // Should always have the "" item and need to remove it } else { - Iter.toArray(Iter.filter(Map.keys(state.state.nft_ledgers), func (x : Text){ x != ""})); // Should always have the "" item and need to remove it + Iter.filter(Map.keys(state.state.nft_ledgers), func (x : Text){ x != ""}); // Should always have the "" item and need to remove it }; + + let ownerSet = Set.new(); + for(thisItem in keys){ + let entry = switch(Map.get(state.state.nft_metadata, thash, thisItem)){ + case(?val) val; + case(null) #Empty; + }; + + switch(Metadata.get_nft_owner(entry)){ + case(#ok(account)){ + Set.add(ownerSet, (Types.account_hash, Types.account_eq), account); + }; + case(#err(err)){}; + }; + }; + + let vals = Map.vals(state.state.nft_ledgers); + var transaction_count = 0; + Iter.iterate>(vals, func(x: SB.StableBuffer,_index){ transaction_count += SB.size(x)}); + let multi_canister = Iter.toArray(Map.keys(state.state.buckets)); + let keysArray = Iter.toArray(keys); + + return #ok({ - fields = fields; - logo = state.state.collection_data.logo; - name = state.state.collection_data.name; - symbol = state.state.collection_data.symbol; - total_supply = ?keys.size(); - owner = ?state.state.collection_data.owner; - managers = ?state.state.collection_data.managers; - network = state.state.collection_data.network; - token_ids = ?keys; - token_ids_count = ?keys.size(); - multi_canister = ?multi_canister; - multi_canister_count = ?multi_canister.size(); - metadata = Map.get(state.state.nft_metadata, Map.thash, ""); - allocated_storage = ?state.state.collection_data.allocated_storage; - available_space = ?state.state.collection_data.available_space; - } + fields = fields; + logo = state.state.collection_data.logo; + name = state.state.collection_data.name; + symbol = state.state.collection_data.symbol; + total_supply = ?keysArray.size(); + owner = ?state.state.collection_data.owner; + managers = ?state.state.collection_data.managers; + network = state.state.collection_data.network; + token_ids = ?keysArray; + token_ids_count = ?keysArray.size(); + multi_canister = ?multi_canister; + multi_canister_count = ?multi_canister.size(); + metadata = Map.get(state.state.nft_metadata, Map.thash, ""); + allocated_storage = ?state.state.collection_data.allocated_storage; + available_space = ?state.state.collection_data.available_space; + created_at = ?created_at; + upgraded_at = ?upgraded_at; + unique_holders = ?Set.size(ownerSet); + transaction_count = ?transaction_count; + } + ); @@ -1060,7 +1105,20 @@ shared (deployer) actor class Nft_Canister(__initargs : Types.InitArgs) = this { return (Metadata.get_NFTs_for_user(get_state(), #principal(user))).size(); }; - // Dip721 balance + // Dip721 balance legacy + public query(msg) func balanceOf(user: Principal) : async Nat{ + + debug if(debug_channel.function_announce) D.print("in balanceOfDip721"); + return (Metadata.get_NFTs_for_user(get_state(), #principal(user))).size(); + }; + + // Dip721 balance legacy + public query(msg) func dip721_balanceOf(user: Principal) : async Nat{ + debug if(debug_channel.function_announce) D.print("in balanceOfDip721"); + return (Metadata.get_NFTs_for_user(get_state(), #principal(user))).size(); + }; + + // EXT balance public query(msg) func balance(request: EXT.BalanceRequest) : async EXT.BalanceResponse{ //legacy ext debug if(debug_channel.function_announce) D.print("in balance"); @@ -1074,7 +1132,7 @@ shared (deployer) actor class Nft_Canister(__initargs : Types.InitArgs) = this { return _getEXTBalance(request); }; - // Ext balance + // used by stoic public query(msg) func tokens_ext(request: Text) : async Result.Result<[Types.EXTTokensResult], EXT.CommonError> { debug if(debug_channel.function_announce) D.print("in tokens_ext"); @@ -1380,7 +1438,15 @@ shared (deployer) actor class Nft_Canister(__initargs : Types.InitArgs) = this { return _ownerOfDip721(tokenAsNat, msg.caller); }; - // For dip721 "v2" downgrade in usability + public query(msg) func dip721_owner_of(tokenAsNat: Nat) : async DIP721.OwnerOfResponse{ + + if(halt == true){throw Error.reject("canister is in maintenance mode");}; + debug if(debug_channel.function_announce) D.print("in ownerOfDIP721"); + return _ownerOfDip721(tokenAsNat, msg.caller); + }; + + + // For dip721 "v2" legacy public query(msg) func ownerOf(tokenAsNat: Nat) : async DIP721.OwnerOfResponse{ if(halt == true){throw Error.reject("canister is in maintenance mode");}; @@ -1388,6 +1454,143 @@ shared (deployer) actor class Nft_Canister(__initargs : Types.InitArgs) = this { return _ownerOfDip721(tokenAsNat, msg.caller); }; + private func _dip_721_metadata(caller: Principal, token_id : Nat) : DIP721.Metadata_3 { + + let token_id_raw = NFTUtils.get_nat_as_token_id(token_id); + + let nft = switch(_nft_origyn(token_id_raw, caller)){ + case(#ok(nft)) nft; + case(#err(e)) return #Err(#TokenNotFound); + }; + + let state = get_state(); + + let owner = switch(Metadata.get_nft_owner(nft.metadata)){ + case(#ok(owner)){ + switch(owner){ + case(#principal(p)) ?p; + case(#account_id(a)) null; + case(#account(a)) ?a.owner; + case(#extensible(e)) null; + }; + }; + case(#err(e)) null; + }; + + return #Ok({transferred_at = null; + transferred_by = null; + owner = owner; + operator = owner; + approved_at = null; + approved_by = null; + properties = [ + ("location", #TextContent("https://" # Principal.toText(state.canister()) # ".raw.ic0.app/-/" # token_id_raw)), + ("thumbnail", #TextContent("https://" # Principal.toText(state.canister()) # ".raw.ic0.app/-/" # token_id_raw # "/preview")), + ("com.origyn.data", #TextContent(JSON.value_to_json(nft.metadata))) + ]; + is_burned = false; + token_identifier = token_id; + burned_at = null; + burned_by = null; + minted_at = 0; + minted_by = state.state.collection_data.owner; + }); + }; + + private func _dip_721_metadata_for_principal(caller: Principal, principal : Principal) :DIP721.Metadata_2{ + // D.print("nft origyn :" # debug_show(token_id)); + + debug if(debug_channel.function_announce) D.print("in nft_origyn"); + let resultBuffer = Buffer.Buffer(1); + let state = get_state(); + + for(this_nft in Map.entries(state.state.nft_metadata)){ + switch(Metadata.is_nft_owner(this_nft.1, #principal(principal))){ + case(#ok(val)){ + if(val == true and this_nft.0 != ""){ + let thismetadata = _dip_721_metadata(caller, NFTUtils.get_token_id_as_nat(this_nft.0)); + switch(thismetadata){ + case(#Ok(data)){ resultBuffer.add(data);}; + case(#Err(err)){return #Err(err)}; + }; + + }; + }; + case(#err(err)){ + + }; + }; + }; + + return #Ok(resultBuffer.toArray()); + } ; + + + public query (msg) func dip721_owner_token_metadata(owner : Principal) : async DIP721.Metadata_2{ + + _dip_721_metadata_for_principal(msg.caller, owner); + }; + + public query (msg) func dip721_operator_token_metadata(operator : Principal) : async DIP721.Metadata_2{ + + _dip_721_metadata_for_principal(msg.caller, operator); + }; + + public query(msg) func dip721_token_metadata(token_id : Nat) : async DIP721.Metadata_3{ + + _dip_721_metadata(msg.caller, token_id); + }; + + public query(msg) func tokenMetadata(token_id : Nat) : async DIP721.Metadata_3{ + + _dip_721_metadata(msg.caller, token_id); + }; + + public query (msg) func ownerTokenMetadata(owner : Principal) : async DIP721.Metadata_2{ + + _dip_721_metadata_for_principal(msg.caller, owner); + }; + + public query (msg) func operaterTokenMetadata(operator : Principal) : async DIP721.Metadata_2{ + + _dip_721_metadata_for_principal(msg.caller, operator); + }; + + public query(msg) func dip721_is_approved_for_all(token_id : Nat) : async DIP721.Result_1{ + + return(#Ok(false)); + }; + + private func _dip_721_get_tokens(caller: Principal, owner: Principal) : DIP721.Metadata_1{ + let nft_results = Buffer.Buffer(1); + let state = get_state(); + + // nyi: check the mint status and compare to msg.caller + // nyi: indexing of NFTs, Escrows, Sales, Offers if this is a performance drain + for(this_nft in Map.entries(state.state.nft_metadata)){ + switch(Metadata.is_nft_owner(this_nft.1, #principal(owner))){ + case(#ok(val)){ + if(val == true and this_nft.0 != ""){ + nft_results.add(this_nft.0); + }; + }; + case(_){}; + }; + + }; + + #Ok(Iter.toArray(Iter.map(nft_results.vals(), func(x){NFTUtils.get_token_id_as_nat(x)}))); + }; + + public query (msg) func dip721_owner_token_identifiers(owner : Principal) : async DIP721.Metadata_1{ + _dip_721_get_tokens(msg.caller, owner); + }; + + public query (msg) func dip721_operator_token_identifiers(operator : Principal) : async DIP721.Metadata_1{ + _dip_721_get_tokens(msg.caller, operator); + }; + + // Supports EXT Bearer public query(msg) func bearerEXT(tokenIdentifier: EXT.TokenIdentifier) : async Result.Result{ @@ -1613,7 +1816,7 @@ shared (deployer) actor class Nft_Canister(__initargs : Types.InitArgs) = this { // Metadata for ext - public query func metadata(token : EXT.TokenIdentifier) : async Result.Result{ + public query func metadataEXT(token : EXT.TokenIdentifier) : async Result.Result{ if(halt == true){throw Error.reject("canister is in maintenance mode");}; debug if(debug_channel.function_announce) D.print("in metadata"); @@ -1721,6 +1924,111 @@ shared (deployer) actor class Nft_Canister(__initargs : Types.InitArgs) = this { return SB.toArray(state.state.log); }; + //metadata for DIP721 + public query func dip721_name() : async ?Text{ + return get_state().state.collection_data.name; + }; + + public query func dip721_logo() : async ?Text{ + return get_state().state.collection_data.logo; + }; + + public query func dip721_symbol() : async ?Text{ + return get_state().state.collection_data.symbol; + }; + + public query func dip721_custodians() : async [Principal]{ + return get_state().state.collection_data.managers; + }; + + public query func dip721_metadata() : async DIP721.Metadata{ + let state = get_state(); + return { + logo = state.state.collection_data.logo; + name = state.state.collection_data.name; + created_at = created_at; + upgraded_at = upgraded_at; + custodians = state.state.collection_data.managers; + symbol = state.state.collection_data.symbol; + }; + }; + + public query func metadata() : async DIP721.Metadata{ + let state = get_state(); + return { + logo = state.state.collection_data.logo; + name = state.state.collection_data.name; + created_at = created_at; + upgraded_at = upgraded_at; + custodians = state.state.collection_data.managers; + symbol = state.state.collection_data.symbol; + }; + }; + + public query(msg) func dip721_total_supply() : async Nat{ + + let state = get_state(); + let keys = if(NFTUtils.is_owner_manager_network(get_state(), msg.caller) == true){ + Iter.toArray(Iter.filter(Map.keys(state.state.nft_metadata), func (x : Text){ x != ""})); // Should always have the "" item and need to remove it + } else { + Iter.toArray(Iter.filter(Map.keys(state.state.nft_ledgers), func (x : Text){ x != ""})); // Should always have the "" item and need to remove it + }; + return keys.size(); + }; + + public query(msg) func dip721_total_transactions() : async Nat{ + let state = get_state(); + let vals = Map.vals(state.state.nft_ledgers); + var count = 0; + Iter.iterate>(vals, func(x: SB.StableBuffer,_index){ count += SB.size(x)}); + return count; + }; + + public query(msg) func dip721_stats() : async DIP721.Stats { + if(halt == true){throw Error.reject("canister is in maintenance mode");}; + debug if(debug_channel.function_announce) D.print("in collection_nft_origyn"); + + let state = get_state(); + let keys = if(NFTUtils.is_owner_manager_network(state, msg.caller) == true){ + Iter.filter(Map.keys(state.state.nft_metadata), func (x : Text){ x != ""}); // Should always have the "" item and need to remove it + } else { + Iter.filter(Map.keys(state.state.nft_ledgers), func (x : Text){ x != ""}); // Should always have the "" item and need to remove it + }; + + let ownerSet = Set.new(); + for(thisItem in keys){ + let entry = switch(Map.get(state.state.nft_metadata, thash, thisItem)){ + case(?val) val; + case(null) #Empty; + }; + + switch(Metadata.get_nft_owner(entry)){ + case(#ok(account)){ + Set.add(ownerSet, (Types.account_hash, Types.account_eq), account); + }; + case(#err(err)){}; + }; + }; + + let vals = Map.vals(state.state.nft_ledgers); + var transaction_count = 0; + Iter.iterate>(vals, func(x: SB.StableBuffer,_index){ transaction_count += SB.size(x)}); + + let keysArray = Iter.toArray(keys); + + return { + cycles = Cycles.balance(); + total_supply = keysArray.size(); + total_unique_holders = Set.size(ownerSet); + total_transactions = transaction_count; + }; + }; + + public query(msg) func dip721_supported_interfaces() : async [DIP721.SupportedInterface] { + return [#TransactionHistory]; + }; + + // ************************* // * CANDID SERIALIZATION ** // ************************* @@ -2112,6 +2420,9 @@ shared (deployer) actor class Nft_Canister(__initargs : Types.InitArgs) = this { //Optional: override default number of log messages to your value canistergeekLogger.setMaxMessagesCount(3000); + upgraded_at := Nat64.fromNat(Int.abs(Time.now())); + + // End Canistergeek }; }; \ No newline at end of file diff --git a/vessel.dhall b/vessel.dhall index fd6211f..f1d3864 100644 --- a/vessel.dhall +++ b/vessel.dhall @@ -1,4 +1,4 @@ { - dependencies = [ "base", "array", "crypto", "hash", "encoding", "matchers","candy_0_1_10","principalmo","ext","httpparser","http","json","format","stablerbtree_0_6_1","stablebuffer_0_2_0","map_6_0_0","map","stablebuffer", "canistergeek" ], + dependencies = [ "base", "array", "crypto", "hash", "encoding", "matchers","candy_0_1_10","candy_0_1_12","principalmo","ext","httpparser","http","json","format","stablerbtree_0_6_1","stablebuffer_0_2_0","map_6_0_0","map","stablebuffer", "canistergeek" ], compiler = Some "0.6.22" }