Creating a simple project management tool for ethereum in less than 150 lines of code .
What we're building
Sol-cli is a command line tool which understands two commands :
1 . init<filename>[options..] - This command creates a smartcontracts directory and config.json file . The smartcontracts directory will contain an empty <filename>.sol file . The init command also accepts two options :
-h - to specify hostname of the ethereum node from where we're planning to deploy the contract .
-p - to specify the rpc port of the node .
2 . compile<filename> - This command will create an artifacts directory with a <filename>.json . This file contains bytecode and abi of the deployed smart contract .
3 . deploy<filename> - This command will deploy our contract to the specified network . It has following options -a - to specify the address to be used as default account -p - to specify the password of the given account
If youre using ganache , you need not specify address and password .
Installing Sol-Cli
Clone the sol-cli github repository
git clone https:github.com/alvinzach/solcli.git
Navigate to the cloned directory . Install the dependencies and create a symbolic link
npm install
sudo npm link
Thats it ! . To test your installation just run
solcli --help
The code
We are using commander for creating our command line application in js.
#!/usr/bin/env node
const program=require('commander')
const solc=require('solc')
const fs=require('fs')
var Web3=require('web3');
program
.version('1.0.0')
.description('solidity compiler')
program
.command('init <filename>')
.description('initiate')
.alias('i')
.option('-p,--port <n>','rpc port')
.option('-h,--host <n>','rpc host')
.action((filename)=>{
var config={
"smartcontracts":[]
}
if(program.port){
config.port=program.port
}else{
config.port="8545"
}
if(program.host){
config.host=program.host
}else{
config.host="localhost"
}
config.smartcontracts.push(filename)
var sampleSol=`pragma solidity ^0.4.23;\ncontract ${filename} {\n}`
fs.writeFile('./config.json',JSON.stringify(config,null,2),(err)=>{
if(err){
console.log('Cannot init solcli')
return
}else{
if (!fs.existsSync('smartcontracts')){
fs.mkdirSync('smartcontracts');
}
fs.writeFile(`./smartcontracts/${filename}.sol`,sampleSol,(err)=>{
if(err){
console.log(err)
return
}else{
console.log(`Edit smartcontracts/${filename}.sol file`)
}
})
}
})
})
program
.command('compile [filename]')
.description('compile solidity file')
.alias('c')
.action((filename)=>{
if(filename){
if (!fs.existsSync('artifacts')){
fs.mkdirSync('artifacts');
}
let input = fs.readFileSync(`./smartcontracts/${filename}.sol`);
let output = solc.compile(input.toString(), 1);
if(output.errors){
output.errors.forEach(element => {
console.log(element)
});
}
if(output.contracts[`:${filename}`]){
var artifacts={
bytecode:output.contracts[`:${filename}`].bytecode,
abi:output.contracts[`:${filename}`].interface,
}
fs.writeFile(`./artifacts/${filename}.json`,JSON.stringify(artifacts,null,2),(err)=>{
if(err){
console.log(err)
return
}else{
console.log('wrote artifacts')
}
})
}
}else{
console.log('no contract specified')
}
})
program
.command('deploy [filename]')
.description('deploy smart contract')
.alias('d')
.option('-a,--address <n>','Address of sender')
.option('-p,--password <n>','password')
.action((filename)=>{
var config=JSON.parse(fs.readFileSync('config.json'))
var web3=new Web3(new Web3.providers.HttpProvider(`${config.host}:${config.port}`));
if(program.address){
web3.eth.defaultAccount=address;
}else{
web3.eth.defaultAccount=web3.eth.accounts[0];
}
if(program.password){
web3.personal.unlockAccount(web3.eth.defaultAccount,program.password,100000)
}
var contract_details=JSON.parse(fs.readFileSync(`artifacts/${filename}.json`));
var contract=web3.eth.contract(JSON.parse(contract_details.abi))
const contractInstance = contract.new({
data: '0x' + contract_details.bytecode,
gas: 90000*5
}, (err, res) => {
if (err) {
console.log(err);
return;
}
console.log("transaction hash :"+res.transactionHash);
var intId=setInterval(()=>{
if(contractInstance.address!==undefined){
contract_details.address=contractInstance.address;
fs.writeFileSync(`artifacts/${filename}.json`,JSON.stringify(contract_details,null,2))
console.log(contractInstance.address)
clearInterval(intId)
return
}
},3000)
});
console.log(config)
})
program.parse(process.argv)
Sol-cli has 2 dependencies - commander to write command-line tools in nodejs and solc ,the solidity compiler .
On receiving the command init<filename> I am creating a new json object called config to contain details of smart contract . config contains a smartcontracts array , host and port as properties . If hosname and port are not specifies in the init command , localhost and 8545 will be taken as default values . The config object is then written into config.json file . A smartcontracts/<filename>.sol file is also created .
The compile<filename> command reads content of smartcontracts/<filename>.sol and passes it to solc.compile() function . The abi and bytecode generated will be stored in the artifacts folder . The deploy<filename> command will create a new instance of the contract . The address of the deployed contract will be present in artifacts/<filename.json>