New tool to add to our toolkit : Truffle boxes !

“Truffle Boxes are helpful boilerplates that allow you to focus on what makes your DApp unique. In addition to Truffle, Truffle Boxes can contain other helpful modules, Solidity contracts & libraries, front-end views and more; all the way up to complete example dapps”

It basically provides with a server for the front end and boilerplates to ease the development process of your dapp.

Setup the project :

  1. Verify you have Truffle installed (In your terminal usetruffle -v) if not you need to install it (in your terminal use npm install -g truffle).
  2. Create your project folder (In your terminal use mkdir starNotaryv1)
  3. Move to the project folder (cd starNotaryv1)
  4. Create your project using (In your terminal truffle unbox webpack)

Then let’s start the truffle environment and connect to the local RPC on metamask :

  1. In your project folder (in your terminal use the command truffle develop or sudo truffle develop).
  2. Check Truffle started in the URl: http://127.0.0.1:9545/

And then in metamask simply connect to the local ethereum network :

You can now also add the private keys (import an account) from your truffle dev environment to your metamask account, simply click on the account image top righ, chose “import account” and copy paste one of the private keys from your truffle dev env.

Alright let’s deploy on remix our first version of the star notary smart contract now :

pragma solidity >=0.4.24;

contract StarNotary {

    string public starName;
    address public starOwner;

    event starClaimed(address owner);

    constructor() public {
        starName = "Awesome Udacity Star"; // we initiate the value of the star name 
    }

    function claimStar() public { // we can claim the star 
        starOwner = msg.sender;
        emit starClaimed(msg.sender);
    }

}

Tests with Mocha and Chai

Mocha and Cha

  • Mocha is a Testing Framework for Javascript, it can be used for front end applications and back end applications like Ethereum Decentralized Apps.
  • Chai is an Assertion Library
  • Both are available as NPM packages
  • Very popular between developer for testing their code
  • Truffle supports and comes preinstalled with Mocha and Chai
beforeEach (async () => {
     library = await Library.deployed();
      await contract ('Udacity', function(acc) {
          accounts = acc;
      })
});

describe ('all tests', () => {
     it ('can create a Library owner', async () => {
             const owner = await library.owner();
             assert.equal(owner, accounts[0]);
      })
}) 

this is just some example code. Tests are grouped underneath the describe keyword, and test cases are grouped under the it keyword.

Alright, setting up the env round 2 :

We deleted the contracts besides Migrations.sol and then created a new smart contract StarNotary.sol, we then modified 2_deploy_contracts.js to remove the previous contracts and add our new StarNotary.sol contract :

And finally we put the code into the TestStarNotary.js :

// Importing the StartNotary Smart Contract ABI (JSON representation of the Smart Contract)
const StarNotary = artifacts.require("StarNotary");

var accounts; // List of development accounts provided by Truffle
var owner; // Global variable use it in the tests cases

// This called the StartNotary Smart contract and initialize it -> test is done in clean contract 
contract('StarNotary', (accs) => {
    accounts = accs; // Assigning test accounts
    owner = accounts[0]; // Assigning the owner test account
});

// Example test case, it will test if the contract is able to return the starName property 
// initialized in the contract constructor
it('has correct name', async () => {
    let instance = await StarNotary.deployed(); // Making sure the Smart Contract is deployed and getting the instance.
    let starName = await instance.starName.call(); // Calling the starName property
    assert.equal(starName, "Awesome Udacity Star"); // Assert if the starName property was initialized correctly
});

It is important to remember that all this test libraries use Promises, that’s why we are using in each function the keywords async and await. Also interesting to notice that the .call() does not require any gas as it calls the contract to get data without changing the state.

And finally whern you have your test .js file in your test folder then simply run “test” in truffle(develop) :

We now add a new test case in the script to test if the star is indeed claimed by the owner

// Example test case, it will test is the Smart Contract function claimStar assigned the Star to the owner address
it('can be claimed', async () => {
    let instance = await StarNotary.deployed(); // Making sure the Smart Contract is deployed and getting the instance.
    await instance.claimStar({from: owner}); // Calling the Smart Contract function claimStar
    let starOwner = await instance.starOwner.call(); // Getting the owner address
    assert.equal(starOwner, owner); // Verifying if the owner address match with owner of the address
});

Finally we add a new test case in the script to test the change of the owner of the star :

