Ethereum DApps without truffle: compile; deploy; use it :)

Duarte Aragão
Jun 14, 2016 · 7 min read

Recently I discovered Ethereum and since than I have been completely obsessed with figuring out how the whole thing works, from blockchain, to smart contracts. Although understanding the blockchain where Ethereum lies is important, there is no need for a deep knowledge on this to start doing something.

Note: I might (and probably) have done some errors, or implemented some weird solutions to a very simple problem. Still this was a nice learning experience which I hope to expand more and to something better. You can get the scripts here: https://github.com/daragao/FirstEthereumExperiment

What should you know before starting:

The first experience I had with Ethereum was to make a simple contract using truffle, the truffle generator allows you to scaffold a whole DApp project, creating a nice file structure and even providing you an example, compile smart contracts, deploy smart contracts, and use your smart contracts with JS. It is amazing! And it can be summarised into 3 main command line calls (although it has much more).

truffle init: creates the scaffolding for your projecttruffle compile: compiles the smart contracts into JS filestruffle deploy: deploys the smart contracts and makes it possible to access the deployed contracts through JS calls

Super simple right?! My only issue was not knowing what the hell was happening behind the scenes. Just by thinking a bit about it is easy to get into conclusions about what is happening but still, I was missing the practice. So I decided to make 3 little scripts that just compile, deploy and use the Greeter example contract.

Setup

The setup is pretty basic. We will use nodejs to execute our scripts, npm to install them, and testrpc to test the contract in a memory network (we can also use an actual private network for that).

“dependencies”: {
“ether-pudding”: “^3.0.3”,
“solc”: “^0.3.4”,
“web3”: “^0.17.0-alpha”
}

You can use package.json with these dependencies to install what you need.

solc: compiles the solidity contract
ether-pudding: promises for web3
web3: Ethereum blockchain JS api (implementation of the RPC calls made to Ethereum)

Contract

The contract we will use is this one that you can get from the Greeter tutorial.

Greeter.sol from https://www.ethereum.org/greeter

The contract is written in solidity, and can be compiled through the command line by the solidity compiler (although in our case we will be using a nodejs script).

Compile the contract

The first step to be able to use this contract is to compile it. For that we can use this simple script, that will search for a .sol file with the same name as its argument.

$> node compile_contract.js Greeter

The first part of the contract gets the .sol file depending on the argument that the nodejs script got and reads it into a string.

The 3 libraries we need to achieve this are the fs from node, the solc library to compile the solidity contract, and the ether-pudding to save the compiled contract file so that we can import the Greeter object later. As you can notice, we don’t need the web3 library to be able to compile anything.

Unfortunately the solc compiler does not do a very complex parsing of the .sol file, so we need to remove any \n or open comments // from the string we just got (here I am assuming the file doesn’t have any // comments on it, and only removing the \n).

var solc = require(‘solc’);
var fs = require(‘fs’);
var Pudding = require(‘ether-pudding’);
//var contractName = ‘Greeter’;
if(process.argv.length < 3) {
console.log(‘needs the contract Name as argument!’);
process.exit(1);
}
var contractName = process.argv[2];
var source = fs.readFileSync(__dirname+’/’+contractName+’.sol’,{encoding: ‘utf8’}).toString().replace(/\n/g,’ ‘);

The next step is to compile the contract using solc. The compilation is done by calling:

var compiled = solc.compile(source, 1);
if(!compiled.contracts[contractName]) {
console.log(‘Contract must have same name as file!’);
process.exit(1);
}

Where the 1 flag is an optimization flag only.

var bytecode = compiled.contracts[contractName].bytecode;
var interface = compiled.contracts[contractName].interface;
var contract_data = {
abi: JSON.parse(interface),
binary: bytecode
};

From the compiled JS contract object we can get our interface object, which tells us, which functions this contract has available.

//interface
[
{
"constant": false,
"inputs": [],
"name": "kill",
"outputs": [],
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "greet",
"outputs": [
{
"name": "",
"type": "string"
}
],
"type": "function"
},
{
"inputs": [
{
"name": "_greeting",
"type": "string"
}
],
"type": "constructor"
}
]

And our bytecode string, which is the binary representation of our contract. This is basically a set of (kind of) assembly instructions for the Ethereum Virtual Machine to execute, each time the functions in the contract are called.

//bytecode
"606060405260405161023e38038061023e8339810160405280510160008054600160a060020a031916331790558060016000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10609f57805160ff19168380011785555b50608e9291505b8082111560cc57600081558301607d565b50505061016e806100d06000396000f35b828001600101855582156076579182015b82811115607657825182600050559160200191906001019060b0565b509056606060405260e060020a600035046341c0e1b58114610026578063cfae321714610068575b005b6100246000543373ffffffffffffffffffffffffffffffffffffffff908116911614156101375760005473ffffffffffffffffffffffffffffffffffffffff16ff5b6100c9600060609081526001805460a06020601f6002600019610100868816150201909416939093049283018190040281016040526080828152929190828280156101645780601f1061013957610100808354040283529160200191610164565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156101295780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b565b820191906000526020600020905b81548152906001019060200180831161014757829003601f168201915b505050505090509056"

