Building an ERC721 non-fungible token smart contract and using metamask to interact with it via a web interface. (PART II)

Daniel Pittman
Coinmonks
11 min readJul 28, 2018

--

Building the Web Interface

We now have a working contract but lets interact with it using a website. You can view a working example deployed on the Ropsten Testnet at Qwoyn.io. Here is the code snippet which I have inserted in to my wordpress site. It’s very basic and needs some love but the functionality is what matters, I will let you make it more beautiful. If you feel like helping me visit my GitHub and create a pull request.

On to the .html:

<script src="https://rawgit.com/ethereum/web3.js/0.16.0/dist/web3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ethjs@0.3.0/dist/ethjs.min.js"></script>
<script>
window.addEventListener('load', function() {
// Check if Web3 has been injected by the browser:
if (typeof web3 !== 'undefined') { // You have a web3 browser! Continue below! startApp(web3); //alert("Web3"); } else { alert("No web3 provider found please install a provider such as metamask to interact with this dApp");
// Warn the user that they need to get a web3 browser // Or install MetaMask, maybe with a nice graphic. }})
const abi = [ { "anonymous": false, "inputs": [ { "indexed": true, "name": "_owner", "type": "address" }, { "indexed": true, "name": "_operator", "type": "address" }, { "indexed": false, "name": "_approved", "type": "bool" } ], "name": "ApprovalForAll", "type": "event" }, { "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_tokenId", "type": "uint256" } ], "name": "approve", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_type", "type": "uint256" }, { "name": "_title", "type": "string" }, { "name": "_description", "type": "string" } ], "name": "buyToken", "outputs": [], "payable": true, "stateMutability": "payable", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "_owner", "type": "address" }, { "indexed": true, "name": "_approved", "type": "address" }, { "indexed": true, "name": "_tokenId", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "buyer", "type": "address" }, { "indexed": false, "name": "tokenId", "type": "uint256" } ], "name": "BoughtToken", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "previousOwner", "type": "address" } ], "name": "OwnershipRenounced", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "previousOwner", "type": "address" }, { "indexed": true, "name": "newOwner", "type": "address" } ], "name": "OwnershipTransferred", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "_from", "type": "address" }, { "indexed": true, "name": "_to", "type": "address" }, { "indexed": true, "name": "_tokenId", "type": "uint256" } ], "name": "Transfer", "type": "event" }, { "constant": false, "inputs": [], "name": "renounceOwnership", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" }, { "name": "_tokenId", "type": "uint256" } ], "name": "safeTransferFrom", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" }, { "name": "_tokenId", "type": "uint256" }, { "name": "_data", "type": "bytes" } ], "name": "safeTransferFrom", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_approved", "type": "bool" } ], "name": "setApprovalForAll", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "newPrice", "type": "uint256" } ], "name": "setCurrentPrice", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" }, { "name": "_tokenId", "type": "uint256" } ], "name": "transferFrom", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_newOwner", "type": "address" } ], "name": "transferOwnership", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }, { "constant": true, "inputs": [ { "name": "_owner", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "_tokenId", "type": "uint256" } ], "name": "exists", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "_tokenId", "type": "uint256" } ], "name": "getApproved", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "getCurrentPrice", "outputs": [ { "name": "price", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "_tokenId", "type": "uint256" } ], "name": "getToken", "outputs": [ { "name": "tokenType_", "type": "uint256" }, { "name": "tokenTitle_", "type": "string" }, { "name": "tokenDescription_", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "InterfaceId_ERC165", "outputs": [ { "name": "", "type": "bytes4" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "_owner", "type": "address" }, { "name": "_operator", "type": "address" } ], "name": "isApprovedForAll", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "myTokens", "outputs": [ { "name": "", "type": "uint256[]" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "name", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "owner", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "_tokenId", "type": "uint256" } ], "name": "ownerOf", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "_interfaceId", "type": "bytes4" } ], "name": "supportsInterface", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "_index", "type": "uint256" } ], "name": "tokenByIndex", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "_owner", "type": "address" }, { "name": "_index", "type": "uint256" } ], "name": "tokenOfOwnerByIndex", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "_tokenId", "type": "uint256" } ], "name": "tokenURI", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }]
EFFORTLESS OPERATIONSNEW FEATURESBUILDING TRAILTRAILAPP.COM
Follow
3 pillars to support growing operations
3 pillars to support growing operations
Strong foundations will help scale your restaurant, takeaway or catering business.

