{"id":20974348,"url":"https://github.com/thirdweb-example/artblocks","last_synced_at":"2025-05-14T12:31:55.623Z","repository":{"id":61988581,"uuid":"527801216","full_name":"thirdweb-example/artblocks","owner":"thirdweb-example","description":"Create Generative Art NFTs with centralized metadata using the Contracts SDK","archived":false,"fork":false,"pushed_at":"2022-10-27T16:12:15.000Z","size":1244,"stargazers_count":4,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2023-03-04T18:10:50.398Z","etag":null,"topics":["contract-kit","extensions"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thirdweb-example.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-08-23T02:16:31.000Z","updated_at":"2023-01-17T23:35:21.000Z","dependencies_parsed_at":"2023-01-20T13:32:20.207Z","dependency_job_id":null,"html_url":"https://github.com/thirdweb-example/artblocks","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thirdweb-example%2Fartblocks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thirdweb-example%2Fartblocks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thirdweb-example%2Fartblocks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thirdweb-example%2Fartblocks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thirdweb-example","download_url":"https://codeload.github.com/thirdweb-example/artblocks/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225294833,"owners_count":17451567,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["contract-kit","extensions"],"created_at":"2024-11-19T04:28:28.953Z","updated_at":"2024-11-19T04:28:29.594Z","avatar_url":"https://github.com/thirdweb-example.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Generative Art NFTs\r\n\r\nThis template shows you how to create an NFT Collection with a **centralized** server, that generates art based on a unique hash that is generated when an NFT gets minted.\r\n\r\nThere are two components to this template:\r\n\r\n1. The `contract` folder: Stores the smart contract for our NFT collection\r\n2. the `generative-art-server` folder: Stores the code that generates the art for our NFTs, intended to be run on a server.\r\n\r\n## Contract Logic\r\n\r\nThe [Contract.sol](./contract/contracts/Contract.sol) file contains our NFT Drop contract that uses the [ERC721Drop](https://portal.thirdweb.com/contracts-sdk/base-contracts/erc-721/erc721drop) base contract.\r\n\r\nThe contract contrains a `script` variable to store the logic to generate the art for a given NFT, which gets set in the constructor (when the contract gets deployed).\r\n\r\n```solidity\r\n// SPDX-License-Identifier: Apache-2.0\r\npragma solidity ^0.8.0;\r\n\r\nimport \"@thirdweb-dev/contracts/base/ERC721Drop.sol\";\r\n\r\ncontract MyGenerativeArt is ERC721Drop {\r\n    string public script;             // declare storage variable\r\n    \r\n    function setScript(string calldata _script) onlyOwner public {\r\n        script = _script;\r\n    }\r\n    \r\n    constructor(\r\n        string memory _name,\r\n        string memory _symbol,\r\n        address _royaltyRecipient,\r\n        uint128 _royaltyBps,\r\n        address _primarySaleRecipient,\r\n\t\tstring memory _script          // script input param\r\n    )\r\n        ERC721Drop(\r\n            _name,\r\n            _symbol,\r\n            _royaltyRecipient,\r\n            _royaltyBps,\r\n            _primarySaleRecipient\r\n        )\r\n    {\r\n\t\tscript = _script; // initialize script variable\r\n\t}\r\n}\r\n```\r\n\r\nTo generate unique art pieces for each NFT in our collection, we generate a **hash value** for each token ID.\r\n\r\n```solidity\r\n// mapping from tokenId to associated hash value\r\nmapping(uint256 =\u003e bytes32) public tokenToHash;\r\n\r\n// mapping of hash to tokenId\r\nmapping(bytes32 =\u003e uint256) public hashToToken;\r\n```\r\n\r\nWe generate a hash value for an NFT (token ID) as it’s minted.\r\n\r\nEach time a user claims an NFT from our drop, a hash will get generated, and subsequently stored in this mapping.\r\n\r\nTo generate the hash value, we’re using a combination of `tokenId`, `block number`, and the receiver’s `wallet address` to create a unique value for each NFT. Combining these values will be unique every time a new NFT gets minted, which means we’ll be able to use these unique values to generate different images for each NFT based on this hash!\r\n\r\n```solidity\r\n// Generative NFT logic\r\nfunction _mintGenerative(address _to, uint256 _startTokenId, uint256 _qty) internal virtual {\r\n    for(uint256 i = 0; i \u003c _qty; i += 1) {\r\n        uint256 _id = _startTokenId + i;\r\n\r\n        // generate hash\r\n        bytes32 mintHash = keccak256(abi.encodePacked(_id, blockhash(block.number - 1), _to));\r\n\r\n\t\t// save hash in mappings\r\n        tokenToHash[_id] = mintHash;\r\n        hashToToken[mintHash] = _id;\r\n    }\r\n}\r\n```\r\n\r\nIn the Drop base contract, tokens are minted inside `transferTokensOnClaim` function. We need to override its logic to include our art generation:\r\n\r\n```solidity\r\nfunction transferTokensOnClaim(address _to, uint256 _quantityBeingClaimed)\r\n    internal\r\n    virtual\r\n    override\r\n    returns (uint256 startTokenId)\r\n{\r\n    startTokenId = _currentIndex;\r\n\t// Call our mintGenerative function here!\r\n    _mintGenerative(_to, startTokenId, _quantityBeingClaimed);\r\n    _safeMint(_to, _quantityBeingClaimed);\r\n}\r\n```\r\n\r\n## Server Logic\r\n\r\nThe `generative-art-server` folder contains the code for a Node.JS server that generates art for our NFTs.\r\n\r\nIt exposes an endpoint that accepts a token ID:\r\n\r\n```js\r\napp.get(\"/token/:tokenId\", async (req, res) =\u003e {\r\n  const hash = await getScript(req.params.tokenId);\r\n\r\n  if (!tokenImages[`img_${req.params.tokenId}`]) {\r\n    const buf = saveImage(hash, req.params.tokenId);\r\n\r\n    // Configure this to the network you deployed your contract to;\r\n    const sdk = new ThirdwebSDK(\"goerli\");\r\n\r\n    const result = await sdk.storage.upload(buf);\r\n\r\n    tokenImages[`img_${req.params.tokenId}`] = `${result.uris[0]}`;\r\n  }\r\n\r\n  res.render(\"piece\", {\r\n    scriptName: `mySketch.js`,\r\n  });\r\n});\r\n```\r\n\r\nWhen a user hits this endpoint, for example, `https://\u003cour-deployed-server-url\u003e/token/1`, the server will generate the art for the NFT with ID `1`.\r\n\r\nFirst, it calls `getScript`, which fetches the `script` variable we set in the contract.\r\n\r\nWe set this to be the [Artblocks example code](https://github.com/ArtBlocks/artblocks-starter-template/blob/main/public/js/pieces/example.js).\r\n\r\nNow we have the JavaScript logic required to generate the unique art for each NFT.\r\n\r\nThis [getScript](./generative-art-server/controllers/sketches.js) function writes a new file called `mySketch.js` to the `public/js/pieces` folder. This generated script concatenates an object containing the token ID and the hash for that token ID with the `script` variable we set in the contract.\r\n\r\n```js\r\n// Configure this to the network you deployed your contract to;\r\nconst sdk = new ThirdwebSDK(\"goerli\");\r\n\r\nconst getScript = async (tokenId) =\u003e {\r\n  // Your contract address from the dashboard\r\n  const contract = await sdk.getContract(\r\n    \"0x0064B1Cd6f1AC6f8c50D1187D20d9fb489CdDfB6\"\r\n  );\r\n  // Get the script from the contract\r\n  const scriptStr = await contract.call(\"script\");\r\n  const hash = await contract.call(\"tokenToHash\", parseInt(tokenId));\r\n\r\n  // this string is appended to the script-string fetched from the contract.\r\n  // it provides hash and tokenId as inputs to the script\r\n  const detailsForThisToken = `const tokenData = {\r\n        hash: \"${hash}\",\r\n        tokenId: ${tokenId}\r\n    }\\n\r\n`;\r\n\r\n  // Write the details for this token + the script to a file ../public/token/js/pieces/mySketch.js and await the result\r\n  const filePath = path.resolve(\r\n    path.dirname(\".\"),\r\n    \"./public/token/js/pieces/mySketch.js\"\r\n  );\r\n\r\n  // Write the file\r\n  await new Promise((resolve, reject) =\u003e {\r\n    fs.writeFile(\r\n      filePath,\r\n      detailsForThisToken + scriptStr.toString(),\r\n      \"utf8\",\r\n      (err) =\u003e {\r\n        if (err) {\r\n          reject(err);\r\n        } else {\r\n          resolve();\r\n        }\r\n      }\r\n    );\r\n  });\r\n\r\n  return hash;\r\n};\r\n```\r\n\r\nUsing a combination of [p5.js](https://p5js.org/) and [handlebars.js](https://handlebarsjs.com/), we render the art for a given NFT in the [saveImage](./generative-art-server/controllers/images.js) function, using `p5.createSketch` to render the art.\r\n\r\nEach time a new NFT is rendered, we upload it to IPFS using the storage SDK and store that IPFS value as the value for the token ID key in our `tokenImages` object.\r\n\r\n```js\r\nconst tokenImages = {};\r\n\r\napp.get(\"/token/:tokenId\", async (req, res) =\u003e {\r\n  const hash = await getScript(req.params.tokenId);\r\n\r\n  if (!tokenImages[`img_${req.params.tokenId}`]) {\r\n    const buf = saveImage(hash, req.params.tokenId);\r\n\r\n    // Configure this to the network you deployed your contract to;\r\n    const sdk = new ThirdwebSDK(\"goerli\");\r\n\r\n    // If this is the first time we've seen this, upload it to IPFS\r\n    const result = await sdk.storage.upload(buf);\r\n\r\n    // \"Cache\" the IPFS value for this token ID so we don't re-render it every time.\r\n    tokenImages[`img_${req.params.tokenId}`] = `${result.uris[0]}`;\r\n  }\r\n});\r\n```\r\n\r\n### Deploying the Node.JS Server\r\n\r\nYou can deploy the `generative-art-server` as a Node.JS server using any cloud tool, such as [Google App Engine](https://cloud.google.com/appengine/).\r\n\r\nLearn how to deploy by following this guide: https://cloud.google.com/appengine/docs/standard/nodejs/building-app\r\n\r\n### Mapping Metadata to Server Deployment\r\n\r\nTo have your NFTs show the art generated by the server, their `image` and `animation_url` fields need to point to the endpoint of that token ID.\r\n\r\nFor example, if you deployed your server to `https://my-server.com/token/1`, your NFTs would have `image` and `animation_url` fields set to `https://my-server.com/token/1`.\r\n\r\nThis way, when sites such as OpenSea attempt to render your NFTs, they will use the server to generate the art and display it.\r\n\r\n**IMPORTANT NOTE:** This is a **centralized** method of generating art for your NFTs, if your server crashes or is unavailable, your NFTs will not show the art generated by the server until it is back up.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthirdweb-example%2Fartblocks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthirdweb-example%2Fartblocks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthirdweb-example%2Fartblocks/lists"}