The interface and bytecode are used for ether-pudding to save a Greeter.sol.js file which serves as an API for our contract. ether-pudding gives us the great advantage of using promises in our code.

Pudding.save(contract_data,‘./’+contractName+’.sol.js’)
.then(function() {
console.log(‘File ‘+’./’+contractName+’.sol.js’+’ was created with the JS contract!’ );
});

Deploy

To deploy out contract to the network we should use testrpc, that will create a network in memory, it is faster and simpler for us.

$> node deploy_contract.js Greeter 'Hello World!'
contract address: 0xe5e5978c67b6fed87bf938053094c7f8791c9e2a

The Greeter contract has a contructor that receives a string and that is the argument that we give after the Greeter filename. The response that we get from the contract is the address of the deployed contract in the network. We will use this address later to be able to interact with the deployed contract.

This is the script we use to deploy the contract

var Web3 = require(‘web3’);
var web3 = new Web3();
var provider = new web3.providers.HttpProvider(‘http://localhost:8545');
web3.setProvider(provider);

The web3 library is used to get the provider object. The provider is simply the node running locally to your computer, this can be a testrpc node, as it can be a proper network.

if(process.argv.length < 3) {
console.log(‘needs the contract Name as argument!’);
process.exit(1);
}
var contractName = process.argv[2];
var GreeterContract = require(‘./’+contractName+’.sol.js’);
GreeterContract.setProvider(provider);
GreeterContract.defaults({from:web3.eth.accounts[0]});
var params = process.argv.slice(3,5);

The first part of the deployment script, gets the contract name, and associates the contract object with the provider (our network node). Because for the deployment we need to specify a owner of the contract and to supply gas (the amount of gas you need is calculated based on the contract functions, the more complex the operations you do, the more gas you need) for it. web3 gives us a neat API to get which accounts are available to our node, the web3.eth.accounts is just an array of accounts that associated with our node. The params value is just to get 2 or less arguments for the Greeter contructor (making it 2 or less was just a random choice at the moment).

GreeterContract.new.apply(GreeterContract,params)
.then(function(greeter) {
console.log(‘contract address: ‘+greeter.address);
}).catch(function(err) {
console.log(“Error creating contract!”);
console.log(err.stack);
});

The deployment after having setup the provider and owner account is just a matter of running the new function on the contract (we used apply to spread the array as arguments of the Greeter contructor, but you can ignore that) with the parameters as arguments. Because we compiled using ether-pudding we can already take advantage of promises. The promise returns then when all went alright, and catch in case of something failing.

Here happens one of the magic steps from truffle: instead of finishing there, truffle gets the address where contract was deployed, stores it in the contract, and re-saves the contract (just like we did in the compile step). By doing that, truffle always knows where the last deployment was made to :)

Note: Besides this, truffle also has another nice step, that is to check the contract dependencies before deploying and after deploying all the contracts dependencies before the contract itself. By not using that this script might break… although it didn’t hehehe

Use

The final step is to use the contract we just deployed.

$> node use_contract.js 0xe5e5978c67b6fed87bf938053094c7f8791c9e2a
contract greeter: Hello World!

As usual the first argument for node is the js script described bellow, and the second argument is the contracts address. The contract address was retrieved from the last deployment we did.

On the first part of the scrip we get the web3 library to be able to setup the provider node, and the contract we want to use. After that we setup the provider in the contract and the account we wish to call functions from, this account, doesn’t need to be the owner of the account.

var Web3 = require('web3');
var web3 = new Web3();

var provider = new web3.providers.HttpProvider('http://localhost:8545');
web3.setProvider(provider);
// the contract name is hardcoded here
var GreeterContract = require('./Greeter.sol.js');
GreeterContract.setProvider(provider);
GreeterContract.defaults({from:web3.eth.accounts[1]});

if(process.argv.length < 3) {
console.log('needs the contract address as argument!');
process.exit(1);
}
var contractAddress = process.argv[2];

There is an important note: if you intend just to get a value from the contract without changing anything in the contract, there is no need to pay for gas, although if you intend to alter the contract in any way, you need to do a transaction so you need gas for that.

var contractInstance = GreeterContract.at(contractAddress);

contractInstance.greet.call().then(function(result) {
console.log('contract greeter: '+result);
}).catch(function(err) {
console.log("Error creating contract!");
console.log(err.stack);
});

To call a function in the contract you need to setup the contract address in the contract (in the case of truffle this is already done for you), and use the call() function of your object. call() makes sure you are calling a function and not making a transaction. If you wish to make a transaction you can just call the function normally, and a transaction will be made, as long as you provide enough gas for it.

Conclusion

There is not really a conclusion here, but truffle is awesome and I intend to use it many times for my experiments, although whenever I want to make something more serious I will probably write my own scripts to be run with a normal task runner like gulp or something.

Also, I am sorry the article isn’t really well written, but I will do better next time. Any corrections, or improvements are very welcome! :)

Have fun.

Duarte Aragão

Written by

computer geek learning about tech all the time

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade