Let's first build the backend of the app. First of all, run npm install inside the Initial directory to install the required dependencies for our backend.
Here is the backend code to run an express service and serve the index.html file and static files:
var express = require("express");
var app = express();
app.use(express.static("public"));
app.get("/", function(req, res){
res.sendFile(__dirname + "/public/html/index.html");
})
app.listen(8080);
The preceding code is self-explanatory. Now let's proceed further. Our app will have two buttons, that is, Compile and Deploy. When the user clicks on the compile button, the contract will be compiled and when the deploy button is clicked on, the contract will be deployed.
We will be compiling and deploying contracts in the backend. Although this can be done in the frontend, we will do it in the backend because solcjs is available only for Node.js (although the JavaScript-based compilers it uses work on the frontend).
When the user clicks on the compile button, the frontend will make a GET request to the /compile path by passing the contract source code. Here is the code for the route:
var solc = require("solc");
app.get("/compile", function(req, res){
var output = solc.compile(req.query.code, 1);
res.send(output);
})
At first, we import the solcjs library here. Then, we define the /compile route and inside the route callback, we simply compile the source code sent by the client with the optimizer enabled. And then we just send the solc.compile method's return value to the frontend and let the client check whether the compilation was successful or not.
When the user clicks on the deploy button, the frontend will make a GET request to the /deploy path by passing the contract source code and constructor arguments from the address and private key. When the user clicks on this button, the contract will be deployed and the transaction hash will be returned to the user.
Here is the code for this:
var Web3 = require("web3");
var BigNumber = require("bignumber.js");
var ethereumjsUtil = require("ethereumjs-util");
var ethereumjsTx = require("ethereumjs-tx");
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
function etherSpentInPendingTransactions(address, callback)
{
web3.currentProvider.sendAsync({
method: "txpool_content",
params: [],
jsonrpc: "2.0",
id: new Date().getTime()
}, function (error, result) {
if(result.result.pending)
{
if(result.result.pending[address])
{
var txns = result.result.pending[address];
var cost = new BigNumber(0);
for(var txn in txns)
{
cost = cost.add((new BigNumber(parseInt(txns[txn].value))).add((new BigNumber(parseInt(txns[txn].gas))).mul(new BigNumber(parseInt(txns[txn].gasPrice)))));
}
callback(null, web3.fromWei(cost, "ether"));
}
else
{
callback(null, "0");
}
}
else
{
callback(null, "0");
}
})
}
function getNonce(address, callback)
{
web3.eth.getTransactionCount(address, function(error, result){
var txnsCount = result;
web3.currentProvider.sendAsync({
method: "txpool_content",
params: [],
jsonrpc: "2.0",
id: new Date().getTime()
}, function (error, result) {
if(result.result.pending)
{
if(result.result.pending[address])
{
txnsCount = txnsCount + Object.keys(result.result.pending[address]).length;
callback(null, txnsCount);
}
else
{
callback(null, txnsCount);
}
}
else
{
callback(null, txnsCount);
}
})
})
}
app.get("/deploy", function(req, res){
var code = req.query.code;
var arguments = JSON.parse(req.query.arguments);
var address = req.query.address;
var output = solc.compile(code, 1);
var contracts = output.contracts;
for(var contractName in contracts)
{
var abi = JSON.parse(contracts[contractName].interface);
var byteCode = contracts[contractName].bytecode;
var contract = web3.eth.contract(abi);
var data = contract.new.getData.call(null, ...arguments, {
data: byteCode
});
var gasRequired = web3.eth.estimateGas({
data: "0x" + data
});
web3.eth.getBalance(address, function(error, balance){
var etherAvailable = web3.fromWei(balance, "ether");
etherSpentInPendingTransactions(address, function(error, balance){
etherAvailable = etherAvailable.sub(balance)
if(etherAvailable.gte(web3.fromWei(new BigNumber(web3.eth.gasPrice).mul(gasRequired), "ether")))
{
getNonce(address, function(error, nonce){
var rawTx = {
gasPrice: web3.toHex(web3.eth.gasPrice),
gasLimit: web3.toHex(gasRequired),
from: address,
nonce: web3.toHex(nonce),
data: "0x" + data
};
var privateKey = ethereumjsUtil.toBuffer(req.query.key, 'hex');
var tx = new ethereumjsTx(rawTx);
tx.sign(privateKey);
web3.eth.sendRawTransaction("0x" + tx.serialize().toString('hex'), function(err, hash) {
res.send({result: {
hash: hash,
}});
});
})
}
else
{
res.send({error: "Insufficient Balance"});
}
})
})
break;
}
})
This is how the preceding code works:
- At first, the Web imports the web3.js, BigNumber.js, ethereumjs-util, and ethereumjs-tx libraries. Then, we create an instance of Web3.
- Then, we define a function named etherInSpentPendingTransactions, which calculates the total ether that's being spent in the pending transactions of an address. As web3.js doesn't provide JavaScript APIs related to the transaction pool, we make a raw JSON-RPC call using web3.currentProvider.sendAsync. sendAsync is used to make raw JSON-RPC calls asynchronously. If you want to make this call synchronously, then use the send method instead of sendAsync. While calculating the total ether in the pending transactions of an address, we look for pending transactions in the transaction pool instead of the pending block due to the issue we discussed earlier. While calculating the total ether, we add the value and gas of each transaction as gas also deducted the ether balance.
- Next, we define a function called getNonce, which retrieves the nonce of an address using the technique we discussed earlier. It simply adds the total number of mined transactions to the total number of pending transactions.
- Finally, we declare the /deploy endpoint. At first, we compile the contract. Then, we deploy only the first contract. Our platform is designed to deploy the first contract if multiple contracts are found in the provided source code. You can later enhance the app to deploy all the compiled contracts instead of just the first one. Then, we create a contract object using web3.eth.contract.
- As we aren't using the hooked-web3-provider or any hack to intercept sendTransactions and convert them into the sendRawTransaction call, in order to deploy the contract, we now need to generate the data part of the transaction, which will have the contract byte code and constructor arguments combined and encoded as a hexadecimal string. The contract object actually lets us generate the data of the transaction. This can be done by calling the getData method with function arguments. If you want to get data to deploy the contract, then call contract.new.getData, and if you want to call a function of the contract, then call contract.functionName.getData. In both the cases, provide the arguments to the getData method. So, in order to generate the data of a transaction, you just need the contract's ABI. To learn how the function name and arguments are combined and encoded to generate data, you can check out https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#examples, but this won't be required if you have the ABI of the contract or know how to create the ABI manually.
- Then, we use web3.eth.estimateGas to calculate the amount of gas that would be required to deploy the contract.
- Later, we check whether the address has enough ether to pay for the gas required to deploy the contract. We find this out by retrieving the balance of the address and subtracting it with the balance spent in the pending transactions and then checking whether the remaining balance is greater than or equal to the amount of ether required for the gas.
- And finally, we get the nonce, signing and broadcasting the transactions. We simply return the transaction hash to the frontend.