While running your own Ethereum node with geth by using web3js from node.js (backend) app is a common solution, connecting to a public Ethereum node such as infura.io and using [truffle-contract](https://github.com/trufflesuite/truffle-contract] gives some benefits:
- No need to maintain your own Ethereum node (syncing a full node requires huge storage space! (around 40 GB for Ropsten))
- Seamlessly integrate your node.js (backend) development workflow with Truffle Framework smart contract development
- Simpler smart contract API with truffle-contract
How to do it?
First, install some dependencies:
npm i --save web3@0.18.4 web3-provider-engine@8.6.1 asyncawait truffle-contract ethereumjs-wallet
asyncawait
module provides async / await capabilities to make code much shorter. If you use node.js version with built-in async / await you can ignore this and just remove parentheses after each async / await in the code sample below.
Then, in a node.js file, include these dependencies:
const async = require('asyncawait/async'); const await = require('asyncawait/await'); const path = require('path'); const fs = require('fs'); const TruffleContract = require('truffle-contract'); const Web3 = require('web3'); const ethereumjsWallet = require('ethereumjs-wallet'); const ProviderEngine = require('web3-provider-engine'); const WalletSubprovider = require('web3-provider-engine/subproviders/wallet.js'); const Web3Subprovider = require('web3-provider-engine/subproviders/web3.js'); const FilterSubprovider = require('web3-provider-engine/subproviders/filters.js');
Next, define some important constants:
const RPC_SERVER = 'https://ropsten.infura.io/YOUR-INFURA-API-TOKEN'; const TOKEN_SALE_CONTRACT = path.resolve(__dirname, '..', '..', 'contracts', 'TokenSale.json'); const privateKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
RPC_SERVER
contains path to infura.io node (in this case, I choose to connect to Ropsten for testing) and your API token (currently you can obtained for free by registering). TOKEN_SALE_CONTRACT
contains the path to your smart contracts build result. Usually it is inside build/contracts
subdirectory within your Truffle project directory. You can just copy them from there to your node.js project directory. While privateKey
is your Ethereum wallet private key from account that you want to use sign the transaction.
Please also make sure you have some ETH in the wallet in case you are invoking transaction (which require gas). Otherwise you will hit insufficient funds error.
Then we need to setup custom provider/engine to enable implicit transaction self-signing:
const wallet = ethereumjsWallet.fromPrivateKey(new Buffer(privateKey, 'hex')); const engine = new ProviderEngine(); engine.addProvider(new FilterSubprovider()); engine.addProvider(new WalletSubprovider(wallet, {})); engine.addProvider(new Web3Subprovider(new Web3.providers.HttpProvider(RPC_SERVER))); engine.start();
Now we can write a function to load the contract using truffle-contract:
function loadContract(file, provider, address) { return new Promise(function (resolve, reject) { fs.readFile(file, 'utf-8', function (err, data) { if (err) { reject(err); } else { let contract = TruffleContract(JSON.parse(data)); contract.setProvider(provider); contract.defaults({ from: address, gas: 4500000 }); resolve(contract); } }); }); }
Finally we can create functions that invoke smart contract methods:
const totalTokenIssued = async(function () { let tokenSaleContract = await(loadContract(TOKEN_SALE_CONTRACT, engine, whitelistAddress)); let tokenSale = await(tokenSaleContract.deployed()); let totalTokenIssued = await(tokenSale.totalTokenIssued()); return totalTokenIssued.toString(); }); const contributeCoins = async(function (contributorAddress, amount, whitelistAddress) { let weiAmount = new web3.BigNumber(web3.toWei(amount, 'ether')); let tokenSaleContract = await(loadContract(TOKEN_SALE_CONTRACT, engine, whitelistAddress)); let tokenSale = await(tokenSaleContract.deployed()); let contributeCoins = await(tokenSale.contributeCoins(contributorAddress, weiAmount, { from: whitelistAddress })); return contributeCoins; });
Since async
method return a promise, you can simply call methods above like this:
totalTokenIssued().then(function(totalToken) { console.log('totalToken=' + totalToken); }).catch(console.error); let contributor = '0x0718197B9Ac69127381ed0C4b5d0f724f857c4d1'; // address derived from our private key & wallet defined above let whitelist = '0x' + wallet.getAddress().toString('hex'); contributeCoins(contributor, 1, whitelist).then(function(totalToken) { console.log('totalToken=' + totalToken); }).catch(console.error);