Stephanie Wedderburn
Apr 17
const contract_address = '0xC345212CAE57D8Aa2f02Fb34e89477F0276C2856'const etherValue = web3.toWei(10, 'ether');function startApp(web3) { const eth = new Eth(web3.currentProvider) const token = eth.contract(abi).at(contract_address);listenForClicks(token,web3)} function listenForClicks (token, web3) {
var documentType = prompt("Please enter 1 for Idea, 2 for Item", "<type goes here>");
var documentTitle = prompt("Please enter a title", "<title goes here>");
var documentDescription = prompt("Please enter the description", "<description goes here>");
var button = document.querySelector('button.updateChain') web3.eth.getAccounts(function(err, accounts) { console.log(accounts); address = accounts.toString(); }) button.addEventListener('click', function() { token.buyToken(documentType,documentTitle,documentDescription,{from: address}) .then(function (txHash) { console.log('Transaction sent') console.dir(txHash) alert('Transaction ' + txHash + ' sent succesfully!'); }) .catch(console.error) })}
</script>

<button class="updateChain">Update ETC Blockchain!</button>

I have copy and pasted this code from my repo, I recommend visiting GitHub and using the code there if you are implementing this yourself.

Here is a closer look:

<script src="https://rawgit.com/ethereum/web3.js/0.16.0/dist/web3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ethjs@0.3.0/dist/ethjs.min.js"></script>

Here we have called out required scripts for web3 and ethjs.

