Ethereum: Deploy Ethereum Smart Contract without Truffle

We are using web3.js to create and manage our smart contract that will be placed in a Ethereum node. We will demonstrate how this is done without Truffle. Truffle is another abstraction layer relied on top of Ganache to provide even better control over creating smart contract.

The tools we will use

The most prominent tools at the moments are:

  • Truffle: It is a development tool for developing and testing Ethereum smart contract. It helps you develop smart contracts, publish them, and test them.
  • Ganache: It is a UI application and  what Ganache does is simple, it creates a virtual Ethereum blockchain, and it generates some fake accounts that we will use during development.
  • Ethereum Wallet: You can download it from Ethereum official site.

Let get started by creating a node.js application

mkdir ~/Training/Greetings
cd ~/Training/Greetings

The purpose of this tutorial is to demonstrate the use of Ganache and Truffle, so we only build a simple Greetings smart contracts.

Let’s initiate a project

npm init

It doesn’t matter the content you put in the npm init . you can just press enters and proceed.

We will be using the following library with the specific versions.

npm install web3@0.20.6 solc@0.4.23

web3 is the Ethereum javascript API, it has many bindings for different languages. Solc is the JavaScript bindings for the Solidity compiler. Right now the version of web3 is 1.0. But we do not want to use it yet because it is still in Beta and we need to make sure it is working properly with solc  and Truffle too.

[panel style=”panel-default”]
[panel-header]
Note
[/panel-header]
[panel-content]

Please read the following documentations when you are done with this tutorial to have a better understanding of the web3.js and Solidity tool set.

web3.js Github link

Solidity documentation
[/panel-content]
[/panel]

Now open up your project with your favorite editor, in my case, it is VSCode.

Please add the following code at Greetings.sol at the root of the project.

file: <project root>/Greetins.sol

pragma solidity ^0.4.23;

contract Greetings{
    string message;

    function Greetings() public {
        message = "i'm ready!";    
    }

    function setGreetings(string _message) public {
        message = _message;
    }

    function getGreetings() public view returns (string) {
        return message;
    }
}

Line 1: pragma solidity is to describe the version of the solidity you will use.

Line 3:contract  is like a class declaration in a normal Object Oriented programming. But since it is a contract, so it is declared as contract .

Line 4: we declare a local variable to store our string.

Line 6: it is a constructor. please note we put our public declaration at the back instead of at the front as we normally do.

Line 1o: we set the greeting string and this will cause a gas as there is an equal assignment.

Line 14: This is to retrieve the greeting string. This will not cost you gas as there is no logical operation. But note we return a view  with type string .

Functions can be specified as being

external, public, internal or private, where the default ispublic. For state variables, external is not possible and the default is internal.

external:
External functions are part of the contract interface, which means they can be called from other contracts and via transactions. An external function f cannot be called internally (i.e. f()does not work, but this.f() works). External functions are sometimes more efficient when they receive large arrays of data.
public:
Public functions are part of the contract interface and can be either called internally or via messages. For public state variables, an automatic getter function (see below) is generated.
internal:
Those functions and state variables can only be accessed internally (i.e. from within the current contract or contracts deriving from it), without using this.
private:
Private functions and state variables are only visible for the contract they are defined in and not in derived contracts.

Ganache

Please open up your Ganache. This is an excellent tool to emulate the Ethereum network. It will help you  set up fake accounts with fake Ether in it and when you have a contract released, it will help you do the fake mining. You can test your contract with Ganache. It is a cool tool.

Web3.js code

Let’s me paste my web3 code here.

file: <project root>/runner.js

Fist you start by using Web3. Please be reminded that the upper case is crucial throughout the whole tutorial.

var Web3 = require('web3');
var solc = require('solc');
var fs = require('fs');

var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545"));

console.log(JSON.stringify(web3.eth.accounts));

Line 1: use of Web3 class.

Line 5: we connect to our Ganache. Our Ganache is at port 7545. Please change it if you set it otherwise.

Line 7: If you are connected, then you should print out the fake accounts from your Ganache. Please make sure this step is working fine.

Next we read on your smart contract we created previously.

var sourceCode = fs.readFileSync('Greetings.sol').toString();
var compiledCode = solc.compile(sourceCode);
console.log(JSON.stringify(compiledCode));
var contractABI = JSON.parse(compiledCode.contracts[':Greetings'].interface);

Line 1: load in our solidity contract

Line 2: compile it

Line 3: display the interface of out contract. It is call a ABI.

Please make sure this step is working without error, or we will not be able to proceed.

[panel style=”panel-default”]
[panel-header]What is an ABI and why is it needed to interact with contracts?[/panel-header]

[panel-content]
The ABI, Application Binary Interface, is basically how you call functions in a contract and get data back. An Ethereum smart contract is bytecode deployed on the Ethereum blockchain. There could be several functions in a contract. An ABI is necessary so that you can specify which function in the contract to invoke, as well as get a guarantee that the function will return data in the format you are expecting. <from stackexchange>
[/panel-content]
[/panel]

If you print out the contract interface

Please check the  name field and you will find setGreetings and getGreetings .