// Example test case, it will test is the Smart Contract function claimStar assigned the Star to the owner address and it can be changed
it('can change owners', async () => {
    let instance = await StarNotary.deployed();
    let secondUser = accounts[1]; //this will be the second owner of the star 
    await instance.claimStar({from: owner}); //we claim the instance with the by default owner 
    let starOwner = await instance.starOwner.call(); // we retrieve the value
    assert.equal(starOwner, owner); //we check
    await instance.claimStar({from: secondUser}); //we claim with the second owner 
    let secondOwner = await instance.starOwner.call(); // we retrieve the value
    assert.equal(secondOwner, secondUser); //we check
 });

And finally let’s add a function to change the name of the star and a piece of code to test it :

//Changing the name, to be added into the StarNotary.sol 

    function changeName (string memory _name) public {
        starName = _name;
    }
    
//and the testing to be added in the TestStarNotary.js 

 it('can change names', async () => {
    await instance.claimStar({from: owner});
    await instance.changeName('New Name', {from: owner});
    assert.equal(await instance.starName.call(), 'New Name');
   })

Had to also add the following into the test file in order to make sure to refresh the instance of the contract with the latest deployed one, other wise I had an error “ReferenceError: instance is not defined” regarding the new function that was added (changeName).

beforeEach(async () => {
    instance = await StarNotary.deployed();
  });

All tests are passing, we are good !

Creating the Frond end

The cool thing about the truffle webpack box that we installed is that it comes already with boilerplates for the frontend in the form of html, css and .js, right here :

let’s replace the code for index.html with our first version of the front end :

<!DOCTYPE html>
<html>
  <head>
    <title>StarNotary DAPP Front End</title>
    <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
  </head>
  <style>
    input {
      display: block;
      margin-bottom: 12px;
    }
  </style>
  <body>

    <h1>StarNotary DAPP</h1>

    <br><label for="name">Star Name:</label><h3 id='name'>Star Name: </h3>
    <br><br><button onclick="App.starNameFunc()">Get Star Name</button>
    <hr>
    <br><label for="owner">Star Owner:</label><h3 id='owner'>Star Owner: </h3>
    <br><br><button onclick="App.starOwnerFunc()">Get Star Owner</button>
    <br><br>
    <hr>
    <br>
      <h1>Claim Star</h1>
      <br><br><button id="claimStar" onclick="App.claimStarFunc()">Claim a Star</button>
      <br><br>
      <br>
      <hr>
        <span id="status"></span>
      <br>

    <script src="index.js"></script>
  </body>
</html>

Now we have all the keys to run our complete dapp, with the smart contract and the front end.

Here how to run it :

Before running your application you need to do the last step and it is to change in the truffle-config.js file the network configuration to make sure Metamask is able to connect with your development environment:

networks: {
    // Useful for testing. The `development` name is special - truffle uses it by default
    // if it's defined here and no other network is specified at the command line.
    // You should run a client (like ganache-cli, geth or parity) in a separate terminal
    // tab if you use this network and you must also set the `host`, `port` and `network_id`
    // options below to some value.
    //
    development: {
      host: "127.0.0.1",     // Localhost (default: none)
      port: 8545,            // Standard Ethereum port (default: none)
      network_id: "*",       // Any network (default: none)
    },

    ...

To run your application you will need to:

  • Run truffle develop or sudo truffle develop commands.
  • Run compile command.
  • Run migrate --reset

Then

  • Open a second terminal window, and make sure you are inside your project directory.
  • Run cd app to move inside the app folder.
  • Run npm run dev command.

This will start our port at 8080. Open http://localhost:8080/ in your browser. Make sure the Metamask extension is installed, and you are logged into it, and also have imported the accounts.

It works ! As soon as you get onto it it opens up metamask and if you are on the custom local network you can interact with the smart contract, getting the name of the star, claiming it, getting the owner address. First Dapp done !!!

what we did :

  • Smart contract coding in remix
  • Truffle boxes for the boiler plate code
  • Contract to truffle
  • Test cases
  • Designed the front end
  • Started the server to host
  • Metamask connected to private network (port 8545)

Alright now we will create the stars as NFT with ERC-721

ERC-721

If you want more information regarding this kind of token : https://brax.gg/nfts-new-frenzy-tokens/

The description of the token can aussi be found here : http://erc721.org/

Alright so we are going now to create a new folder for the new project and add a new library : openzeppelin-solidity

truffle unbox webpack
npm install openzeppelin-solidity

We create a new smart contract in the contract folder called StarNotary.sol

It was a pain to adapt the contract because the code of the training is not updated but here wo go :

pragma solidity >=0.5.0;
//in StarNotary.sol 
import "../node_modules/openzeppelin-solidity/contracts/token/ERC721/ERC721.sol";

contract StarNotary is ERC721 {

    constructor() public ERC721("GameItem", "ITM"){}

    struct Star {
        string name;
    }

    mapping(uint256 => Star) public tokenIdToStarInfo;
    mapping(uint256 => uint256) public starsForSale;

    // Create Star using the Struct
    function createStar(string memory _name, uint256 _tokenId) public { // Passing the name and tokenId as a parameters
        Star memory newStar = Star(_name); // Star is an struct so we are creating a new Star
        tokenIdToStarInfo[_tokenId] = newStar; // Creating in memory the Star -> tokenId mapping
        _mint(msg.sender, _tokenId); // _mint assign the the star with _tokenId to the sender address (ownership)
    }

    // Putting an Star for sale (Adding the star tokenid into the mapping starsForSale, first verify that the sender is the owner)
    function putStarUpForSale(uint256 _tokenId, uint256 _price) public {
        require(ownerOf(_tokenId) == msg.sender, "You can't sale the Star you don't owned");
        starsForSale[_tokenId] = _price;
    }


    // Function that allows you to convert an address into a payable address
    //function _make_payable(address x) internal pure returns (address payable) {
    //   return payable(x);
    //}

    function buyStar(uint256 _tokenId) public  payable {
        require(starsForSale[_tokenId] > 0, "The Star should be up for sale");
        uint256 starCost = starsForSale[_tokenId];
        address ownerAddress = ownerOf(_tokenId);
        require(msg.value > starCost, "You need to have enough Ether");
        transferFrom(ownerAddress, msg.sender, _tokenId); // We can't use _addTokenTo or_removeTokenFrom functions, now we have to use _transferFrom
        //address payable ownerAddressPayable = _make_payable(ownerAddress); // We need to make this conversion to be able to use transfer() function to transfer ethers
        //address payable addr3 = address(uint160(ownerAddress));
        address payable addr3 = payable(ownerAddress); 
        addr3.transfer(starCost);
        if(msg.value > starCost) {
            address payable addr4 = payable(msg.sender);
            //address payable addr4 = address(uint160(msg.sender));
            addr4.transfer(msg.value - starCost);
        }
    }

}
//in 2_deploy_contract.js 
const StarNotary = artifacts.require("StarNotary");

module.exports = function(deployer) {
  deployer.deploy(StarNotary);
};

I also had to change the version in the truffle-config.js to : version: “0.8.0”, and to change the limitation regarding the version in the Migrations.sol file.

Finally it compiled properly :

Now let’s run a few tests, here as well I had to bug fix to make it work with latest version of ther ERC-721 :

const StarNotary = artifacts.require("StarNotary");

var accounts;
var owner;

contract('StarNotary', (accs) => {
    accounts = accs;
    owner = accounts[0];
});

it('can Create a Star', async() => {
    let tokenId = 1;
    let instance = await StarNotary.deployed();
    await instance.createStar('Awesome Star!', tokenId, {from: accounts[0]}) //creation of a star 
    assert.equal(await instance.tokenIdToStarInfo.call(tokenId), 'Awesome Star!') //check name of star 
});

it('lets user1 put up their star for sale', async() => {
    let instance = await StarNotary.deployed();
    let user1 = accounts[1];
    let starId = 2;
    let starPrice = web3.utils.toWei(".01", "ether");
    await instance.createStar('awesome star', starId, {from: user1}); //creation of star 
    await instance.putStarUpForSale(starId, starPrice, {from: user1}); // put for sale 
    assert.equal(await instance.starsForSale.call(starId), starPrice); //check that the star is for sale
});

it("lets user1 get the funds after the sale", async () => {
    let instance = await StarNotary.deployed();
    let user1 = accounts[1];
    let user2 = accounts[2];
    let starId = 3;
    let starPrice = web3.utils.toWei(".01", "ether");
    let balance = web3.utils.toWei(".05", "ether");
    await instance.createStar("awesome star", starId, { from: user1 }); //creation of star 
    await instance.putStarUpForSale(starId, starPrice, { from: user1 }); // put for sale 
    let balanceOfUser1BeforeTransaction = await web3.eth.getBalance(user1); //balance of user 
    //requires that the caller is the owner or approved to transfer that token. `user2` 
    //is not the token owner so it has to be approved.
    await instance.approve(user2, starId, {from: user1, gasPrice: 0})
    await instance.buyStar(starId, { from: user2, value: balance, gasPrice: 0});
    let balanceOfUser1AfterTransaction = await web3.eth.getBalance(user1);
    //Check that the funds were transmitted
    let value1 = Number(balanceOfUser1BeforeTransaction) + Number(starPrice);
    let value2 = Number(balanceOfUser1AfterTransaction);
    assert.equal(value1, value2);
    
  });

it('lets user2 buy a star, if it is put up for sale', async() => {
    let instance = await StarNotary.deployed();
    let user1 = accounts[1];
    let user2 = accounts[2];
    let starId = 4;
    let starPrice = web3.utils.toWei(".01", "ether");
    let balance = web3.utils.toWei(".05", "ether");
    await instance.createStar('awesome star', starId, {from: user1});
    await instance.putStarUpForSale(starId, starPrice, {from: user1});
    let balanceOfUser1BeforeTransaction = await web3.eth.getBalance(user2);
    //requires that the caller is the owner or approved to transfer that token. `user2` 
    //is not the token owner so it has to be approved.
    await instance.approve(user2, starId, {from: user1, gasPrice: 0})
    await instance.buyStar(starId, {from: user2, value: balance});
    //Check that the star was sold 
    assert.equal(await instance.ownerOf.call(starId), user2);
});

it('lets user2 buy a star and decreases its balance in ether', async() => {
    let instance = await StarNotary.deployed();
    let user1 = accounts[1];
    let user2 = accounts[2];
    let starId = 5;
    let starPrice = web3.utils.toWei(".01", "ether");
    let balance = web3.utils.toWei(".05", "ether");
    await instance.createStar('awesome star', starId, {from: user1});
    await instance.putStarUpForSale(starId, starPrice, {from: user1});
    let balanceOfUser1BeforeTransaction = await web3.eth.getBalance(user2);
    const balanceOfUser2BeforeTransaction = await web3.eth.getBalance(user2);
    //requires that the caller is the owner or approved to transfer that token. `user2` 
    //is not the token owner so it has to be approved.
    await instance.approve(user2, starId, {from: user1, gasPrice: 0})
    await instance.buyStar(starId, {from: user2, value: balance, gasPrice:0});
    const balanceAfterUser2BuysStar = await web3.eth.getBalance(user2);
    let value = Number(balanceOfUser2BeforeTransaction) - Number(balanceAfterUser2BuysStar);
    //Check that the funds were transmitted
    assert.equal(value, starPrice);
  });

Next we do the front end, as before by replacing the content of the html file in the app folder

<!DOCTYPE html>
<html>
  <head>
    <title>StarNotary DApp</title>
  </head>
  <style>
    input {
      display: block;
      margin-bottom: 12px;
    }
  </style>
  <body>
    <h1>StarNotary Token DAPP</h1>

  <hr>

  <br>
    <h1>Create a Star</h1>
    <br><label for="starName">Star Name:</label><input type="text" id="starName"></input>
    <br><label for="starId">Star ID:</label><input type="text" id="starId"></input>
    <br><br><button id="createStar" onclick="App.createStar()">Create Star</button>
    <br><br>
    <br>

    <span id="status"></span>

    <br>
    <script src="index.js"></script>
  </body>
</html>

Time to test it !

I changed the code in the truffle config, exactly as in the previous version of the Dapp. and then :

To run your application you will need to:

  • Run truffle develop or sudo truffle develop commands.
  • Run compile command.
  • Run migrate --reset

Then

  • Open a second terminal window, and make sure you are inside your project directory.
  • Run cd app to move inside the app folder.
  • Run npm run dev command.

This will start our port at 8080. Open http://localhost:8080/ in your browser. Make sure the Metamask extension is installed, and you are logged into it, and also have imported the accounts.

So I had an error when I tried interacting with the smart contract, the error was “the tx doesn’t have the correct nonce. account has nonce of:”

All I had to do to correct the error was to reset my account in my metamask, here :

it clears out the history of transactions and solved the error when I tried again.


Brax

Dude in his 30s starting his digital notepad