window.addEventListener('load', function() { 
// Check if Web3 has been injected by the browser:
if (typeof web3 !== 'undefined') {
// You have a web3 browser! Continue below!
startApp(web3); //alert("Web3"); } else { alert("No web3 provider found please install a provider such as metamask to interact with this dApp");
// Warn the user that they need to get a web3 browser // Or install MetaMask, maybe with a nice graphic. }})

This function checks to see if web3 has been injected in to the browser.

const abi = [ {  "anonymous": false,  "inputs": [   {    "indexed": true,    "name": "_owner",    "type": "address"   },   {    "indexed": true,    "name": "_operator",    "type": "address"   },   {    "indexed": false,    "name": "_approved",    "type": "bool"   }  ],  "name": "ApprovalForAll",  "type": "event" }, {  "constant": false,  "inputs": [   {    "name": "_to",    "type": "address"   },   {    "name": "_tokenId",    "type": "uint256"   }  ],  "name": "approve",  "outputs": [],  "payable": false,  "stateMutability": "nonpayable",  "type": "function" }, {  "constant": false,  "inputs": [   {    "name": "_type",    "type": "uint256"   },   {    "name": "_title",    "type": "string"   },   {    "name": "_description",    "type": "string"   }  ],  "name": "buyToken",  "outputs": [],  "payable": true,  "stateMutability": "payable",  "type": "function" }, {  "anonymous": false,  "inputs": [   {    "indexed": true,    "name": "_owner",    "type": "address"   },   {    "indexed": true,    "name": "_approved",    "type": "address"   },   {    "indexed": true,    "name": "_tokenId",    "type": "uint256"   }  ],  "name": "Approval",  "type": "event" }, {  "anonymous": false,  "inputs": [   {    "indexed": true,    "name": "buyer",    "type": "address"   },   {    "indexed": false,    "name": "tokenId",    "type": "uint256"   }  ],  "name": "BoughtToken",  "type": "event" }, {  "anonymous": false,  "inputs": [   {    "indexed": true,    "name": "previousOwner",    "type": "address"   }  ],  "name": "OwnershipRenounced",  "type": "event" }, {  "anonymous": false,  "inputs": [   {    "indexed": true,    "name": "previousOwner",    "type": "address"   },   {    "indexed": true,    "name": "newOwner",    "type": "address"   }  ],  "name": "OwnershipTransferred",  "type": "event" }, {  "anonymous": false,  "inputs": [   {    "indexed": true,    "name": "_from",    "type": "address"   },   {    "indexed": true,    "name": "_to",    "type": "address"   },   {    "indexed": true,    "name": "_tokenId",    "type": "uint256"   }  ],  "name": "Transfer",  "type": "event" }, {  "constant": false,  "inputs": [],  "name": "renounceOwnership",  "outputs": [],  "payable": false,  "stateMutability": "nonpayable",  "type": "function" }, {  "constant": false,  "inputs": [   {    "name": "_from",    "type": "address"   },   {    "name": "_to",    "type": "address"   },   {    "name": "_tokenId",    "type": "uint256"   }  ],  "name": "safeTransferFrom",  "outputs": [],  "payable": false,  "stateMutability": "nonpayable",  "type": "function" }, {  "constant": false,  "inputs": [   {    "name": "_from",    "type": "address"   },   {    "name": "_to",    "type": "address"   },   {    "name": "_tokenId",    "type": "uint256"   },   {    "name": "_data",    "type": "bytes"   }  ],  "name": "safeTransferFrom",  "outputs": [],  "payable": false,  "stateMutability": "nonpayable",  "type": "function" }, {  "constant": false,  "inputs": [   {    "name": "_to",    "type": "address"   },   {    "name": "_approved",    "type": "bool"   }  ],  "name": "setApprovalForAll",  "outputs": [],  "payable": false,  "stateMutability": "nonpayable",  "type": "function" }, {  "constant": false,  "inputs": [   {    "name": "newPrice",    "type": "uint256"   }  ],  "name": "setCurrentPrice",  "outputs": [],  "payable": false,  "stateMutability": "nonpayable",  "type": "function" }, {  "constant": false,  "inputs": [   {    "name": "_from",    "type": "address"   },   {    "name": "_to",    "type": "address"   },   {    "name": "_tokenId",    "type": "uint256"   }  ],  "name": "transferFrom",  "outputs": [],  "payable": false,  "stateMutability": "nonpayable",  "type": "function" }, {  "constant": false,  "inputs": [   {    "name": "_newOwner",    "type": "address"   }  ],  "name": "transferOwnership",  "outputs": [],  "payable": false,  "stateMutability": "nonpayable",  "type": "function" }, {  "inputs": [],  "payable": false,  "stateMutability": "nonpayable",  "type": "constructor" }, {  "constant": true,  "inputs": [   {    "name": "_owner",    "type": "address"   }  ],  "name": "balanceOf",  "outputs": [   {    "name": "",    "type": "uint256"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [   {    "name": "_tokenId",    "type": "uint256"   }  ],  "name": "exists",  "outputs": [   {    "name": "",    "type": "bool"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [   {    "name": "_tokenId",    "type": "uint256"   }  ],  "name": "getApproved",  "outputs": [   {    "name": "",    "type": "address"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [],  "name": "getCurrentPrice",  "outputs": [   {    "name": "price",    "type": "uint256"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [   {    "name": "_tokenId",    "type": "uint256"   }  ],  "name": "getToken",  "outputs": [   {    "name": "tokenType_",    "type": "uint256"   },   {    "name": "tokenTitle_",    "type": "string"   },   {    "name": "tokenDescription_",    "type": "string"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [],  "name": "InterfaceId_ERC165",  "outputs": [   {    "name": "",    "type": "bytes4"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [   {    "name": "_owner",    "type": "address"   },   {    "name": "_operator",    "type": "address"   }  ],  "name": "isApprovedForAll",  "outputs": [   {    "name": "",    "type": "bool"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [],  "name": "myTokens",  "outputs": [   {    "name": "",    "type": "uint256[]"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [],  "name": "name",  "outputs": [   {    "name": "",    "type": "string"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [],  "name": "owner",  "outputs": [   {    "name": "",    "type": "address"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [   {    "name": "_tokenId",    "type": "uint256"   }  ],  "name": "ownerOf",  "outputs": [   {    "name": "",    "type": "address"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [   {    "name": "_interfaceId",    "type": "bytes4"   }  ],  "name": "supportsInterface",  "outputs": [   {    "name": "",    "type": "bool"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [],  "name": "symbol",  "outputs": [   {    "name": "",    "type": "string"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [   {    "name": "_index",    "type": "uint256"   }  ],  "name": "tokenByIndex",  "outputs": [   {    "name": "",    "type": "uint256"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [   {    "name": "_owner",    "type": "address"   },   {    "name": "_index",    "type": "uint256"   }  ],  "name": "tokenOfOwnerByIndex",  "outputs": [   {    "name": "",    "type": "uint256"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [   {    "name": "_tokenId",    "type": "uint256"   }  ],  "name": "tokenURI",  "outputs": [   {    "name": "",    "type": "string"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }, {  "constant": true,  "inputs": [],  "name": "totalSupply",  "outputs": [   {    "name": "",    "type": "uint256"   }  ],  "payable": false,  "stateMutability": "view",  "type": "function" }]

This JSON is our contract abi, it will enable us to use the functions in our contract.

const contract_address = '0xC345212CAE57D8Aa2f02Fb34e89477F0276C2856'const etherValue = web3.toWei(10, 'ether');

Our contract address on the blockchain. Remember we are using the Ropsten Testnet but you may use any ethereum network you have deployed the contract to.

function startApp(web3) { const eth = new Eth(web3.currentProvider) const token = eth.contract(abi).at(contract_address);

This function initializes our app.

listenForClicks(token,web3)} function listenForClicks (token, web3) { 
var documentType = prompt("Please enter 1 for Idea, 2 for Item", "<type goes here>");
var documentTitle = prompt("Please enter a title", "<title goes here>");
var documentDescription = prompt("Please enter the description", "<description goes here>");
var button = document.querySelector('button.updateChain') web3.eth.getAccounts(function(err, accounts) { console.log(accounts); address = accounts.toString(); }) button.addEventListener('click', function() { token.buyToken(documentType,documentTitle,documentDescription,{from: address}) .then(function (txHash) { console.log('Transaction sent') console.dir(txHash) alert('Transaction ' + txHash + ' sent succesfully!'); }) .catch(console.error) })}

The listenForClicks(token,web3) function asks the user to enter the type, title and description in a simple prompt. This information is used in the buyToken function within our smart contract during execution.

token.buyToken(documentType,documentTitle,documentDescription,{from: address}) .then(function (txHash) { console.log('Transaction sent') console.dir(txHash) alert('Transaction ' + txHash + ' sent succesfully!'); }) .catch(console.error) })}

Above is that implementation. Finally lets take a look at the last line.

<button class="updateChain">Update ETC Blockchain!</button>

Here we create a button which executes our contract via updateChain.

Go to Qwoyn.io
Pick a type.
Pick a title
Enter a description
Click “Update ETC Blockchain”
Find your transaction id by pressing F12 and opening the browser console.

I hope you have enjoyed this short tutorial on building dApps. Don’t hesitate to ask questions!

Our developers volunteer their time but happily accept donations:

ETC: 0x007e60C669cf96dC32655d1Eb1c1eBcf96459975

BTC: 15X8rjV7EaTtsC6BhrffuqH28LZpqZ9Tdi

BCC: 1KrwR96hbCrLavAaWkXz2oy2F51ryohme1

ETH: 0x00c24b3346AFc5c3710AFb27C86431ebB5ce8163

--

--