├── src └── hzl_nft │ ├── public_types.mo │ ├── CreatorService.mo │ ├── Nft.mo │ └── main.mo ├── .gitignore ├── README.md ├── package.json ├── dfx.json └── webpack.config.js /src/hzl_nft/public_types.mo: -------------------------------------------------------------------------------- 1 | import Result "mo:base/Result"; 2 | module { 3 | public type Hub = actor { 4 | requestId : shared() -> async Result.Result; 5 | } 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Various IDEs and Editors 2 | .vscode/ 3 | .idea/ 4 | **/*~ 5 | 6 | # Mac OSX temporary files 7 | .DS_Store 8 | **/.DS_Store 9 | 10 | # dfx temporary files 11 | .dfx/ 12 | 13 | # frontend code 14 | node_modules/ 15 | dist/ 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dfinity Nft Example 2 | ## (っ◔◡◔)っ ♥ very wip ♥ 3 | 4 | My take on how NFTs might come to Dfinity. 5 | 6 | * Hub -> Spawns Creator Services 7 | * Creator Services -> Mints NFT Services 8 | * NFT Services -> Is an NFT :) 9 | 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hzl_nft_assets", 3 | "version": "0.1.0", 4 | "description": "", 5 | "keywords": [], 6 | "scripts": { 7 | "build": "webpack" 8 | }, 9 | "devDependencies": { 10 | "@dfinity/agent": "0.6.26", 11 | "terser-webpack-plugin": "2.2.2", 12 | "webpack": "4.41.3", 13 | "webpack-cli": "3.3.10" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "canisters": { 3 | "hzl_nft": { 4 | "main": "src/hzl_nft/main.mo", 5 | "type": "motoko" 6 | } 7 | }, 8 | "defaults": { 9 | "build": { 10 | "packtool": "" 11 | } 12 | }, 13 | "dfx": "0.6.26", 14 | "networks": { 15 | "local": { 16 | "bind": "127.0.0.1:8000", 17 | "type": "ephemeral" 18 | } 19 | }, 20 | "version": 1 21 | } -------------------------------------------------------------------------------- /src/hzl_nft/CreatorService.mo: -------------------------------------------------------------------------------- 1 | import Nft "Nft"; 2 | import Principal "mo:base/Principal"; 3 | import Result "mo:base/Result"; 4 | import Types "public_types"; 5 | 6 | actor class CreatorService(owner : Principal, spawn : Principal) { 7 | 8 | let hub : Types.Hub = actor(Principal.toText(spawn)); 9 | 10 | public shared(msg) func mintNft(data : [Nat8], dataType : Text) : async Result.Result { 11 | if (msg.caller != owner) { 12 | return #err("Not Authorized"); 13 | }; 14 | 15 | 16 | switch(await hub.requestId()) { 17 | case (#ok(v)) { 18 | let minted = await Nft.Nft(owner, v, data, dataType); 19 | return #ok(Principal.fromActor(minted)); 20 | }; 21 | case (#err(e)) { 22 | return #err(e); 23 | }; 24 | }; 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /src/hzl_nft/Nft.mo: -------------------------------------------------------------------------------- 1 | // NFT Canister 2 | // Is this a good idea? Who knows! 3 | // This canister is designed to 4 | actor class Nft(creator : Principal, id : Nat, payloadData : [Nat8], payloadDataType : Text) { 5 | type Data = { 6 | dataType : Text; 7 | data : [Nat8]; // We're being lazy pals - sending the data as a Nat8 Array 8 | }; 9 | 10 | var owner : Principal = creator; 11 | 12 | let data : Data = { 13 | dataType = payloadDataType; 14 | data = payloadData; 15 | }; 16 | 17 | public shared(msg) func transferOwner(newOwner : Principal) : async () { 18 | if (msg.caller != owner) { 19 | return; 20 | }; 21 | 22 | owner := newOwner; 23 | return; 24 | }; 25 | 26 | public query func getData() : async Data { 27 | return data; 28 | }; 29 | 30 | public query func getOwner() : async Principal { 31 | return owner; 32 | }; 33 | 34 | public query func getId() : async Nat { 35 | return id; 36 | }; 37 | } -------------------------------------------------------------------------------- /src/hzl_nft/main.mo: -------------------------------------------------------------------------------- 1 | import CreatorService "CreatorService"; 2 | import Debug "mo:base/Debug"; 3 | import ExperimentalCycles "mo:base/ExperimentalCycles"; 4 | import Hash "mo:base/Hash"; 5 | import HashMap "mo:base/HashMap"; 6 | import Nat "mo:base/Nat"; 7 | import Nft "Nft"; 8 | import Prim "mo:prim"; 9 | import Principal "mo:base/Principal"; 10 | import Result "mo:base/Result"; 11 | 12 | actor Hub { 13 | var creatorMap : HashMap.HashMap = HashMap.HashMap(0, Principal.equal, Principal.hash); 14 | var nftMap : HashMap.HashMap = HashMap.HashMap(0, Nat.equal, Hash.hash); 15 | 16 | var idCounter : Nat = 0; 17 | 18 | public shared(msg) func requestId() : async Result.Result { 19 | switch(creatorMap.get(msg.caller)) { 20 | case (?v) { 21 | idCounter := idCounter + 1; 22 | return #ok(idCounter); 23 | }; 24 | case (None) { 25 | return #err("Invalid Request"); 26 | } 27 | } 28 | }; 29 | 30 | public shared query func getNftAddress(id : Nat) : async ?Principal { 31 | return nftMap.get(id); 32 | }; 33 | 34 | public shared(msg) func spawnCreator() : async Text { 35 | let existing = creatorMap.get(msg.caller); 36 | 37 | switch(creatorMap.get(msg.caller)) { 38 | case (?v) { 39 | return Principal.toText(Principal.fromActor(v)); 40 | }; 41 | case (None) { 42 | let new = await CreatorService.CreatorService(msg.caller, Prim.principalOfActor(Hub)); 43 | creatorMap.put(msg.caller, new); 44 | return Principal.toText(Principal.fromActor(new)); 45 | }; 46 | }; 47 | 48 | return "Ok"; 49 | }; 50 | 51 | // noop 52 | public func deposit() : async () { 53 | let v = ExperimentalCycles.available(); 54 | let _ = ExperimentalCycles.accept(v); 55 | return; 56 | }; 57 | 58 | public func http_request() : async Text { 59 | Debug.print("hello World"); 60 | return "ok"; 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | const dfxJson = require("./dfx.json"); 4 | 5 | // List of all aliases for canisters. This creates the module alias for 6 | // the `import ... from "ic:canisters/xyz"` where xyz is the name of a 7 | // canister. 8 | const aliases = Object.entries(dfxJson.canisters).reduce( 9 | (acc, [name, _value]) => { 10 | // Get the network name, or `local` by default. 11 | const networkName = process.env["DFX_NETWORK"] || "local"; 12 | const outputRoot = path.join( 13 | __dirname, 14 | ".dfx", 15 | networkName, 16 | "canisters", 17 | name 18 | ); 19 | 20 | return { 21 | ...acc, 22 | ["ic:canisters/" + name]: path.join(outputRoot, name + ".js"), 23 | ["ic:idl/" + name]: path.join(outputRoot, name + ".did.js"), 24 | }; 25 | }, 26 | {} 27 | ); 28 | 29 | /** 30 | * Generate a webpack configuration for a canister. 31 | */ 32 | function generateWebpackConfigForCanister(name, info) { 33 | if (typeof info.frontend !== "object") { 34 | return; 35 | } 36 | 37 | return { 38 | mode: "production", 39 | entry: { 40 | index: path.join(__dirname, info.frontend.entrypoint), 41 | }, 42 | node: { 43 | fs: "empty" 44 | }, 45 | devtool: "source-map", 46 | optimization: { 47 | minimize: true, 48 | minimizer: [new TerserPlugin()], 49 | }, 50 | resolve: { 51 | alias: aliases, 52 | }, 53 | output: { 54 | filename: "[name].js", 55 | path: path.join(__dirname, "dist", name), 56 | }, 57 | 58 | // Depending in the language or framework you are using for 59 | // front-end development, add module loaders to the default 60 | // webpack configuration. For example, if you are using React 61 | // modules and CSS as described in the "Adding a stylesheet" 62 | // tutorial, uncomment the following lines: 63 | // module: { 64 | // rules: [ 65 | // { test: /\.(ts|tsx|jsx)$/, loader: "ts-loader" }, 66 | // { test: /\.css$/, use: ['style-loader','css-loader'] } 67 | // ] 68 | // }, 69 | plugins: [], 70 | }; 71 | } 72 | 73 | // If you have additional webpack configurations you want to build 74 | // as part of this configuration, add them to the section below. 75 | module.exports = [ 76 | ...Object.entries(dfxJson.canisters) 77 | .map(([name, info]) => { 78 | return generateWebpackConfigForCanister(name, info); 79 | }) 80 | .filter((x) => !!x), 81 | ]; 82 | --------------------------------------------------------------------------------