Build contract Factory

Now we have our compile code and let’s build a factory.

// this is the contract Factory build for this interface ABI
var greetingsContract = web3.eth.contract(contractABI);
var byteCode = compiledCode.contracts[':Greetings'].bytecode;

Line 2: this is our contract factory

Line 3: let’s extract our byteCode for later use.

Deployment

let’s deploy~!

// '0x' + byteCode
var greetingsDeployed = greetingsContract.new([], {
    data: byteCode,
    from: web3.eth.accounts[0],
    gas: gasEstimate + 50000
}, function (err, greetingsDeployed) {
    if (!err) {
        // NOTE: The callback will fire twice!
        // Once the contract has the transactionHash property set and once its deployed on an address.

        // e.g. check tx hash on the first call (transaction send)
        if (!greetingsDeployed.address) {
            console.log(`greetingsDeployed.transactionHash = ${greetingsDeployed.transactionHash}`); // The hash of the transaction, which deploys the contract
          
        // check address on the second call (contract deployed)
        } else {
            console.log(`greetingsDeployed.address = ${greetingsDeployed.address}`); // the contract address
            global.contractAddress = greetingsDeployed.address;
        }
      
        // Note that the returned "greetingsDeployedReturned" === "greetingsDeployed",
        // so the returned "greetingsDeployedReturned" object will also get the address set.
    } else {
        console.log(err);
    }
    var greetingsInstance = greetingsContract.at(greetingsDeployed.address);
    //greetingsInstance.getGreetings();

});


Line 2: This is the deployment by using new

Line 3: Remember we get the byteCode from previous section. Use it here

Line 18: This is important, we need the address to get an instance of the contract in order to use it.

 

If you check your Ganache, you will see the following.

 

When you get your address, then you can use the address to obtain an instance of the contract. The following code will be inserted after line 18.

    // Greeting instance generated by Factory using at

    var greetingsInstance = greetingsContract.at(greetingsDeployed.address);
    console.log(greetingsInstance.getGreetings());
    greetingsInstance.setGreetings("Hello Admin", {
        from: web3.eth.accounts[0]
    });
    console.log(greetingsInstance.getGreetings());

Now we successfully interact with the contract.

 

As you can see. It is pretty tedious to write and test a contract. The following sect I”ll write again using Truffle and we can cut down the steps. There is another tool called Parity and it is very popular now. I may touch on that too.

 

Full code

var Web3 = require('web3');
var solc = require('solc');
var fs = require('fs');
var prettyjson = require('prettyjson');

var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545"));

if (!web3.isConnected()) {
    throw new Error('unable to connect to ethereum node at ' + ethereumUri);
} else {
    console.log(JSON.stringify(web3.eth.accounts));

    var sourceCode = fs.readFileSync('Greetings.sol').toString();
    var compiledCode = solc.compile(sourceCode);
    //console.log(JSON.stringify(compiledCode));

    var contractABI = JSON.parse(compiledCode.contracts[':Greetings'].interface);
    console.log('************** Interface ************* \n');
    console.log(prettyjson.render(contractABI));
}
/*
* deploy contract
*/
var byteCode = compiledCode.contracts[':Greetings'].bytecode;

let gasEstimate = web3.eth.estimateGas({ data: '0x' + byteCode });
console.log('gasEstimate = ' + gasEstimate);

// this is the contract Factory build for this interface ABI
var greetingsContract = web3.eth.contract(contractABI);
console.log('deploying contract...');




// '0x' + byteCode
var greetingsDeployed = greetingsContract.new([], {
    data: byteCode,
    from: web3.eth.accounts[0],
    gas: gasEstimate + 50000
}, function (err, greetingsDeployed) {
    if (!err) {
        // NOTE: The callback will fire twice!
        // Once the contract has the transactionHash property set and once its deployed on an address.
        console.log('************** greetingsDeployed.address ************* \n');
        console.log(greetingsDeployed.address);

        // e.g. check tx hash on the first call (transaction send)
        if (!greetingsDeployed.address) {
            console.log(`greetingsDeployed.transactionHash = ${greetingsDeployed.transactionHash}`); // The hash of the transaction, which deploys the contract

            // check address on the second call (contract deployed)
        } else {
            console.log(`greetingsDeployed.address = ${greetingsDeployed.address}`); // the contract address
            global.contractAddress = greetingsDeployed.address;

            // Greeting instance generated by Factory using at
            var greetingsInstance = greetingsContract.at(greetingsDeployed.address);
            console.log(greetingsInstance.getGreetings());
            greetingsInstance.setGreetings("Hello Admin", {
                from: web3.eth.accounts[0]
            });
            console.log(greetingsInstance.getGreetings());
        }

        // Note that the returned "greetingsDeployedReturned" === "greetingsDeployed",
        // so the returned "greetingsDeployedReturned" object will also get the address set.
    } else {
        console.log(err);
    }
    var greetingsInstance = greetingsContract.at(greetingsDeployed.address);
    //greetingsInstance.getGreetings();

});

(function wait() {
    setTimeout(wait, 3000);
})();

Leave a Reply

Your email address will not be published. Required fields are marked *