{"id":4864,"date":"2022-03-11T09:03:18","date_gmt":"2022-03-11T14:03:18","guid":{"rendered":"https:\/\/www.lpi.org\/creating-nfts-free-and-open-source-software\/"},"modified":"2023-05-10T04:26:19","modified_gmt":"2023-05-10T08:26:19","slug":"creating-nfts-free-and-open-source-software","status":"publish","type":"post","link":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/","title":{"rendered":"Creating NFTs with Free and Open Source Software"},"content":{"rendered":"<p>An unusual educational opportunity came to Linux Professional Institute (LPI) over the summer of 2021. Two interns\u2013Alex, a Public Relations student and myself, Rozilyn, studying electrical and computer engineering\u2013decided to create a set of NFTs as a learning excercise. This article describes the tools used by Alex and I in enough detail that you can try them out yourself.<\/p>\n<p>Until we started our internships, Alex and I didn&#8217;t know any more about NFTs than what we had casually read in the press. When we decided to do an NFT project for our internships, we had to quickly come up to speed on cryptocurrencies: graphics that could be licensed through NFTs, and NFT contracts. In this posting, I\u2019ll describe the technologies used to 1) create the artwork, and 2) write a smart contract that can mint (i.e. create) the NFTs for the corresponding images.<\/p>\n<p>LPI has no plans to use or distribute these NFTs at present, but images of a dressed-up Tux penguin make for an appealing and fun summer project to help young IT professionals learn more about emerging technologies.<\/p>\n<h2>The Development Process<\/h2>\n<p>Convenient tools exist for developing NFTs and smart contracts, which can be used by those with limited technical skills. For example, <a href=\"https:\/\/opensea.io\/\" target=\"_blank\" rel=\"noopener\">OpenSea<\/a> and <a href=\"https:\/\/rarible.com\/\" target=\"_blank\" rel=\"noopener\">Rarible<\/a> make it easy to create and market NFTs. In our case, because our primary goal was to learn the technology, we opted for using open-source tools and going through the process more manually. By the summer\u2019s end, we:\u00a0<\/p>\n<ul>\n<li>Created artwork for LPI based on a CC-licensed image of the popular Tux penguin representing Linux<\/li>\n<li>Created NFTs with a customized <a href=\"https:\/\/openzeppelin.com\/contracts\/\" target=\"_blank\" rel=\"noopener\">Solidity contract<\/a><\/li>\n<li>Deployed and verified the contract on a public test network (testnet)<\/li>\n<li>Minted NFTs that our testers could manage in their wallets<\/li>\n<\/ul>\n<p>The rest of this posting walks through how we created a series of NFTs using entirely open-source tools.<\/p>\n<h2>Creating the Images<\/h2>\n<p>For the artwork, we adopted the method common to many popular existing NFTs (e.g., <a href=\"https:\/\/www.larvalabs.com\/cryptopunks\" target=\"_blank\" rel=\"noopener\">CryptoPunks<\/a> and <a href=\"https:\/\/www.cryptokitties.co\/\" target=\"_blank\" rel=\"noopener\">CryptoKitties<\/a>): Generating a random combination of attributes to customize a base image. This process creates several variations of a common character or theme in a series of NFTs.<\/p>\n<p>Alex created the images for the base Tux and additional attributes using <a href=\"https:\/\/www.gimp.org\/\" target=\"_blank\" rel=\"noopener\">GIMP<\/a> and <a href=\"http:\/\/www.graphicsmagick.org\/\" target=\"_blank\" rel=\"noopener\">GraphicsMagick<\/a>. The images were organized into designated directories (Figure 1) for each area of Tux that Alex customized: shirts, pants, hats, beak, flippers, and so on.<\/p>\n<p><figure class=\"image\" style=\"margin-right:10px; float:left\"><img fetchpriority=\"high\" decoding=\"async\" alt=\"Figure 1: Component image layout (partial)\" height=\"356\" src=\"https:\/\/www.lpi.org\/sites\/default\/files\/nft-figure-1_02.jpg\" width=\"1054\" \/><br \/><figcaption>Figure 1: Component image layout (partial)<\/figcaption><\/figure>\n<\/p>\n<p>The final images were created using a Bash script. First, the script randomly selected one out of several base images of Tux. Then the script overlaid one attribute from each attribute directory onto the base image and saved the final, combined image. Since we were picking random attributes, the script also checks before saving the new image that the new combination of attributes did not repeat an existing combination. The entire process is repeated until the desired number of images is fulfilled.<\/p>\n<p>The GraphicsMagick <a href=\"http:\/\/www.graphicsmagick.org\/composite.html\" target=\"_blank\" rel=\"noopener\"><span style=\"font-family:courier new,courier,monospace;\">gm composite<\/span><\/a> command combined the images. Since the gm command works with one base image and only one overlay image at a time, the script made wrapped calls to the command in a loop (Figure 2) to have the resulting image include multiple attributes.<\/p>\n<p><figure class=\"image\" style=\"margin-right:10px; float:left\"><img decoding=\"async\" alt=\"Figure 2: Layering process\" height=\"356\" src=\"https:\/\/www.lpi.org\/sites\/default\/files\/nft-figure-2-02.jpg\" width=\"1054\" \/><br \/><figcaption>Figure 2: Layering process<\/figcaption><\/figure>\n<\/p>\n<p>The key command-line tools used in the script were:\u00a0<\/p>\n<ul>\n<li><a href=\"https:\/\/man7.org\/linux\/man-pages\/man1\/sort.1.html\" target=\"_blank\" rel=\"noopener\"><span style=\"font-family:courier new,courier,monospace;\">sort<\/span><\/a>: Used with the little-known &#8211;random-sort or -R option, this command returns the attributes in each directory in a randomized order.<\/li>\n<li><span style=\"font-family:courier new,courier,monospace;\"><a href=\"https:\/\/man7.org\/linux\/man-pages\/man1\/head.1.html\" target=\"_blank\" rel=\"noopener\">head<\/a><\/span>: This command selects the first image from the previously mentioned, randomly ordered \u00a0list.<\/li>\n<li><span style=\"font-family:courier new,courier,monospace;\"><a href=\"https:\/\/man7.org\/linux\/man-pages\/man1\/sed.1.html\" target=\"_blank\" rel=\"noopener\">sed<\/a><\/span>: A streaming text editor, this command creates the metadata file from the template (shown below), by replacing <span style=\"font-family:courier new,courier,monospace;\">##TOKENNUM##<\/span> and <span style=\"font-family:courier new,courier,monospace;\">##TOKENURI##<\/span> with the current number and URI for the image the script is currently generating.<\/li>\n<\/ul>\n<p>Each NFT was supplemented by documentation specifying its title, description, and image URI (similar to an address). These details were implemented in our smart contract using a metadata file specified in the <a href=\"https:\/\/eips.ethereum.org\/EIPS\/eip-721#:~:text=%E2%80%9CERC721%20Metadata%20JSON%20Schema%E2%80%9D\" target=\"_blank\" rel=\"noopener\">ERC-721 JSON standard<\/a> for NFTs, as seen in the following sample:<\/p>\n<p><span style=\"font-family:courier new,courier,monospace;\">{<br \/>\n\u00a0 \u00a0 &#8220;title&#8221;: &#8220;MyToken&#8221;,<br \/>\n\u00a0 \u00a0 &#8220;type&#8221;: &#8220;object&#8221;,<br \/>\n\u00a0 \u00a0 &#8220;properties&#8221;: {<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 &#8220;name&#8221;: {<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 &#8220;type&#8221;: &#8220;string&#8221;,<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 &#8220;description&#8221;: &#8220;MyToken ###TOKENNUM##&#8221;<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 },<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 &#8220;description&#8221;: {<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 &#8220;type&#8221;: &#8220;string&#8221;,<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 &#8220;description&#8221;: &#8220;MyToken ###TOKENNUM##&#8221;<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 },<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 &#8220;image&#8221;: {<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 &#8220;type&#8221;: &#8220;string&#8221;,<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 &#8220;description&#8221;: &#8220;##TOKENURI##&#8221;<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 }<br \/>\n\u00a0 \u00a0 }<br \/>\n}<\/span><\/p>\n<p><em>Example 1: NFT template metadata file for sed to transform<\/em><\/p>\n<p>While we opted for our metadata files to solely contain the base provided by the ERC-721 standard, you may want to include different, unique features in your own.\u00a0<\/p>\n<h2>Writing an NFT Contract<\/h2>\n<p>Smart contracts are software programs that are stored and executed on a blockchain network. This software is used to add additional features to transactions, such as allowing the licensee to transfer NFTs to other people. Smart contracts can serve a wide variety of possible functions, including facilitating the storage of transaction information on the network, verifying ownerships, and transferring tokens.\u00a0<\/p>\n<p>The main tools and resources that I used to create the contracts were:\u00a0<\/p>\n<ul>\n<li><a href=\"https:\/\/openzeppelin.com\/\" target=\"_blank\" rel=\"noopener\">OpenZeppellin<\/a>: Described on <a href=\"https:\/\/zeppelin.apache.org\/\" target=\"_blank\" rel=\"noopener\">Zeppelin&#8217;s<\/a> website as \u201c<a href=\"https:\/\/academy.moralis.io\/blog\/defi-deep-dive-what-is-openzeppelin#:~:text=describes%20itself%20as%20an%20open%2Dsource%20framework%20for%20building%20secure%20smart%20contracts%2C%20meant%20to%20simplify%20the%20process%20of%20building%20smart%20contracts.\" target=\"_blank\" rel=\"noopener\">an open-source framework for building secure smart contracts<\/a>\u201d, this project provides documentation, libraries, and support for the construction of smart contracts on decentralized blockchain networks.<\/li>\n<li><a href=\"https:\/\/docs.soliditylang.org\/en\/v0.8.10\/\" target=\"_blank\" rel=\"noopener\">Solidity<\/a>: Used by me to create our smart contracts, this is the programming language for both the sample work and OpenZeppelin\u2019s <a href=\"https:\/\/docs.openzeppelin.com\/contracts\/4.x\/wizard\" target=\"_blank\" rel=\"noopener\">Contracts Wizard<\/a>. It is also commonly known for its usage in writing and implementing smart contracts in general.<\/li>\n<li><a href=\"https:\/\/trufflesuite.com\/\" target=\"_blank\" rel=\"noopener\">Truffle<\/a>: Described as a \u201c<a href=\"https:\/\/trufflesuite.com\/#:~:text=development%20environment%2C%20testing%20framework%20and%20asset%20pipeline%20for%20blockchains\" target=\"_blank\" rel=\"noopener\">personal blockchain for Ethereum development you can use to deploy contracts, develop your applications, and run tests<\/a>\u201d, Truffle provides an isolated environment to test smart contracts prior to deployment on an active blockchain. You can use Truffle to compile, deploy, and interact with your software on your local system.\u00a0<\/li>\n<li><a href=\"https:\/\/trufflesuite.com\/ganache\/\" target=\"_blank\" rel=\"noopener\">Ganache<\/a>: A subset of the Truffle ecosystem, this \u00a0is a developer environment and command-line tool that assists in the development, testing, and deployment of safe (that is, secure) smart contracts on a local (personal) Truffle blockchain network. Developers using Ganache gain the added benefit of not requiring personal test or real ether (ETH), the cryptocurrency used on the Ethereum blockchain. Avoiding ether is convenient for reasons outlined in the following sections.<\/li>\n<li><a href=\"https:\/\/www.anyblockanalytics.com\/networks\/ethereum\/ropsten\/#:~:text=Ropsten%20Ethereum%C2%A0(also%20known%20as%20%E2%80%9CEthereum%20Testnet%E2%80%9D)%20is%20an%20Ethereum%20test%20network%20that%20allows%20for%20blockchain%20development%20testing%20before%20deployment%20on%20Mainnet%2C%20the%20main%20Ethereum%20network.\" target=\"_blank\" rel=\"noopener\">Ropsten<\/a>: This is a public Ethereum testnet. We used it in our final testing phase, as it has the closest resemblance to the Ethereum Mainnet. Ropsten is the only <a href=\"https:\/\/www.youtube.com\/watch?v=8-_CuPtzoDU\" target=\"_blank\" rel=\"noopener\">proof-of-work<\/a> testnet. It requires developers to have personal test cryptocurrency.<\/li>\n<li><a href=\"https:\/\/etherscan.io\/\" target=\"_blank\" rel=\"noopener\">Etherscan<\/a>: This is a search engine that allows you to view contracts, tokens, accounts, transactions, and blocks stored on Ethereum Mainnet or testnets. Because Etherscan can search for smart contracts, it can be used after deploying your own to verify that the contract has successfully become active.<\/li>\n<li><a href=\"https:\/\/metamask.io\/\" target=\"_blank\" rel=\"noopener\">MetaMask<\/a>: A blockchain wallet\u2013much like those in real life\u2013that acts as digital storage for information that provides access to assets (the assets themselves are stored on a blockchain). Metamask is a wallet specified for the Ethereum blockchain in the form of a browser plugin and mobile app that allows users to view or transfer (and <a href=\"https:\/\/metamask.io\/swaps\/\" target=\"_blank\" rel=\"noopener\">swap<\/a>, when on the Mainnet) assets.<\/li>\n<\/ul>\n<p>Creating and interacting with a smart contract for an NFT is very manageable using OpenZeppellin\u2019s <a href=\"https:\/\/docs.openzeppelin.com\/learn\/developing-smart-contracts\" target=\"_blank\" rel=\"noopener\">smart contracts documentation<\/a> and <a href=\"https:\/\/docs.openzeppelin.com\/contracts\/4.x\/wizard\" target=\"_blank\" rel=\"noopener\">Contract Wizard<\/a>, with or without knowledge of Solidity. However, with a working knowledge of Solidity, a programmer can easily add their own customizations to the generated contracts.<\/p>\n<p>For example, the code produced by the wizard for a contract includes a function for minting the tokens, safeMint. The function mints only one token at a time. Thus, to mint 10,000 tokens, you have to call the mint function 10,000 times. I added the following code written to a contract to handle multiple mints at once:\u00a0<\/p>\n<p><span style=\"font-family:courier new,courier,monospace;\">function mintMany(uint256 num, address to) public onlyOwner {<br \/>\n\u00a0 \u00a0 for (uint256 i = 0; i < num; i++){<br \/>\n\u00a0 \u00a0 \u00a0 \u00a0 safeMint(to);<br \/>\n\u00a0 \u00a0 }<br \/>\n}<\/span><\/p>\n<p><em>Example 2: Basic custom contract function sample<\/em><\/p>\n<p>One of the more important features to note on the Contract Wizard is selecting your contract\u2019s token <a href=\"https:\/\/github.com\/PhABC\/ethereum-token-standards-list\" target=\"_blank\" rel=\"noopener\">standard<\/a>. Standards have been developed for tokens to keep their contracts compatible with each other and to ensure that they provide the basic functionality (ex. minting, owning, transferring, tracking, etc). <a href=\"https:\/\/eips.ethereum.org\/EIPS\/eip-721\" target=\"_blank\" rel=\"noopener\">ERC-721<\/a> is the standard interface when creating non-fungible tokens\u2013that is, tokens that cannot be used interchangeably but are each unique\u2013and was what we selected when creating a contract with the wizard.\u00a0<\/p>\n<p>Transactions on a blockchain network have an associated cost in a given cryptocurrency. This fee is commonly referred to as <a href=\"https:\/\/www.investopedia.com\/terms\/g\/gas-ethereum.asp#:~:text=gas%20refers%20to%20the%20cost%20necessary%20to%20perform%20a%20transaction%20on%20the%20network.\" target=\"_blank\" rel=\"noopener\">gas<\/a>. For the Ethereum blockchain, ether is the currency used to pay the gas fee in the unit of <em>gwei<\/em>, equivalent to 10E-9 ether.\u00a0<\/p>\n<p>With the current market value of ether (ETH, the coin used on the Ethereum blockchain), it is relatively expensive to deploy contracts. So when you do it on the Ethereum Mainnet, you\u2019d want to be certain there won\u2019t be any issues or errors that would cause you to have to redo a transaction. You pay the gas <a href=\"https:\/\/metamask.zendesk.com\/hc\/en-us\/articles\/360045439051-Why-did-I-pay-gas-fees-for-a-failed-transaction\" target=\"_blank\" rel=\"noopener\">whether or not a transaction is successful<\/a>.\u00a0<\/p>\n<p>Taking the financial costs into account, if you\u2019re just learning how to interact with blockchain networks, you wouldn\u2019t want to do your practice on the Mainnet. Instead, you would likely do so on a testnet where you can pay gas fees with test ether that <a href=\"https:\/\/ethereum.org\/en\/developers\/docs\/networks\/#:~:text=ETH%20on%20testnets%20has%20no%20real%20value\" target=\"_blank\" rel=\"noopener\">has no real value<\/a>.<\/p>\n<p>Therefore, in order of testing, we first learned the basics of deploying and interacting with the sample contracts provided by OpenZeppellin on Ganache, a local, personal network. Then, after some practice, we moved to Ropsten, a public test network.<\/p>\n<p>Because our testnet of choice was Ropsten, we needed currency to use on the network\u2013in this case, <a href=\"https:\/\/medium.com\/bitfwd\/get-ropsten-ethereum-the-easy-way-f2d6ece21763#:~:text=Ethereum%20has%20several,the%20Ropsten%20Network.\" target=\"_blank\" rel=\"noopener\">ropsten ether (rETH)<\/a>. Unlike normal ETH, rETH can be obtained for free using rETH faucets. These faucets allow you to enter the address you wish to transfer the test ether to, typically once every 24 hours. This delay is part of what makes Ganache\u2019s wait-free network ideal for initial experimentation. There are various faucets that drip different amounts at different time intervals. The faucets we used were:<\/p>\n<ul>\n<li><a href=\"https:\/\/app.mycrypto.com\/faucet\" target=\"_blank\" rel=\"noopener\">MyCrypto<\/a>: Can request \u00a00.01 RopstenETH as frequently as you\u2019d like\u00a0<\/li>\n<li><a href=\"https:\/\/faucet.dimensions.network\/\" target=\"_blank\" rel=\"noopener\">Dimensions Network Faucet<\/a>: Can request 5 rETH every 24 hours<\/li>\n<li><a href=\"https:\/\/faucet.ropsten.be\/\" target=\"_blank\" rel=\"noopener\">Ropsten Ethereum Faucet<\/a>: Can request 1 rETH every 24 hours\u00a0<\/li>\n<\/ul>\n<p>After you have your test ether to cover the gas price, you can then proceed with deployment to your chosen public testnet. You can confirm and see your work using Etherscan and MetaMask by using the contract address from the deployment receipt (see the following Javascript output from deploying the contract) to search for your contract (Figure 3) and import your tokens to your wallet (Figure 4).\u00a0<\/p>\n<p><span style=\"font-family:courier new,courier,monospace;\">2_deploy.js<br \/>\n===========<\/span><\/p>\n<p><span style=\"font-family:courier new,courier,monospace;\">\u00a0 \u00a0 Deploying \u2018MyToken\u2019<br \/>\n\u00a0 \u00a0 &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211;\u00a0<br \/>\n\u00a0 \u00a0 > transaction hash:\u00a0\u00a0 \u00a00x1b2f67de68204ae3c137985379103ce8435a51a45452f624487e13b182ab2052<br \/>\n\u00a0 \u00a0 > Blocks: 1\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0Seconds: 18<br \/>\n\u00a0 \u00a0 > contract address: \u00a0\u00a0 \u00a00x3dd50Bcb9E125f1B7537f257fB6f4C165a02cB0f<br \/>\n\u00a0 \u00a0 > block number:\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a010822250<br \/>\n\u00a0 \u00a0 > block timestamp:\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a01628784232<br \/>\n\u00a0 \u00a0 > account:\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a00x6dfA9B40F8a6AC500BcD0F09Bfa7709c6bD74dBC<br \/>\n\u00a0 \u00a0 > balance:\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a016.44630489<br \/>\n\u00a0 \u00a0 > gas used:\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a03171561 (0x3064e9)<br \/>\n\u00a0 \u00a0 > gas price:\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a020 gwei<br \/>\n\u00a0 \u00a0 > value sent:\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a00 ETH<br \/>\n\u00a0 \u00a0 > total cost:\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a00.06343122 ETH<\/span><\/p>\n<p><span style=\"font-family:courier new,courier,monospace;\">\u00a0 \u00a0 > Saving artifacts\u00a0<br \/>\n\u00a0 \u00a0 &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211; &#8211;\u00a0<br \/>\n\u00a0 \u00a0 > Total cost:\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a00.06343122 ETH<\/span><\/p>\n<p><em>Example 3: JS output (deployment receipt) from deploying MyToken contract<\/em><\/p>\n<p><figure class=\"image\" style=\"margin-right:10px; float:left\"><img decoding=\"async\" alt=\"Figure 3: MyToken Contract on Etherscan\" height=\"356\" src=\"https:\/\/www.lpi.org\/sites\/default\/files\/nft-figure-3-02.jpg\" width=\"1054\" \/><br \/><figcaption>Figure 3: <a href=\"https:\/\/ropsten.etherscan.io\/address\/0x3dd50Bcb9E125f1B7537f257fB6f4C165a02cB0f\">MyToken<\/a> Contract on Etherscan<\/figcaption><\/figure>\n<\/p>\n<p><figure class=\"image\" style=\"margin-right:10px; float:left\"><img loading=\"lazy\" decoding=\"async\" alt=\"Figure 4: Test NFT called MyToken (MTK), viewed in a MetaMask wallet\" height=\"356\" src=\"https:\/\/www.lpi.org\/sites\/default\/files\/nft-figure-4-02.jpg\" width=\"1054\" \/><br \/><figcaption>Figure 4: Test NFT called <a href=\"https:\/\/ropsten.etherscan.io\/token\/0x3dd50bcb9e125f1b7537f257fb6f4c165a02cb0f\">MyToken (MTK)<\/a>, viewed in a MetaMask wallet<\/figcaption><\/figure>\n<\/p>\n<p>You can verify your deployed contract manually on Etherscan; however, we had difficulties doing so as our contract had imported files. Running the following <a href=\"https:\/\/trufflesuite.com\/docs\/truffle\/reference\/truffle-commands.html\" target=\"_blank\" rel=\"noopener\">Truffle<\/a> command on the command line was successful and effortless:<\/p>\n<p><span style=\"font-family:courier new,courier,monospace;\">truffle run verify <contract name> &#8211;network <network name><\/span><\/p>\n<p>Verifying your contract makes the code public on Etherscan, allows for transparency with users, and gives them the ability to query and write to available <a href=\"https:\/\/ropsten.etherscan.io\/address\/0x3dd50Bcb9E125f1B7537f257fB6f4C165a02cB0f#code\" target=\"_blank\" rel=\"noopener\">public functions<\/a>. Although there are various functions to have publicly accessible in your smart contract, the only one we included in our test contract was for non-owners to be able to transfer their NFT.<br \/>\n\u00a0 \u00a0\u00a0<br \/>\nWhile testing, the first way we found to transfer a token from one owner to another was by connecting to <a href=\"https:\/\/www.freecodecamp.org\/news\/what-is-web3\/\" target=\"_blank\" rel=\"noopener\">Web3<\/a>, via MetaMask or <a href=\"https:\/\/walletconnect.com\/\" target=\"_blank\" rel=\"noopener\">WalletConnect<\/a> (both Web3 wallets), on Etherscan and using the <span style=\"font-family:courier new,courier,monospace;\">safeTransferFrom<\/span> function with the wallet account number of the NFT owner, the account to transfer to, and the NFT\u2019s ID. We successfully <a href=\"https:\/\/ropsten.etherscan.io\/tx\/0x7f98432a9f1cfb6c7909d5822b605cd362be05d39919f8abae19061c5ce069fb\" target=\"_blank\" rel=\"noopener\">transferred an MTK token<\/a> (after a 0.00010114 rETH gas fee) using this method.\u00a0<\/p>\n<p>An alternative method, which is potentially more user friendly, is MetaMask\u2019s own \u201c<a href=\"https:\/\/docs.metamask.io\/guide\/sending-transactions.html\" target=\"_blank\" rel=\"noopener\">Send<\/a>\u201d feature. Noting that this functionally is currently available only on the mobile app and not the extension.\u00a0<\/p>\n<p>We noted with concern that functions that should only be usable for the contract owner were publicly available functions \u2013 specifically the ownership and the minting functions. While all functions can and should be publically viewable for transparency, only the NFT owner should have access to privileged functions. So I attempted to use the previously mentioned <a href=\"https:\/\/docs.metamask.io\/guide\/sending-transactions.html\" target=\"_blank\" rel=\"noopener\">mintMany<\/a> function with an account that was not the owner of the contract, and appropriately, the transaction was not successful. To its credit, MetaMask advised against attempting the transaction, warning, \u201cThis transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.\u201d After we continued anyway, the transaction did indeed report \u00a0\u201c<a href=\"https:\/\/ropsten.etherscan.io\/tx\/0xa6d86cfbccefeac64bfb99dbe2dfc5a8090fbdb1d3802af0c6b3dc89f7a35e3d\" target=\"_blank\" rel=\"noopener\">Fail with error &#8216;Ownable: caller is not the owner<\/a>&#8216;\u201d.<\/p>\n<h2>Conclusion\u00a0<\/h2>\n<p>While we have made significant progress in developing our understanding of NFTs, smart contracts, and testnets, this work has been only the beginning of our understanding of the development and implementation of NFTs. NFTs and other smart contracts can be very intricate, flaws can be subtle and dangerous, and there are always more features to add. However, changing a contract once it is deployed is quite difficult.<\/p>\n<p>This was a fun project for us as interns. \u00a0It gave us the opportunity to demystify a lot of the jargon and confusion around NFTs, and to learn how to create them. Given the enormous interest in cryptocurrencies and smart contracts, the knowledge we obtained can be very useful in our development as young IT professionals. \u00a0We definitely learned a lot from our internships at LPI, and hopefully will get another project to work on in this area someday<br \/>\n\u00a0<\/p>\n","protected":false},"excerpt":{"rendered":"<p>An unusual educational opportunity came  &#8230; <a href=\"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/\" class=\"button-link\">Read more<\/a><\/p>\n","protected":false},"author":12,"featured_media":4865,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[],"country":[],"language":[432],"ppma_author":[507],"class_list":["post-4864","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","language-english-vi"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.2 (Yoast SEO v27.3) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Creating NFTs with Free and Open Source Software - Linux Professional Institute (LPI)<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/\" \/>\n<meta property=\"og:locale\" content=\"zh_TW\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Creating NFTs with Free and Open Source Software\" \/>\n<meta property=\"og:description\" content=\"An unusual educational opportunity came ... Read more\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/\" \/>\n<meta property=\"og:site_name\" content=\"Linux Professional Institute (LPI)\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/LPIConnect\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-11T14:03:18+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-05-10T08:26:19+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lpi.org\/wp-content\/uploads\/2023\/05\/article-Creating-NFTs.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1440\" \/>\n\t<meta property=\"og:image:height\" content=\"994\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Rozilyn Marco\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@lpiconnect\" \/>\n<meta name=\"twitter:site\" content=\"@lpiconnect\" \/>\n<meta name=\"twitter:label1\" content=\"\u4f5c\u8005:\" \/>\n\t<meta name=\"twitter:data1\" content=\"Rozilyn Marco\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u9810\u4f30\u95b1\u8b80\u6642\u9593\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 \u5206\u9418\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/\"},\"author\":{\"name\":\"Rozilyn Marco\",\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/#\\\/schema\\\/person\\\/8215dde9a373f85bfd0332901705b9d8\"},\"headline\":\"Creating NFTs with Free and Open Source Software\",\"datePublished\":\"2022-03-11T14:03:18+00:00\",\"dateModified\":\"2023-05-10T08:26:19+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/\"},\"wordCount\":2317,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lpi.org\\\/wp-content\\\/uploads\\\/2023\\\/05\\\/article-Creating-NFTs.jpg\",\"inLanguage\":\"zh-TW\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/\",\"url\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/\",\"name\":\"Creating NFTs with Free and Open Source Software - Linux Professional Institute (LPI)\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lpi.org\\\/wp-content\\\/uploads\\\/2023\\\/05\\\/article-Creating-NFTs.jpg\",\"datePublished\":\"2022-03-11T14:03:18+00:00\",\"dateModified\":\"2023-05-10T08:26:19+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/#breadcrumb\"},\"inLanguage\":\"zh-TW\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-TW\",\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.lpi.org\\\/wp-content\\\/uploads\\\/2023\\\/05\\\/article-Creating-NFTs.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lpi.org\\\/wp-content\\\/uploads\\\/2023\\\/05\\\/article-Creating-NFTs.jpg\",\"width\":1440,\"height\":994},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/blog\\\/2022\\\/03\\\/11\\\/creating-nfts-free-and-open-source-software\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Creating NFTs with Free and Open Source Software\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/#website\",\"url\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/\",\"name\":\"Linux Professional Institute (LPI)\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"zh-TW\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/#organization\",\"name\":\"Linux Professional Institute (LPI)\",\"url\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-TW\",\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.lpi.org\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/logo.png\",\"contentUrl\":\"https:\\\/\\\/www.lpi.org\\\/wp-content\\\/uploads\\\/2023\\\/04\\\/logo.png\",\"width\":496,\"height\":175,\"caption\":\"Linux Professional Institute (LPI)\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/LPIConnect\",\"https:\\\/\\\/x.com\\\/lpiconnect\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/35136\",\"https:\\\/\\\/www.instagram.com\\\/lpi_org\\\/\",\"https:\\\/\\\/fosstodon.org\\\/@LPI\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.lpi.org\\\/zh-hant\\\/#\\\/schema\\\/person\\\/8215dde9a373f85bfd0332901705b9d8\",\"name\":\"Rozilyn Marco\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-TW\",\"@id\":\"https:\\\/\\\/www.lpi.org\\\/wp-content\\\/uploads\\\/2023\\\/05\\\/picture-1687-1647008761-96x96.png531caabea882ff648e72a4523fe17802\",\"url\":\"https:\\\/\\\/www.lpi.org\\\/wp-content\\\/uploads\\\/2023\\\/05\\\/picture-1687-1647008761-96x96.png\",\"contentUrl\":\"https:\\\/\\\/www.lpi.org\\\/wp-content\\\/uploads\\\/2023\\\/05\\\/picture-1687-1647008761-96x96.png\",\"caption\":\"Rozilyn Marco\"},\"description\":\"Rozilyn Marco is an Engineering Science student at the University of Toronto (UofT), majoring in Computer and Electrical Engineering. While at the Linux Professional Institute as an IT intern, she is also a Project Lead for VolunteerYE - an initiative under UofT's Engineers Without Borders Chapter that aims to support youth to take action and feel the first-hand impact they can make in their communities through volunteering.\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Creating NFTs with Free and Open Source Software - Linux Professional Institute (LPI)","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/","og_locale":"zh_TW","og_type":"article","og_title":"Creating NFTs with Free and Open Source Software","og_description":"An unusual educational opportunity came ... Read more","og_url":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/","og_site_name":"Linux Professional Institute (LPI)","article_publisher":"https:\/\/www.facebook.com\/LPIConnect","article_published_time":"2022-03-11T14:03:18+00:00","article_modified_time":"2023-05-10T08:26:19+00:00","og_image":[{"width":1440,"height":994,"url":"https:\/\/www.lpi.org\/wp-content\/uploads\/2023\/05\/article-Creating-NFTs.jpg","type":"image\/jpeg"}],"author":"Rozilyn Marco","twitter_card":"summary_large_image","twitter_creator":"@lpiconnect","twitter_site":"@lpiconnect","twitter_misc":{"\u4f5c\u8005:":"Rozilyn Marco","\u9810\u4f30\u95b1\u8b80\u6642\u9593":"12 \u5206\u9418"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/#article","isPartOf":{"@id":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/"},"author":{"name":"Rozilyn Marco","@id":"https:\/\/www.lpi.org\/zh-hant\/#\/schema\/person\/8215dde9a373f85bfd0332901705b9d8"},"headline":"Creating NFTs with Free and Open Source Software","datePublished":"2022-03-11T14:03:18+00:00","dateModified":"2023-05-10T08:26:19+00:00","mainEntityOfPage":{"@id":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/"},"wordCount":2317,"commentCount":0,"publisher":{"@id":"https:\/\/www.lpi.org\/zh-hant\/#organization"},"image":{"@id":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lpi.org\/wp-content\/uploads\/2023\/05\/article-Creating-NFTs.jpg","inLanguage":"zh-TW","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/","url":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/","name":"Creating NFTs with Free and Open Source Software - Linux Professional Institute (LPI)","isPartOf":{"@id":"https:\/\/www.lpi.org\/zh-hant\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/#primaryimage"},"image":{"@id":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lpi.org\/wp-content\/uploads\/2023\/05\/article-Creating-NFTs.jpg","datePublished":"2022-03-11T14:03:18+00:00","dateModified":"2023-05-10T08:26:19+00:00","breadcrumb":{"@id":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/#breadcrumb"},"inLanguage":"zh-TW","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/"]}]},{"@type":"ImageObject","inLanguage":"zh-TW","@id":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/#primaryimage","url":"https:\/\/www.lpi.org\/wp-content\/uploads\/2023\/05\/article-Creating-NFTs.jpg","contentUrl":"https:\/\/www.lpi.org\/wp-content\/uploads\/2023\/05\/article-Creating-NFTs.jpg","width":1440,"height":994},{"@type":"BreadcrumbList","@id":"https:\/\/www.lpi.org\/zh-hant\/blog\/2022\/03\/11\/creating-nfts-free-and-open-source-software\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lpi.org\/zh-hant\/"},{"@type":"ListItem","position":2,"name":"Creating NFTs with Free and Open Source Software"}]},{"@type":"WebSite","@id":"https:\/\/www.lpi.org\/zh-hant\/#website","url":"https:\/\/www.lpi.org\/zh-hant\/","name":"Linux Professional Institute (LPI)","description":"","publisher":{"@id":"https:\/\/www.lpi.org\/zh-hant\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lpi.org\/zh-hant\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"zh-TW"},{"@type":"Organization","@id":"https:\/\/www.lpi.org\/zh-hant\/#organization","name":"Linux Professional Institute (LPI)","url":"https:\/\/www.lpi.org\/zh-hant\/","logo":{"@type":"ImageObject","inLanguage":"zh-TW","@id":"https:\/\/www.lpi.org\/zh-hant\/#\/schema\/logo\/image\/","url":"https:\/\/www.lpi.org\/wp-content\/uploads\/2023\/04\/logo.png","contentUrl":"https:\/\/www.lpi.org\/wp-content\/uploads\/2023\/04\/logo.png","width":496,"height":175,"caption":"Linux Professional Institute (LPI)"},"image":{"@id":"https:\/\/www.lpi.org\/zh-hant\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/LPIConnect","https:\/\/x.com\/lpiconnect","https:\/\/www.linkedin.com\/company\/35136","https:\/\/www.instagram.com\/lpi_org\/","https:\/\/fosstodon.org\/@LPI"]},{"@type":"Person","@id":"https:\/\/www.lpi.org\/zh-hant\/#\/schema\/person\/8215dde9a373f85bfd0332901705b9d8","name":"Rozilyn Marco","image":{"@type":"ImageObject","inLanguage":"zh-TW","@id":"https:\/\/www.lpi.org\/wp-content\/uploads\/2023\/05\/picture-1687-1647008761-96x96.png531caabea882ff648e72a4523fe17802","url":"https:\/\/www.lpi.org\/wp-content\/uploads\/2023\/05\/picture-1687-1647008761-96x96.png","contentUrl":"https:\/\/www.lpi.org\/wp-content\/uploads\/2023\/05\/picture-1687-1647008761-96x96.png","caption":"Rozilyn Marco"},"description":"Rozilyn Marco is an Engineering Science student at the University of Toronto (UofT), majoring in Computer and Electrical Engineering. While at the Linux Professional Institute as an IT intern, she is also a Project Lead for VolunteerYE - an initiative under UofT's Engineers Without Borders Chapter that aims to support youth to take action and feel the first-hand impact they can make in their communities through volunteering."}]}},"views":2078,"authors":[{"term_id":507,"user_id":12,"is_guest":0,"slug":"rmarcoexample-com","display_name":"Rozilyn Marco","avatar_url":"https:\/\/www.lpi.org\/wp-content\/uploads\/2023\/05\/picture-1687-1647008761-96x96.png","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/posts\/4864","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/users\/12"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/comments?post=4864"}],"version-history":[{"count":2,"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/posts\/4864\/revisions"}],"predecessor-version":[{"id":11009,"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/posts\/4864\/revisions\/11009"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/media\/4865"}],"wp:attachment":[{"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/media?parent=4864"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/categories?post=4864"},{"taxonomy":"country","embeddable":true,"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/country?post=4864"},{"taxonomy":"language","embeddable":true,"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/language?post=4864"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.lpi.org\/zh-hant\/wp-json\/wp\/v2\/ppma_author?post=4864"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}